@runfusion/fusion 0.9.4 → 0.10.0
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/bin.js +1382 -557
- package/dist/client/assets/{AgentDetailView-5W1q48YS.js → AgentDetailView-BtpZ4jxh.js} +1 -1
- package/dist/client/assets/{AgentsView-DcEnemu0.js → AgentsView-Dxdtt0Bm.js} +3 -3
- package/dist/client/assets/ChatView-Bra9fNAG.js +1 -0
- package/dist/client/assets/{DevServerView-LOrDrAYm.js → DevServerView-UkgjEw9-.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-Bgp6PCbu.js → DirectoryPicker-Cls4HWxP.js} +1 -1
- package/dist/client/assets/{DocumentsView-CNbnZ7Q3.js → DocumentsView-BRBUPFVA.js} +1 -1
- package/dist/client/assets/{InsightsView-CmJwV-ZC.js → InsightsView-BRDqHCLb.js} +1 -1
- package/dist/client/assets/{MemoryView-Bwi5p79s.js → MemoryView-DvTrwFnQ.js} +1 -1
- package/dist/client/assets/{NodesView-1pZii99I.js → NodesView-C4Ffl_o0.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-CokhM-MB.js → PiExtensionsManager-CeI1syeZ.js} +3 -3
- package/dist/client/assets/{PluginManager-cHaGKMgY.js → PluginManager-BgeoYhLk.js} +1 -1
- package/dist/client/assets/{ResearchView-CQDI2y7Q.js → ResearchView-fmEOm4A2.js} +1 -1
- package/dist/client/assets/{RoadmapsView-BTo3BT0I.js → RoadmapsView-DNb4x75S.js} +1 -1
- package/dist/client/assets/SettingsModal-CDPDmHhd.css +1 -0
- package/dist/client/assets/{SettingsModal-D5slUUsC.js → SettingsModal-CRMr4tL6.js} +1 -1
- package/dist/client/assets/SettingsModal-D1xq0WZm.js +31 -0
- package/dist/client/assets/{SetupWizardModal-1qSn8Yl0.js → SetupWizardModal-tF8B_aG_.js} +1 -1
- package/dist/client/assets/{SkillsView-CY3I5OYc.js → SkillsView-uyl47gSf.js} +1 -1
- package/dist/client/assets/{TodoView-B1GDwwhR.js → TodoView-CbzDtV53.js} +1 -1
- package/dist/client/assets/{folder-open-DPESt6bg.js → folder-open-DPpmGJ-v.js} +1 -1
- package/dist/client/assets/{index-2_pvFDiN.css → index-BqK6TvSa.css} +1 -1
- package/dist/client/assets/index-DyXZm9QN.js +656 -0
- package/dist/client/assets/{list-checks-D7D9kx7Y.js → list-checks-D62pw1I8.js} +1 -1
- package/dist/client/assets/{star-C59_6aNu.js → star-B8EbxNgI.js} +1 -1
- package/dist/client/assets/{upload-1I0eQddJ.js → upload-CpnLno9z.js} +1 -1
- package/dist/client/assets/{users-DH50eBCX.js → users-B_C_0qzA.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/extension.js +1025 -358
- package/dist/pi-claude-cli/index.ts +31 -5
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/pi-claude-cli/src/__tests__/process-manager.test.ts +90 -0
- package/dist/pi-claude-cli/src/__tests__/provider.test.ts +13 -3
- package/dist/pi-claude-cli/src/process-manager.ts +65 -0
- package/package.json +1 -1
- package/dist/client/assets/ChatView-CTc6mP8y.js +0 -1
- package/dist/client/assets/SettingsModal-EEQwF0Ql.js +0 -31
- package/dist/client/assets/SettingsModal-FfIAhzcJ.css +0 -1
- package/dist/client/assets/index-DNIrnlpO.js +0 -656
|
@@ -9,8 +9,8 @@ import { getModels } from "@mariozechner/pi-ai";
|
|
|
9
9
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
10
10
|
import { streamViaCli } from "./src/provider.js";
|
|
11
11
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
validateCliPresenceAsync,
|
|
13
|
+
validateCliAuthAsync,
|
|
14
14
|
killAllProcesses,
|
|
15
15
|
} from "./src/process-manager.js";
|
|
16
16
|
import { createHash } from "node:crypto";
|
|
@@ -26,6 +26,31 @@ process.on("exit", killAllProcesses);
|
|
|
26
26
|
|
|
27
27
|
const PROVIDER_ID = "pi-claude-cli";
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Run CLI presence + auth probes at most once per process, asynchronously.
|
|
31
|
+
*
|
|
32
|
+
* The factory below is invoked on every `createFnAgent` call (the dashboard
|
|
33
|
+
* does this per chat message). Doing the probes synchronously with execSync
|
|
34
|
+
* froze the entire Node event loop for a few seconds while `claude` cold-
|
|
35
|
+
* started. Memoizing as a Promise + spawning the probes async means the
|
|
36
|
+
* factory returns immediately and other requests keep flowing; the result
|
|
37
|
+
* is logged once on first run and reused thereafter.
|
|
38
|
+
*/
|
|
39
|
+
let cliValidationPromise: Promise<void> | undefined;
|
|
40
|
+
|
|
41
|
+
function runCliValidationOnce(): Promise<void> {
|
|
42
|
+
if (cliValidationPromise) return cliValidationPromise;
|
|
43
|
+
cliValidationPromise = (async () => {
|
|
44
|
+
const presence = await validateCliPresenceAsync();
|
|
45
|
+
if (!presence.ok) {
|
|
46
|
+
console.warn(`[pi-claude-cli] ${presence.error.message}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
await validateCliAuthAsync();
|
|
50
|
+
})();
|
|
51
|
+
return cliValidationPromise;
|
|
52
|
+
}
|
|
53
|
+
|
|
29
54
|
let cachedMcpConfig: { hash: string; configPath: string } | undefined;
|
|
30
55
|
const DEBUG_MCP = process.env.PI_CLAUDE_CLI_DEBUG === "1";
|
|
31
56
|
|
|
@@ -116,9 +141,10 @@ function ensureMcpConfig(
|
|
|
116
141
|
|
|
117
142
|
export default function (pi: ExtensionAPI) {
|
|
118
143
|
try {
|
|
119
|
-
// Startup validation
|
|
120
|
-
|
|
121
|
-
|
|
144
|
+
// Startup validation: kick off async, memoized presence + auth probes
|
|
145
|
+
// without blocking the factory. Failures surface via warnings; the actual
|
|
146
|
+
// `claude` subprocess in streamViaCli still reports hard errors on send.
|
|
147
|
+
void runCliValidationOnce();
|
|
122
148
|
|
|
123
149
|
const catalogModels = getModels("anthropic").map((model) => ({
|
|
124
150
|
id: model.id,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion/pi-claude-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Fusion vendored fork: pi coding-agent extension that routes LLM calls through the Claude Code CLI. Forked from rchern/pi-claude-cli (MIT). See UPSTREAM.md.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": true,
|
|
@@ -47,6 +47,8 @@ import {
|
|
|
47
47
|
captureStderr,
|
|
48
48
|
validateCliPresence,
|
|
49
49
|
validateCliAuth,
|
|
50
|
+
validateCliPresenceAsync,
|
|
51
|
+
validateCliAuthAsync,
|
|
50
52
|
forceKillProcess,
|
|
51
53
|
registerProcess,
|
|
52
54
|
killAllProcesses,
|
|
@@ -407,6 +409,94 @@ describe("validateCliAuth", () => {
|
|
|
407
409
|
});
|
|
408
410
|
});
|
|
409
411
|
|
|
412
|
+
describe("validateCliPresenceAsync", () => {
|
|
413
|
+
beforeEach(() => {
|
|
414
|
+
vi.clearAllMocks();
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it("resolves ok=true when claude --version exits 0", async () => {
|
|
418
|
+
const EventEmitter = require("node:events");
|
|
419
|
+
(spawn as any).mockImplementationOnce(() => {
|
|
420
|
+
const proc = new EventEmitter();
|
|
421
|
+
proc.kill = vi.fn();
|
|
422
|
+
setImmediate(() => proc.emit("exit", 0));
|
|
423
|
+
return proc;
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const result = await validateCliPresenceAsync();
|
|
427
|
+
expect(result).toEqual({ ok: true });
|
|
428
|
+
const args = (spawn as any).mock.calls[0][1] as string[];
|
|
429
|
+
expect(args).toEqual(["--version"]);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("resolves ok=false with install message when spawn errors", async () => {
|
|
433
|
+
const EventEmitter = require("node:events");
|
|
434
|
+
(spawn as any).mockImplementationOnce(() => {
|
|
435
|
+
const proc = new EventEmitter();
|
|
436
|
+
proc.kill = vi.fn();
|
|
437
|
+
setImmediate(() => proc.emit("error", new Error("ENOENT")));
|
|
438
|
+
return proc;
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const result = await validateCliPresenceAsync();
|
|
442
|
+
expect(result.ok).toBe(false);
|
|
443
|
+
if (!result.ok) {
|
|
444
|
+
expect(result.error.message).toContain("Claude Code CLI not found");
|
|
445
|
+
expect(result.error.message).toContain("npm install");
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it("resolves ok=false when claude --version exits non-zero", async () => {
|
|
450
|
+
const EventEmitter = require("node:events");
|
|
451
|
+
(spawn as any).mockImplementationOnce(() => {
|
|
452
|
+
const proc = new EventEmitter();
|
|
453
|
+
proc.kill = vi.fn();
|
|
454
|
+
setImmediate(() => proc.emit("exit", 1));
|
|
455
|
+
return proc;
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const result = await validateCliPresenceAsync();
|
|
459
|
+
expect(result.ok).toBe(false);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
describe("validateCliAuthAsync", () => {
|
|
464
|
+
beforeEach(() => {
|
|
465
|
+
vi.clearAllMocks();
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it("resolves true when claude auth status exits 0", async () => {
|
|
469
|
+
const EventEmitter = require("node:events");
|
|
470
|
+
(spawn as any).mockImplementationOnce(() => {
|
|
471
|
+
const proc = new EventEmitter();
|
|
472
|
+
proc.kill = vi.fn();
|
|
473
|
+
setImmediate(() => proc.emit("exit", 0));
|
|
474
|
+
return proc;
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
expect(await validateCliAuthAsync()).toBe(true);
|
|
478
|
+
const args = (spawn as any).mock.calls[0][1] as string[];
|
|
479
|
+
expect(args).toEqual(["auth", "status"]);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it("resolves false and warns when claude auth status fails", async () => {
|
|
483
|
+
const EventEmitter = require("node:events");
|
|
484
|
+
(spawn as any).mockImplementationOnce(() => {
|
|
485
|
+
const proc = new EventEmitter();
|
|
486
|
+
proc.kill = vi.fn();
|
|
487
|
+
setImmediate(() => proc.emit("exit", 1));
|
|
488
|
+
return proc;
|
|
489
|
+
});
|
|
490
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
491
|
+
|
|
492
|
+
expect(await validateCliAuthAsync()).toBe(false);
|
|
493
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
494
|
+
expect.stringContaining("not authenticated"),
|
|
495
|
+
);
|
|
496
|
+
warnSpy.mockRestore();
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
410
500
|
describe("CLI flags", () => {
|
|
411
501
|
beforeEach(() => {
|
|
412
502
|
vi.clearAllMocks();
|
|
@@ -168,10 +168,10 @@ describe("provider registration (default export)", () => {
|
|
|
168
168
|
});
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
-
describe("streamViaCli", () => {
|
|
171
|
+
describe("streamViaCli", { timeout: 90_000 }, () => {
|
|
172
172
|
beforeEach(() => {
|
|
173
173
|
vi.clearAllMocks();
|
|
174
|
-
vi.useFakeTimers();
|
|
174
|
+
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
|
175
175
|
vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
176
176
|
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
177
177
|
delete process.env.PI_CLAUDE_CLI_DEBUG;
|
|
@@ -183,7 +183,7 @@ describe("streamViaCli", () => {
|
|
|
183
183
|
delete process.env.PI_CLAUDE_CLI_DEBUG;
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
-
it("returns an AssistantMessageEventStream", () => {
|
|
186
|
+
it("returns an AssistantMessageEventStream", async () => {
|
|
187
187
|
const model = mockModels[0] as any;
|
|
188
188
|
const context = {
|
|
189
189
|
messages: [{ role: "user", content: "Hello" }],
|
|
@@ -194,6 +194,16 @@ describe("streamViaCli", () => {
|
|
|
194
194
|
expect(result).toBeDefined();
|
|
195
195
|
expect(result.push).toBeDefined();
|
|
196
196
|
expect(result.end).toBeDefined();
|
|
197
|
+
|
|
198
|
+
// Ensure the spawned process/readline lifecycle completes so fake timers
|
|
199
|
+
// don't leave the test hanging on the inactivity timeout.
|
|
200
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
201
|
+
const proc = (spawn as any).mock.results[0].value;
|
|
202
|
+
proc.stdout.write(
|
|
203
|
+
`${JSON.stringify({ type: "result", subtype: "success", result: "ok" })}\n`,
|
|
204
|
+
);
|
|
205
|
+
proc.stdout.end();
|
|
206
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
197
207
|
});
|
|
198
208
|
|
|
199
209
|
it("logs PID and spawn args when debug mode is enabled", async () => {
|
|
@@ -241,3 +241,68 @@ export function validateCliAuth(): boolean {
|
|
|
241
241
|
return false;
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Run a one-shot `claude <args>` and resolve to the exit code.
|
|
247
|
+
*
|
|
248
|
+
* Why: the sync execSync variants block the Node event loop for the duration
|
|
249
|
+
* of a Claude CLI cold start (1–3s, occasionally longer). When pi-claude-cli's
|
|
250
|
+
* factory is invoked from a per-request createFnAgent path (Fusion dashboard
|
|
251
|
+
* does this on every chat send), those sync probes freeze every other request.
|
|
252
|
+
* This async variant uses spawn so the loop keeps turning while the subprocess
|
|
253
|
+
* starts up.
|
|
254
|
+
*/
|
|
255
|
+
function runClaudeProbe(args: string[], timeoutMs = 5000): Promise<number> {
|
|
256
|
+
return new Promise((resolve) => {
|
|
257
|
+
const proc = spawn("claude", args, { stdio: "ignore" });
|
|
258
|
+
const timer = setTimeout(() => {
|
|
259
|
+
try {
|
|
260
|
+
proc.kill("SIGKILL");
|
|
261
|
+
} catch {
|
|
262
|
+
// already dead
|
|
263
|
+
}
|
|
264
|
+
resolve(124);
|
|
265
|
+
}, timeoutMs);
|
|
266
|
+
proc.once("error", () => {
|
|
267
|
+
clearTimeout(timer);
|
|
268
|
+
resolve(127);
|
|
269
|
+
});
|
|
270
|
+
proc.once("exit", (code) => {
|
|
271
|
+
clearTimeout(timer);
|
|
272
|
+
resolve(code ?? 1);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Async, non-blocking variant of validateCliPresence.
|
|
279
|
+
* Resolves with `{ok: true}` on success, `{ok: false, error}` on failure —
|
|
280
|
+
* never rejects, so callers can fire-and-forget without unhandled rejections.
|
|
281
|
+
*/
|
|
282
|
+
export async function validateCliPresenceAsync(): Promise<
|
|
283
|
+
{ ok: true } | { ok: false; error: Error }
|
|
284
|
+
> {
|
|
285
|
+
const code = await runClaudeProbe(["--version"]);
|
|
286
|
+
if (code === 0) return { ok: true };
|
|
287
|
+
return {
|
|
288
|
+
ok: false,
|
|
289
|
+
error: new Error(
|
|
290
|
+
"Claude Code CLI not found. Install it: npm install -g @anthropic-ai/claude-code\n" +
|
|
291
|
+
"Then authenticate: claude auth login",
|
|
292
|
+
),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Async, non-blocking variant of validateCliAuth.
|
|
298
|
+
* Returns true if authenticated. Logs a warning (does not throw) otherwise.
|
|
299
|
+
*/
|
|
300
|
+
export async function validateCliAuthAsync(): Promise<boolean> {
|
|
301
|
+
const code = await runClaudeProbe(["auth", "status"]);
|
|
302
|
+
if (code === 0) return true;
|
|
303
|
+
console.warn(
|
|
304
|
+
"[pi-claude-cli] Claude CLI is not authenticated. " +
|
|
305
|
+
"Run 'claude auth login' to authenticate.",
|
|
306
|
+
);
|
|
307
|
+
return false;
|
|
308
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runfusion/fusion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Fusion CLI: HTTP API server, daemon, dashboard launcher, and task tooling for the Fusion AI coding agent.",
|
|
6
6
|
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as s,j as t}from"./vendor-react-K0fH_qHe.js";import{j as ze,aG as Mt,g as Tt,aH as At,a as $t,aI as Pt,aJ as Rt,aK as Dt,aL as Et,aM as Ft,aN as Lt,s as _t,aO as It,aP as zt,aQ as Ot,h as Ut,aR as Ht,a7 as Vt,a8 as Gt,S as Bt,N as Xe,D as Ze,ak as Kt,aS as qt,B as Ae,a4 as et,a5 as tt,aT as Jt,aU as Wt,aV as Qt,aW as Yt,aX as Xt,aY as Zt,aZ as st,i as es,u as nt,l as ts}from"./index-DNIrnlpO.js";import"./vendor-xterm-DzcZoU0P.js";const Ie="kb-chat-active-session";function ss(a){const r=a?.toolCalls;if(!Array.isArray(r))return;const c=r.map(l=>{if(!l||typeof l!="object")return null;const o=l,C=typeof o.toolName=="string"?o.toolName:"";if(!C)return null;const M=o.args;return{toolName:C,...M&&typeof M=="object"?{args:M}:{},isError:!!o.isError,result:o.result,status:"completed"}}).filter(l=>l!==null);return c.length>0?c:void 0}function at(a){return{id:a.id,sessionId:a.sessionId,role:a.role,content:a.content,thinkingOutput:a.thinkingOutput,toolCalls:ss(a.metadata),attachments:a.attachments,createdAt:a.createdAt}}function ns(a){const[r,c]=s.useState([]),[l,o]=s.useState(null),[C,M]=s.useState(!0),[R,k]=s.useState([]),[V,I]=s.useState(!1),[p,F]=s.useState(!1),[G,D]=s.useState(""),[E,T]=s.useState(""),[z,A]=s.useState([]),[B,H]=s.useState(""),[se,ne]=s.useState(""),[$,K]=s.useState(!0),[u,w]=s.useState(new Map),x=s.useRef(null),P=s.useRef(!1),O=s.useRef(""),Se=s.useRef(r),W=s.useRef(l),je=s.useRef(p);Se.current=r,W.current=l,je.current=p,s.useEffect(()=>{O.current=B},[B]);const oe=s.useRef(new Set),ae=s.useRef(0),ye=s.useRef(a);ye.current!==a&&(ye.current=a,ae.current++),s.useEffect(()=>{const d=ae.current;ze(void 0,a).then(g=>{if(ae.current!==d)return;const h=new Map;for(const j of g)h.set(j.id,j);w(h)}).catch(()=>{})},[a]);const ie=s.useCallback(async()=>{M(!0);try{const g=[...(await Mt(a)).sessions].sort((h,j)=>new Date(j.updatedAt).getTime()-new Date(h.updatedAt).getTime());c(g)}catch{}finally{M(!1)}},[a]);s.useEffect(()=>{ie()},[ie]);const X=s.useRef(()=>{});s.useEffect(()=>{if(C)return;const d=Tt(Ie,a);d&&r.find(h=>h.id===d)&&X.current(d)},[C,r,a]);const q=s.useCallback(async(d,g)=>{I(!0);try{const h=await At(d,{limit:50,...g},a),j=h.messages.map(at);g?.offset&&g.offset>0?k(Z=>[...j,...Z]):k(j),K(h.messages.length>=50)}catch{}finally{I(!1)}},[a]),re=s.useCallback((d,g)=>{x.current&&(x.current.close(),x.current=null);const h=g??r.find(j=>j.id===d);o(h||null),D(""),T(""),A([]),F(!1),K(!0),d?q(d):k([]),d?$t(Ie,d,a):Pt(Ie,a)},[r,q,a]);X.current=re;const ge=s.useCallback(async d=>{const g=await Rt(d,a);x.current&&(x.current.close(),x.current=null);const h={id:g.session.id,title:g.session.title,agentId:g.session.agentId,status:g.session.status,modelProvider:g.session.modelProvider,modelId:g.session.modelId,createdAt:g.session.createdAt,updatedAt:g.session.updatedAt};return c(j=>[h,...j]),re(h.id,h),k([]),h},[a,re]),fe=s.useCallback(async d=>{await Dt(d,{status:"archived"},a),c(g=>g.filter(h=>h.id!==d)),l?.id===d&&(o(null),k([]))},[l,a]),de=s.useCallback(async d=>{l?.id===d&&x.current&&(x.current.close(),x.current=null),await Et(d,a),c(g=>g.filter(h=>h.id!==d)),l?.id===d&&(o(null),k([]))},[l,a]),pe=s.useCallback(async()=>{!l||!$||await q(l.id,{offset:R.length})},[l,$,q,R.length]),le=s.useCallback(()=>{l&&(P.current=!0,x.current?.close(),x.current=null,Ft(l.id,a).catch(()=>{}),F(!1),D(""),T(""),A([]))},[l,a]),Q=s.useCallback(()=>{O.current="",H("")},[]),Y=s.useCallback((d,g)=>{if(!l)return;if(p){O.current=d,H(d);return}P.current=!1,x.current&&(x.current.close(),x.current=null);const h=`temp-${Date.now()}`,j={id:h,sessionId:l.id,role:"user",content:d,createdAt:new Date().toISOString()};k(b=>[...b,j]),D(""),T(""),A([]),F(!0);let Z="",U="",L=[];const we={onThinking:b=>{U+=b,T(U)},onText:b=>{Z+=b,D(Z)},onToolStart:b=>{L=[...L,{toolName:b.toolName,args:b.args,isError:!1,status:"running"}],A(L)},onToolEnd:b=>{const f=[...L];for(let S=f.length-1;S>=0;S--){const N=f[S];if(N?.toolName===b.toolName&&N.status==="running"){f[S]={...N,status:"completed",isError:b.isError,result:b.result},L=f,A(f);return}}L=[...f,{toolName:b.toolName,isError:b.isError,result:b.result,status:"completed"}],A(L)},onDone:b=>{const f={id:b.messageId||`msg-${Date.now()}`,sessionId:l.id,role:"assistant",content:Z,thinkingOutput:U,toolCalls:L.length>0?L:void 0,createdAt:new Date().toISOString()};oe.current.add(f.id),k(N=>[...N,f]),D(""),T(""),A([]),F(!1),x.current=null,setTimeout(()=>{oe.current.delete(f.id)},1e3),ie();const S=O.current.trim();S&&(O.current="",H(""),Y(S))},onError:b=>{if(k(f=>f.filter(S=>S.id!==h)),D(""),T(""),A([]),F(!1),x.current=null,console.error("[useChat] Stream error:",b),!P.current){const f=O.current.trim();f&&(O.current="",H(""),Y(f))}}};x.current=Lt(l.id,d,we,g,a)},[l,p,a,ie]),be=se?r.filter(d=>d.title?.toLowerCase().includes(se.toLowerCase())||d.agentId.toLowerCase().includes(se.toLowerCase())):r;return s.useEffect(()=>{const d=ae.current,g=a?`?projectId=${encodeURIComponent(a)}`:"",h=()=>ae.current!==d,j=f=>{if(h())return;const S=JSON.parse(f.data);c(N=>N.some(m=>m.id===S.id)?N:[S,...N])},Z=f=>{if(h())return;const S=JSON.parse(f.data);c(N=>[...N.map(ce=>ce.id===S.id?S:ce)]),W.current?.id===S.id&&o(S)},U=f=>{if(h())return;const{id:S}=JSON.parse(f.data);c(N=>N.filter(m=>m.id!==S)),W.current?.id===S&&(o(null),k([]))},L=f=>{if(h())return;const S=JSON.parse(f.data),N=at(S);oe.current.has(N.id)||W.current?.id===N.sessionId&&!je.current&&k(m=>m.some(ce=>ce.id===N.id)?m:[...m,N])},we=f=>{if(h())return;const{id:S}=JSON.parse(f.data);k(N=>N.filter(m=>m.id!==S))};return _t(`/api/events${g}`,{events:{"chat:session:created":j,"chat:session:updated":Z,"chat:session:deleted":U,"chat:message:added":L,"chat:message:deleted":we}})},[a]),s.useEffect(()=>()=>{x.current&&(x.current.close(),x.current=null)},[]),{sessions:r,activeSession:l,sessionsLoading:C,messages:R,messagesLoading:V,isStreaming:p,streamingText:G,streamingThinking:E,streamingToolCalls:z,pendingMessage:B,selectSession:re,createSession:ge,archiveSession:fe,deleteSession:de,sendMessage:Y,stopStreaming:le,clearPendingMessage:Q,loadMoreMessages:pe,hasMoreMessages:$,searchQuery:se,setSearchQuery:ne,filteredSessions:be,refreshSessions:ie,agentsMap:u}}function it(a){const r=new Date(a),l=new Date().getTime()-r.getTime(),o=Math.floor(l/1e3),C=Math.floor(o/60),M=Math.floor(C/60),R=Math.floor(M/24);return o<60?"just now":C<60?`${C}m ago`:M<24?`${M}h ago`:R<7?`${R}d ago`:r.toLocaleDateString()}function rt(a,r){if(!a||!r)return null;const c=r.toLowerCase();if(c.includes("claude")){let o=r.replace(/^claude[- ]/i,"Claude ").replace(/sonnet[- ](\d+)[- ](\d+)/i,"Sonnet $1.$2").replace(/sonnet[- ](\d+)/i,"Sonnet $1").replace(/haiku[- ](\d+)/i,"Haiku $1").replace(/opus[- ](\d+)/i,"Opus $1").replace(/sonnet/i,"Sonnet").replace(/haiku/i,"Haiku").replace(/opus/i,"Opus").replace(/-/g," ").trim();return o=o.replace(/\s+/g," "),o.length>30?o.slice(0,30)+"…":o}if(c.includes("gpt")||c.includes("openai")){const o=r.replace(/^gpt-4-turbo$/i,"GPT-4 Turbo").replace(/^gpt-4o-mini$/i,"GPT-4o Mini").replace(/^gpt-4o$/i,"GPT-4o").replace(/^gpt-4$/i,"GPT-4").replace(/^gpt-o1-preview$/i,"GPT-o1 Preview").replace(/^gpt-o1-mini$/i,"GPT-o1 Mini").replace(/^gpt-o1$/i,"GPT-o1").replace(/^gpt/i,"GPT").trim();return o.length>30?o.slice(0,30)+"…":o}if(c.includes("gemini")){const o=r.replace(/^gemini[- ]/i,"Gemini ").replace(/pro[- ](\d+)[- ](\d+)/i,"Pro $1.$2").replace(/pro[- ](\d+)/i,"Pro $1").replace(/-/g," ").replace(/\s+/g," ").trim();return o.length>30?o.slice(0,30)+"…":o}const l=r.replace(/-/g," ").replace(/^\w/,o=>o.toUpperCase()).replace(/\s+/g," ").trim();return l.length>30?l.slice(0,30)+"…":l}function $e(a,r){return a.length<=r?a:`${a.slice(0,r)}…`}function as(a){if(!a)return null;const r=Object.entries(a);return r.length===0?null:r.map(([c,l])=>{const o=typeof l=="string"?l:(()=>{try{return JSON.stringify(l)}catch{return String(l)}})();return`${c}=${$e(o,50)}`}).join(", ")}function is(a){if(a===void 0)return null;if(typeof a=="string")return $e(a,200);try{return $e(JSON.stringify(a),200)}catch{return $e(String(a),200)}}function lt(a){if(!a||a.length===0)return null;const r=(p,F)=>{const G=p.status==="running",D=p.status==="completed"&&p.isError,E=as(p.args),T=is(p.result),z=G?E:T?`result: ${T}`:E?`args: ${E}`:null,A=G?"running":D?"error":"completed";return t.jsxs("details",{className:`chat-tool-call${G?" chat-tool-call--running":""}${D?" chat-tool-call--error":""}`,open:G,children:[t.jsxs("summary",{children:[t.jsx("span",{className:"chat-tool-call-status-dot","aria-hidden":"true"}),t.jsx("span",{className:"chat-tool-call-name",children:p.toolName}),z&&t.jsx("span",{className:"chat-tool-call-preview",title:z,children:z}),t.jsx("span",{className:"chat-tool-call-status-text",children:A})]}),t.jsxs("div",{className:"chat-tool-call-content",children:[E&&t.jsxs("div",{className:"chat-tool-call-row",children:[t.jsx("span",{className:"chat-tool-call-label",children:"args"}),t.jsx("span",{className:"chat-tool-call-value",children:E})]}),T&&t.jsxs("div",{className:`chat-tool-call-row${D?" chat-tool-call-row--error":""}`,children:[t.jsx("span",{className:"chat-tool-call-label",children:"result"}),t.jsx("span",{className:"chat-tool-call-value",children:T})]})]})]},`${p.toolName}-${F}`)},c="chat-tool-calls";if(a.length===1)return t.jsxs("div",{className:c,"data-testid":"chat-tool-calls",children:[t.jsxs("div",{className:"chat-tool-calls-header",children:[t.jsx(st,{size:12,"aria-hidden":"true"}),t.jsx("span",{children:"Tool calls"})]}),r(a[0],0)]});const l=a.filter(p=>p.status==="running").length,o=a.filter(p=>p.status==="completed"&&p.isError).length,C=l>0,M=Array.from(new Set(a.map(p=>p.toolName))),R=M.slice(0,5),k=Math.max(0,M.length-R.length),V=k>0?`${R.join(", ")}, +${k} more`:R.join(", "),I=C?`(${l} running)`:o>0?`(${o} ${o===1?"error":"errors"})`:null;return t.jsx("div",{className:c,"data-testid":"chat-tool-calls",children:t.jsxs("details",{className:"chat-tool-calls-group","data-testid":"chat-tool-calls-group",open:C,children:[t.jsxs("summary",{className:"chat-tool-calls-group-summary",children:[t.jsx(st,{size:12,"aria-hidden":"true"}),t.jsxs("span",{children:[a.length," tool calls"]}),t.jsx("span",{className:"chat-tool-calls-names",title:V,children:V}),I&&t.jsx("span",{className:"chat-tool-calls-group-status",children:I})]}),a.map((p,F)=>r(p,F))]})})}const rs={pre:({children:a,...r})=>t.jsx("pre",{...r,className:"chat-markdown-pre",children:a}),table:({children:a,...r})=>t.jsx("table",{...r,className:"chat-markdown-table",children:a})},Pe="__fn_agent__",ls=["image/png","image/jpeg","image/gif","image/webp","text/plain","application/json","text/yaml","text/markdown","text/csv","application/xml","text/x-log"];function ct(a){const r=/(^|[\s])\/([^\s]*)$/.exec(a);if(!r)return null;const c=r[1]??"",l=r[2]??"",o=r.index+c.length;return{filter:l,start:o,end:a.length}}function cs(a,r){const c=a.slice(0,r),l=/(^|[\s\n])@([\w-]*)$/.exec(c);if(!l)return null;const o=l[2]??"",C=c.length-o.length-1;return{filter:o,start:C,end:r}}function os({projectId:a,onClose:r,onCreate:c}){const[l,o]=s.useState("agent"),[C,M]=s.useState([]),[R,k]=s.useState(!0),[V,I]=s.useState(""),[p,F]=s.useState([]),[G,D]=s.useState(!0),[E,T]=s.useState(""),[z,A]=s.useState([]),[B,H]=s.useState([]);s.useEffect(()=>{let u=!1;return k(!0),ze(void 0,a).then(w=>{u||M(w)}).catch(()=>{u||M([])}).finally(()=>{u||k(!1)}),()=>{u=!0}},[a]),s.useEffect(()=>{D(!0),es().then(u=>{F(u.models),A(u.favoriteProviders),H(u.favoriteModels)}).catch(()=>{F([]),A([]),H([])}).finally(()=>{D(!1)})},[]);const se=s.useCallback(async u=>{const w=z,P=w.includes(u)?w.filter(O=>O!==u):[u,...w];A(P);try{await nt({favoriteProviders:P,favoriteModels:B})}catch{A(w)}},[z,B]),ne=s.useCallback(async u=>{const w=B,P=w.includes(u)?w.filter(O=>O!==u):[u,...w];H(P);try{await nt({favoriteProviders:z,favoriteModels:P})}catch{H(w)}},[B,z]),$=u=>{if(u.preventDefault(),l==="agent"){if(!V)return;c({agentId:V});return}if(!E)return;const w=E.indexOf("/");if(w<=0)return;const x=E.slice(0,w),P=E.slice(w+1);c({agentId:Pe,modelProvider:x,modelId:P})},K=l==="agent"?!V:!E;return t.jsx("div",{className:"chat-new-dialog-backdrop",onClick:r,role:"dialog","aria-modal":"true",children:t.jsxs("div",{className:"chat-new-dialog",onClick:u=>u.stopPropagation(),children:[t.jsx("h3",{children:"New Chat"}),t.jsxs("div",{className:"chat-new-dialog-mode-toggle","data-testid":"chat-new-dialog-mode-toggle",children:[t.jsx("button",{type:"button",className:`chat-new-dialog-mode-btn${l==="agent"?" chat-new-dialog-mode-btn--active":""}`,"data-testid":"chat-new-dialog-mode-agent",onClick:()=>{o("agent"),T("")},children:"Agent"}),t.jsx("button",{type:"button",className:`chat-new-dialog-mode-btn${l==="model"?" chat-new-dialog-mode-btn--active":""}`,"data-testid":"chat-new-dialog-mode-model",onClick:()=>{o("model"),I("")},children:"Model"})]}),t.jsxs("form",{onSubmit:$,children:[l==="agent"&&t.jsxs("label",{className:"chat-new-dialog-model-label",children:["Agent",R?t.jsx("div",{className:"chat-new-dialog-loading",children:"Loading agents..."}):C.length===0?t.jsx("div",{className:"chat-new-dialog-empty",children:"No agents available"}):t.jsx("div",{className:"chat-new-dialog-agent-list",children:C.map(u=>t.jsxs("button",{type:"button",className:`chat-new-dialog-agent-item${V===u.id?" chat-new-dialog-agent-item--selected":""}`,onClick:()=>I(u.id),"data-testid":`agent-option-${u.id}`,children:[t.jsx(Ae,{size:16}),t.jsx("span",{className:"chat-new-dialog-agent-name",children:u.name}),t.jsx("span",{className:"chat-new-dialog-agent-role",children:u.role})]},u.id))})]}),l==="model"&&t.jsx("div",{className:"chat-new-dialog-model-dropdown","data-testid":"chat-new-dialog-model-section",children:G?t.jsx("div",{className:"chat-new-dialog-loading",children:"Loading models..."}):t.jsx(ts,{models:p,value:E,onChange:T,label:"Model",placeholder:"Select a model",favoriteProviders:z,onToggleFavorite:se,favoriteModels:B,onToggleModelFavorite:ne})}),t.jsxs("div",{className:"chat-new-dialog-actions",children:[t.jsx("button",{type:"button",className:"btn btn-sm",onClick:r,children:"Cancel"}),t.jsx("button",{type:"submit",className:"btn btn-sm btn-primary",disabled:K,children:"Create"})]})]})]})})}function gs({projectId:a,addToast:r}){const{activeSession:c,sessionsLoading:l,messages:o,messagesLoading:C,isStreaming:M,streamingText:R,streamingThinking:k,streamingToolCalls:V,selectSession:I,createSession:p,archiveSession:F,deleteSession:G,sendMessage:D,stopStreaming:E,pendingMessage:T,clearPendingMessage:z,searchQuery:A,setSearchQuery:B,filteredSessions:H}=ns(a),[se,ne]=s.useState(!1),[$,K]=s.useState(""),[u,w]=s.useState(null),[x,P]=s.useState(null),[O,Se]=s.useState(!0),[W,je]=s.useState(new Map),[oe,ae]=s.useState([]),[ye,ie]=s.useState(!0),[X,q]=s.useState(!1),[re,ge]=s.useState(""),[fe,de]=s.useState(0),[pe,le]=s.useState(""),[Q,Y]=s.useState(!1),[be,d]=s.useState(0),[g,h]=s.useState(-1),[j,Z]=s.useState(()=>new Set),[U,L]=s.useState([]),[we,b]=s.useState(!1),[,f]=s.useState(!1),[S,N]=s.useState({top:0,left:0}),m=It({projectId:a}),ce=s.useCallback(e=>{if(!e||!m.mentionActive)return;const n=e.getBoundingClientRect();N({top:n.top-260,left:n.left+8})},[m.mentionActive]),Oe=s.useRef(null),ee=s.useRef(null),Ue=s.useRef(null),_=s.useRef(null),He=s.useRef(null),Ve=s.useRef([]),ke=s.useRef(0),ue=zt()==="mobile",{keyboardOverlap:Ce,viewportHeight:Ge}=Ot({enabled:ue&&!!c}),ot=Ce>0?{"--keyboard-overlap":`${Ce}px`,...Ge!==null?{"--vv-height":`${Ge}px`}:{}}:{},J=s.useMemo(()=>{const e=re.trim().toLowerCase();return(e?oe.filter(i=>i.name.toLowerCase().includes(e)):oe).slice(0,10)},[oe,re]),xe=s.useMemo(()=>Array.from(W.values()),[W]),he=s.useMemo(()=>{const e=pe.trim().toLowerCase();return e?xe.filter(n=>n.name.toLowerCase().includes(e)):xe},[xe,pe]),Be=s.useMemo(()=>{const e=new Map;for(const n of xe)e.set(n.name.toLowerCase(),n);return e},[xe]);s.useEffect(()=>{de(0)},[J]),s.useEffect(()=>{d(0)},[pe,Q]),s.useEffect(()=>()=>{ee.current!==null&&window.clearTimeout(ee.current)},[]),s.useEffect(()=>{Oe.current?.scrollIntoView({behavior:"smooth"})},[o,R]),s.useEffect(()=>{if(Ce<=0)return;const e=Ue.current;e&&(e.scrollTop=e.scrollHeight)},[Ce]),s.useEffect(()=>{const e=()=>w(null);if(u)return document.addEventListener("click",e),()=>document.removeEventListener("click",e)},[u]),s.useEffect(()=>{let e=!1;const n=a;return ze(void 0,a).then(i=>{if(e||n!==a)return;const v=new Map;for(const y of i)v.set(y.id,y);je(v)}).catch(()=>{}),()=>{e=!0}},[a]),s.useEffect(()=>{let e=!1;return ie(!0),Ut(a).then(n=>{e||ae(n)}).catch(()=>{e||ae([])}).finally(()=>{e||ie(!1)}),()=>{e=!0}},[a]),s.useEffect(()=>{Ve.current=U},[U]),s.useEffect(()=>()=>{for(const e of Ve.current)e.previewUrl&&URL.revokeObjectURL(e.previewUrl)},[]);const Me=s.useCallback(e=>{if(!e||e.length===0)return;const n=[];for(const i of Array.from(e)){if(!ls.includes(i.type))continue;const v=i.type.startsWith("image/");n.push({file:i,previewUrl:v?URL.createObjectURL(i):""})}n.length>0&&L(i=>[...i,...n])},[]),dt=s.useCallback(e=>{L(n=>{const i=n[e];return i?.previewUrl&&URL.revokeObjectURL(i.previewUrl),n.filter((v,y)=>y!==e)})},[]),ut=s.useCallback(e=>{const n=e.clipboardData?.files;if(!n||n.length===0)return;const i=Array.from(n).filter(v=>v.type.startsWith("image/"));i.length!==0&&Me(i)},[Me]),ht=s.useCallback(async e=>{try{await p(e),ne(!1),ue&&Se(!1)}catch{r("Failed to create chat session","error")}},[p,r,ue]),Re=s.useCallback(()=>{const e=$.trim(),n=U.map(i=>i.file);!e&&n.length===0||!c||(K(""),q(!1),ge(""),Y(!1),le(""),h(-1),D(e,n),L(i=>{for(const v of i)v.previewUrl&&URL.revokeObjectURL(v.previewUrl);return[]}))},[$,U,c,D]),De=s.useCallback(e=>{K(n=>{const i=ct(n);if(!i)return n;const v=`/skill:${e.name} `,y=n.slice(0,i.start)+v+n.slice(i.end);return window.requestAnimationFrame(()=>{_.current&&(_.current.style.height="auto",_.current.style.height=`${Math.min(_.current.scrollHeight,120)}px`,_.current.focus())}),y}),q(!1),ge(""),de(0)},[]),Ee=s.useCallback(e=>{const n=_.current;if(!n||g<0)return;const i=n.selectionStart??ke.current,v=n.selectionEnd??i,y=Math.max(i,v),Ne=Math.min(g,y),me=`${`@${e.name.replace(/\s+/g,"_")}`} `,_e=$.slice(0,Ne)+me+$.slice(y),ve=Ne+me.length;K(_e),Y(!1),le(""),d(0),h(-1),window.requestAnimationFrame(()=>{_.current&&(_.current.style.height="auto",_.current.style.height=`${Math.min(_.current.scrollHeight,120)}px`,_.current.focus(),_.current.setSelectionRange(ve,ve))})},[g,$]),mt=s.useCallback(e=>{const n=/@([\w-]+)/g,i=[];let v=0,y=n.exec(e);for(;y;){const[Ne,Ye=""]=y,me=y.index;me>v&&i.push(e.slice(v,me));const _e=Ye.replace(/_/g," ").toLowerCase(),ve=Be.get(_e);ve?i.push(t.jsxs("span",{className:"chat-mention-chip",children:["@",ve.name.replace(/\s+/g,"_")]},`${ve.id}-${me}`)):i.push(Ne),v=me+Ne.length,y=n.exec(e)}return v<e.length&&i.push(e.slice(v)),i.length===0?e:i},[Be]),Ke=s.useCallback(e=>c?`/api/chat/sessions/${encodeURIComponent(c.id)}/attachments/${encodeURIComponent(e)}`:"",[c]),gt=s.useCallback(e=>!e||e.length===0?null:t.jsx("div",{className:"chat-message-attachments",children:e.map(n=>{const i=n.mimeType.startsWith("image/"),v=n.id||n.filename,y=Ke(n.filename);return i?t.jsx("a",{className:"chat-message-attachment-link","data-testid":"chat-message-attachment",href:y,target:"_blank",rel:"noopener noreferrer",children:t.jsx("img",{className:"chat-message-attachment",src:y,alt:n.originalName})},v):t.jsxs("a",{className:"chat-message-attachment-file","data-testid":"chat-message-attachment",href:y,target:"_blank",rel:"noopener noreferrer",children:[t.jsx(Ht,{size:14}),t.jsx("span",{children:n.originalName})]},v)})}),[Ke]),ft=s.useCallback(e=>{if(ke.current=e.currentTarget.selectionStart??ke.current,m.mentionActive&&m.files.length>0){if(m.handleKeyDown(e,$),e.key==="Enter"||e.key==="Tab"){const n=m.files[m.selectedIndex];if(n){const i=m.selectFile(n,$);K(i),m.dismissMention(),f(!1)}}return}if(Q&&e.key==="ArrowDown"){e.preventDefault(),he.length>0&&d(n=>(n+1)%he.length);return}if(Q&&e.key==="ArrowUp"){e.preventDefault(),he.length>0&&d(n=>n===0?he.length-1:n-1);return}if(Q&&e.key==="Enter"){e.preventDefault();const n=he[be]??he[0];n&&Ee(n);return}if(Q&&e.key==="Escape"){e.preventDefault(),Y(!1),le(""),h(-1);return}if(X&&e.key==="ArrowDown"){e.preventDefault(),J.length>0&&de(n=>(n+1)%J.length);return}if(X&&e.key==="ArrowUp"){e.preventDefault(),J.length>0&&de(n=>n===0?J.length-1:n-1);return}if(X&&(e.key==="Enter"||e.key==="Tab")&&J.length>0){e.preventDefault();const n=J[fe]??J[0];n&&De(n);return}if(X&&e.key==="Escape"){e.preventDefault(),q(!1);return}e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Re())},[Q,he,be,Ee,X,J,fe,De,Re,m,$]),Te=s.useCallback((e,n)=>{const i=cs(e,n);if(i){Y(!0),le(i.filter),h(i.start);return}Y(!1),le(""),h(-1)},[]),pt=s.useCallback(e=>{const n=e.target,i=n.value,v=n.selectionStart??i.length;ke.current=v,K(i);const y=ct(i);y?(q(!0),ge(y.filter)):(q(!1),ge("")),Te(i,v),m.detectMention(i,v),f(m.mentionActive),m.mentionActive&&ce(n),n.style.height="auto",n.style.height=`${Math.min(n.scrollHeight,120)}px`},[Te]),Fe=s.useCallback(e=>{const n=e.currentTarget,i=n.selectionStart??n.value.length;ke.current=i,Te(n.value,i),m.detectMention(n.value,i),f(m.mentionActive),m.mentionActive&&ce(n)},[Te,m,ce]),xt=s.useCallback(e=>{e.key!=="Escape"&&Fe(e)},[Fe]),vt=s.useCallback(()=>{ee.current!==null&&window.clearTimeout(ee.current),ee.current=window.setTimeout(()=>{q(!1),Y(!1),le(""),h(-1),f(!1),m.dismissMention(),ee.current=null},120)},[m]),St=s.useCallback(()=>{ee.current!==null&&(window.clearTimeout(ee.current),ee.current=null)},[]),bt=s.useCallback(async e=>{w(null);try{await F(e),r("Conversation archived","success")}catch{r("Failed to archive conversation","error")}},[F,r]),wt=s.useCallback(async e=>{P(null),w(null);try{await G(e),r("Conversation deleted","success")}catch{r("Failed to delete conversation","error")}},[G,r]),kt=s.useCallback(e=>{I(e),ue&&Se(!1)},[I,ue]),Nt=s.useCallback(()=>{I(""),Se(!0)},[I]),jt=()=>t.jsxs("div",{className:"chat-empty-state",children:[t.jsx(Zt,{size:48,strokeWidth:1.5}),t.jsx("h2",{children:"Start a new conversation"}),t.jsxs("button",{className:"btn btn-primary",onClick:()=>ne(!0),children:[t.jsx(Ze,{size:16}),"New Chat"]})]}),te=rt(c?.modelProvider,c?.modelId),qe=c?.agentId===Pe?te??"Fusion":c?.title||W.get(c?.agentId??"")?.name||c?.agentId||"Chat",yt=!!(te&&te!==qe),Le=W.get(c?.agentId??"")?.name||(c?.agentId===Pe?te??"Fusion":c?.agentId?.slice(0,30)??"Fusion"),Je=!!(te&&te!==Le),Ct=T.length>50?`${T.slice(0,50)}…`:T,We=s.useCallback(e=>{Z(n=>{const i=new Set(n);return i.has(e)?i.delete(e):i.add(e),i})},[]),Qe=s.useCallback((e,n=!1)=>n?t.jsx("div",{className:"chat-message-content chat-message-content--plain",children:e}):t.jsx("div",{className:"chat-message-content chat-message-content--markdown",children:t.jsx(Vt,{remarkPlugins:[Gt],components:rs,children:e})}),[]);return t.jsxs("div",{className:"chat-view",children:[t.jsxs("div",{className:`chat-sidebar${O?"":" chat-sidebar--hidden"}`,children:[t.jsx("div",{className:"chat-sidebar-search",children:t.jsxs("div",{className:"chat-sidebar-search-wrapper",children:[t.jsx(Bt,{size:14,className:"chat-sidebar-search-icon"}),t.jsx("input",{type:"text",className:"chat-sidebar-search",placeholder:"Search conversations...",value:A,onChange:e=>B(e.target.value),"data-testid":"chat-search-input"})]})}),t.jsx("div",{className:"chat-session-list chat-sidebar-list",children:l?t.jsx("div",{style:{padding:"12px",color:"var(--text-secondary)",fontSize:"13px"},children:"Loading..."}):H.length===0?t.jsx("div",{style:{padding:"12px",color:"var(--text-secondary)",fontSize:"13px"},children:"No conversations yet"}):H.map(e=>t.jsxs("div",{className:`chat-session-item${c?.id===e.id?" chat-session-item--active":""}`,onClick:()=>kt(e.id),onContextMenu:n=>{n.preventDefault(),w({sessionId:e.id,x:n.clientX,y:n.clientY})},"data-testid":`chat-session-${e.id}`,children:[t.jsx("button",{className:"chat-session-delete-btn",onClick:n=>{n.stopPropagation(),P(e.id)},"data-testid":"chat-session-delete-btn","aria-label":"Delete conversation",children:t.jsx(Xe,{size:14})}),t.jsx("div",{className:"chat-session-title",children:e.title||"Untitled"}),t.jsx("div",{className:"chat-session-preview",children:e.lastMessagePreview||"No messages"}),t.jsxs("div",{className:"chat-session-meta",children:[t.jsx("span",{children:W.get(e.agentId)?.name||(e.agentId===Pe?rt(e.modelProvider,e.modelId)??"Fusion":e.agentId.slice(0,30))}),t.jsx("span",{children:e.updatedAt?it(e.updatedAt):""})]})]},e.id))}),t.jsx("div",{className:"chat-sidebar-footer",children:t.jsxs("button",{className:"btn btn-sm btn-primary chat-sidebar-footer-btn",onClick:()=>ne(!0),"data-testid":"chat-new-btn",children:[t.jsx(Ze,{size:14}),"New Chat"]})})]}),u&&t.jsxs("div",{className:"chat-session-context-menu",style:{top:u.y,left:u.x},onClick:e=>e.stopPropagation(),children:[t.jsxs("button",{onClick:()=>bt(u.sessionId),"data-testid":"chat-context-archive",children:[t.jsx(Kt,{size:14}),"Archive"]}),t.jsxs("button",{onClick:()=>{w(null),P(u.sessionId)},"data-testid":"chat-context-delete",children:[t.jsx(Xe,{size:14}),"Delete"]})]}),x&&t.jsx("div",{className:"chat-new-dialog-backdrop",onClick:()=>P(null),children:t.jsxs("div",{className:"chat-new-dialog",onClick:e=>e.stopPropagation(),children:[t.jsx("h3",{children:"Delete Conversation?"}),t.jsx("p",{style:{fontSize:"14px",color:"var(--text-secondary)",marginBottom:"16px"},children:"This action cannot be undone. All messages in this conversation will be permanently deleted."}),t.jsxs("div",{className:"chat-new-dialog-actions",children:[t.jsx("button",{className:"btn btn-sm",onClick:()=>P(null),children:"Cancel"}),t.jsx("button",{className:"btn btn-sm btn-danger",onClick:()=>void wt(x),children:"Delete"})]})]})}),t.jsxs("div",{className:"chat-thread",style:ot,children:[(c||!ue)&&t.jsxs("div",{className:"chat-thread-header",children:[ue&&c&&t.jsx("button",{className:"btn-icon",onClick:Nt,"data-testid":"chat-back-btn",children:t.jsx(qt,{size:16})}),t.jsx(Ae,{size:16}),t.jsx("span",{className:"chat-thread-header-title",children:qe}),yt&&t.jsx("span",{className:"chat-model-tag",children:te})]}),t.jsxs("div",{className:"chat-messages",ref:Ue,children:[C?t.jsx("div",{style:{color:"var(--text-secondary)",fontSize:"13px"},children:"Loading messages..."}):o.length===0&&!c?jt():o.length===0&&c?t.jsx("div",{style:{color:"var(--text-secondary)",fontSize:"13px"},children:"No messages yet. Start the conversation!"}):t.jsxs(t.Fragment,{children:[o.map(e=>{const n=e.role==="assistant",i=j.has(e.id);return t.jsxs("div",{className:`chat-message chat-message--${e.role}`,"data-testid":`chat-message-${e.id}`,children:[n&&t.jsxs("div",{className:"chat-message-avatar",children:[t.jsx(Ae,{size:14}),t.jsx("span",{children:Le}),Je&&t.jsx("span",{className:"chat-model-tag",children:te}),t.jsx("button",{type:"button",className:`chat-message-render-toggle${i?" chat-message-render-toggle--plain":""}`,"data-testid":"chat-message-render-toggle","aria-label":i?"Show rendered markdown":"Show plain text",onClick:()=>We(e.id),children:i?t.jsx(et,{size:14}):t.jsx(tt,{size:14})})]}),n?Qe(e.content,i):t.jsx("div",{className:"chat-message-content",children:mt(e.content)}),lt(e.toolCalls),e.thinkingOutput&&t.jsxs("details",{className:"chat-message-thinking",children:[t.jsx("summary",{children:"Thinking"}),t.jsx("pre",{className:"chat-message-thinking-content",children:e.thinkingOutput})]}),gt(e.attachments),t.jsx("div",{className:"chat-message-time",children:it(e.createdAt)})]},e.id)}),M&&t.jsxs("div",{className:"chat-message chat-message--assistant chat-message--streaming",children:[t.jsxs("div",{className:"chat-message-avatar",children:[t.jsx(Ae,{size:14}),t.jsx("span",{children:Le}),Je&&t.jsx("span",{className:"chat-model-tag",children:te}),t.jsx("button",{type:"button",className:`chat-message-render-toggle${j.has("__streaming__")?" chat-message-render-toggle--plain":""}`,"data-testid":"chat-message-render-toggle","aria-label":j.has("__streaming__")?"Show rendered markdown":"Show plain text",onClick:()=>We("__streaming__"),children:j.has("__streaming__")?t.jsx(et,{size:14}):t.jsx(tt,{size:14})})]}),R?Qe(R,j.has("__streaming__")):t.jsx("div",{className:"chat-message-content chat-message-content--waiting",children:k?"Thinking…":"Connecting…"}),lt(V),k&&t.jsxs("details",{className:"chat-message-thinking",children:[t.jsx("summary",{children:"Thinking"}),t.jsx("pre",{className:"chat-message-thinking-content",children:k})]}),t.jsxs("div",{className:"chat-typing-indicator",children:[t.jsx("span",{}),t.jsx("span",{}),t.jsx("span",{})]})]})]}),t.jsx("div",{ref:Oe})]}),c&&t.jsxs("div",{className:"chat-input-area",children:[t.jsx("input",{ref:He,type:"file",accept:"image/*,.txt,.json,.yaml,.yml,.log,.csv,.xml,.md",multiple:!0,style:{display:"none"},onChange:e=>{Me(e.target.files),e.target.value=""}}),X&&t.jsx("div",{className:"chat-skill-menu","data-testid":"chat-skill-menu",role:"listbox","aria-label":"Skill suggestions",children:ye?t.jsx("div",{className:"chat-skill-menu-empty",children:"Loading skills…"}):J.length===0?t.jsx("div",{className:"chat-skill-menu-empty",children:re?"No skills found":"No skills available"}):J.map((e,n)=>t.jsxs("button",{type:"button",role:"option","aria-selected":n===fe,className:`chat-skill-menu-item${n===fe?" chat-skill-menu-item--highlighted":""}`,onMouseDown:i=>i.preventDefault(),onMouseEnter:()=>de(n),onClick:()=>De(e),children:[t.jsx("span",{className:"chat-skill-menu-item-name",children:e.name}),t.jsx("span",{className:"chat-skill-menu-item-description",title:e.relativePath,children:e.relativePath})]},e.id))}),U.length>0&&t.jsx("div",{className:"chat-attachment-previews","data-testid":"chat-attachment-previews",children:U.map((e,n)=>t.jsxs("div",{className:"chat-attachment-preview","data-testid":`chat-attachment-preview-${n}`,children:[e.previewUrl?t.jsx("img",{src:e.previewUrl,alt:e.file.name}):t.jsx("span",{className:"chat-attachment-preview-name",children:e.file.name}),t.jsx("button",{type:"button",className:"chat-attachment-remove",onClick:()=>dt(n),"data-testid":`chat-attachment-remove-${n}`,"aria-label":`Remove ${e.file.name}`,children:"×"})]},e.previewUrl||`${e.file.name}-${n}`))}),t.jsxs("div",{className:"chat-input-row",children:[t.jsx("button",{type:"button",className:"btn-icon chat-attach-btn","data-testid":"chat-attach-btn","aria-label":"Attach files",onClick:()=>He.current?.click(),children:t.jsx(Jt,{size:16})}),t.jsxs("div",{className:`chat-input-wrapper${we?" chat-input-wrapper--dragover":""}`,onDragOver:e=>{e.preventDefault(),b(!0)},onDragLeave:()=>b(!1),onDrop:e=>{e.preventDefault(),b(!1),Me(e.dataTransfer.files)},children:[t.jsx("textarea",{ref:_,className:"chat-input-textarea",placeholder:"Type a message...",value:$,onChange:pt,onKeyDown:ft,onKeyUp:xt,onClick:Fe,onBlur:vt,onFocus:St,onPaste:ut,rows:1,"data-testid":"chat-input"}),t.jsx(Wt,{agents:xe,filter:pe,highlightedIndex:be,visible:Q,onSelect:Ee,position:"below"}),t.jsx(Qt,{visible:m.mentionActive&&!Q,position:S,files:m.files,selectedIndex:m.selectedIndex,onSelect:e=>{const n=m.selectFile(e,$);K(n),m.dismissMention(),f(!1),_.current?.focus()},loading:m.loading}),T&&t.jsxs("div",{className:"chat-pending-message","data-testid":"chat-pending-indicator",children:[t.jsx("span",{children:`Queued: ${Ct}`}),t.jsx("button",{type:"button",className:"chat-pending-message-dismiss","aria-label":"Dismiss queued message","data-testid":"chat-pending-dismiss",onClick:z,children:"×"})]})]}),M?t.jsx("button",{className:"chat-input-stop",onClick:E,"aria-label":"Stop generation","data-testid":"chat-stop-btn",children:t.jsx(Yt,{size:14})}):t.jsx("button",{className:"chat-input-send",onClick:()=>void Re(),disabled:!$.trim()&&U.length===0,"data-testid":"chat-send-btn",children:t.jsx(Xt,{size:16})})]})]})]}),se&&t.jsx(os,{projectId:a,onClose:()=>ne(!1),onCreate:ht})]})}export{gs as ChatView};
|