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.
Files changed (216) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +15 -6
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +9 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/server/index.js +9 -2
  7. package/dist/server/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/web/.next/BUILD_ID +1 -1
  10. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  11. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  12. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  13. package/web/.next/standalone/web/.next/server/app/(main)/page.js +3 -3
  14. package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
  15. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  16. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js +3 -3
  17. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  18. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  19. package/web/.next/standalone/web/.next/server/app/_global-error/page.js +2 -2
  20. package/web/.next/standalone/web/.next/server/app/_global-error/page.js.nft.json +1 -1
  21. package/web/.next/standalone/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  22. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  23. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  24. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  25. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_not-found/page.js +3 -3
  30. package/web/.next/standalone/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +13 -12
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +13 -12
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +4 -3
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/api/config/route.js +1 -1
  41. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  42. package/web/.next/standalone/web/.next/server/app/api/health/route.js +1 -1
  43. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  44. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  45. package/web/.next/standalone/web/.next/server/app/index.rsc +20 -19
  46. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  47. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  48. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +20 -19
  49. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +4 -3
  51. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__36edac7c._.js +3 -0
  53. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__74ebc442._.js +3 -0
  54. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_1b669458._.js → 2374f_1d78db71._.js} +1 -1
  55. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_10a13bff._.js → 2374f_30f9df13._.js} +1 -1
  56. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_42ae2ff5._.js → 2374f_378282b1._.js} +1 -1
  57. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_21324e82._.js → 2374f_5de336d2._.js} +1 -1
  58. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ba57e34a._.js → 2374f_8825dcc9._.js} +1 -1
  59. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_47e888fe._.js → 2374f_9bf3c7f3._.js} +2 -2
  60. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_63823b79._.js → 2374f_bbc99511._.js} +1 -1
  61. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_9c15fcde._.js → 2374f_d94c2b70._.js} +1 -1
  62. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__0f6b5fa7._.js +3 -0
  63. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__e1f2996b._.js → [root-of-the-server]__513c6b45._.js} +2 -2
  64. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__da40fb08._.js → [root-of-the-server]__7f04455b._.js} +2 -2
  65. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__be5e2967._.js +3 -0
  66. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__10aab2ca._.js → [root-of-the-server]__c3a1e22c._.js} +2 -2
  67. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__d2ce4b79._.js +3 -0
  68. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__de58a952._.js +3 -0
  69. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__f18f92f4._.js +10 -0
  70. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_d3c9d897._.js → web_19b6934c._.js} +2 -2
  71. package/web/.next/standalone/web/.next/server/chunks/ssr/web_96bca05b._.js +1 -1
  72. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  73. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  74. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  75. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  76. package/web/.next/standalone/web/.next/static/chunks/25a97bfee12ea1f6.js +1 -0
  77. package/web/.next/standalone/web/.next/static/chunks/2f9f08f6c6276b0f.js +1 -0
  78. package/web/.next/{static/chunks/c7e9604ccdb5cb09.js → standalone/web/.next/static/chunks/5ec82ce8f3aabaf0.js} +3 -3
  79. package/web/.next/standalone/web/.next/static/static/chunks/25a97bfee12ea1f6.js +1 -0
  80. package/web/.next/standalone/web/.next/static/static/chunks/2f9f08f6c6276b0f.js +1 -0
  81. package/web/.next/standalone/web/.next/static/{chunks/c7e9604ccdb5cb09.js → static/chunks/5ec82ce8f3aabaf0.js} +3 -3
  82. package/web/.next/standalone/web/README.md +63 -0
  83. package/web/.next/standalone/web/components.json +22 -0
  84. package/web/.next/standalone/web/eslint.config.mjs +18 -0
  85. package/web/.next/standalone/web/next.config.ts +9 -0
  86. package/web/.next/standalone/web/package-lock.json +15061 -0
  87. package/web/.next/standalone/web/postcss.config.mjs +7 -0
  88. package/web/.next/standalone/web/runtime-config.json +3 -0
  89. package/web/.next/standalone/web/src/app/(main)/layout.tsx +22 -0
  90. package/web/.next/standalone/web/src/app/(main)/page.tsx +230 -0
  91. package/web/.next/standalone/web/src/app/(main)/session/[id]/page.tsx +64 -0
  92. package/web/.next/standalone/web/src/app/api/config/route.ts +106 -0
  93. package/web/.next/standalone/web/src/app/api/health/route.ts +63 -0
  94. package/web/.next/standalone/web/src/app/apple-icon.png +0 -0
  95. package/web/.next/standalone/web/src/app/favicon.ico +0 -0
  96. package/web/.next/standalone/web/src/app/globals.css +311 -0
  97. package/web/.next/standalone/web/src/app/icon.png +0 -0
  98. package/web/.next/standalone/web/src/app/layout.tsx +100 -0
  99. package/web/.next/standalone/web/src/app/opengraph-image.png +0 -0
  100. package/web/.next/standalone/web/src/app/twitter-image.png +0 -0
  101. package/web/.next/standalone/web/src/components/ai-elements/agent.tsx +141 -0
  102. package/web/.next/standalone/web/src/components/ai-elements/artifact.tsx +147 -0
  103. package/web/.next/standalone/web/src/components/ai-elements/attachments.tsx +421 -0
  104. package/web/.next/standalone/web/src/components/ai-elements/audio-player.tsx +231 -0
  105. package/web/.next/standalone/web/src/components/ai-elements/bash-tool.tsx +299 -0
  106. package/web/.next/standalone/web/src/components/ai-elements/canvas.tsx +22 -0
  107. package/web/.next/standalone/web/src/components/ai-elements/chain-of-thought.tsx +231 -0
  108. package/web/.next/standalone/web/src/components/ai-elements/checkpoint.tsx +71 -0
  109. package/web/.next/standalone/web/src/components/ai-elements/code-block.tsx +529 -0
  110. package/web/.next/standalone/web/src/components/ai-elements/commit.tsx +448 -0
  111. package/web/.next/standalone/web/src/components/ai-elements/confirmation.tsx +176 -0
  112. package/web/.next/standalone/web/src/components/ai-elements/connection.tsx +28 -0
  113. package/web/.next/standalone/web/src/components/ai-elements/context.tsx +408 -0
  114. package/web/.next/standalone/web/src/components/ai-elements/controls.tsx +18 -0
  115. package/web/.next/standalone/web/src/components/ai-elements/conversation.tsx +104 -0
  116. package/web/.next/standalone/web/src/components/ai-elements/edge.tsx +140 -0
  117. package/web/.next/standalone/web/src/components/ai-elements/environment-variables.tsx +295 -0
  118. package/web/.next/standalone/web/src/components/ai-elements/file-tree.tsx +258 -0
  119. package/web/.next/standalone/web/src/components/ai-elements/image.tsx +24 -0
  120. package/web/.next/standalone/web/src/components/ai-elements/inline-citation.tsx +287 -0
  121. package/web/.next/standalone/web/src/components/ai-elements/linter-tool.tsx +330 -0
  122. package/web/.next/standalone/web/src/components/ai-elements/load-skill-tool.tsx +215 -0
  123. package/web/.next/standalone/web/src/components/ai-elements/loader.tsx +96 -0
  124. package/web/.next/standalone/web/src/components/ai-elements/message.tsx +421 -0
  125. package/web/.next/standalone/web/src/components/ai-elements/mic-selector.tsx +370 -0
  126. package/web/.next/standalone/web/src/components/ai-elements/model-selector.tsx +211 -0
  127. package/web/.next/standalone/web/src/components/ai-elements/node.tsx +71 -0
  128. package/web/.next/standalone/web/src/components/ai-elements/open-in-chat.tsx +365 -0
  129. package/web/.next/standalone/web/src/components/ai-elements/package-info.tsx +233 -0
  130. package/web/.next/standalone/web/src/components/ai-elements/panel.tsx +15 -0
  131. package/web/.next/standalone/web/src/components/ai-elements/persona.tsx +270 -0
  132. package/web/.next/standalone/web/src/components/ai-elements/plan.tsx +142 -0
  133. package/web/.next/standalone/web/src/components/ai-elements/prompt-input.tsx +1263 -0
  134. package/web/.next/standalone/web/src/components/ai-elements/queue.tsx +274 -0
  135. package/web/.next/standalone/web/src/components/ai-elements/read-file-tool.tsx +251 -0
  136. package/web/.next/standalone/web/src/components/ai-elements/reasoning.tsx +198 -0
  137. package/web/.next/standalone/web/src/components/ai-elements/sandbox.tsx +131 -0
  138. package/web/.next/standalone/web/src/components/ai-elements/schema-display.tsx +458 -0
  139. package/web/.next/standalone/web/src/components/ai-elements/shimmer.tsx +64 -0
  140. package/web/.next/standalone/web/src/components/ai-elements/snippet.tsx +139 -0
  141. package/web/.next/standalone/web/src/components/ai-elements/sources.tsx +77 -0
  142. package/web/.next/standalone/web/src/components/ai-elements/speech-input.tsx +300 -0
  143. package/web/.next/standalone/web/src/components/ai-elements/stack-trace.tsx +482 -0
  144. package/web/.next/standalone/web/src/components/ai-elements/suggestion.tsx +60 -0
  145. package/web/.next/standalone/web/src/components/ai-elements/task.tsx +87 -0
  146. package/web/.next/standalone/web/src/components/ai-elements/terminal.tsx +261 -0
  147. package/web/.next/standalone/web/src/components/ai-elements/test-results.tsx +485 -0
  148. package/web/.next/standalone/web/src/components/ai-elements/todo-panel.tsx +178 -0
  149. package/web/.next/standalone/web/src/components/ai-elements/todo-tool.tsx +246 -0
  150. package/web/.next/standalone/web/src/components/ai-elements/tool.tsx +174 -0
  151. package/web/.next/standalone/web/src/components/ai-elements/toolbar.tsx +16 -0
  152. package/web/.next/standalone/web/src/components/ai-elements/transcription.tsx +124 -0
  153. package/web/.next/standalone/web/src/components/ai-elements/voice-selector.tsx +479 -0
  154. package/web/.next/standalone/web/src/components/ai-elements/web-preview.tsx +263 -0
  155. package/web/.next/standalone/web/src/components/ai-elements/write-file-tool.tsx +368 -0
  156. package/web/.next/standalone/web/src/components/api-init.tsx +21 -0
  157. package/web/.next/standalone/web/src/components/chat-interface.tsx +2074 -0
  158. package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +875 -0
  159. package/web/.next/standalone/web/src/components/ui/accordion.tsx +66 -0
  160. package/web/.next/standalone/web/src/components/ui/alert.tsx +66 -0
  161. package/web/.next/standalone/web/src/components/ui/avatar.tsx +109 -0
  162. package/web/.next/standalone/web/src/components/ui/badge.tsx +48 -0
  163. package/web/.next/standalone/web/src/components/ui/button-group.tsx +83 -0
  164. package/web/.next/standalone/web/src/components/ui/button.tsx +64 -0
  165. package/web/.next/standalone/web/src/components/ui/card.tsx +92 -0
  166. package/web/.next/standalone/web/src/components/ui/carousel.tsx +241 -0
  167. package/web/.next/standalone/web/src/components/ui/collapsible.tsx +33 -0
  168. package/web/.next/standalone/web/src/components/ui/command.tsx +184 -0
  169. package/web/.next/standalone/web/src/components/ui/dialog.tsx +158 -0
  170. package/web/.next/standalone/web/src/components/ui/dropdown-menu.tsx +257 -0
  171. package/web/.next/standalone/web/src/components/ui/hover-card.tsx +44 -0
  172. package/web/.next/standalone/web/src/components/ui/input-group.tsx +170 -0
  173. package/web/.next/standalone/web/src/components/ui/input.tsx +22 -0
  174. package/web/.next/standalone/web/src/components/ui/label.tsx +24 -0
  175. package/web/.next/standalone/web/src/components/ui/popover.tsx +89 -0
  176. package/web/.next/standalone/web/src/components/ui/progress.tsx +31 -0
  177. package/web/.next/standalone/web/src/components/ui/scroll-area.tsx +58 -0
  178. package/web/.next/standalone/web/src/components/ui/select.tsx +190 -0
  179. package/web/.next/standalone/web/src/components/ui/separator.tsx +28 -0
  180. package/web/.next/standalone/web/src/components/ui/sheet.tsx +143 -0
  181. package/web/.next/standalone/web/src/components/ui/sidebar.tsx +726 -0
  182. package/web/.next/standalone/web/src/components/ui/skeleton.tsx +13 -0
  183. package/web/.next/standalone/web/src/components/ui/switch.tsx +35 -0
  184. package/web/.next/standalone/web/src/components/ui/tabs.tsx +91 -0
  185. package/web/.next/standalone/web/src/components/ui/textarea.tsx +18 -0
  186. package/web/.next/standalone/web/src/components/ui/tooltip.tsx +61 -0
  187. package/web/.next/standalone/web/src/hooks/use-mobile.ts +19 -0
  188. package/web/.next/standalone/web/src/hooks/use-sessions.ts +28 -0
  189. package/web/.next/standalone/web/src/lib/api.ts +568 -0
  190. package/web/.next/standalone/web/src/lib/config.ts +178 -0
  191. package/web/.next/standalone/web/src/lib/utils.ts +6 -0
  192. package/web/.next/standalone/web/src/test/api.test.ts +125 -0
  193. package/web/.next/standalone/web/src/test/setup.ts +1 -0
  194. package/web/.next/standalone/web/tsconfig.json +43 -0
  195. package/web/.next/standalone/web/vitest.config.ts +17 -0
  196. package/web/.next/static/chunks/25a97bfee12ea1f6.js +1 -0
  197. package/web/.next/static/chunks/2f9f08f6c6276b0f.js +1 -0
  198. package/web/.next/{standalone/web/.next/static/static/chunks/c7e9604ccdb5cb09.js → static/chunks/5ec82ce8f3aabaf0.js} +3 -3
  199. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__7ba4776a._.js +0 -3
  200. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__d907af4e._.js +0 -3
  201. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__3a7ef2b7._.js +0 -3
  202. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__5369f47d._.js +0 -3
  203. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__c1f0d54f._.js +0 -3
  204. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__fd3d4d25._.js +0 -10
  205. package/web/.next/standalone/web/.next/static/chunks/85dcb2949e032468.js +0 -1
  206. package/web/.next/standalone/web/.next/static/static/chunks/85dcb2949e032468.js +0 -1
  207. package/web/.next/static/chunks/85dcb2949e032468.js +0 -1
  208. /package/web/.next/standalone/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_buildManifest.js +0 -0
  209. /package/web/.next/standalone/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_clientMiddlewareManifest.json +0 -0
  210. /package/web/.next/standalone/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_ssgManifest.js +0 -0
  211. /package/web/.next/standalone/web/.next/static/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_buildManifest.js +0 -0
  212. /package/web/.next/standalone/web/.next/static/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_clientMiddlewareManifest.json +0 -0
  213. /package/web/.next/standalone/web/.next/static/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_ssgManifest.js +0 -0
  214. /package/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_buildManifest.js +0 -0
  215. /package/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_clientMiddlewareManifest.json +0 -0
  216. /package/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_ssgManifest.js +0 -0
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,3 @@
1
+ {
2
+ "apiBaseUrl": "http://127.0.0.1:3144"
3
+ }
@@ -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
+ }