mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-10 17:24:45 +08:00
feat: support static website
This commit is contained in:
26
frontend/src/app/mock/api/mcp/config/route.ts
Normal file
26
frontend/src/app/mock/api/mcp/config/route.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export function GET() {
|
||||
return Response.json({
|
||||
mcp_servers: {
|
||||
"mcp-github-trending": {
|
||||
enabled: true,
|
||||
type: "stdio",
|
||||
command: "uvx",
|
||||
args: ["mcp-github-trending"],
|
||||
env: {},
|
||||
url: null,
|
||||
headers: {},
|
||||
description:
|
||||
"A MCP server that provides access to GitHub trending repositories and developers data",
|
||||
},
|
||||
"context-7": {
|
||||
enabled: true,
|
||||
description:
|
||||
"Get the latest documentation and code into Cursor, Claude, or other LLMs",
|
||||
},
|
||||
"feishu-importer": {
|
||||
enabled: true,
|
||||
description: "Import Feishu documents",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
30
frontend/src/app/mock/api/models/route.ts
Normal file
30
frontend/src/app/mock/api/models/route.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export function GET() {
|
||||
return Response.json({
|
||||
models: [
|
||||
{
|
||||
id: "doubao-seed-1.8",
|
||||
name: "doubao-seed-1.8",
|
||||
display_name: "Doubao Seed 1.8",
|
||||
supports_thinking: true,
|
||||
},
|
||||
{
|
||||
id: "deepseek-v3.2",
|
||||
name: "deepseek-v3.2",
|
||||
display_name: "DeepSeek v3.2",
|
||||
supports_thinking: true,
|
||||
},
|
||||
{
|
||||
id: "gpt-5",
|
||||
name: "gpt-5",
|
||||
display_name: "GPT-5",
|
||||
supports_thinking: true,
|
||||
},
|
||||
{
|
||||
id: "gemini-3-pro",
|
||||
name: "gemini-3-pro",
|
||||
display_name: "Gemini 3 Pro",
|
||||
supports_thinking: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
70
frontend/src/app/mock/api/skills/route.ts
Normal file
70
frontend/src/app/mock/api/skills/route.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
export function GET() {
|
||||
return Response.json({
|
||||
skills: [
|
||||
{
|
||||
name: "frontend-design",
|
||||
description:
|
||||
"Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.",
|
||||
license: "Complete terms in LICENSE.txt",
|
||||
category: "public",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: "pdf-processing",
|
||||
description:
|
||||
"Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale.",
|
||||
license: "Proprietary. LICENSE.txt has complete terms",
|
||||
category: "public",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: "vercel-deploy",
|
||||
description:
|
||||
'Deploy applications and websites to Vercel. Use this skill when the user requests deployment actions such as "Deploy my app", "Deploy this to production", "Create a preview deployment", "Deploy and give me the link", or "Push this live". No authentication required - returns preview URL and claimable deployment link.',
|
||||
license: null,
|
||||
category: "public",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: "web-design-guidelines",
|
||||
description:
|
||||
'Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".',
|
||||
license: null,
|
||||
category: "public",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: "cartoon-generator",
|
||||
description:
|
||||
'Generate cartoon images based on a description. Use when asked to "generate a cartoon image", "create a cartoon", "draw a cartoon", or "generate a cartoon image based on a description".',
|
||||
license: null,
|
||||
category: "custom",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: "podcast-generator",
|
||||
description:
|
||||
'Generate a podcast episode based on a topic. Use when asked to "generate a podcast episode", "create a podcast episode", "generate a podcast episode based on a topic", or "generate a podcast episode based on a description".',
|
||||
license: null,
|
||||
category: "custom",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: "advanced-data-analysis",
|
||||
description:
|
||||
'Perform advanced data analysis and visualization. Use when asked to "analyze data", "visualize data", "analyze data based on a description", or "visualize data based on a description".',
|
||||
license: null,
|
||||
category: "custom",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: "3d-model-generator",
|
||||
description:
|
||||
'Generate 3D models based on a description. Use when asked to "generate a 3D model", "create a 3D model", "generate a 3D model based on a description", or "generate a 3D model based on a description".',
|
||||
license: null,
|
||||
category: "custom",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{
|
||||
thread_id: string;
|
||||
artifact_path?: string[] | undefined;
|
||||
}>;
|
||||
},
|
||||
) {
|
||||
const threadId = (await params).thread_id;
|
||||
let artifactPath = (await params).artifact_path?.join("/") ?? "";
|
||||
if (artifactPath.startsWith("mnt/")) {
|
||||
artifactPath = path.resolve(
|
||||
process.cwd(),
|
||||
artifactPath.replace("mnt/", `public/demo/threads/${threadId}/`),
|
||||
);
|
||||
if (fs.existsSync(artifactPath)) {
|
||||
if (request.nextUrl.searchParams.get("download") === "true") {
|
||||
// Attach the file to the response
|
||||
const headers = new Headers();
|
||||
headers.set(
|
||||
"Content-Disposition",
|
||||
`attachment; filename="${artifactPath}"`,
|
||||
);
|
||||
return new Response(fs.readFileSync(artifactPath), {
|
||||
status: 200,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
return new Response(fs.readFileSync(artifactPath), { status: 200 });
|
||||
}
|
||||
}
|
||||
return new Response("File not found", { status: 404 });
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ thread_id: string }> },
|
||||
) {
|
||||
const threadId = (await params).thread_id;
|
||||
const jsonString = fs.readFileSync(
|
||||
path.resolve(process.cwd(), `public/demo/threads/${threadId}/thread.json`),
|
||||
"utf8",
|
||||
);
|
||||
const json = JSON.parse(jsonString);
|
||||
if (Array.isArray(json.history)) {
|
||||
return Response.json(json);
|
||||
}
|
||||
return Response.json([json]);
|
||||
}
|
||||
27
frontend/src/app/mock/api/threads/search/route.ts
Normal file
27
frontend/src/app/mock/api/threads/search/route.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
export function POST() {
|
||||
const threadsDir = fs.readdirSync(
|
||||
path.resolve(process.cwd(), "public/demo/threads"),
|
||||
{
|
||||
withFileTypes: true,
|
||||
},
|
||||
);
|
||||
const threadData = threadsDir
|
||||
.map((threadId) => {
|
||||
if (threadId.isDirectory() && !threadId.name.startsWith(".")) {
|
||||
const threadData = fs.readFileSync(
|
||||
path.resolve(`public/demo/threads/${threadId.name}/thread.json`),
|
||||
"utf8",
|
||||
);
|
||||
return {
|
||||
thread_id: threadId.name,
|
||||
values: JSON.parse(threadData).values,
|
||||
};
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.filter(Boolean);
|
||||
return Response.json(threadData);
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import { type AgentThread } from "@/core/threads";
|
||||
import { useSubmitThread, useThreadStream } from "@/core/threads/hooks";
|
||||
import { pathOfThread, titleOfThread } from "@/core/threads/utils";
|
||||
import { uuid } from "@/core/utils/uuid";
|
||||
import { env } from "@/env";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export default function ChatPage() {
|
||||
@@ -176,12 +177,18 @@ export default function ChatPage() {
|
||||
status={thread.isLoading ? "streaming" : "ready"}
|
||||
context={settings.context}
|
||||
extraHeader={isNewThread && <Welcome />}
|
||||
disabled={env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true"}
|
||||
onContextChange={(context) =>
|
||||
setSettings("context", context)
|
||||
}
|
||||
onSubmit={handleSubmit}
|
||||
onStop={handleStop}
|
||||
/>
|
||||
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" && (
|
||||
<div className="text-muted-foreground/67 w-full -translate-y-2 text-center text-xs">
|
||||
{t.common.notAvailableInDemoMode}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -51,9 +51,11 @@ export default function ChatsPage() {
|
||||
<div>
|
||||
<div>{titleOfThread(thread)}</div>
|
||||
</div>
|
||||
<div className="text-muted-foreground text-sm">
|
||||
{formatTimeAgo(thread.updated_at)}
|
||||
</div>
|
||||
{thread.updated_at && (
|
||||
<div className="text-muted-foreground text-sm">
|
||||
{formatTimeAgo(thread.updated_at)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { env } from "@/env";
|
||||
|
||||
export default function WorkspacePage() {
|
||||
if (env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true") {
|
||||
const firstThread = fs
|
||||
.readdirSync(path.resolve(process.cwd(), "public/demo/threads"), {
|
||||
withFileTypes: true,
|
||||
})
|
||||
.find((thread) => thread.isDirectory() && !thread.name.startsWith("."));
|
||||
if (firstThread) {
|
||||
return redirect(`/workspace/chats/${firstThread.name}`);
|
||||
}
|
||||
}
|
||||
return redirect("/workspace/chats/new");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user