diff --git a/frontend/Makefile b/frontend/Makefile index 48d23b97b..bf6c351e2 100644 --- a/frontend/Makefile +++ b/frontend/Makefile @@ -18,3 +18,7 @@ lint: format: pnpm format:write + +build-static: + NEXT_CONFIG_BUILD_OUTPUT=standalone SKIP_ENV_VALIDATION=1 NEXT_PUBLIC_STATIC_WEBSITE_ONLY=true pnpm build + @if [ -d .next/static ]; then mkdir -p .next/standalone/.next && cp -R .next/static .next/standalone/.next/static; fi diff --git a/frontend/next.config.js b/frontend/next.config.js index 5b20aad5f..7007d59fc 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -16,6 +16,10 @@ const withNextra = nextra({}); /** @type {import("next").NextConfig} */ const config = { + output: + process.env.NEXT_CONFIG_BUILD_OUTPUT === "standalone" + ? "standalone" + : undefined, i18n: { locales: ["en", "zh"], defaultLocale: "en", diff --git a/frontend/public/demo/threads/7f9dc56c-e49c-4671-a3d2-c492ff4dce0c/user-data/outputs/leica-master-photography-article.md b/frontend/public/demo/threads/7f9dc56c-e49c-4671-a3d2-c492ff4dce0c/user-data/outputs/leica-master-photography-article.md index 6735fb56f..75e82aec4 100644 --- a/frontend/public/demo/threads/7f9dc56c-e49c-4671-a3d2-c492ff4dce0c/user-data/outputs/leica-master-photography-article.md +++ b/frontend/public/demo/threads/7f9dc56c-e49c-4671-a3d2-c492ff4dce0c/user-data/outputs/leica-master-photography-article.md @@ -32,7 +32,7 @@ Even with digital Leicas, photographers often emulate film characteristics: natu ### Image 1: Parisian Decisive Moment -![Paris Decisive Moment](/frontend/public/demo/threads/7f9dc56c-e49c-4671-a3d2-c492ff4dce0c/user-data/outputs/leica-paris-decisive-moment.jpg) +![Paris Decisive Moment](/demo/threads/7f9dc56c-e49c-4671-a3d2-c492ff4dce0c/user-data/outputs/leica-paris-decisive-moment.jpg) This image captures the essence of Cartier-Bresson's philosophy. A woman in a red coat leaps over a puddle while a cyclist passes in perfect synchrony. The composition follows the rule of thirds, with the subject positioned at the intersection of grid lines. Shot with a simulated Leica M11 and 35mm Summicron lens at f/2.8, the image features shallow depth of field, natural film grain, and the warm, muted color palette characteristic of Leica photography. @@ -40,7 +40,7 @@ The "decisive moment" here isn't just about timing—it's about the alignment of ### Image 2: Tokyo Night Reflections -![Tokyo Night Scene](/frontend/public/demo/threads/7f9dc56c-e49c-4671-a3d2-c492ff4dce0c/user-data/outputs/leica-tokyo-night.jpg) +![Tokyo Night Scene](/demo/threads/7f9dc56c-e49c-4671-a3d2-c492ff4dce0c/user-data/outputs/leica-tokyo-night.jpg) Moving to Shinjuku, Tokyo, this image explores the atmospheric possibilities of Leica's legendary Noctilux lens. Simulating a Leica M10-P with a 50mm f/0.95 Noctilux wide open, the image creates extremely shallow depth of field with beautiful bokeh balls from neon signs reflected in wet pavement. @@ -48,7 +48,7 @@ A salaryman waits under glowing kanji signs, steam rising from a nearby ramen sh ### Image 3: New York City Candid -![NYC Candid Scene](/frontend/public/demo/threads/7f9dc56c-e49c-4671-a3d2-c492ff4dce0c/user-data/outputs/leica-nyc-candid.jpg) +![NYC Candid Scene](/demo/threads/7f9dc56c-e49c-4671-a3d2-c492ff4dce0c/user-data/outputs/leica-nyc-candid.jpg) This Chinatown scene demonstrates the documentary power of Leica's Q2 camera with its fixed 28mm Summilux lens. The wide angle captures environmental context while maintaining intimate proximity to the subjects. A fishmonger hands a live fish to a customer while tourists photograph the scene—a moment of cultural contrast and authentic urban life. diff --git a/frontend/src/app/workspace/chats/[thread_id]/layout.tsx b/frontend/src/app/workspace/chats/[thread_id]/layout.tsx index 877103774..b855034ec 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/layout.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/layout.tsx @@ -1,19 +1,15 @@ -"use client"; +import { DEMO_THREAD_IDS } from "@/core/threads/static-demo"; -import { PromptInputProvider } from "@/components/ai-elements/prompt-input"; -import { ArtifactsProvider } from "@/components/workspace/artifacts"; -import { SubtasksProvider } from "@/core/tasks/context"; +import { ChatProviders } from "./providers"; + +export function generateStaticParams() { + return DEMO_THREAD_IDS.map((thread_id) => ({ thread_id })); +} export default function ChatLayout({ children, }: { children: React.ReactNode; }) { - return ( - - - {children} - - - ); + return {children}; } diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 6f865ade8..ce3912b91 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -227,6 +227,7 @@ export default function ChatPage() { isWelcomeMode && } disabled={ + isMock || env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" || isUploading } diff --git a/frontend/src/app/workspace/chats/[thread_id]/providers.tsx b/frontend/src/app/workspace/chats/[thread_id]/providers.tsx new file mode 100644 index 000000000..46d4a4cef --- /dev/null +++ b/frontend/src/app/workspace/chats/[thread_id]/providers.tsx @@ -0,0 +1,15 @@ +"use client"; + +import { PromptInputProvider } from "@/components/ai-elements/prompt-input"; +import { ArtifactsProvider } from "@/components/workspace/artifacts"; +import { SubtasksProvider } from "@/core/tasks/context"; + +export function ChatProviders({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ); +} diff --git a/frontend/src/app/workspace/layout.tsx b/frontend/src/app/workspace/layout.tsx index c2d567339..0d214f0d3 100644 --- a/frontend/src/app/workspace/layout.tsx +++ b/frontend/src/app/workspace/layout.tsx @@ -43,12 +43,14 @@ export default async function WorkspaceLayout({ > Retry - - Logout & Reset - +
+ +
); diff --git a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx index 17e642fc5..1851223a1 100644 --- a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -83,7 +83,7 @@ export function ArtifactFileDetail({ const isSupportPreview = useMemo(() => { return language === "html" || language === "markdown"; }, [language]); - const { content } = useArtifactContent({ + const { content, url } = useArtifactContent({ threadId, filepath: filepathFromProps, enabled: isCodeFile && !isWriteFile, @@ -254,7 +254,9 @@ export function ArtifactFileDetail({ (language === "markdown" || language === "html") && ( )} {isCodeFile && viewMode === "code" && ( @@ -277,28 +279,15 @@ export function ArtifactFileDetail({ export function ArtifactFilePreview({ content, + isWriteFile, language, + url, }: { content: string; + isWriteFile: boolean; language: string; + url?: string; }) { - const [htmlPreviewUrl, setHtmlPreviewUrl] = useState(); - - useEffect(() => { - if (language !== "html") { - setHtmlPreviewUrl(undefined); - return; - } - - const blob = new Blob([content ?? ""], { type: "text/html" }); - const url = URL.createObjectURL(blob); - setHtmlPreviewUrl(url); - - return () => { - URL.revokeObjectURL(url); - }; - }, [content, language]); - if (language === "markdown") { return (
@@ -317,8 +306,13 @@ export function ArtifactFilePreview({