sparkecoder 0.1.64 → 0.1.66
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/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +815 -30
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +1042 -165
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js.map +1 -1
- package/dist/{index-Dn-eCGLe.d.ts → index-Db23cukG.d.ts} +35 -25
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1058 -146
- package/dist/index.js.map +1 -1
- package/dist/{schema-XcP0dedO.d.ts → schema-C7Mm4Ykn.d.ts} +3 -3
- package/dist/{search-DINnDTgj.d.ts → search-CVVfuBPZ.d.ts} +6 -4
- package/dist/server/index.js +1058 -146
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/qa.md +317 -0
- package/dist/tools/index.d.ts +31 -4
- package/dist/tools/index.js +433 -7
- package/dist/tools/index.js.map +1 -1
- package/package.json +3 -1
- package/src/skills/default/qa.md +317 -0
- 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.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.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.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_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 +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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 +2 -2
- 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.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
- 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 +4 -4
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5c78460e._.js → 2374f_02a118f9._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_bfc8ef7d._.js → 2374f_0ed477f8._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_04a544c8._.js → 2374f_12bad06e._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_e366206f._.js → 2374f_2526ca80._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_45534372._.js → 2374f_3b51a934._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d5f5b9ba._.js → 2374f_3e519469._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5e9eb6da._.js → 2374f_5ebfcf1a._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_db790cfe._.js → 2374f_a0f483d1._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c61a33b3._.js → 2374f_acf3dfe4._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ab5b97d8._.js → 2374f_ad08e83a._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_68abddfe._.js → 2374f_c1d54c16._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_de60e6ea._.js → 2374f_db3e363b._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_72fb9db7._.js → 2374f_f0d7e130._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5d0b3394._.js → 2374f_fc992d90._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__aa788b85._.js → [root-of-the-server]__06818a54._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__d04c460d._.js → [root-of-the-server]__c71f29f9._.js} +4 -4
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_2b3a5919._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_38156da8._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_5cca707f._.js +7 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_935e81f5._.js +7 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_6fb589ac._.js → web_cc5f7515._.js} +2 -2
- 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/{eea48be65cdb3f4b.js → 31208ade542a0fcb.js} +3 -3
- package/web/.next/{static/chunks/054deec0c7b19894.js → standalone/web/.next/static/chunks/4e673433173ad456.js} +3 -3
- package/web/.next/standalone/web/.next/static/chunks/{7fb141141caa4fac.js → 515f0c0bd6087843.js} +5 -5
- package/web/.next/standalone/web/.next/static/chunks/fd39dd62879495e1.css +1 -0
- package/web/.next/{static/chunks/eea48be65cdb3f4b.js → standalone/web/.next/static/static/chunks/31208ade542a0fcb.js} +3 -3
- package/web/.next/standalone/web/.next/static/static/chunks/{054deec0c7b19894.js → 4e673433173ad456.js} +3 -3
- package/web/.next/standalone/web/.next/static/static/chunks/{7fb141141caa4fac.js → 515f0c0bd6087843.js} +5 -5
- package/web/.next/standalone/web/.next/static/static/chunks/fd39dd62879495e1.css +1 -0
- package/web/.next/standalone/web/src/components/ai-elements/browser-recording.tsx +100 -0
- package/web/.next/standalone/web/src/components/browser-preview.tsx +196 -0
- package/web/.next/standalone/web/src/components/chat-interface.tsx +188 -4
- package/web/.next/standalone/web/src/lib/api.ts +119 -0
- package/web/.next/{standalone/web/.next/static/static/chunks/eea48be65cdb3f4b.js → static/chunks/31208ade542a0fcb.js} +3 -3
- package/web/.next/{standalone/web/.next/static/chunks/054deec0c7b19894.js → static/chunks/4e673433173ad456.js} +3 -3
- package/web/.next/static/chunks/{7fb141141caa4fac.js → 515f0c0bd6087843.js} +5 -5
- package/web/.next/static/chunks/fd39dd62879495e1.css +1 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_08bbd8c8._.js +0 -7
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_c729ad51._.js +0 -7
- package/web/.next/standalone/web/.next/static/chunks/1f42a42914068041.css +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/1f42a42914068041.css +0 -1
- package/web/.next/static/chunks/1f42a42914068041.css +0 -1
- /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_ssgManifest.js +0 -0
- /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_buildManifest.js +0 -0
- /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{9M2ys377uFtNH8BEy1_vL → UMGGmtMDTCI6fL-AIFkiM}/_ssgManifest.js +0 -0
|
@@ -112,6 +112,11 @@ import {
|
|
|
112
112
|
type Checkpoint,
|
|
113
113
|
type RunAgentAttachment,
|
|
114
114
|
type VersionInfo,
|
|
115
|
+
getSessionFiles,
|
|
116
|
+
getBrowserStreamStatus,
|
|
117
|
+
getBrowserRecordings,
|
|
118
|
+
type SessionFile as ApiSessionFile,
|
|
119
|
+
type BrowserRecordingInfo,
|
|
115
120
|
} from '@/lib/api';
|
|
116
121
|
import { TodoPanel } from '@/components/ai-elements/todo-panel';
|
|
117
122
|
import { getConfig, type AppConfig } from '@/lib/config';
|
|
@@ -124,7 +129,7 @@ import {
|
|
|
124
129
|
SelectTrigger,
|
|
125
130
|
SelectValue,
|
|
126
131
|
} from '@/components/ui/select';
|
|
127
|
-
import { MessageSquare, Copy, RefreshCw, AlertTriangle, Terminal as TerminalIcon, FileCode, Radio, Pencil, Check, Settings, RotateCcw, FolderOpen, PanelLeft, FileIcon, Download, X, ChevronDown, ChevronUp, Play, ArrowUp, Trash2, Monitor, ListChecks } from 'lucide-react';
|
|
132
|
+
import { MessageSquare, Copy, RefreshCw, AlertTriangle, Terminal as TerminalIcon, FileCode, Radio, Pencil, Check, Settings, RotateCcw, FolderOpen, PanelLeft, FileIcon, Download, X, ChevronDown, ChevronUp, Play, ArrowUp, Trash2, Monitor, ListChecks, MoreVertical, Globe, Video } from 'lucide-react';
|
|
128
133
|
import { useSidebar } from '@/components/ui/sidebar';
|
|
129
134
|
import {
|
|
130
135
|
Dialog,
|
|
@@ -136,6 +141,13 @@ import {
|
|
|
136
141
|
import { Switch } from '@/components/ui/switch';
|
|
137
142
|
import { Label } from '@/components/ui/label';
|
|
138
143
|
import { Input } from '@/components/ui/input';
|
|
144
|
+
import {
|
|
145
|
+
DropdownMenu,
|
|
146
|
+
DropdownMenuContent,
|
|
147
|
+
DropdownMenuItem,
|
|
148
|
+
DropdownMenuSeparator,
|
|
149
|
+
DropdownMenuTrigger,
|
|
150
|
+
} from '@/components/ui/dropdown-menu';
|
|
139
151
|
import { WriteFileTool, type WriteFileInput, type WriteFileOutput } from '@/components/ai-elements/write-file-tool';
|
|
140
152
|
import { ReadFileTool, type ReadFileInput, type ReadFileOutput } from '@/components/ai-elements/read-file-tool';
|
|
141
153
|
import { BashTool, type BashInput, type BashOutput } from '@/components/ai-elements/bash-tool';
|
|
@@ -146,6 +158,8 @@ import { LinterTool, type LinterInput, type LinterOutput } from '@/components/ai
|
|
|
146
158
|
import { CodeGraphTool, type CodeGraphInput, type CodeGraphOutput } from '@/components/ai-elements/code-graph-tool';
|
|
147
159
|
import { CompleteTaskTool } from '@/components/ai-elements/complete-task-tool';
|
|
148
160
|
import { SpeechInput } from '@/components/ai-elements/speech-input';
|
|
161
|
+
import { BrowserPreview } from '@/components/browser-preview';
|
|
162
|
+
import { BrowserRecording } from '@/components/ai-elements/browser-recording';
|
|
149
163
|
import {
|
|
150
164
|
MentionInputProvider,
|
|
151
165
|
MentionTextarea,
|
|
@@ -424,6 +438,25 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
|
|
|
424
438
|
const [updateBannerDismissed, setUpdateBannerDismissed] = useState(false);
|
|
425
439
|
const [isCreatingUpdateSession, setIsCreatingUpdateSession] = useState(false);
|
|
426
440
|
|
|
441
|
+
// Browser PIP preview state
|
|
442
|
+
const [browserPreviewActive, setBrowserPreviewActive] = useState(false);
|
|
443
|
+
const [browserPreviewFrame, setBrowserPreviewFrame] = useState<string | null>(null);
|
|
444
|
+
const [browserPreviewMetadata, setBrowserPreviewMetadata] = useState<{
|
|
445
|
+
deviceWidth: number;
|
|
446
|
+
deviceHeight: number;
|
|
447
|
+
pageScaleFactor: number;
|
|
448
|
+
offsetTop: number;
|
|
449
|
+
scrollOffsetX: number;
|
|
450
|
+
scrollOffsetY: number;
|
|
451
|
+
} | null>(null);
|
|
452
|
+
const [browserPreviewDismissed, setBrowserPreviewDismissed] = useState(false);
|
|
453
|
+
const browserFrameTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
454
|
+
|
|
455
|
+
// Session files (GCS-backed recordings, uploads, etc.)
|
|
456
|
+
const [sessionFiles, setSessionFiles] = useState<ApiSessionFile[]>([]);
|
|
457
|
+
const [browserRecordingsOpen, setBrowserRecordingsOpen] = useState(false);
|
|
458
|
+
const [browserRecordings, setBrowserRecordings] = useState<BrowserRecordingInfo[]>([]);
|
|
459
|
+
|
|
427
460
|
// Load config for available models
|
|
428
461
|
useEffect(() => {
|
|
429
462
|
getConfig().then(setConfig);
|
|
@@ -947,6 +980,36 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
|
|
|
947
980
|
return;
|
|
948
981
|
}
|
|
949
982
|
|
|
983
|
+
// Browser PIP preview events
|
|
984
|
+
if (event.type === 'browser-frame') {
|
|
985
|
+
setBrowserPreviewFrame(event.data);
|
|
986
|
+
setBrowserPreviewMetadata(event.metadata || null);
|
|
987
|
+
if (!browserPreviewDismissed) {
|
|
988
|
+
setBrowserPreviewActive(true);
|
|
989
|
+
}
|
|
990
|
+
if (browserFrameTimeoutRef.current) {
|
|
991
|
+
clearTimeout(browserFrameTimeoutRef.current);
|
|
992
|
+
}
|
|
993
|
+
browserFrameTimeoutRef.current = setTimeout(() => {
|
|
994
|
+
setBrowserPreviewActive(false);
|
|
995
|
+
}, 30000);
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
if (event.type === 'browser-status') {
|
|
1000
|
+
if (event.connected === false) {
|
|
1001
|
+
if (browserFrameTimeoutRef.current) {
|
|
1002
|
+
clearTimeout(browserFrameTimeoutRef.current);
|
|
1003
|
+
}
|
|
1004
|
+
browserFrameTimeoutRef.current = setTimeout(() => {
|
|
1005
|
+
setBrowserPreviewActive(false);
|
|
1006
|
+
setBrowserPreviewFrame(null);
|
|
1007
|
+
setBrowserPreviewDismissed(false);
|
|
1008
|
+
}, 5000);
|
|
1009
|
+
}
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
950
1013
|
switch (event.type) {
|
|
951
1014
|
case 'start':
|
|
952
1015
|
// Message started - could use messageId for tracking
|
|
@@ -1325,7 +1388,7 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
|
|
|
1325
1388
|
setIsWatching(false);
|
|
1326
1389
|
playDing();
|
|
1327
1390
|
|
|
1328
|
-
//
|
|
1391
|
+
// Refresh checkpoints and session files (recordings may have been uploaded)
|
|
1329
1392
|
{
|
|
1330
1393
|
const finishSessionId = session.id;
|
|
1331
1394
|
getSessionCheckpoints(finishSessionId)
|
|
@@ -1334,6 +1397,16 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
|
|
|
1334
1397
|
setCheckpoints(checkpointsData.checkpoints || []);
|
|
1335
1398
|
})
|
|
1336
1399
|
.catch(() => {});
|
|
1400
|
+
// Delay file refresh slightly to allow recording upload to complete
|
|
1401
|
+
setTimeout(() => {
|
|
1402
|
+
if (sessionIdRef.current !== finishSessionId) return;
|
|
1403
|
+
getSessionFiles(finishSessionId)
|
|
1404
|
+
.then((files) => {
|
|
1405
|
+
if (sessionIdRef.current !== finishSessionId) return;
|
|
1406
|
+
setSessionFiles(files);
|
|
1407
|
+
})
|
|
1408
|
+
.catch(() => {});
|
|
1409
|
+
}, 3000);
|
|
1337
1410
|
}
|
|
1338
1411
|
|
|
1339
1412
|
// Send next queued message directly from the finish event.
|
|
@@ -1414,12 +1487,17 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
|
|
|
1414
1487
|
currentTextRef.current = '';
|
|
1415
1488
|
currentReasoningRef.current = '';
|
|
1416
1489
|
toolCallsRef.current = [];
|
|
1490
|
+
setBrowserPreviewActive(false);
|
|
1491
|
+
setBrowserPreviewFrame(null);
|
|
1492
|
+
setBrowserPreviewDismissed(false);
|
|
1493
|
+
setSessionFiles([]);
|
|
1417
1494
|
|
|
1418
1495
|
try {
|
|
1419
|
-
// Load existing messages and
|
|
1420
|
-
const [apiMessages, checkpointsData] = await Promise.all([
|
|
1496
|
+
// Load existing messages, checkpoints, and session files in parallel
|
|
1497
|
+
const [apiMessages, checkpointsData, filesData] = await Promise.all([
|
|
1421
1498
|
getSessionMessages(session.id),
|
|
1422
1499
|
getSessionCheckpoints(session.id).catch(() => ({ checkpoints: [] })),
|
|
1500
|
+
getSessionFiles(session.id).catch(() => []),
|
|
1423
1501
|
]);
|
|
1424
1502
|
|
|
1425
1503
|
// Don't update state if session changed during async work
|
|
@@ -1431,6 +1509,7 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
|
|
|
1431
1509
|
const converted = convertApiMessages(sorted);
|
|
1432
1510
|
setChatItems(converted);
|
|
1433
1511
|
setCheckpoints(checkpointsData.checkpoints || []);
|
|
1512
|
+
setSessionFiles(filesData);
|
|
1434
1513
|
|
|
1435
1514
|
// Check if there's an active stream to watch
|
|
1436
1515
|
const streamInfo = await getActiveStream(session.id);
|
|
@@ -2404,6 +2483,16 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
|
|
|
2404
2483
|
</TooltipProvider>
|
|
2405
2484
|
</div>
|
|
2406
2485
|
<div className="flex items-center gap-2 shrink-0">
|
|
2486
|
+
{/* Browser recordings */}
|
|
2487
|
+
{sessionFiles.filter(f => f.category === 'browser-recording' && f.downloadUrl).map((file) => (
|
|
2488
|
+
<BrowserRecording
|
|
2489
|
+
key={file.id}
|
|
2490
|
+
fileName={file.fileName}
|
|
2491
|
+
downloadUrl={file.downloadUrl!}
|
|
2492
|
+
sizeBytes={file.sizeBytes}
|
|
2493
|
+
createdAt={file.createdAt}
|
|
2494
|
+
/>
|
|
2495
|
+
))}
|
|
2407
2496
|
{isWatching && (
|
|
2408
2497
|
<TooltipProvider>
|
|
2409
2498
|
<Tooltip>
|
|
@@ -2451,6 +2540,44 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
|
|
|
2451
2540
|
<TooltipContent>Session Settings</TooltipContent>
|
|
2452
2541
|
</Tooltip>
|
|
2453
2542
|
</TooltipProvider>
|
|
2543
|
+
|
|
2544
|
+
{/* Browser / More actions menu */}
|
|
2545
|
+
<DropdownMenu>
|
|
2546
|
+
<DropdownMenuTrigger asChild>
|
|
2547
|
+
<Button size="icon" variant="ghost" className="size-7 hover:bg-accent transition-colors">
|
|
2548
|
+
<MoreVertical className="size-4" />
|
|
2549
|
+
</Button>
|
|
2550
|
+
</DropdownMenuTrigger>
|
|
2551
|
+
<DropdownMenuContent align="end" className="w-52">
|
|
2552
|
+
<DropdownMenuItem
|
|
2553
|
+
onClick={() => {
|
|
2554
|
+
setBrowserPreviewDismissed(false);
|
|
2555
|
+
// Check if there's an active browser stream
|
|
2556
|
+
getBrowserStreamStatus(session.id).then((status) => {
|
|
2557
|
+
if (status?.active) {
|
|
2558
|
+
setBrowserPreviewActive(true);
|
|
2559
|
+
console.log('[browser] Reconnecting PIP — stream active');
|
|
2560
|
+
} else {
|
|
2561
|
+
console.log('[browser] No active browser stream');
|
|
2562
|
+
}
|
|
2563
|
+
});
|
|
2564
|
+
}}
|
|
2565
|
+
>
|
|
2566
|
+
<Globe className="size-4 mr-2" />
|
|
2567
|
+
Open Browser PIP
|
|
2568
|
+
</DropdownMenuItem>
|
|
2569
|
+
<DropdownMenuItem
|
|
2570
|
+
onClick={async () => {
|
|
2571
|
+
const recs = await getBrowserRecordings(session.id);
|
|
2572
|
+
setBrowserRecordings(recs);
|
|
2573
|
+
setBrowserRecordingsOpen(true);
|
|
2574
|
+
}}
|
|
2575
|
+
>
|
|
2576
|
+
<Video className="size-4 mr-2" />
|
|
2577
|
+
Browser Recordings
|
|
2578
|
+
</DropdownMenuItem>
|
|
2579
|
+
</DropdownMenuContent>
|
|
2580
|
+
</DropdownMenu>
|
|
2454
2581
|
</div>
|
|
2455
2582
|
</div>
|
|
2456
2583
|
)}
|
|
@@ -2531,6 +2658,51 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
|
|
|
2531
2658
|
</DialogContent>
|
|
2532
2659
|
</Dialog>
|
|
2533
2660
|
|
|
2661
|
+
{/* Browser Recordings Dialog */}
|
|
2662
|
+
<Dialog open={browserRecordingsOpen} onOpenChange={setBrowserRecordingsOpen}>
|
|
2663
|
+
<DialogContent className="max-w-2xl w-full p-0 overflow-hidden rounded-xl">
|
|
2664
|
+
<DialogHeader className="px-5 pt-5 pb-3">
|
|
2665
|
+
<DialogTitle className="flex items-center gap-2 text-sm font-medium">
|
|
2666
|
+
<div className="flex items-center justify-center size-6 rounded-lg bg-violet-500/10 dark:bg-violet-400/10">
|
|
2667
|
+
<Video className="size-3.5 text-violet-600 dark:text-violet-400" />
|
|
2668
|
+
</div>
|
|
2669
|
+
Browser Recordings
|
|
2670
|
+
</DialogTitle>
|
|
2671
|
+
</DialogHeader>
|
|
2672
|
+
<div className="px-5 pb-5 space-y-3">
|
|
2673
|
+
{browserRecordings.length === 0 ? (
|
|
2674
|
+
<p className="text-sm text-muted-foreground py-4 text-center">No browser recordings for this session</p>
|
|
2675
|
+
) : (
|
|
2676
|
+
browserRecordings.map((rec) => (
|
|
2677
|
+
<div key={rec.id} className="flex items-center gap-3 p-3 rounded-lg border bg-muted/30 hover:bg-muted/50 transition-colors">
|
|
2678
|
+
<div className="flex items-center justify-center size-10 rounded-lg bg-violet-500/10 dark:bg-violet-400/10 shrink-0">
|
|
2679
|
+
<Play className="size-4 text-violet-600 dark:text-violet-400 ml-0.5" />
|
|
2680
|
+
</div>
|
|
2681
|
+
<div className="min-w-0 flex-1">
|
|
2682
|
+
<p className="text-sm font-medium truncate">{rec.fileName}</p>
|
|
2683
|
+
<p className="text-xs text-muted-foreground">
|
|
2684
|
+
{rec.createdAt && new Date(rec.createdAt).toLocaleString()}
|
|
2685
|
+
{rec.sizeBytes != null && ` · ${rec.sizeBytes > 1024 * 1024 ? `${(rec.sizeBytes / (1024 * 1024)).toFixed(1)} MB` : `${(rec.sizeBytes / 1024).toFixed(0)} KB`}`}
|
|
2686
|
+
</p>
|
|
2687
|
+
</div>
|
|
2688
|
+
{rec.downloadUrl && (
|
|
2689
|
+
<Button
|
|
2690
|
+
size="sm"
|
|
2691
|
+
variant="outline"
|
|
2692
|
+
className="shrink-0"
|
|
2693
|
+
onClick={() => window.open(rec.downloadUrl!, '_blank')}
|
|
2694
|
+
>
|
|
2695
|
+
<Play className="size-3 mr-1" />
|
|
2696
|
+
Watch
|
|
2697
|
+
</Button>
|
|
2698
|
+
)}
|
|
2699
|
+
</div>
|
|
2700
|
+
))
|
|
2701
|
+
)}
|
|
2702
|
+
</div>
|
|
2703
|
+
</DialogContent>
|
|
2704
|
+
</Dialog>
|
|
2705
|
+
|
|
2534
2706
|
{/* Update Available Banner - hidden in embed mode */}
|
|
2535
2707
|
{!isEmbed && versionInfo?.updateAvailable && !updateBannerDismissed && (
|
|
2536
2708
|
<div className="px-4 py-2 border-b border-amber-500/30 bg-amber-500/10 flex items-center justify-between gap-3">
|
|
@@ -3431,6 +3603,18 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
|
|
|
3431
3603
|
</MentionInputProvider>
|
|
3432
3604
|
</div>
|
|
3433
3605
|
</div>
|
|
3606
|
+
|
|
3607
|
+
{/* Browser PIP preview */}
|
|
3608
|
+
<BrowserPreview
|
|
3609
|
+
sessionId={session.id}
|
|
3610
|
+
frame={browserPreviewFrame}
|
|
3611
|
+
metadata={browserPreviewMetadata}
|
|
3612
|
+
active={browserPreviewActive && !browserPreviewDismissed}
|
|
3613
|
+
onDismiss={() => {
|
|
3614
|
+
setBrowserPreviewDismissed(true);
|
|
3615
|
+
setBrowserPreviewActive(false);
|
|
3616
|
+
}}
|
|
3617
|
+
/>
|
|
3434
3618
|
</div>
|
|
3435
3619
|
);
|
|
3436
3620
|
}
|
|
@@ -486,11 +486,22 @@ export type SSEEvent =
|
|
|
486
486
|
| { type: 'data-stream-id'; streamId: string }
|
|
487
487
|
| { type: 'data-session'; data: { id: string; name: string; workingDirectory: string; model: string } }
|
|
488
488
|
| { type: 'data-user-message'; data: { id: string; content: string } }
|
|
489
|
+
| { type: 'browser-frame'; data: string; metadata: BrowserFrameMetadata }
|
|
490
|
+
| { type: 'browser-status'; connected: boolean; screencasting: boolean; viewportWidth?: number; viewportHeight?: number }
|
|
489
491
|
| { type: 'finish-step' }
|
|
490
492
|
| { type: 'finish'; finishReason?: string }
|
|
491
493
|
| { type: 'abort' }
|
|
492
494
|
| { type: 'error'; errorText: string };
|
|
493
495
|
|
|
496
|
+
export interface BrowserFrameMetadata {
|
|
497
|
+
deviceWidth: number;
|
|
498
|
+
deviceHeight: number;
|
|
499
|
+
pageScaleFactor: number;
|
|
500
|
+
offsetTop: number;
|
|
501
|
+
scrollOffsetX: number;
|
|
502
|
+
scrollOffsetY: number;
|
|
503
|
+
}
|
|
504
|
+
|
|
494
505
|
// Terminal stream events
|
|
495
506
|
export interface TerminalStreamEvent {
|
|
496
507
|
type: 'status' | 'stdout' | 'exit';
|
|
@@ -848,3 +859,111 @@ export async function getWorkspaceFiles(
|
|
|
848
859
|
|
|
849
860
|
return res.json();
|
|
850
861
|
}
|
|
862
|
+
|
|
863
|
+
// ============================================
|
|
864
|
+
// Browser Input API (pair-browsing)
|
|
865
|
+
// ============================================
|
|
866
|
+
|
|
867
|
+
export interface BrowserInputEvent {
|
|
868
|
+
type: 'input_mouse' | 'input_keyboard' | 'input_touch';
|
|
869
|
+
eventType: string;
|
|
870
|
+
x?: number;
|
|
871
|
+
y?: number;
|
|
872
|
+
button?: string;
|
|
873
|
+
clickCount?: number;
|
|
874
|
+
deltaX?: number;
|
|
875
|
+
deltaY?: number;
|
|
876
|
+
key?: string;
|
|
877
|
+
code?: string;
|
|
878
|
+
text?: string;
|
|
879
|
+
modifiers?: number;
|
|
880
|
+
touchPoints?: Array<{ x: number; y: number; id?: number }>;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
export async function sendBrowserInput(
|
|
884
|
+
sessionId: string,
|
|
885
|
+
event: BrowserInputEvent
|
|
886
|
+
): Promise<void> {
|
|
887
|
+
await fetch(`${getApiBase()}/agents/${sessionId}/browser-input`, {
|
|
888
|
+
method: 'POST',
|
|
889
|
+
headers: { 'Content-Type': 'application/json' },
|
|
890
|
+
body: JSON.stringify(event),
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// ============================================
|
|
895
|
+
// Session Files API (GCS-backed)
|
|
896
|
+
// ============================================
|
|
897
|
+
|
|
898
|
+
export interface SessionFile {
|
|
899
|
+
id: string;
|
|
900
|
+
fileName: string;
|
|
901
|
+
contentType: string;
|
|
902
|
+
sizeBytes: number | null;
|
|
903
|
+
category: string;
|
|
904
|
+
createdAt: string;
|
|
905
|
+
downloadUrl: string | null;
|
|
906
|
+
downloadUrlExpiresAt: string | null;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
export async function getSessionFiles(sessionId: string): Promise<SessionFile[]> {
|
|
910
|
+
const res = await fetch(`${getApiBase()}/sessions/${sessionId}/session-files`);
|
|
911
|
+
if (!res.ok) {
|
|
912
|
+
if (res.status === 404) return [];
|
|
913
|
+
throw new Error(`Failed to get session files: ${res.statusText}`);
|
|
914
|
+
}
|
|
915
|
+
const data = await res.json();
|
|
916
|
+
return data.files || [];
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
export async function getFileDownloadUrl(fileId: string): Promise<{ downloadUrl: string; expiresAt: string }> {
|
|
920
|
+
const res = await fetch(`${getApiBase()}/sessions/files/${fileId}/download`);
|
|
921
|
+
if (!res.ok) {
|
|
922
|
+
throw new Error(`Failed to get download URL: ${res.statusText}`);
|
|
923
|
+
}
|
|
924
|
+
return res.json();
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// ============================================
|
|
928
|
+
// Browser Recording API
|
|
929
|
+
// ============================================
|
|
930
|
+
|
|
931
|
+
export interface BrowserRecordingInfo {
|
|
932
|
+
id: string;
|
|
933
|
+
fileName: string;
|
|
934
|
+
sizeBytes: number | null;
|
|
935
|
+
createdAt: string;
|
|
936
|
+
downloadUrl: string | null;
|
|
937
|
+
expiresAt: string | null;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
export async function getBrowserRecordings(sessionId: string): Promise<BrowserRecordingInfo[]> {
|
|
941
|
+
const res = await fetch(`${getApiBase()}/sessions/${sessionId}/browser-recording`);
|
|
942
|
+
if (!res.ok) return [];
|
|
943
|
+
const data = await res.json();
|
|
944
|
+
return data.recordings || [];
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// ============================================
|
|
948
|
+
// Browser Stream Status API
|
|
949
|
+
// ============================================
|
|
950
|
+
|
|
951
|
+
export interface BrowserStreamStatus {
|
|
952
|
+
sessionId: string;
|
|
953
|
+
active: boolean;
|
|
954
|
+
hasProxy: boolean;
|
|
955
|
+
latestFrame: {
|
|
956
|
+
metadata: { deviceWidth: number; deviceHeight: number };
|
|
957
|
+
timestamp: number;
|
|
958
|
+
} | null;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
export async function getBrowserStreamStatus(sessionId: string): Promise<BrowserStreamStatus | null> {
|
|
962
|
+
try {
|
|
963
|
+
const res = await fetch(`${getApiBase()}/agents/${sessionId}/browser-stream`);
|
|
964
|
+
if (!res.ok) return null;
|
|
965
|
+
return res.json();
|
|
966
|
+
} catch {
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
}
|