diff --git a/app/api/files/[...path]/route.ts b/app/api/files/[...path]/route.ts index 12e69683..7f0d5f4c 100644 --- a/app/api/files/[...path]/route.ts +++ b/app/api/files/[...path]/route.ts @@ -13,6 +13,7 @@ const IGNORED_SUFFIXES = [".pyc"]; const TEXT_PREVIEW_MAX_BYTES = 256 * 1024; const IMAGE_PREVIEW_MAX_BYTES = 10 * 1024 * 1024; +const DOCX_PREVIEW_MAX_BYTES = 10 * 1024 * 1024; const IMAGE_EXT_TO_MIME: Record = { png: "image/png", @@ -39,6 +40,11 @@ const AUDIO_EXT_TO_MIME: Record = { webm: "audio/webm", }; +const DOCUMENT_EXT_TO_MIME: Record = { + pdf: "application/pdf", + docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", +}; + function getExt(filePath: string): string { const ext = path.basename(filePath).toLowerCase().split(".").pop() ?? ""; return ext; @@ -52,6 +58,10 @@ function getAudioMime(filePath: string): string | null { return AUDIO_EXT_TO_MIME[getExt(filePath)] ?? null; } +function getDocumentMime(filePath: string): string | null { + return DOCUMENT_EXT_TO_MIME[getExt(filePath)] ?? null; +} + const EXT_TO_LANGUAGE: Record = { ts: "typescript", tsx: "typescript", js: "javascript", jsx: "javascript", mjs: "javascript", cjs: "javascript", py: "python", rb: "ruby", @@ -64,6 +74,7 @@ const EXT_TO_LANGUAGE: Record = { sql: "sql", graphql: "graphql", gql: "graphql", dockerfile: "dockerfile", tf: "hcl", hcl: "hcl", env: "bash", gitignore: "bash", txt: "text", + pdf: "pdf", docx: "word", }; function getLanguage(filePath: string): string { @@ -187,11 +198,24 @@ function createFileBodyStream(filePath: string, range?: { start: number; end: nu }); } +function encodeHeaderValue(value: string): string { + return encodeURIComponent(value).replace(/[!'()*]/g, (ch) => + `%${ch.charCodeAt(0).toString(16).toUpperCase()}` + ); +} + +function getContentDisposition(filePath: string): string { + const fileName = path.basename(filePath); + const fallback = fileName.replace(/[^\x20-\x7E]|["\\;\r\n]/g, "_") || "download"; + return `inline; filename="${fallback}"; filename*=UTF-8''${encodeHeaderValue(fileName)}`; +} + function streamFile(filePath: string, stat: fs.Stats, contentType: string, rangeHeader: string | null): Response { const headers = { "Content-Type": contentType, "Cache-Control": "no-cache", "Accept-Ranges": "bytes", + "Content-Disposition": getContentDisposition(filePath), }; if (!rangeHeader) { @@ -244,6 +268,71 @@ function streamFile(filePath: string, stat: fs.Stats, contentType: string, range }); } +function documentPreviewKind(filePath: string): "pdf" | "docx" | null { + const ext = getExt(filePath); + if (ext === "pdf") return "pdf"; + if (ext === "docx") return "docx"; + return null; +} + +function escapeHtml(text: string): string { + return text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +function wrapDocxPreviewHtml(bodyHtml: string, fileName: string): string { + return ` + + + + + + + +
+
${escapeHtml(fileName)}
+${bodyHtml} +
+ +`; +} + export async function GET( request: NextRequest, { params }: { params: Promise<{ path: string[] }> } @@ -280,6 +369,10 @@ export async function GET( if (audioMime) { return streamFile(filePath, stat, audioMime, request.headers.get("range")); } + const documentMime = getDocumentMime(filePath); + if (documentMime) { + return streamFile(filePath, stat, documentMime, request.headers.get("range")); + } if (stat.size > TEXT_PREVIEW_MAX_BYTES) { return NextResponse.json({ error: "File too large for preview (>256KB)" }, { status: 413 }); } @@ -288,6 +381,52 @@ export async function GET( return NextResponse.json({ content, language, size: stat.size }); } + if (type === "meta") { + if (!stat.isFile()) { + return NextResponse.json({ error: "Not a file" }, { status: 400 }); + } + const imageMime = getImageMime(filePath); + const audioMime = getAudioMime(filePath); + const documentMime = getDocumentMime(filePath); + return NextResponse.json({ + size: stat.size, + language: getLanguage(filePath), + mime: imageMime || audioMime || documentMime || "text/plain", + previewKind: documentPreviewKind(filePath), + }); + } + + if (type === "preview") { + if (!stat.isFile()) { + return NextResponse.json({ error: "Not a file" }, { status: 400 }); + } + if (getExt(filePath) !== "docx") { + return NextResponse.json({ error: "Preview not available for this file type" }, { status: 400 }); + } + if (stat.size > DOCX_PREVIEW_MAX_BYTES) { + return NextResponse.json({ error: "DOCX too large for preview (>10MB)" }, { status: 413 }); + } + + const mammoth = await import("mammoth"); + const result = await mammoth.convertToHtml( + { path: filePath }, + { + externalFileAccess: false, + convertImage: mammoth.images.dataUri, + } + ); + const html = wrapDocxPreviewHtml(result.value, path.basename(filePath)); + return new Response(html, { + headers: { + "Content-Type": "text/html; charset=utf-8", + "Cache-Control": "no-cache", + "Content-Security-Policy": "default-src 'none'; img-src data:; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'self'", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }, + }); + } + if (type === "watch") { if (!stat.isFile()) { return NextResponse.json({ error: "Not a file" }, { status: 400 }); diff --git a/app/api/sessions/[id]/route.ts b/app/api/sessions/[id]/route.ts index 07542cc8..166bc116 100644 --- a/app/api/sessions/[id]/route.ts +++ b/app/api/sessions/[id]/route.ts @@ -10,6 +10,41 @@ import { } from "@/lib/session-reader"; import { getRpcSession } from "@/lib/rpc-manager"; +/** + * Compress linear single-child chains to prevent JSON.stringify stack overflow. + * Preserves the root node, branch anchors, and terminal leaves while + * contracting intermediate chains via iterative descent (no stack growth). + */ +function compressTree(nodes: T[]): T[] { + function walk(node: T): T { + // Leaf: return as-is + if (node.children.length === 0) { + return node; + } + + // Linear single-child chain: skip the middle nodes iteratively + if (node.children.length === 1) { + let next = node.children[0]; + while (next.children.length === 1) { + next = next.children[0]; + } + // Keep current node (chain head), connect directly to the tail + return { + ...node, + children: [walk(next)], + }; + } + + // Branch point: recurse on each child + return { + ...node, + children: node.children.map(walk), + }; + } + + return nodes.map(walk); +} + export async function GET( req: Request, { params }: { params: Promise<{ id: string }> } @@ -23,8 +58,8 @@ export async function GET( const sm = SessionManager.open(filePath); const entries = sm.getEntries() as never; - const tree = sm.getTree(); const leafId = sm.getLeafId(); + const tree = compressTree(sm.getTree()); const context = buildSessionContext(entries, leafId); const header = sm.getHeader(); @@ -66,8 +101,8 @@ export async function GET( sessionId: id, filePath, info, - tree, leafId, + tree, context, ...(agentState !== undefined ? { agentState } : {}), }); diff --git a/app/api/tools/route.ts b/app/api/tools/route.ts new file mode 100644 index 00000000..50a818ed --- /dev/null +++ b/app/api/tools/route.ts @@ -0,0 +1,108 @@ +import { NextResponse } from "next/server"; +import { existsSync, readFileSync, writeFileSync } from "fs"; +import { join } from "path"; +import { + getAgentDir, + createAgentSession, + SessionManager, +} from "@earendil-works/pi-coding-agent"; + +const SETTINGS_FILE = "settings.json"; +const ACTIVE_TOOLS_KEY = "activeTools"; + +function getSettingsPath(): string { + return join(getAgentDir(), SETTINGS_FILE); +} + +function readActiveTools(): string[] | null { + const path = getSettingsPath(); + if (!existsSync(path)) return null; + try { + const settings = JSON.parse(readFileSync(path, "utf8")); + const tools = settings[ACTIVE_TOOLS_KEY]; + return Array.isArray(tools) && tools.length > 0 ? tools : (Array.isArray(tools) ? [] : null); + } catch { + return null; + } +} + +function writeActiveTools(activeTools: string[]): void { + const path = getSettingsPath(); + let settings: Record = {}; + if (existsSync(path)) { + try { + settings = JSON.parse(readFileSync(path, "utf8")); + } catch {} + } + settings[ACTIVE_TOOLS_KEY] = activeTools; + writeFileSync(path, JSON.stringify(settings, null, 2) + "\n", "utf8"); +} + +// Enumerate all available tools (built-in + extensions) by creating a temp session +async function enumerateTools(cwd: string) { + if (!cwd || !existsSync(cwd)) return []; + + const agentDir = getAgentDir(); + const sessionManager = SessionManager.create(cwd, undefined); + const { session } = await createAgentSession({ + cwd, + agentDir, + sessionManager, + }); + + const allTools: { name: string; description: string; active: boolean }[] = []; + const toolEntries = session.getAllTools?.() ?? []; + const savedActive = readActiveTools(); + const activeSet = savedActive + ? new Set(savedActive) + : new Set(session.getActiveToolNames?.() ?? []); + + for (const t of toolEntries) { + allTools.push({ + name: t.name, + description: t.description ?? "", + active: activeSet.has(t.name), + }); + } + + session.dispose?.(); + return allTools; +} + +// GET /api/tools?cwd=xxx — returns saved config + tool list +export async function GET(req: Request) { + const { searchParams } = new URL(req.url); + const cwd = searchParams.get("cwd"); + + const activeTools = readActiveTools(); + let tools: { name: string; description: string; active: boolean }[] = []; + + if (cwd) { + try { + tools = await enumerateTools(cwd); + } catch (e) { + console.error("Failed to enumerate tools:", e); + } + } + + return NextResponse.json({ config: { activeTools }, tools }); +} + +// POST /api/tools — saves active tools to settings.json +// body: { activeTools: string[] } +export async function POST(req: Request) { + try { + const body = await req.json() as { activeTools: string[] }; + const { activeTools } = body; + if (!Array.isArray(activeTools)) { + return NextResponse.json( + { error: "activeTools must be an array" }, + { status: 400 } + ); + } + writeActiveTools(activeTools); + return NextResponse.json({ success: true }); + } catch (e) { + return NextResponse.json({ error: String(e) }, { status: 500 }); + } +} diff --git a/app/api/version/route.ts b/app/api/version/route.ts new file mode 100644 index 00000000..70821338 --- /dev/null +++ b/app/api/version/route.ts @@ -0,0 +1,30 @@ +import { NextResponse } from "next/server"; +import { readFileSync } from "fs"; +import { join } from "path"; + +export async function GET() { + let piVersion = "unknown"; + try { + const piPkgPath = join( + process.cwd(), + "node_modules/@earendil-works/pi-coding-agent/package.json" + ); + piVersion = JSON.parse(readFileSync(piPkgPath, "utf8")).version; + } catch { + // fallback to build-time env + piVersion = process.env.NEXT_PUBLIC_PI_VERSION ?? "unknown"; + } + + let appVersion = "0.0.0"; + try { + const pkgPath = join(process.cwd(), "package.json"); + appVersion = JSON.parse(readFileSync(pkgPath, "utf8")).version; + } catch { + appVersion = process.env.NEXT_PUBLIC_APP_VERSION ?? "0.0.0"; + } + + return NextResponse.json({ + appVersion, + piVersion, + }); +} diff --git a/app/globals.css b/app/globals.css index 16428710..603fdae3 100644 --- a/app/globals.css +++ b/app/globals.css @@ -153,6 +153,24 @@ pre, code { .markdown-body th { background: var(--bg-panel); font-weight: 600; } .markdown-body hr { border: none; border-top: 1px solid var(--border); margin: 10px 0; } +.mermaid-block { + padding: 12px; + overflow-x: auto; + background: var(--bg); +} +.mermaid-block svg { + display: block; + max-width: none; +} +.mermaid-block-loading { + min-height: 120px; +} +.mermaid-block-error { + color: var(--text-muted); + font-family: var(--font-mono); + font-size: 12px; +} + /* File viewer markdown preview — larger headings */ .markdown-file-preview h1 { font-size: 1.8em; } .markdown-file-preview h2 { font-size: 1.4em; } @@ -285,6 +303,7 @@ span.linenumber { /* Mobile sidebar: slide in/out as overlay */ @media (max-width: 640px) { +.typewriter-hint { display: none !important; } .chat-stats-center { display: none !important; } @@ -322,5 +341,3 @@ span.linenumber { display: none !important; } } - - diff --git a/bun.lock b/bun.lock index 143b024a..fab52b28 100644 --- a/bun.lock +++ b/bun.lock @@ -9,6 +9,7 @@ "@earendil-works/pi-coding-agent": "^0.75.5", "@lobehub/icons": "^5.6.0", "@types/react-syntax-highlighter": "^15.5.13", + "mammoth": "^1.12.0", "next": "16.2.1", "react": "^19.2.4", "react-dom": "^19.2.4", @@ -784,6 +785,8 @@ "@use-gesture/react": ["@use-gesture/react@10.3.1", "", { "dependencies": { "@use-gesture/core": "10.3.1" }, "peerDependencies": { "react": ">= 16.8.0" } }, "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g=="], + "@xmldom/xmldom": ["@xmldom/xmldom@0.8.13", "", {}, "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw=="], + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -800,7 +803,7 @@ "antd-style": ["antd-style@4.1.0", "", { "dependencies": { "@ant-design/cssinjs": "^2.0.0", "@babel/runtime": "^7.24.1", "@emotion/cache": "^11.11.0", "@emotion/css": "^11.11.2", "@emotion/react": "^11.11.4", "@emotion/serialize": "^1.1.3", "@emotion/utils": "^1.2.1", "use-merge-value": "^1.2.0" }, "peerDependencies": { "antd": ">=6.0.0", "react": ">=18" } }, "sha512-vnPBGg0OVlSz90KRYZhxd89aZiOImTiesF+9MQqN8jsLGZUQTjbP04X9jTdEfsztKUuMbBWg/RmB/wHTakbtMQ=="], - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], @@ -848,6 +851,8 @@ "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + "bluebird": ["bluebird@3.4.7", "", {}, "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="], + "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], @@ -912,6 +917,8 @@ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], "cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="], @@ -1026,10 +1033,14 @@ "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + "dingbat-to-unicode": ["dingbat-to-unicode@1.0.1", "", {}, "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w=="], + "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], "dompurify": ["dompurify@3.4.2", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA=="], + "duck": ["duck@0.1.12", "", { "dependencies": { "underscore": "^1.13.1" } }, "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], @@ -1280,6 +1291,8 @@ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], + "immer": ["immer@11.1.7", "", {}, "sha512-LFVFtAROHcDy1er5UI6nodRFnZ2SgdCXhfNSI+DpObO8N7Pur/muBGsjzH5wpnFHCYhYVQxZskCkV4koQ//3/Q=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -1288,6 +1301,8 @@ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], @@ -1366,7 +1381,7 @@ "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="], - "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], @@ -1402,6 +1417,8 @@ "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], + "jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="], + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], @@ -1424,6 +1441,8 @@ "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], @@ -1470,6 +1489,8 @@ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "lop": ["lop@0.4.2", "", { "dependencies": { "duck": "^0.1.12", "option": "~0.2.1", "underscore": "^1.13.1" } }, "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw=="], + "lowlight": ["lowlight@1.20.0", "", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="], "lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="], @@ -1480,6 +1501,8 @@ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "mammoth": ["mammoth@1.12.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.6", "argparse": "~1.0.3", "base64-js": "^1.5.1", "bluebird": "~3.4.0", "dingbat-to-unicode": "^1.0.1", "jszip": "^3.7.1", "lop": "^0.4.2", "path-is-absolute": "^1.0.0", "underscore": "^1.13.1", "xmlbuilder": "^10.0.0" }, "bin": { "mammoth": "bin/mammoth" } }, "sha512-cwnK1RIcRdDMi2HRx2EXGYlxqIEh0Oo3bLhorgnsVJi2UkbX1+jKxuBNR9PC5+JaX7EkmJxFPmo6mjLpqShI2w=="], + "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], @@ -1666,6 +1689,8 @@ "openai": ["openai@6.26.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA=="], + "option": ["option@0.2.4", "", {}, "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], @@ -1678,6 +1703,8 @@ "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], + "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], @@ -1694,6 +1721,8 @@ "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="], + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], @@ -1720,6 +1749,8 @@ "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], @@ -1788,6 +1819,8 @@ "react-zoom-pan-pinch": ["react-zoom-pan-pinch@3.7.0", "", { "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA=="], + "readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "recma-build-jsx": ["recma-build-jsx@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew=="], "recma-jsx": ["recma-jsx@1.0.1", "", { "dependencies": { "acorn-jsx": "^5.0.0", "estree-util-to-js": "^2.0.0", "recma-parse": "^1.0.0", "recma-stringify": "^1.0.0", "unified": "^11.0.0" }, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w=="], @@ -1860,7 +1893,7 @@ "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], @@ -1886,6 +1919,8 @@ "set-value": ["set-value@2.0.1", "", { "dependencies": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", "is-plain-object": "^2.0.3", "split-string": "^3.0.1" } }, "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw=="], + "setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="], + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -1916,6 +1951,8 @@ "split-string": ["split-string@3.1.0", "", { "dependencies": { "extend-shallow": "^3.0.0" } }, "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw=="], + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], @@ -1934,6 +1971,8 @@ "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], + "string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], @@ -2006,6 +2045,8 @@ "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], + "underscore": ["underscore@1.13.8", "", {}, "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ=="], + "undici": ["undici@8.3.0", "", {}, "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q=="], "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], @@ -2040,6 +2081,8 @@ "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "uuid": ["uuid@13.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw=="], "v8n": ["v8n@1.5.1", "", {}, "sha512-LdabyT4OffkyXFCe9UT+uMkxNBs5rcTVuZClvxQr08D5TUgo1OFKkoT65qYRCsiKBl/usHjpXvP4hHMzzDRj3A=="], @@ -2084,6 +2127,8 @@ "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="], + "xmlbuilder": ["xmlbuilder@10.1.1", "", {}, "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg=="], + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], @@ -2244,6 +2289,8 @@ "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], + "ecdsa-sig-formatter/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], @@ -2260,6 +2307,12 @@ "is-bun-module/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "jwa/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "jws/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "mermaid/marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], @@ -2284,6 +2337,10 @@ "react-rnd/tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="], + "safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + + "safe-push-apply/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + "set-value/is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="], "sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], @@ -2292,6 +2349,8 @@ "split-string/extend-shallow": ["extend-shallow@3.0.2", "", { "dependencies": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" } }, "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q=="], + "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + "@aws-crypto/crc32/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], "@aws-crypto/sha256-browser/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], diff --git a/components/AppShell.tsx b/components/AppShell.tsx index e123cdff..cf94ec56 100644 --- a/components/AppShell.tsx +++ b/components/AppShell.tsx @@ -8,6 +8,7 @@ import { FileViewer } from "./FileViewer"; import { TabBar, type Tab } from "./TabBar"; import { ModelsConfig } from "./ModelsConfig"; import { SkillsConfig } from "./SkillsConfig"; +import { ToolsConfig } from "./ToolsConfig"; import { BranchNavigator } from "./BranchNavigator"; import { useTheme } from "@/hooks/useTheme"; import type { SessionInfo, SessionTreeNode } from "@/lib/types"; @@ -26,6 +27,7 @@ export function AppShell() { const [modelsConfigOpen, setModelsConfigOpen] = useState(false); const [modelsRefreshKey, setModelsRefreshKey] = useState(0); const [skillsConfigOpen, setSkillsConfigOpen] = useState(false); + const [toolsConfigOpen, setToolsConfigOpen] = useState(false); const [sidebarOpen, setSidebarOpen] = useState(true); const chatInputRef = useRef(null); const topBarRef = useRef(null); @@ -270,6 +272,16 @@ export function AppShell() { ), }, + { + label: "Tools", + onClick: () => setToolsConfigOpen(true), + disabled: false, + icon: ( + + + + ), + }, ] as { label: string; onClick: () => void; disabled: boolean; icon: React.ReactNode }[]).map(({ label, onClick, disabled, icon }) => ( - {toolDropdownOpen && ( -
- {TOOL_PRESETS.map((lvl) => { - const preset = TOOL_PRESET_MAP[lvl]; - const isActive = (toolPreset ?? "default") === preset; - const desc = lvl === "off" ? "无工具,纯聊天" : lvl === "default" ? "4 项内置工具" : "全部内置工具"; - return ( - - ); - })} -
- )} - - )} + {/* Tool preset removed — use sidebar Tools button instead */} {!isStreaming && onCompact && (
diff --git a/components/ChatWindow.tsx b/components/ChatWindow.tsx index 6fc4224a..c9b22be3 100644 --- a/components/ChatWindow.tsx +++ b/components/ChatWindow.tsx @@ -269,18 +269,11 @@ export function ChatWindow({ session, newSessionCwd, onAgentEnd, onSessionCreate
π Pi Agent Web - +
-
- - web v{process.env.NEXT_PUBLIC_APP_VERSION ?? "0.0.0"} - - - pi v{process.env.NEXT_PUBLIC_PI_VERSION ?? "0.0.0"} - -
+
{chatInputElement} @@ -383,4 +376,25 @@ export function ChatWindow({ session, newSessionCwd, onAgentEnd, onSessionCreate )} ); +} + +// Dynamic version display — reads from /api/version at runtime +function VersionDisplay() { + const [versions, setVersions] = useState({ appVersion: "0.0.0", piVersion: "0.0.0" }); + useEffect(() => { + fetch("/api/version") + .then(r => r.json()) + .then(v => setVersions(v)) + .catch(() => {}); + }, []); + return ( +
+ + web v{versions.appVersion} + + + pi v{versions.piVersion} + +
+ ); } \ No newline at end of file diff --git a/components/FileIcons.tsx b/components/FileIcons.tsx index 3dd54a84..6ce7a9a6 100644 --- a/components/FileIcons.tsx +++ b/components/FileIcons.tsx @@ -179,6 +179,12 @@ export function LockFileIcon({ size = 14 }: IconProps) { ); } +export function DocFileIcon({ size = 14 }: IconProps) { + return ; +} +export function PdfFileIcon({ size = 14 }: IconProps) { + return ; +} export function ConfigIcon({ size = 14 }: IconProps) { return ( @@ -235,6 +241,8 @@ export function getFileIcon(name: string, size = 14): React.ReactNode { case "gql": return ; case "tf": case "hcl": return ; + case "docx": return ; + case "pdf": return ; case "lock": return ; default: return ; } diff --git a/components/FileViewer.tsx b/components/FileViewer.tsx index 451183f8..e37660db 100644 --- a/components/FileViewer.tsx +++ b/components/FileViewer.tsx @@ -22,6 +22,8 @@ interface FileData { const IMAGE_EXTS = new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "ico", "avif"]); const AUDIO_EXTS = new Set(["mp3", "wav", "ogg", "oga", "opus", "m4a", "aac", "flac", "weba", "webm"]); +const DOCUMENT_PREVIEW_EXTS = new Set(["pdf", "docx"]); +const DOCX_PREVIEW_MAX_BYTES = 10 * 1024 * 1024; function isImagePath(filePath: string): boolean { const base = getFileName(filePath); @@ -35,6 +37,37 @@ function isAudioPath(filePath: string): boolean { return AUDIO_EXTS.has(ext); } +function getFileExt(filePath: string): string { + return getFileName(filePath).toLowerCase().split(".").pop() ?? ""; +} + +function isDocumentPreviewPath(filePath: string): boolean { + return DOCUMENT_PREVIEW_EXTS.has(getFileExt(filePath)); +} + +function DownloadLink({ filePath, label = "Download" }: { filePath: string; label?: string }) { + const encoded = encodeFilePathForApi(filePath); + return ( + + {label} + + ); +} + type DiffLine = | { type: "unchanged"; text: string; lineNo: number } | { type: "removed"; text: string; lineNo: number } @@ -521,6 +554,128 @@ function AudioViewer({ filePath, cwd }: { filePath: string; cwd?: string }) { ); } +function DocumentViewer({ filePath, cwd }: { filePath: string; cwd?: string }) { + const [watching, setWatching] = useState(false); + const [bust, setBust] = useState(0); + const [size, setSize] = useState(null); + const [error, setError] = useState(null); + const esRef = useRef(null); + + const ext = getFileExt(filePath); + const encoded = encodeFilePathForApi(filePath); + const isPdf = ext === "pdf"; + const previewUrl = isPdf + ? `/api/files/${encoded}?type=read${bust ? `&v=${bust}` : ""}` + : `/api/files/${encoded}?type=preview${bust ? `&v=${bust}` : ""}`; + + useEffect(() => { + setBust(0); + setSize(null); + setError(null); + setWatching(false); + + if (esRef.current) { + esRef.current.close(); + esRef.current = null; + } + + fetch(`/api/files/${encoded}?type=meta`) + .then((r) => r.json()) + .then((d: { size?: number; error?: string }) => { + if (d.error) setError(d.error); + if (typeof d.size === "number") { + setSize(d.size); + if (!isPdf && d.size > DOCX_PREVIEW_MAX_BYTES) { + setError("DOCX too large for preview (>10MB)"); + } + } + }) + .catch((e) => setError(String(e))); + + const es = new EventSource(`/api/files/${encoded}?type=watch`); + esRef.current = es; + + es.addEventListener("connected", () => setWatching(true)); + es.addEventListener("change", (e) => { + try { + const d = JSON.parse((e as MessageEvent).data) as { size?: number }; + if (typeof d.size === "number") { + setSize(d.size); + if (!isPdf && d.size > DOCX_PREVIEW_MAX_BYTES) { + setError("DOCX too large for preview (>10MB)"); + return; + } + } + } catch { /* ignore */ } + setError(null); + setBust((b) => b + 1); + }); + es.addEventListener("error", () => setWatching(false)); + es.onerror = () => setWatching(false); + + return () => { + es.close(); + esRef.current = null; + }; + }, [encoded, isPdf]); + + return ( +
+
+ + {getRelativeFilePath(filePath, cwd)} + + {ext === "docx" ? "docx preview" : "pdf"} + {size != null && {formatSize(size)}} + + + + {watching ? "live" : "static"} + +
+
+ {error ? ( +
+ {error} +
+ ) : ( +