From 60cb7d160b7c56d709bf825632d21de0e622bef6 Mon Sep 17 00:00:00 2001 From: Kabochar Date: Tue, 4 Aug 2026 23:15:48 +0800 Subject: [PATCH] feat: integrate pi theme JSON system with theme picker - Add lib/theme.ts parser that resolves pi CLI theme JSON (~/.pi/agent/themes/) into CSS variables with WCAG contrast guarantees for text, accent and border - Add /api/themes discovery and /api/themes/[name] resolution routes - Extend useTheme with themeName state, setTheme and CSS variable application - Add top-bar theme picker panel listing all available theme sets - Add prism-theme.ts so syntax highlighting follows theme CSS variables - Derive --composer-focus-bg from theme surface colors (Kabochar-specific) - Differentiate sidebar, user bubbles, selected state and borders per theme - Add node:test suite (9 tests) covering parsing, pairing and contrast levels --- app/api/themes/[name]/route.ts | 33 ++ app/api/themes/route.ts | 16 + app/globals.css | 12 + app/layout.tsx | 2 +- components/AppShell.tsx | 154 +++++++++- components/FileExplorer.tsx | 2 +- components/FileViewer.tsx | 7 +- components/MermaidBlock.tsx | 6 +- components/MessageView.tsx | 4 +- hooks/useTheme.ts | 126 +++++++- lib/i18n/messages/en.ts | 3 + lib/i18n/messages/zh-CN.ts | 3 + lib/prism-theme.ts | 50 +++ lib/theme.test.mjs | 215 +++++++++++++ lib/theme.ts | 541 +++++++++++++++++++++++++++++++++ 15 files changed, 1142 insertions(+), 32 deletions(-) create mode 100644 app/api/themes/[name]/route.ts create mode 100644 app/api/themes/route.ts create mode 100644 lib/prism-theme.ts create mode 100644 lib/theme.test.mjs create mode 100644 lib/theme.ts diff --git a/app/api/themes/[name]/route.ts b/app/api/themes/[name]/route.ts new file mode 100644 index 000000000..5db73f2fe --- /dev/null +++ b/app/api/themes/[name]/route.ts @@ -0,0 +1,33 @@ +import { NextRequest, NextResponse } from "next/server"; +import { resolveTheme, type ThemeVariant } from "@/lib/theme"; + +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ name: string }> }, +) { + try { + const { name } = await params; + const { searchParams } = new URL(request.url); + const mode = (searchParams.get("mode") || "dark") as ThemeVariant; + + const resolved = resolveTheme( + decodeURIComponent(name), + mode === "light" ? "light" : "dark", + ); + + if (!resolved) { + return NextResponse.json( + { error: `Theme "${name}" variant "${mode}" not found` }, + { status: 404 }, + ); + } + + return NextResponse.json(resolved); + } catch (error) { + console.error("Failed to resolve theme:", error); + return NextResponse.json( + { error: "Failed to resolve theme" }, + { status: 500 }, + ); + } +} diff --git a/app/api/themes/route.ts b/app/api/themes/route.ts new file mode 100644 index 000000000..8e2829d70 --- /dev/null +++ b/app/api/themes/route.ts @@ -0,0 +1,16 @@ +import { NextResponse } from "next/server"; +import { listThemeSets } from "@/lib/theme"; + +export async function GET() { + try { + const themeSets = listThemeSets(); + + return NextResponse.json({ themeSets }); + } catch (error) { + console.error("Failed to list themes:", error); + return NextResponse.json( + { error: "Failed to list themes" }, + { status: 500 }, + ); + } +} diff --git a/app/globals.css b/app/globals.css index 1c0e917bd..df4f55e05 100644 --- a/app/globals.css +++ b/app/globals.css @@ -23,12 +23,18 @@ --bg-panel: #f5f5f5; --bg-hover: #eeeeee; --bg-selected: #e8e8e8; + --bg-sidebar: #eeeeee; --border: #e0e0e0; --text: #1a1a1a; --text-muted: #6b7280; --text-dim: #9ca3af; --accent: #2563eb; --accent-hover: #1d4ed8; + /* 语法高亮语义色(prism-theme 引用;主题 JSON 映射会覆盖) */ + --accent-blue: #2563eb; + --accent-red: #dc2626; + --accent-green: #16a34a; + --accent-orange: #d97706; --user-bg: #eff6ff; --assistant-bg: #ffffff; --tool-bg: #f9fafb; @@ -40,12 +46,18 @@ html.dark { --bg-panel: #242424; --bg-hover: #2e2e2e; --bg-selected: #383838; + --bg-sidebar: #2e2e2e; --border: #3a3a3a; --text: #e8e8e8; --text-muted: #9ca3af; --text-dim: #6b7280; --accent: #60a5fa; --accent-hover: #93c5fd; + /* 语法高亮语义色(prism-theme 引用;主题 JSON 映射会覆盖) */ + --accent-blue: #60a5fa; + --accent-red: #f87171; + --accent-green: #4ade80; + --accent-orange: #fb923c; --user-bg: #1e293b; --assistant-bg: #1a1a1a; --tool-bg: #1f2937; diff --git a/app/layout.tsx b/app/layout.tsx index 054989f56..60ed55931 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -63,7 +63,7 @@ export default function RootLayout({