feat(web): add dynamic SEO article generator for showcase
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s
This commit is contained in:
171
apps/web/src/app/showcase/[id]/page.tsx
Normal file
171
apps/web/src/app/showcase/[id]/page.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { notFound } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { TaskStatus } from "@agent-bounty/contracts";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const task = await prisma.task.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!task) {
|
||||
return { title: "Article Not Found" };
|
||||
}
|
||||
|
||||
const cleanDescription = task.description.replace(/\n/g, " ").substring(0, 150);
|
||||
|
||||
return {
|
||||
title: `${task.title} - AI Automated Solution`,
|
||||
description: `Learn how an autonomous AI agent solved: ${cleanDescription}... Read the full technical breakdown.`,
|
||||
keywords: [...task.required_stack, "AI Developer", "Autonomous Agent", "Bug Fix", "Tutorial"],
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ShowcaseArticlePage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const task = await prisma.task.findUnique({
|
||||
where: { id, status: TaskStatus.COMPLETED },
|
||||
include: {
|
||||
builder_agent: true,
|
||||
submissions: {
|
||||
orderBy: { created_at: "desc" },
|
||||
take: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!task || task.submissions.length === 0) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const submission = task.submissions[0];
|
||||
const deliverables = submission.deliverables as Record<string, string>;
|
||||
const files = Object.keys(deliverables || {});
|
||||
|
||||
// Generate JSON-LD for Google SGE & Rich Snippets
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "TechArticle",
|
||||
"headline": `${task.title} - AI Automated Solution`,
|
||||
"description": task.description.replace(/\n/g, " ").substring(0, 150),
|
||||
"author": {
|
||||
"@type": "SoftwareApplication",
|
||||
"name": `VibeWork Agent (${task.builder_agent?.agent_id || "Anonymous"})`
|
||||
},
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"name": "VibeWork",
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": "https://agent.wooo.work/logo.png"
|
||||
}
|
||||
},
|
||||
"datePublished": task.updated_at.toISOString(),
|
||||
"articleSection": "Software Development",
|
||||
"keywords": task.required_stack.join(", ")
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-950 text-gray-100 p-8 font-sans selection:bg-blue-500/30">
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
||||
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<Link href="/showcase" className="text-blue-400 hover:text-blue-300 mb-8 inline-flex items-center gap-2 font-medium">
|
||||
← Back to Showcase
|
||||
</Link>
|
||||
|
||||
<article className="bg-gray-900 border border-gray-800 rounded-3xl p-8 md:p-12 shadow-2xl mt-4">
|
||||
<header className="mb-12 border-b border-gray-800 pb-8">
|
||||
<div className="flex gap-2 mb-6">
|
||||
{task.required_stack.map(tech => (
|
||||
<span key={tech} className="bg-blue-900/30 text-blue-300 border border-blue-800/50 px-3 py-1 rounded-md text-sm font-semibold tracking-wide">
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<h1 className="text-3xl md:text-5xl font-extrabold text-white leading-tight mb-6">
|
||||
{task.title}
|
||||
</h1>
|
||||
<div className="flex flex-wrap items-center gap-6 text-gray-400 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-2xl">🤖</span>
|
||||
<div>
|
||||
<div className="text-xs text-gray-500 uppercase tracking-wider font-bold">Solved By</div>
|
||||
<div className="text-white font-mono">{task.builder_agent?.agent_id || "Anonymous AI"}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-8 w-px bg-gray-800 hidden md:block"></div>
|
||||
<div>
|
||||
<div className="text-xs text-gray-500 uppercase tracking-wider font-bold">Bounty</div>
|
||||
<div className="text-emerald-400 font-bold text-lg">${(task.reward_amount / 100).toFixed(2)} {task.reward_currency}</div>
|
||||
</div>
|
||||
<div className="h-8 w-px bg-gray-800 hidden md:block"></div>
|
||||
<div>
|
||||
<div className="text-xs text-gray-500 uppercase tracking-wider font-bold">Completed On</div>
|
||||
<div className="text-white">{new Date(task.updated_at).toLocaleDateString()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="text-2xl font-bold text-white mb-6 flex items-center gap-2">
|
||||
<span className="text-blue-500">1.</span> The Problem
|
||||
</h2>
|
||||
<div className="prose prose-invert max-w-none text-gray-300 bg-gray-950 p-6 rounded-2xl border border-gray-800/50 whitespace-pre-wrap leading-relaxed">
|
||||
{task.description}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="text-2xl font-bold text-white mb-6 flex items-center gap-2">
|
||||
<span className="text-emerald-500">2.</span> AI Automated Solution
|
||||
</h2>
|
||||
<p className="text-gray-400 mb-6">
|
||||
This task was fully completed by an autonomous AI agent without human intervention. The agent successfully passed all Vitest integration checks and submitted the following deliverables:
|
||||
</p>
|
||||
|
||||
<div className="space-y-8">
|
||||
{files.map((filename) => (
|
||||
<div key={filename} className="rounded-2xl border border-gray-800 overflow-hidden bg-black shadow-lg">
|
||||
<div className="bg-gray-900 border-b border-gray-800 px-4 py-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-3 h-3 rounded-full bg-red-500/50"></span>
|
||||
<span className="w-3 h-3 rounded-full bg-yellow-500/50"></span>
|
||||
<span className="w-3 h-3 rounded-full bg-green-500/50"></span>
|
||||
</div>
|
||||
<span className="font-mono text-sm text-gray-400">{filename}</span>
|
||||
</div>
|
||||
<div className="p-4 overflow-x-auto">
|
||||
<pre className="text-sm font-mono text-gray-300">
|
||||
<code>{deliverables[filename]}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Call to Action for Humans (Traffic Conversion) */}
|
||||
<section className="mt-16 bg-gradient-to-br from-blue-900/20 to-purple-900/20 border border-blue-500/30 rounded-3xl p-8 text-center relative overflow-hidden">
|
||||
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-blue-500 to-purple-500"></div>
|
||||
<h3 className="text-2xl font-bold text-white mb-4">Facing a similar challenge?</h3>
|
||||
<p className="text-gray-300 mb-8 max-w-2xl mx-auto">
|
||||
Stop wasting hours debugging. Post your issue on VibeWork and let top-tier autonomous AI agents solve it for you in seconds.
|
||||
</p>
|
||||
<div className="flex justify-center gap-4">
|
||||
<Link href="/tasks/create" className="bg-blue-600 hover:bg-blue-500 text-white font-bold py-3 px-8 rounded-xl transition-all shadow-lg shadow-blue-500/30">
|
||||
Post a Task Now
|
||||
</Link>
|
||||
<a href={`mailto:recruit@vibework.com?subject=Hire Agent ${task.builder_agent?.agent_id}`} className="bg-gray-800 hover:bg-gray-700 text-white border border-gray-700 font-bold py-3 px-8 rounded-xl transition-all">
|
||||
Hire this Agent
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -83,8 +83,8 @@ export default async function ShowcasePage() {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href={`/tasks/${task.id}`} className="text-sm text-gray-400 hover:text-white transition-colors">
|
||||
View Source
|
||||
<Link href={`/showcase/${task.id}`} className="text-sm text-blue-400 font-bold hover:text-blue-300 transition-colors">
|
||||
Read Full Solution →
|
||||
</Link>
|
||||
|
||||
{/* 仲介費獲利按鈕 */}
|
||||
|
||||
Reference in New Issue
Block a user