sparkecoder 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +15 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/server/index.js +9 -2
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/page.js +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error/page.js +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page.js +3 -3
- package/web/.next/standalone/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +13 -12
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +13 -12
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +4 -3
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/api/config/route.js +1 -1
- package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/api/health/route.js +1 -1
- package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +20 -19
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +20 -19
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +4 -3
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__36edac7c._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__74ebc442._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_1b669458._.js → 2374f_1d78db71._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_10a13bff._.js → 2374f_30f9df13._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_42ae2ff5._.js → 2374f_378282b1._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_21324e82._.js → 2374f_5de336d2._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ba57e34a._.js → 2374f_8825dcc9._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_47e888fe._.js → 2374f_9bf3c7f3._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_63823b79._.js → 2374f_bbc99511._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_9c15fcde._.js → 2374f_d94c2b70._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__0f6b5fa7._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__e1f2996b._.js → [root-of-the-server]__513c6b45._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__da40fb08._.js → [root-of-the-server]__7f04455b._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__be5e2967._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__10aab2ca._.js → [root-of-the-server]__c3a1e22c._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__d2ce4b79._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__de58a952._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__f18f92f4._.js +10 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_d3c9d897._.js → web_19b6934c._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_96bca05b._.js +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/25a97bfee12ea1f6.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/2f9f08f6c6276b0f.js +1 -0
- package/web/.next/{static/chunks/c7e9604ccdb5cb09.js → standalone/web/.next/static/chunks/5ec82ce8f3aabaf0.js} +3 -3
- package/web/.next/standalone/web/.next/static/static/chunks/25a97bfee12ea1f6.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/2f9f08f6c6276b0f.js +1 -0
- package/web/.next/standalone/web/.next/static/{chunks/c7e9604ccdb5cb09.js → static/chunks/5ec82ce8f3aabaf0.js} +3 -3
- package/web/.next/standalone/web/README.md +63 -0
- package/web/.next/standalone/web/components.json +22 -0
- package/web/.next/standalone/web/eslint.config.mjs +18 -0
- package/web/.next/standalone/web/next.config.ts +9 -0
- package/web/.next/standalone/web/package-lock.json +15061 -0
- package/web/.next/standalone/web/postcss.config.mjs +7 -0
- package/web/.next/standalone/web/runtime-config.json +3 -0
- package/web/.next/standalone/web/src/app/(main)/layout.tsx +22 -0
- package/web/.next/standalone/web/src/app/(main)/page.tsx +230 -0
- package/web/.next/standalone/web/src/app/(main)/session/[id]/page.tsx +64 -0
- package/web/.next/standalone/web/src/app/api/config/route.ts +106 -0
- package/web/.next/standalone/web/src/app/api/health/route.ts +63 -0
- package/web/.next/standalone/web/src/app/apple-icon.png +0 -0
- package/web/.next/standalone/web/src/app/favicon.ico +0 -0
- package/web/.next/standalone/web/src/app/globals.css +311 -0
- package/web/.next/standalone/web/src/app/icon.png +0 -0
- package/web/.next/standalone/web/src/app/layout.tsx +100 -0
- package/web/.next/standalone/web/src/app/opengraph-image.png +0 -0
- package/web/.next/standalone/web/src/app/twitter-image.png +0 -0
- package/web/.next/standalone/web/src/components/ai-elements/agent.tsx +141 -0
- package/web/.next/standalone/web/src/components/ai-elements/artifact.tsx +147 -0
- package/web/.next/standalone/web/src/components/ai-elements/attachments.tsx +421 -0
- package/web/.next/standalone/web/src/components/ai-elements/audio-player.tsx +231 -0
- package/web/.next/standalone/web/src/components/ai-elements/bash-tool.tsx +299 -0
- package/web/.next/standalone/web/src/components/ai-elements/canvas.tsx +22 -0
- package/web/.next/standalone/web/src/components/ai-elements/chain-of-thought.tsx +231 -0
- package/web/.next/standalone/web/src/components/ai-elements/checkpoint.tsx +71 -0
- package/web/.next/standalone/web/src/components/ai-elements/code-block.tsx +529 -0
- package/web/.next/standalone/web/src/components/ai-elements/commit.tsx +448 -0
- package/web/.next/standalone/web/src/components/ai-elements/confirmation.tsx +176 -0
- package/web/.next/standalone/web/src/components/ai-elements/connection.tsx +28 -0
- package/web/.next/standalone/web/src/components/ai-elements/context.tsx +408 -0
- package/web/.next/standalone/web/src/components/ai-elements/controls.tsx +18 -0
- package/web/.next/standalone/web/src/components/ai-elements/conversation.tsx +104 -0
- package/web/.next/standalone/web/src/components/ai-elements/edge.tsx +140 -0
- package/web/.next/standalone/web/src/components/ai-elements/environment-variables.tsx +295 -0
- package/web/.next/standalone/web/src/components/ai-elements/file-tree.tsx +258 -0
- package/web/.next/standalone/web/src/components/ai-elements/image.tsx +24 -0
- package/web/.next/standalone/web/src/components/ai-elements/inline-citation.tsx +287 -0
- package/web/.next/standalone/web/src/components/ai-elements/linter-tool.tsx +330 -0
- package/web/.next/standalone/web/src/components/ai-elements/load-skill-tool.tsx +215 -0
- package/web/.next/standalone/web/src/components/ai-elements/loader.tsx +96 -0
- package/web/.next/standalone/web/src/components/ai-elements/message.tsx +421 -0
- package/web/.next/standalone/web/src/components/ai-elements/mic-selector.tsx +370 -0
- package/web/.next/standalone/web/src/components/ai-elements/model-selector.tsx +211 -0
- package/web/.next/standalone/web/src/components/ai-elements/node.tsx +71 -0
- package/web/.next/standalone/web/src/components/ai-elements/open-in-chat.tsx +365 -0
- package/web/.next/standalone/web/src/components/ai-elements/package-info.tsx +233 -0
- package/web/.next/standalone/web/src/components/ai-elements/panel.tsx +15 -0
- package/web/.next/standalone/web/src/components/ai-elements/persona.tsx +270 -0
- package/web/.next/standalone/web/src/components/ai-elements/plan.tsx +142 -0
- package/web/.next/standalone/web/src/components/ai-elements/prompt-input.tsx +1263 -0
- package/web/.next/standalone/web/src/components/ai-elements/queue.tsx +274 -0
- package/web/.next/standalone/web/src/components/ai-elements/read-file-tool.tsx +251 -0
- package/web/.next/standalone/web/src/components/ai-elements/reasoning.tsx +198 -0
- package/web/.next/standalone/web/src/components/ai-elements/sandbox.tsx +131 -0
- package/web/.next/standalone/web/src/components/ai-elements/schema-display.tsx +458 -0
- package/web/.next/standalone/web/src/components/ai-elements/shimmer.tsx +64 -0
- package/web/.next/standalone/web/src/components/ai-elements/snippet.tsx +139 -0
- package/web/.next/standalone/web/src/components/ai-elements/sources.tsx +77 -0
- package/web/.next/standalone/web/src/components/ai-elements/speech-input.tsx +300 -0
- package/web/.next/standalone/web/src/components/ai-elements/stack-trace.tsx +482 -0
- package/web/.next/standalone/web/src/components/ai-elements/suggestion.tsx +60 -0
- package/web/.next/standalone/web/src/components/ai-elements/task.tsx +87 -0
- package/web/.next/standalone/web/src/components/ai-elements/terminal.tsx +261 -0
- package/web/.next/standalone/web/src/components/ai-elements/test-results.tsx +485 -0
- package/web/.next/standalone/web/src/components/ai-elements/todo-panel.tsx +178 -0
- package/web/.next/standalone/web/src/components/ai-elements/todo-tool.tsx +246 -0
- package/web/.next/standalone/web/src/components/ai-elements/tool.tsx +174 -0
- package/web/.next/standalone/web/src/components/ai-elements/toolbar.tsx +16 -0
- package/web/.next/standalone/web/src/components/ai-elements/transcription.tsx +124 -0
- package/web/.next/standalone/web/src/components/ai-elements/voice-selector.tsx +479 -0
- package/web/.next/standalone/web/src/components/ai-elements/web-preview.tsx +263 -0
- package/web/.next/standalone/web/src/components/ai-elements/write-file-tool.tsx +368 -0
- package/web/.next/standalone/web/src/components/api-init.tsx +21 -0
- package/web/.next/standalone/web/src/components/chat-interface.tsx +2074 -0
- package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +875 -0
- package/web/.next/standalone/web/src/components/ui/accordion.tsx +66 -0
- package/web/.next/standalone/web/src/components/ui/alert.tsx +66 -0
- package/web/.next/standalone/web/src/components/ui/avatar.tsx +109 -0
- package/web/.next/standalone/web/src/components/ui/badge.tsx +48 -0
- package/web/.next/standalone/web/src/components/ui/button-group.tsx +83 -0
- package/web/.next/standalone/web/src/components/ui/button.tsx +64 -0
- package/web/.next/standalone/web/src/components/ui/card.tsx +92 -0
- package/web/.next/standalone/web/src/components/ui/carousel.tsx +241 -0
- package/web/.next/standalone/web/src/components/ui/collapsible.tsx +33 -0
- package/web/.next/standalone/web/src/components/ui/command.tsx +184 -0
- package/web/.next/standalone/web/src/components/ui/dialog.tsx +158 -0
- package/web/.next/standalone/web/src/components/ui/dropdown-menu.tsx +257 -0
- package/web/.next/standalone/web/src/components/ui/hover-card.tsx +44 -0
- package/web/.next/standalone/web/src/components/ui/input-group.tsx +170 -0
- package/web/.next/standalone/web/src/components/ui/input.tsx +22 -0
- package/web/.next/standalone/web/src/components/ui/label.tsx +24 -0
- package/web/.next/standalone/web/src/components/ui/popover.tsx +89 -0
- package/web/.next/standalone/web/src/components/ui/progress.tsx +31 -0
- package/web/.next/standalone/web/src/components/ui/scroll-area.tsx +58 -0
- package/web/.next/standalone/web/src/components/ui/select.tsx +190 -0
- package/web/.next/standalone/web/src/components/ui/separator.tsx +28 -0
- package/web/.next/standalone/web/src/components/ui/sheet.tsx +143 -0
- package/web/.next/standalone/web/src/components/ui/sidebar.tsx +726 -0
- package/web/.next/standalone/web/src/components/ui/skeleton.tsx +13 -0
- package/web/.next/standalone/web/src/components/ui/switch.tsx +35 -0
- package/web/.next/standalone/web/src/components/ui/tabs.tsx +91 -0
- package/web/.next/standalone/web/src/components/ui/textarea.tsx +18 -0
- package/web/.next/standalone/web/src/components/ui/tooltip.tsx +61 -0
- package/web/.next/standalone/web/src/hooks/use-mobile.ts +19 -0
- package/web/.next/standalone/web/src/hooks/use-sessions.ts +28 -0
- package/web/.next/standalone/web/src/lib/api.ts +568 -0
- package/web/.next/standalone/web/src/lib/config.ts +178 -0
- package/web/.next/standalone/web/src/lib/utils.ts +6 -0
- package/web/.next/standalone/web/src/test/api.test.ts +125 -0
- package/web/.next/standalone/web/src/test/setup.ts +1 -0
- package/web/.next/standalone/web/tsconfig.json +43 -0
- package/web/.next/standalone/web/vitest.config.ts +17 -0
- package/web/.next/static/chunks/25a97bfee12ea1f6.js +1 -0
- package/web/.next/static/chunks/2f9f08f6c6276b0f.js +1 -0
- package/web/.next/{standalone/web/.next/static/static/chunks/c7e9604ccdb5cb09.js → static/chunks/5ec82ce8f3aabaf0.js} +3 -3
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__7ba4776a._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__d907af4e._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__3a7ef2b7._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__5369f47d._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__c1f0d54f._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__fd3d4d25._.js +0 -10
- package/web/.next/standalone/web/.next/static/chunks/85dcb2949e032468.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/85dcb2949e032468.js +0 -1
- package/web/.next/static/chunks/85dcb2949e032468.js +0 -1
- /package/web/.next/standalone/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_ssgManifest.js +0 -0
- /package/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_buildManifest.js +0 -0
- /package/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { SidebarProvider, SidebarInset } from '@/components/ui/sidebar';
|
|
4
|
+
import { SessionsSidebar } from '@/components/sessions-sidebar';
|
|
5
|
+
|
|
6
|
+
export default function MainLayout({
|
|
7
|
+
children,
|
|
8
|
+
}: {
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}) {
|
|
11
|
+
return (
|
|
12
|
+
<SidebarProvider defaultOpen={true}>
|
|
13
|
+
<SessionsSidebar />
|
|
14
|
+
<SidebarInset className="h-screen flex flex-col bg-background">
|
|
15
|
+
{/* Main content */}
|
|
16
|
+
<div className="flex-1 overflow-hidden">
|
|
17
|
+
{children}
|
|
18
|
+
</div>
|
|
19
|
+
</SidebarInset>
|
|
20
|
+
</SidebarProvider>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import Image from 'next/image';
|
|
6
|
+
import { Plus, Loader2, Key, ExternalLink, AlertCircle } from 'lucide-react';
|
|
7
|
+
import {
|
|
8
|
+
Tooltip,
|
|
9
|
+
TooltipContent,
|
|
10
|
+
TooltipProvider,
|
|
11
|
+
TooltipTrigger,
|
|
12
|
+
} from '@/components/ui/tooltip';
|
|
13
|
+
import { Button } from '@/components/ui/button';
|
|
14
|
+
import { Input } from '@/components/ui/input';
|
|
15
|
+
import { createSession, setApiKey } from '@/lib/api';
|
|
16
|
+
import { getConfig, type AppConfig } from '@/lib/config';
|
|
17
|
+
import { useSessions, mutateSessions } from '@/hooks/use-sessions';
|
|
18
|
+
|
|
19
|
+
export default function Home() {
|
|
20
|
+
const router = useRouter();
|
|
21
|
+
const { sessions } = useSessions();
|
|
22
|
+
const [creating, setCreating] = useState(false);
|
|
23
|
+
const [config, setConfig] = useState<AppConfig | null>(null);
|
|
24
|
+
const [apiKeyInput, setApiKeyInput] = useState('');
|
|
25
|
+
const [savingKey, setSavingKey] = useState(false);
|
|
26
|
+
const [keyError, setKeyError] = useState<string | null>(null);
|
|
27
|
+
|
|
28
|
+
// Load config for default model
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
getConfig().then(setConfig);
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
// Handle saving API key
|
|
34
|
+
const handleSaveApiKey = async () => {
|
|
35
|
+
if (!apiKeyInput.trim() || savingKey) return;
|
|
36
|
+
setSavingKey(true);
|
|
37
|
+
setKeyError(null);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const result = await setApiKey('ai-gateway', apiKeyInput.trim());
|
|
41
|
+
if (result.success) {
|
|
42
|
+
// Refresh config to update apiKeyConfigured status
|
|
43
|
+
const newConfig = await getConfig();
|
|
44
|
+
setConfig(newConfig);
|
|
45
|
+
setApiKeyInput('');
|
|
46
|
+
} else {
|
|
47
|
+
setKeyError('Failed to save API key. Please try again.');
|
|
48
|
+
}
|
|
49
|
+
} catch (err) {
|
|
50
|
+
setKeyError('Failed to save API key. Please try again.');
|
|
51
|
+
} finally {
|
|
52
|
+
setSavingKey(false);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Handle creating a new session
|
|
57
|
+
const handleCreateSession = async () => {
|
|
58
|
+
if (creating) return;
|
|
59
|
+
setCreating(true);
|
|
60
|
+
try {
|
|
61
|
+
const session = await createSession({
|
|
62
|
+
name: `Session ${sessions.length + 1}`,
|
|
63
|
+
model: config?.defaultModel || 'anthropic/claude-opus-4-5',
|
|
64
|
+
toolApprovals: config?.defaultToolApprovals || {},
|
|
65
|
+
});
|
|
66
|
+
mutateSessions();
|
|
67
|
+
router.push(`/session/${session.id}`);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error('Failed to create session:', err);
|
|
70
|
+
} finally {
|
|
71
|
+
setCreating(false);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Keyboard shortcut: Cmd+T or Ctrl+T to create session
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
78
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 't') {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
handleCreateSession();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
85
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
86
|
+
}, [creating, sessions.length, config]);
|
|
87
|
+
|
|
88
|
+
// Show API key setup if not configured
|
|
89
|
+
if (config && !config.apiKeyConfigured) {
|
|
90
|
+
return (
|
|
91
|
+
<div className="flex items-center justify-center h-full bg-gradient-to-b from-background to-muted/20">
|
|
92
|
+
<div className="text-center max-w-lg mx-auto p-8">
|
|
93
|
+
{/* Mascot */}
|
|
94
|
+
<div className="flex justify-center mb-8">
|
|
95
|
+
<div className="relative size-28 rounded-2xl overflow-hidden shadow-xl ring-1 ring-white/10">
|
|
96
|
+
<Image
|
|
97
|
+
src="/sparke-coder.png"
|
|
98
|
+
alt="Sparke - AI Coding Assistant"
|
|
99
|
+
fill
|
|
100
|
+
className="object-cover"
|
|
101
|
+
priority
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{/* Title */}
|
|
107
|
+
<h1 className="text-3xl font-bold mb-3 text-primary">
|
|
108
|
+
Welcome to SparkECoder
|
|
109
|
+
</h1>
|
|
110
|
+
|
|
111
|
+
{/* API Key Setup */}
|
|
112
|
+
<div className="bg-muted/50 rounded-lg p-6 mb-6 text-left">
|
|
113
|
+
<div className="flex items-center gap-2 mb-3">
|
|
114
|
+
<Key className="size-5 text-amber-500" />
|
|
115
|
+
<h2 className="font-semibold">API Key Required</h2>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
119
|
+
SparkECoder uses the Vercel AI Gateway. Enter your API key to get started.
|
|
120
|
+
</p>
|
|
121
|
+
|
|
122
|
+
<div className="flex gap-2 mb-3">
|
|
123
|
+
<Input
|
|
124
|
+
type="password"
|
|
125
|
+
placeholder="Enter your AI Gateway API key"
|
|
126
|
+
value={apiKeyInput}
|
|
127
|
+
onChange={(e) => setApiKeyInput(e.target.value)}
|
|
128
|
+
onKeyDown={(e) => e.key === 'Enter' && handleSaveApiKey()}
|
|
129
|
+
className="flex-1"
|
|
130
|
+
/>
|
|
131
|
+
<Button
|
|
132
|
+
onClick={handleSaveApiKey}
|
|
133
|
+
disabled={!apiKeyInput.trim() || savingKey}
|
|
134
|
+
>
|
|
135
|
+
{savingKey ? (
|
|
136
|
+
<Loader2 className="size-4 animate-spin" />
|
|
137
|
+
) : (
|
|
138
|
+
'Save'
|
|
139
|
+
)}
|
|
140
|
+
</Button>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
{keyError && (
|
|
144
|
+
<div className="flex items-center gap-2 text-sm text-destructive mb-3">
|
|
145
|
+
<AlertCircle className="size-4" />
|
|
146
|
+
{keyError}
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
<a
|
|
151
|
+
href="https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%2Fapi-keys"
|
|
152
|
+
target="_blank"
|
|
153
|
+
rel="noopener noreferrer"
|
|
154
|
+
className="inline-flex items-center gap-1 text-sm text-primary hover:underline"
|
|
155
|
+
>
|
|
156
|
+
Get an API key from Vercel
|
|
157
|
+
<ExternalLink className="size-3" />
|
|
158
|
+
</a>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div className="flex items-center justify-center h-full bg-gradient-to-b from-background to-muted/20">
|
|
167
|
+
<div className="text-center max-w-lg mx-auto p-8">
|
|
168
|
+
{/* Mascot */}
|
|
169
|
+
<div className="flex justify-center mb-8">
|
|
170
|
+
<TooltipProvider>
|
|
171
|
+
<Tooltip>
|
|
172
|
+
<TooltipTrigger asChild>
|
|
173
|
+
<div className="relative group cursor-pointer">
|
|
174
|
+
{/* Glow effect */}
|
|
175
|
+
<div className="absolute inset-0 rounded-3xl blur-2xl opacity-40 group-hover:opacity-60 transition-opacity bg-primary/30" />
|
|
176
|
+
|
|
177
|
+
{/* Mascot container */}
|
|
178
|
+
<div className="relative size-28 rounded-2xl overflow-hidden shadow-xl ring-1 ring-white/10 hover-lift">
|
|
179
|
+
<Image
|
|
180
|
+
src="/sparke-coder.png"
|
|
181
|
+
alt="Sparke - AI Coding Assistant"
|
|
182
|
+
fill
|
|
183
|
+
className="object-cover sparke-idle"
|
|
184
|
+
priority
|
|
185
|
+
/>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</TooltipTrigger>
|
|
189
|
+
<TooltipContent>Meet Sparke, your coding companion!</TooltipContent>
|
|
190
|
+
</Tooltip>
|
|
191
|
+
</TooltipProvider>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
{/* Title */}
|
|
195
|
+
<h1 className="text-3xl font-bold mb-3 text-primary">
|
|
196
|
+
Welcome to SparkECoder
|
|
197
|
+
</h1>
|
|
198
|
+
|
|
199
|
+
<p className="text-muted-foreground mb-8 leading-relaxed">
|
|
200
|
+
Your AI-powered coding assistant. Start a new session to begin building amazing things.
|
|
201
|
+
</p>
|
|
202
|
+
|
|
203
|
+
{/* Start Session Button */}
|
|
204
|
+
<Button
|
|
205
|
+
size="lg"
|
|
206
|
+
onClick={handleCreateSession}
|
|
207
|
+
disabled={creating}
|
|
208
|
+
className="gap-2 px-8"
|
|
209
|
+
>
|
|
210
|
+
{creating ? (
|
|
211
|
+
<>
|
|
212
|
+
<Loader2 className="size-4 animate-spin" />
|
|
213
|
+
Creating...
|
|
214
|
+
</>
|
|
215
|
+
) : (
|
|
216
|
+
<>
|
|
217
|
+
<Plus className="size-4" />
|
|
218
|
+
Start a Session
|
|
219
|
+
</>
|
|
220
|
+
)}
|
|
221
|
+
</Button>
|
|
222
|
+
|
|
223
|
+
{/* Keyboard shortcut hint */}
|
|
224
|
+
<p className="mt-8 text-xs text-muted-foreground/70">
|
|
225
|
+
Press <kbd className="px-1.5 py-0.5 bg-muted rounded text-[10px] font-mono">⌘</kbd> + <kbd className="px-1.5 py-0.5 bg-muted rounded text-[10px] font-mono">T</kbd> to create a new session
|
|
226
|
+
</p>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { use, useEffect, useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { ChatInterface } from '@/components/chat-interface';
|
|
6
|
+
import { getSession, type Session } from '@/lib/api';
|
|
7
|
+
import { Loader2 } from 'lucide-react';
|
|
8
|
+
|
|
9
|
+
export default function SessionPage({
|
|
10
|
+
params,
|
|
11
|
+
}: {
|
|
12
|
+
params: Promise<{ id: string }>;
|
|
13
|
+
}) {
|
|
14
|
+
const { id } = use(params);
|
|
15
|
+
const router = useRouter();
|
|
16
|
+
const [session, setSession] = useState<Session | null>(null);
|
|
17
|
+
const [loading, setLoading] = useState(true);
|
|
18
|
+
const [error, setError] = useState<string | null>(null);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
async function loadSession() {
|
|
22
|
+
try {
|
|
23
|
+
const data = await getSession(id);
|
|
24
|
+
if (!data) {
|
|
25
|
+
setError('Session not found');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
setSession(data);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error('Failed to load session:', err);
|
|
31
|
+
setError('Failed to load session');
|
|
32
|
+
} finally {
|
|
33
|
+
setLoading(false);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
loadSession();
|
|
37
|
+
}, [id]);
|
|
38
|
+
|
|
39
|
+
if (loading) {
|
|
40
|
+
return (
|
|
41
|
+
<div className="flex items-center justify-center h-full">
|
|
42
|
+
<Loader2 className="size-8 animate-spin text-muted-foreground" />
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (error || !session) {
|
|
48
|
+
return (
|
|
49
|
+
<div className="flex items-center justify-center h-full">
|
|
50
|
+
<div className="text-center">
|
|
51
|
+
<p className="text-muted-foreground mb-4">{error || 'Session not found'}</p>
|
|
52
|
+
<button
|
|
53
|
+
onClick={() => router.push('/')}
|
|
54
|
+
className="text-primary hover:underline"
|
|
55
|
+
>
|
|
56
|
+
Go back home
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return <ChatInterface session={session} />;
|
|
64
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Runtime configuration endpoint.
|
|
7
|
+
*
|
|
8
|
+
* Reads the API base URL from runtime-config.json written by the CLI.
|
|
9
|
+
* This avoids NEXT_PUBLIC_* env var build-time issues.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Available models for the UI
|
|
13
|
+
const AVAILABLE_MODELS = [
|
|
14
|
+
{ id: 'anthropic/claude-opus-4-5', name: 'Claude Opus 4.5', provider: 'Anthropic' },
|
|
15
|
+
{ id: 'anthropic/claude-sonnet-4', name: 'Claude Sonnet 4', provider: 'Anthropic' },
|
|
16
|
+
{ id: 'anthropic/claude-haiku-4-5', name: 'Claude Haiku 4.5', provider: 'Anthropic' },
|
|
17
|
+
{ id: 'openai/gpt-5-2-codex', name: 'GPT-5.2 Codex', provider: 'OpenAI' },
|
|
18
|
+
{ id: 'openai/gpt-4o', name: 'GPT-4o', provider: 'OpenAI' },
|
|
19
|
+
{ id: 'google/gemini-3-pro-preview', name: 'Gemini 3 Pro Preview', provider: 'Google' },
|
|
20
|
+
{ id: 'google/gemini-3-flash', name: 'Gemini 3 Flash', provider: 'Google' },
|
|
21
|
+
{ id: 'xai/grok-code-fast-1', name: 'Grok Code Fast', provider: 'xAI' },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// Available tools
|
|
25
|
+
const AVAILABLE_TOOLS = [
|
|
26
|
+
{ id: 'bash', name: 'Bash', description: 'Execute shell commands', dangerous: true },
|
|
27
|
+
{ id: 'write_file', name: 'Write File', description: 'Create or edit files', dangerous: true },
|
|
28
|
+
{ id: 'read_file', name: 'Read File', description: 'Read file contents', dangerous: false },
|
|
29
|
+
{ id: 'todo', name: 'Todo', description: 'Manage task lists', dangerous: false },
|
|
30
|
+
{ id: 'load_skill', name: 'Load Skill', description: 'Load skills into context', dangerous: false },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const DEFAULT_API_BASE = 'http://localhost:3141';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Read the API base URL from runtime-config.json
|
|
37
|
+
* The CLI writes this file when starting the web server
|
|
38
|
+
*/
|
|
39
|
+
function getApiBaseUrl(): string {
|
|
40
|
+
const cwd = process.cwd();
|
|
41
|
+
const possiblePaths = [
|
|
42
|
+
// Development: web directory root
|
|
43
|
+
path.join(cwd, 'runtime-config.json'),
|
|
44
|
+
// Standalone: various relative locations
|
|
45
|
+
path.join(cwd, '..', 'runtime-config.json'),
|
|
46
|
+
path.join(cwd, '..', '..', 'runtime-config.json'),
|
|
47
|
+
path.join(cwd, '..', '..', '..', 'runtime-config.json'),
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
for (const configPath of possiblePaths) {
|
|
51
|
+
try {
|
|
52
|
+
if (fs.existsSync(configPath)) {
|
|
53
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
54
|
+
const config = JSON.parse(content);
|
|
55
|
+
if (config.apiBaseUrl) {
|
|
56
|
+
return config.apiBaseUrl;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// Ignore errors, try next path
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// No env var fallback - always use config file or default
|
|
65
|
+
return DEFAULT_API_BASE;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const dynamic = 'force-dynamic';
|
|
69
|
+
|
|
70
|
+
export async function GET() {
|
|
71
|
+
const apiBaseUrl = getApiBaseUrl();
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// Add timeout to prevent hanging if backend is unreachable
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
77
|
+
|
|
78
|
+
const healthRes = await fetch(`${apiBaseUrl}/health`, {
|
|
79
|
+
cache: 'no-store',
|
|
80
|
+
signal: controller.signal,
|
|
81
|
+
});
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
|
|
84
|
+
const healthData = await healthRes.json();
|
|
85
|
+
|
|
86
|
+
return NextResponse.json({
|
|
87
|
+
availableModels: AVAILABLE_MODELS,
|
|
88
|
+
availableTools: AVAILABLE_TOOLS,
|
|
89
|
+
defaultModel: healthData.config?.defaultModel || 'anthropic/claude-opus-4-5',
|
|
90
|
+
defaultToolApprovals: healthData.config?.defaultToolApprovals || {},
|
|
91
|
+
serverConnected: true,
|
|
92
|
+
apiKeyConfigured: healthData.apiKeyConfigured ?? false,
|
|
93
|
+
apiBaseUrl,
|
|
94
|
+
});
|
|
95
|
+
} catch {
|
|
96
|
+
return NextResponse.json({
|
|
97
|
+
availableModels: AVAILABLE_MODELS,
|
|
98
|
+
availableTools: AVAILABLE_TOOLS,
|
|
99
|
+
defaultModel: 'anthropic/claude-opus-4-5',
|
|
100
|
+
defaultToolApprovals: {},
|
|
101
|
+
serverConnected: false,
|
|
102
|
+
apiKeyConfigured: false,
|
|
103
|
+
apiBaseUrl,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Health check endpoint for the web app.
|
|
7
|
+
* Proxies to the backend API and adds identifier for CLI detection.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const DEFAULT_API_BASE = 'http://localhost:3141';
|
|
11
|
+
|
|
12
|
+
function getApiBaseUrl(): string {
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
const possiblePaths = [
|
|
15
|
+
path.join(cwd, 'runtime-config.json'),
|
|
16
|
+
path.join(cwd, '..', 'runtime-config.json'),
|
|
17
|
+
path.join(cwd, '..', '..', 'runtime-config.json'),
|
|
18
|
+
path.join(cwd, '..', '..', '..', 'runtime-config.json'),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
for (const configPath of possiblePaths) {
|
|
22
|
+
try {
|
|
23
|
+
if (fs.existsSync(configPath)) {
|
|
24
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
25
|
+
const config = JSON.parse(content);
|
|
26
|
+
if (config.apiBaseUrl) {
|
|
27
|
+
return config.apiBaseUrl;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// Ignore errors
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// No env var fallback - always use config file or default
|
|
36
|
+
return DEFAULT_API_BASE;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const dynamic = 'force-dynamic';
|
|
40
|
+
|
|
41
|
+
export async function GET() {
|
|
42
|
+
const apiBaseUrl = getApiBaseUrl();
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Add timeout to prevent hanging
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
48
|
+
|
|
49
|
+
const res = await fetch(`${apiBaseUrl}/health/status`, {
|
|
50
|
+
cache: 'no-store',
|
|
51
|
+
signal: controller.signal,
|
|
52
|
+
});
|
|
53
|
+
clearTimeout(timeout);
|
|
54
|
+
|
|
55
|
+
const data = await res.json();
|
|
56
|
+
return NextResponse.json({ ...data, name: 'sparkecoder-web' });
|
|
57
|
+
} catch {
|
|
58
|
+
return NextResponse.json(
|
|
59
|
+
{ name: 'sparkecoder-web', error: 'Failed to connect to API server' },
|
|
60
|
+
{ status: 503 }
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
Binary file
|
|
Binary file
|