@nextclaw/ui 0.11.0 → 0.11.2
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/CHANGELOG.md +16 -2
- package/dist/assets/{ChannelsList-BqsOYnXz.js → ChannelsList-CKl1Zg8f.js} +3 -3
- package/dist/assets/ChatPage-BJgO27mk.js +37 -0
- package/dist/assets/{DocBrowser-BmL0QXBZ.js → DocBrowser-DYRBs4-z.js} +1 -1
- package/dist/assets/{LogoBadge-C1HiPZPf.js → LogoBadge-33Qlv3Hg.js} +1 -1
- package/dist/assets/MarketplacePage-B8BZVtjV.js +49 -0
- package/dist/assets/{McpMarketplacePage-CLHFnNBd.js → McpMarketplacePage-BRuE5fJJ.js} +2 -2
- package/dist/assets/{ModelConfig-LQSR58tc.js → ModelConfig-BiFblwO-.js} +1 -1
- package/dist/assets/ProvidersList-9goRgHE4.js +1 -0
- package/dist/assets/RemoteAccessPage-5vCxZPS6.js +1 -0
- package/dist/assets/RuntimeConfig-BmDFHBdW.js +1 -0
- package/dist/assets/{SearchConfig-Chzo_JGs.js → SearchConfig-CJx5CKwG.js} +1 -1
- package/dist/assets/{SecretsConfig-CEIbjZYA.js → SecretsConfig-B91efXoK.js} +2 -2
- package/dist/assets/SessionsConfig-CbFPVmx3.js +2 -0
- package/dist/assets/index-BtAuUyww.css +1 -0
- package/dist/assets/index-COJomMe9.js +8 -0
- package/dist/assets/{label-GACO2RzW.js → label-BnSDpjhL.js} +1 -1
- package/dist/assets/ncp-session-adapter-w8ZHprab.js +1 -0
- package/dist/assets/{page-layout-DjXaK3A3.js → page-layout-B1RIu5-r.js} +1 -1
- package/dist/assets/popover-ChzbCIfO.js +1 -0
- package/dist/assets/security-config-eYa6Ovfa.js +1 -0
- package/dist/assets/skeleton-D4Eyop0R.js +1 -0
- package/dist/assets/{status-dot-IWEBezqb.js → status-dot-CrCw5tkJ.js} +1 -1
- package/dist/assets/{switch-DCHAJSrA.js → switch-C3vVTpfU.js} +1 -1
- package/dist/assets/tabs-custom-Ilrgt6n1.js +1 -0
- package/dist/assets/useConfirmDialog-BeaFLDO8.js +1 -0
- package/dist/assets/{vendor-CNhxtHCf.js → vendor-waGu-koL.js} +101 -86
- package/dist/index.html +3 -3
- package/package.json +5 -5
- package/src/App.test.tsx +42 -10
- package/src/App.tsx +5 -40
- package/src/api/api-base.test.ts +37 -0
- package/src/api/api-base.ts +0 -4
- package/src/api/config.ts +2 -270
- package/src/api/types.ts +0 -117
- package/src/components/chat/ChatPage.tsx +1 -11
- package/src/components/chat/ChatSidebar.test.tsx +1 -50
- package/src/components/chat/ChatSidebar.tsx +0 -5
- package/src/components/chat/README.md +2 -0
- package/src/components/chat/adapters/chat-message.adapter.test.ts +71 -4
- package/src/components/chat/adapters/chat-message.adapter.ts +195 -78
- package/src/components/chat/chat-attachment-upload-limit.test.ts +41 -0
- package/src/components/chat/chat-session-display.ts +9 -0
- package/src/components/chat/chat-session-label.service.ts +3 -12
- package/src/components/chat/chat-session-preference-sync.test.ts +10 -13
- package/src/components/chat/chat-stream/types.ts +4 -57
- package/src/components/chat/containers/chat-message-list.container.tsx +7 -0
- package/src/components/chat/ncp/NcpChatPage.tsx +3 -3
- package/src/components/chat/useHydratedNcpAgent.test.tsx +77 -0
- package/src/components/config/README.md +2 -0
- package/src/components/config/SessionsConfig.tsx +152 -132
- package/src/hooks/use-auth.test.ts +3 -3
- package/src/hooks/use-auth.ts +16 -4
- package/src/hooks/use-realtime-query-bridge.ts +0 -24
- package/src/hooks/useConfig.ts +10 -137
- package/src/lib/i18n.chat.ts +7 -0
- package/src/lib/session-run-status.ts +1 -63
- package/src/vite-env.d.ts +1 -0
- package/vite.config.ts +4 -4
- package/dist/assets/ChatPage-CJBYKR-Y.js +0 -38
- package/dist/assets/MarketplacePage-BIRP0NRS.js +0 -49
- package/dist/assets/ProvidersList-CwI-mxah.js +0 -1
- package/dist/assets/RemoteAccessPage-Cw5BqZb6.js +0 -1
- package/dist/assets/RuntimeConfig-DbowSRAb.js +0 -1
- package/dist/assets/SessionsConfig-BR8GfGWL.js +0 -2
- package/dist/assets/chat-message-CPG7zxRR.js +0 -3
- package/dist/assets/index-j6A_-1b6.js +0 -8
- package/dist/assets/index-kaPUhd-8.css +0 -1
- package/dist/assets/popover-DTaFiTmU.js +0 -1
- package/dist/assets/security-config-Dk-yoKvK.js +0 -1
- package/dist/assets/skeleton-Dm2xOBSA.js +0 -1
- package/dist/assets/tabs-custom-DKSbDSB9.js +0 -1
- package/dist/assets/useConfirmDialog-ByJ8A8n7.js +0 -1
- package/src/api/config.stream.test.ts +0 -115
- package/src/components/chat/chat-chain.test.ts +0 -22
- package/src/components/chat/chat-chain.ts +0 -23
- package/src/components/chat/chat-page-data.ts +0 -171
- package/src/components/chat/chat-page-runtime.ts +0 -190
- package/src/components/chat/chat-stream/nextbot-parsers.ts +0 -52
- package/src/components/chat/chat-stream/nextbot-runtime-agent.ts +0 -413
- package/src/components/chat/chat-stream/stream-event-adapter.ts +0 -98
- package/src/components/chat/chat-stream/transport.ts +0 -253
- package/src/components/chat/legacy/LegacyChatPage.tsx +0 -223
- package/src/components/chat/managers/chat-input.manager.ts +0 -228
- package/src/components/chat/managers/chat-thread.manager.ts +0 -87
- package/src/components/chat/presenter/chat.presenter.ts +0 -32
- package/src/components/chat/useChatRuntimeController.ts +0 -134
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as c,j as e,e as M}from"./vendor-CNhxtHCf.js";import{a9 as O,aa as R,ab as I,ac as B,ad as _,C as u,a6 as h,a7 as x,t as s,a8 as m,J as p,B as j,I as l}from"./index-j6A_-1b6.js";import{L as o}from"./label-GACO2RzW.js";import{S as G}from"./switch-DCHAJSrA.js";import{P as J,a as V}from"./page-layout-DjXaK3A3.js";const W=8;function A(r){return r.trim().length>=W}function L(r,n){return r!==n?(M.error(s("authPasswordMismatch")),!1):!0}function q(){const r=O(),n=R(),S=I(),g=B(),y=_(),[f,U]=c.useState(""),[i,N]=c.useState(""),[w,b]=c.useState(""),[d,v]=c.useState(""),[P,C]=c.useState(""),t=r.data,E=f.trim().length>0&&A(i)&&i===w&&!n.isPending,D=A(d)&&d===P&&!g.isPending,T=async()=>{if(L(i,w))try{await n.mutateAsync({username:f.trim(),password:i}),N(""),b("")}catch{}},F=async()=>{if(L(d,P))try{await g.mutateAsync({password:d}),v(""),C("")}catch{}},H=async a=>{try{await S.mutateAsync({enabled:a})}catch{}},k=async()=>{try{await y.mutateAsync()}catch{}};return r.isLoading&&!t?e.jsxs(u,{children:[e.jsxs(h,{children:[e.jsx(x,{children:s("authSecurityTitle")}),e.jsx(m,{children:s("authSecurityDescription")})]}),e.jsx(p,{className:"text-sm text-gray-500",children:s("loading")})]}):r.isError||!t?e.jsxs(u,{children:[e.jsxs(h,{children:[e.jsx(x,{children:s("authSecurityTitle")}),e.jsx(m,{children:s("authSecurityDescription")})]}),e.jsxs(p,{className:"space-y-4",children:[e.jsx("p",{className:"text-sm text-gray-500",children:s("authStatusLoadFailed")}),e.jsx(j,{variant:"outline",onClick:()=>{r.refetch()},children:s("authRetryStatus")})]})]}):t.configured?e.jsxs(u,{children:[e.jsxs(h,{children:[e.jsx(x,{children:s("authSecurityTitle")}),e.jsx(m,{children:s("authSecurityDescription")})]}),e.jsxs(p,{className:"space-y-6",children:[e.jsxs("div",{className:"rounded-xl border border-gray-200 p-4",children:[e.jsxs("div",{className:"flex flex-col gap-4 md:flex-row md:items-start md:justify-between",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx("p",{className:"text-sm font-medium text-gray-900",children:s("authStatusLabel")}),e.jsx("p",{className:"text-sm text-gray-600",children:s("authStatusConfiguredUser").replace("{username}",t.username??"")}),e.jsx("p",{className:"text-xs text-gray-500",children:s("authUsernameFixedHelp")})]}),e.jsx("span",{className:"inline-flex items-center rounded-full bg-gray-100 px-3 py-1 text-xs font-medium text-gray-700",children:t.enabled?s("enabled"):s("disabled")})]}),e.jsxs("div",{className:"mt-4 flex flex-col gap-4 border-t border-gray-200 pt-4 md:flex-row md:items-center md:justify-between",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx("p",{className:"text-sm font-medium text-gray-900",children:s("authEnableLabel")}),e.jsx("p",{className:"text-xs text-gray-500",children:t.enabled?s("authEnableOnHelp"):s("authEnableOffHelp")})]}),e.jsx(G,{checked:t.enabled,disabled:S.isPending,onCheckedChange:a=>{H(a)}})]})]}),e.jsxs("div",{className:"rounded-xl border border-gray-200 p-4 space-y-4",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx("p",{className:"text-sm font-medium text-gray-900",children:s("authPasswordSectionTitle")}),e.jsx("p",{className:"text-xs text-gray-500",children:s("authPasswordSectionDescription")})]}),e.jsxs("div",{className:"grid gap-4 md:grid-cols-2",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx(o,{htmlFor:"auth-password-next",children:s("authPassword")}),e.jsx(l,{id:"auth-password-next",type:"password",value:d,onChange:a=>v(a.target.value),placeholder:s("authPasswordPlaceholder")})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(o,{htmlFor:"auth-password-confirm",children:s("authConfirmPassword")}),e.jsx(l,{id:"auth-password-confirm",type:"password",value:P,onChange:a=>C(a.target.value),placeholder:s("authConfirmPasswordPlaceholder")})]})]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[e.jsx(j,{type:"button",disabled:!D,onClick:()=>void F(),children:g.isPending?s("authPasswordUpdating"):s("authPasswordAction")}),t.enabled&&t.authenticated?e.jsx(j,{type:"button",variant:"outline",disabled:y.isPending,onClick:()=>void k(),children:y.isPending?s("authLoggingOut"):s("authLogoutAction")}):null]}),e.jsx("p",{className:"text-xs text-gray-500",children:s("authSessionMemoryNotice")})]})]})]}):e.jsxs(u,{children:[e.jsxs(h,{children:[e.jsx(x,{children:s("authSecurityTitle")}),e.jsx(m,{children:s("authSecurityDescription")})]}),e.jsxs(p,{className:"space-y-5",children:[e.jsxs("div",{className:"rounded-xl border border-dashed border-gray-200 bg-gray-50/70 p-4",children:[e.jsx("p",{className:"text-sm font-medium text-gray-900",children:s("authSetupTitle")}),e.jsx("p",{className:"mt-1 text-sm text-gray-500",children:s("authSetupDescription")})]}),e.jsxs("div",{className:"grid gap-4 md:grid-cols-2",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx(o,{htmlFor:"auth-setup-username",children:s("authUsername")}),e.jsx(l,{id:"auth-setup-username",value:f,onChange:a=>U(a.target.value),placeholder:s("authUsernamePlaceholder")})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(o,{htmlFor:"auth-setup-password",children:s("authPassword")}),e.jsx(l,{id:"auth-setup-password",type:"password",value:i,onChange:a=>N(a.target.value),placeholder:s("authPasswordPlaceholder")})]})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(o,{htmlFor:"auth-setup-confirm",children:s("authConfirmPassword")}),e.jsx(l,{id:"auth-setup-confirm",type:"password",value:w,onChange:a=>b(a.target.value),placeholder:s("authConfirmPasswordPlaceholder")}),e.jsx("p",{className:"text-xs text-gray-500",children:s("authPasswordMinLengthHint")})]}),e.jsx(j,{type:"button",disabled:!E,onClick:()=>void T(),children:n.isPending?s("authSettingUp"):s("authSetupAction")})]})]})}function Z(){return e.jsxs(J,{className:"space-y-6",children:[e.jsx(V,{title:s("authSecurityTitle"),description:s("authSecurityDescription")}),e.jsx(q,{})]})}export{Z as SecurityConfig};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{j as t}from"./vendor-CNhxtHCf.js";import{c as o}from"./index-j6A_-1b6.js";function m({className:e,...s}){return t.jsx("div",{className:o("animate-pulse rounded-md bg-slate-200",e),...s})}export{m as S};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{j as e}from"./vendor-CNhxtHCf.js";import{as as m,c as s}from"./index-j6A_-1b6.js";function c({tabs:a,activeTab:i,onChange:o,className:n}){return e.jsx("div",{className:s("flex items-center gap-6 border-b border-gray-200/60 mb-6",n),children:a.map(t=>{const r=i===t.id;return e.jsxs("button",{onClick:()=>o(t.id),className:s("relative pb-3 text-[14px] font-medium transition-all duration-fast flex items-center gap-1.5",r?"text-gray-900":"text-gray-600 hover:text-gray-900"),children:[t.label,t.count!==void 0&&e.jsx("span",{className:s("text-[11px] font-medium","text-gray-500"),children:m(t.count)}),r&&e.jsx("div",{className:"absolute bottom-0 left-0 right-0 h-[2px] bg-primary rounded-full"})]},t.id)})})}export{c as T};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{j as a,r as t}from"./vendor-CNhxtHCf.js";import{am as p,an as C,ao as h,ap as x,aq as g,ar as D,B as d,t as i}from"./index-j6A_-1b6.js";const j=({open:l,onOpenChange:r,title:c,description:o,confirmLabel:s=i("confirm"),cancelLabel:e=i("cancel"),variant:n="default",onConfirm:u,onCancel:f})=>{const m=()=>{u(),r(!1)},v=()=>{f(),r(!1)};return a.jsx(p,{open:l,onOpenChange:r,children:a.jsxs(C,{className:"[&>:last-child]:hidden",onCloseAutoFocus:b=>b.preventDefault(),children:[a.jsxs(h,{children:[a.jsx(x,{children:c}),o?a.jsx(g,{children:o}):null]}),a.jsxs(D,{className:"gap-2 sm:gap-0",children:[a.jsx(d,{type:"button",variant:"outline",onClick:v,children:e}),a.jsx(d,{type:"button",variant:n==="destructive"?"destructive":"default",onClick:m,children:s})]})]})})},L={open:!1,title:"",description:"",confirmLabel:i("confirm"),cancelLabel:i("cancel"),variant:"default",resolve:null};function y(){const[l,r]=t.useState(L),c=t.useCallback(e=>new Promise(n=>{r({open:!0,title:e.title,description:e.description??"",confirmLabel:e.confirmLabel??i("confirm"),cancelLabel:e.cancelLabel??i("cancel"),variant:e.variant??"default",resolve:u=>{n(u),r(f=>({...f,open:!1,resolve:null}))}})}),[]),o=t.useCallback(e=>{r(n=>(!e&&n.resolve&&n.resolve(!1),{...n,open:e,resolve:e?n.resolve:null}))},[]),s=t.useCallback(()=>a.jsx(j,{open:l.open,onOpenChange:o,title:l.title,description:l.description||void 0,confirmLabel:l.confirmLabel,cancelLabel:l.cancelLabel,variant:l.variant,onConfirm:()=>{var e;return(e=l.resolve)==null?void 0:e.call(l,!0)},onCancel:()=>{var e;return(e=l.resolve)==null?void 0:e.call(l,!1)}}),[l,o]);return{confirm:c,ConfirmDialog:s}}export{y as u};
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { sendChatTurnStream, streamChatRun } from '@/api/config';
|
|
2
|
-
|
|
3
|
-
const mocks = vi.hoisted(() => ({
|
|
4
|
-
request: vi.fn(),
|
|
5
|
-
openStream: vi.fn()
|
|
6
|
-
}));
|
|
7
|
-
|
|
8
|
-
vi.mock('@/transport', () => ({
|
|
9
|
-
appClient: {
|
|
10
|
-
request: mocks.request,
|
|
11
|
-
openStream: mocks.openStream
|
|
12
|
-
}
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
describe('api/config stream routing', () => {
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
mocks.request.mockReset();
|
|
18
|
-
mocks.openStream.mockReset();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('routes sendChatTurnStream through appClient.openStream', async () => {
|
|
22
|
-
const onReady = vi.fn();
|
|
23
|
-
const onDelta = vi.fn();
|
|
24
|
-
const onSessionEvent = vi.fn();
|
|
25
|
-
|
|
26
|
-
mocks.openStream.mockImplementation(({ onEvent }) => {
|
|
27
|
-
onEvent({ name: 'ready', payload: { sessionKey: 's1' } });
|
|
28
|
-
onEvent({ name: 'delta', payload: { delta: 'hello' } });
|
|
29
|
-
onEvent({ name: 'session_event', payload: { type: 'session.updated' } });
|
|
30
|
-
onEvent({ name: 'final', payload: { sessionKey: 's1', reply: 'hello world' } });
|
|
31
|
-
return {
|
|
32
|
-
finished: Promise.resolve({ sessionKey: 's1', reply: 'hello world' }),
|
|
33
|
-
cancel: vi.fn()
|
|
34
|
-
};
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const result = await sendChatTurnStream(
|
|
38
|
-
{ message: 'hi' } as never,
|
|
39
|
-
{ onReady, onDelta, onSessionEvent }
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
expect(mocks.openStream).toHaveBeenCalledWith({
|
|
43
|
-
method: 'POST',
|
|
44
|
-
path: '/api/chat/turn/stream',
|
|
45
|
-
body: { message: 'hi' },
|
|
46
|
-
signal: undefined,
|
|
47
|
-
onEvent: expect.any(Function)
|
|
48
|
-
});
|
|
49
|
-
expect(onReady).toHaveBeenCalledWith({ sessionKey: 's1' });
|
|
50
|
-
expect(onDelta).toHaveBeenCalledWith({ delta: 'hello' });
|
|
51
|
-
expect(onSessionEvent).toHaveBeenCalledWith({ data: { type: 'session.updated' } });
|
|
52
|
-
expect(result).toEqual({ sessionKey: 's1', reply: 'hello world' });
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('routes streamChatRun through appClient.openStream and preserves query params', async () => {
|
|
56
|
-
const onReady = vi.fn();
|
|
57
|
-
const onDelta = vi.fn();
|
|
58
|
-
const onSessionEvent = vi.fn();
|
|
59
|
-
|
|
60
|
-
mocks.openStream.mockImplementation(() => ({
|
|
61
|
-
finished: Promise.resolve({ sessionKey: 's1', reply: 'resumed' }),
|
|
62
|
-
cancel: vi.fn()
|
|
63
|
-
}));
|
|
64
|
-
|
|
65
|
-
const result = await streamChatRun(
|
|
66
|
-
{ runId: 'run-1', fromEventIndex: 42 },
|
|
67
|
-
{ onReady, onDelta, onSessionEvent }
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
expect(mocks.openStream).toHaveBeenCalledWith({
|
|
71
|
-
method: 'GET',
|
|
72
|
-
path: '/api/chat/runs/run-1/stream?fromEventIndex=42',
|
|
73
|
-
signal: undefined,
|
|
74
|
-
onEvent: expect.any(Function)
|
|
75
|
-
});
|
|
76
|
-
expect(onReady).not.toHaveBeenCalled();
|
|
77
|
-
expect(onDelta).not.toHaveBeenCalled();
|
|
78
|
-
expect(onSessionEvent).not.toHaveBeenCalled();
|
|
79
|
-
expect(result).toEqual({ sessionKey: 's1', reply: 'resumed' });
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('surfaces transport error events as rejected stream promises', async () => {
|
|
83
|
-
mocks.openStream.mockImplementation(({ onEvent }) => {
|
|
84
|
-
let resolveFinished!: () => void;
|
|
85
|
-
let rejectFinished!: (error: Error) => void;
|
|
86
|
-
const finished = new Promise<void>((resolve, reject) => {
|
|
87
|
-
resolveFinished = resolve;
|
|
88
|
-
rejectFinished = reject;
|
|
89
|
-
});
|
|
90
|
-
queueMicrotask(() => {
|
|
91
|
-
try {
|
|
92
|
-
onEvent({ name: 'error', payload: { message: 'chat stream failed' } });
|
|
93
|
-
resolveFinished();
|
|
94
|
-
} catch (error) {
|
|
95
|
-
rejectFinished(error instanceof Error ? error : new Error(String(error)));
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
return {
|
|
99
|
-
finished,
|
|
100
|
-
cancel: vi.fn()
|
|
101
|
-
};
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
await expect(
|
|
105
|
-
sendChatTurnStream(
|
|
106
|
-
{ message: 'hi' } as never,
|
|
107
|
-
{
|
|
108
|
-
onReady: vi.fn(),
|
|
109
|
-
onDelta: vi.fn(),
|
|
110
|
-
onSessionEvent: vi.fn()
|
|
111
|
-
}
|
|
112
|
-
)
|
|
113
|
-
).rejects.toThrow('chat stream failed');
|
|
114
|
-
});
|
|
115
|
-
});
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { resolveChatChain } from '@/components/chat/chat-chain';
|
|
3
|
-
|
|
4
|
-
describe('resolveChatChain', () => {
|
|
5
|
-
it('defaults to ncp when no query or env override is provided', () => {
|
|
6
|
-
vi.stubEnv('VITE_CHAT_CHAIN', '');
|
|
7
|
-
|
|
8
|
-
expect(resolveChatChain('')).toBe('ncp');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('allows explicit legacy rollback from query string', () => {
|
|
12
|
-
vi.stubEnv('VITE_CHAT_CHAIN', 'ncp');
|
|
13
|
-
|
|
14
|
-
expect(resolveChatChain('?chatChain=legacy')).toBe('legacy');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('accepts env override when query string is absent', () => {
|
|
18
|
-
vi.stubEnv('VITE_CHAT_CHAIN', 'legacy');
|
|
19
|
-
|
|
20
|
-
expect(resolveChatChain('')).toBe('legacy');
|
|
21
|
-
});
|
|
22
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export type ChatChain = 'legacy' | 'ncp';
|
|
2
|
-
|
|
3
|
-
const DEFAULT_CHAT_CHAIN: ChatChain = 'ncp';
|
|
4
|
-
|
|
5
|
-
function normalizeChatChain(value: string | null | undefined): ChatChain | null {
|
|
6
|
-
if (typeof value !== 'string') {
|
|
7
|
-
return null;
|
|
8
|
-
}
|
|
9
|
-
const normalized = value.trim().toLowerCase();
|
|
10
|
-
if (normalized === 'legacy' || normalized === 'ncp') {
|
|
11
|
-
return normalized;
|
|
12
|
-
}
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function resolveChatChain(search: string): ChatChain {
|
|
17
|
-
const fromSearch = normalizeChatChain(new URLSearchParams(search).get('chatChain'));
|
|
18
|
-
if (fromSearch) {
|
|
19
|
-
return fromSearch;
|
|
20
|
-
}
|
|
21
|
-
const fromEnv = normalizeChatChain(import.meta.env.VITE_CHAT_CHAIN);
|
|
22
|
-
return fromEnv ?? DEFAULT_CHAT_CHAIN;
|
|
23
|
-
}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
import type { Dispatch, SetStateAction } from 'react';
|
|
3
|
-
import type { SessionEntryView, ThinkingLevel } from '@/api/types';
|
|
4
|
-
import type { ChatModelOption } from '@/components/chat/chat-input.types';
|
|
5
|
-
import { useChatSessionTypeState } from '@/components/chat/useChatSessionTypeState';
|
|
6
|
-
import {
|
|
7
|
-
resolveRecentSessionPreferredThinking,
|
|
8
|
-
resolveRecentSessionPreferredModel,
|
|
9
|
-
useSyncSelectedModel,
|
|
10
|
-
useSyncSelectedThinking
|
|
11
|
-
} from '@/components/chat/chat-session-preference-governance';
|
|
12
|
-
import {
|
|
13
|
-
useChatCapabilities,
|
|
14
|
-
useChatSessionTypes,
|
|
15
|
-
useConfig,
|
|
16
|
-
useConfigMeta,
|
|
17
|
-
useSessionHistory,
|
|
18
|
-
useSessions,
|
|
19
|
-
} from '@/hooks/useConfig';
|
|
20
|
-
import { useMarketplaceInstalled } from '@/hooks/useMarketplace';
|
|
21
|
-
import { buildProviderModelCatalog, composeProviderModel, resolveModelThinkingCapability } from '@/lib/provider-models';
|
|
22
|
-
|
|
23
|
-
type UseChatPageDataParams = {
|
|
24
|
-
query: string;
|
|
25
|
-
selectedSessionKey: string | null;
|
|
26
|
-
selectedAgentId: string;
|
|
27
|
-
currentSelectedModel: string;
|
|
28
|
-
pendingSessionType: string;
|
|
29
|
-
setPendingSessionType: Dispatch<SetStateAction<string>>;
|
|
30
|
-
setSelectedModel: Dispatch<SetStateAction<string>>;
|
|
31
|
-
setSelectedThinkingLevel: Dispatch<SetStateAction<ThinkingLevel | null>>;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export function useChatPageData(params: UseChatPageDataParams) {
|
|
35
|
-
const configQuery = useConfig();
|
|
36
|
-
const configMetaQuery = useConfigMeta();
|
|
37
|
-
const sessionsQuery = useSessions({ q: params.query.trim() || undefined, limit: 120, activeMinutes: 0 });
|
|
38
|
-
const installedSkillsQuery = useMarketplaceInstalled('skill');
|
|
39
|
-
const chatCapabilitiesQuery = useChatCapabilities({
|
|
40
|
-
sessionKey: params.selectedSessionKey,
|
|
41
|
-
agentId: params.selectedAgentId
|
|
42
|
-
});
|
|
43
|
-
const historyQuery = useSessionHistory(params.selectedSessionKey, 300);
|
|
44
|
-
const sessionTypesQuery = useChatSessionTypes();
|
|
45
|
-
const isProviderStateResolved =
|
|
46
|
-
(configQuery.isFetched || configQuery.isSuccess) &&
|
|
47
|
-
(configMetaQuery.isFetched || configMetaQuery.isSuccess);
|
|
48
|
-
|
|
49
|
-
const modelOptions = useMemo<ChatModelOption[]>(() => {
|
|
50
|
-
const providers = buildProviderModelCatalog({
|
|
51
|
-
meta: configMetaQuery.data,
|
|
52
|
-
config: configQuery.data,
|
|
53
|
-
onlyConfigured: true
|
|
54
|
-
});
|
|
55
|
-
const seen = new Set<string>();
|
|
56
|
-
const options: ChatModelOption[] = [];
|
|
57
|
-
for (const provider of providers) {
|
|
58
|
-
for (const localModel of provider.models) {
|
|
59
|
-
const value = composeProviderModel(provider.prefix, localModel);
|
|
60
|
-
if (!value || seen.has(value)) {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
seen.add(value);
|
|
64
|
-
options.push({
|
|
65
|
-
value,
|
|
66
|
-
modelLabel: localModel,
|
|
67
|
-
providerLabel: provider.displayName,
|
|
68
|
-
thinkingCapability: resolveModelThinkingCapability(provider.modelThinking, localModel, provider.aliases)
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return options.sort((left, right) => {
|
|
73
|
-
const providerCompare = left.providerLabel.localeCompare(right.providerLabel);
|
|
74
|
-
if (providerCompare !== 0) {
|
|
75
|
-
return providerCompare;
|
|
76
|
-
}
|
|
77
|
-
return left.modelLabel.localeCompare(right.modelLabel);
|
|
78
|
-
});
|
|
79
|
-
}, [configMetaQuery.data, configQuery.data]);
|
|
80
|
-
|
|
81
|
-
const sessions = useMemo(() => sessionsQuery.data?.sessions ?? [], [sessionsQuery.data?.sessions]);
|
|
82
|
-
const skillRecords = useMemo(() => installedSkillsQuery.data?.records ?? [], [installedSkillsQuery.data?.records]);
|
|
83
|
-
const selectedSession = useMemo(
|
|
84
|
-
() => sessions.find((session) => session.key === params.selectedSessionKey) ?? null,
|
|
85
|
-
[params.selectedSessionKey, sessions]
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const sessionTypeState = useChatSessionTypeState({
|
|
89
|
-
selectedSession,
|
|
90
|
-
selectedSessionKey: params.selectedSessionKey,
|
|
91
|
-
pendingSessionType: params.pendingSessionType,
|
|
92
|
-
setPendingSessionType: params.setPendingSessionType,
|
|
93
|
-
sessionTypesData: sessionTypesQuery.data
|
|
94
|
-
});
|
|
95
|
-
const recentSessionPreferredModel = useMemo(
|
|
96
|
-
() =>
|
|
97
|
-
resolveRecentSessionPreferredModel({
|
|
98
|
-
sessions,
|
|
99
|
-
selectedSessionKey: params.selectedSessionKey,
|
|
100
|
-
sessionType: sessionTypeState.selectedSessionType
|
|
101
|
-
}),
|
|
102
|
-
[params.selectedSessionKey, sessionTypeState.selectedSessionType, sessions]
|
|
103
|
-
);
|
|
104
|
-
const currentModelOption = useMemo(
|
|
105
|
-
() => modelOptions.find((option) => option.value === params.currentSelectedModel),
|
|
106
|
-
[modelOptions, params.currentSelectedModel]
|
|
107
|
-
);
|
|
108
|
-
const supportedThinkingLevels = useMemo(
|
|
109
|
-
() => (currentModelOption?.thinkingCapability?.supported as ThinkingLevel[] | undefined) ?? [],
|
|
110
|
-
[currentModelOption?.thinkingCapability?.supported]
|
|
111
|
-
);
|
|
112
|
-
const defaultThinkingLevel = useMemo(
|
|
113
|
-
() => (currentModelOption?.thinkingCapability?.default as ThinkingLevel | null | undefined) ?? null,
|
|
114
|
-
[currentModelOption?.thinkingCapability?.default]
|
|
115
|
-
);
|
|
116
|
-
const recentSessionPreferredThinking = useMemo(
|
|
117
|
-
() =>
|
|
118
|
-
resolveRecentSessionPreferredThinking({
|
|
119
|
-
sessions,
|
|
120
|
-
selectedSessionKey: params.selectedSessionKey,
|
|
121
|
-
sessionType: sessionTypeState.selectedSessionType
|
|
122
|
-
}),
|
|
123
|
-
[params.selectedSessionKey, sessionTypeState.selectedSessionType, sessions]
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
useSyncSelectedModel({
|
|
127
|
-
modelOptions,
|
|
128
|
-
selectedSessionKey: params.selectedSessionKey,
|
|
129
|
-
selectedSessionExists: Boolean(selectedSession),
|
|
130
|
-
selectedSessionPreferredModel: selectedSession?.preferredModel,
|
|
131
|
-
fallbackPreferredModel: recentSessionPreferredModel,
|
|
132
|
-
defaultModel: configQuery.data?.agents.defaults.model,
|
|
133
|
-
setSelectedModel: params.setSelectedModel
|
|
134
|
-
});
|
|
135
|
-
useSyncSelectedThinking({
|
|
136
|
-
supportedThinkingLevels,
|
|
137
|
-
selectedSessionKey: params.selectedSessionKey,
|
|
138
|
-
selectedSessionExists: Boolean(selectedSession),
|
|
139
|
-
selectedSessionPreferredThinking: selectedSession?.preferredThinking ?? null,
|
|
140
|
-
fallbackPreferredThinking: recentSessionPreferredThinking ?? null,
|
|
141
|
-
defaultThinkingLevel,
|
|
142
|
-
setSelectedThinkingLevel: params.setSelectedThinkingLevel
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const historyMessages = useMemo(() => historyQuery.data?.messages ?? [], [historyQuery.data?.messages]);
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
configQuery,
|
|
149
|
-
configMetaQuery,
|
|
150
|
-
sessionsQuery,
|
|
151
|
-
installedSkillsQuery,
|
|
152
|
-
chatCapabilitiesQuery,
|
|
153
|
-
historyQuery,
|
|
154
|
-
sessionTypesQuery,
|
|
155
|
-
isProviderStateResolved,
|
|
156
|
-
modelOptions,
|
|
157
|
-
sessions,
|
|
158
|
-
skillRecords,
|
|
159
|
-
selectedSession,
|
|
160
|
-
historyMessages,
|
|
161
|
-
...sessionTypeState
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export function sessionDisplayName(session: SessionEntryView): string {
|
|
166
|
-
if (session.label && session.label.trim()) {
|
|
167
|
-
return session.label.trim();
|
|
168
|
-
}
|
|
169
|
-
const chunks = session.key.split(':');
|
|
170
|
-
return chunks[chunks.length - 1] || session.key;
|
|
171
|
-
}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import type { ChatRunView } from '@/api/types';
|
|
3
|
-
import { useChatRuns } from '@/hooks/useConfig';
|
|
4
|
-
import { buildActiveRunBySessionKey, buildSessionRunStatusByKey } from '@/lib/session-run-status';
|
|
5
|
-
|
|
6
|
-
export type ChatMainPanelView = 'chat' | 'cron' | 'skills';
|
|
7
|
-
|
|
8
|
-
export function useSessionRunStatus(params: {
|
|
9
|
-
view: ChatMainPanelView;
|
|
10
|
-
selectedSessionKey: string | null;
|
|
11
|
-
activeBackendRunId: string | null;
|
|
12
|
-
isLocallyRunning: boolean;
|
|
13
|
-
resumeRun: (run: ChatRunView) => Promise<void>;
|
|
14
|
-
}) {
|
|
15
|
-
const { view, selectedSessionKey, activeBackendRunId, isLocallyRunning, resumeRun } = params;
|
|
16
|
-
const [suppressedSessionState, setSuppressedSessionState] = useState<{
|
|
17
|
-
sessionKey: string;
|
|
18
|
-
runId?: string;
|
|
19
|
-
} | null>(null);
|
|
20
|
-
const wasLocallyRunningRef = useRef(false);
|
|
21
|
-
const resumedRunBySessionRef = useRef(new Map<string, string>());
|
|
22
|
-
const completedRunBySessionRef = useRef(new Map<string, string>());
|
|
23
|
-
const locallySettledAtBySessionRef = useRef(new Map<string, number>());
|
|
24
|
-
const latestBackendRunIdRef = useRef<string | null>(activeBackendRunId);
|
|
25
|
-
const autoResumeEligibleSessionsRef = useRef(new Set<string>());
|
|
26
|
-
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
if (!selectedSessionKey) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
autoResumeEligibleSessionsRef.current.add(selectedSessionKey);
|
|
32
|
-
}, [selectedSessionKey]);
|
|
33
|
-
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
if (!selectedSessionKey) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (isLocallyRunning) {
|
|
39
|
-
autoResumeEligibleSessionsRef.current.delete(selectedSessionKey);
|
|
40
|
-
}
|
|
41
|
-
}, [isLocallyRunning, selectedSessionKey]);
|
|
42
|
-
|
|
43
|
-
const sessionStatusRunsQuery = useChatRuns(
|
|
44
|
-
view === 'chat'
|
|
45
|
-
? {
|
|
46
|
-
states: ['queued', 'running'],
|
|
47
|
-
limit: 200,
|
|
48
|
-
syncActiveStates: true,
|
|
49
|
-
isLocallyRunning
|
|
50
|
-
}
|
|
51
|
-
: undefined
|
|
52
|
-
);
|
|
53
|
-
const activeRunBySessionKey = useMemo(
|
|
54
|
-
() => buildActiveRunBySessionKey(sessionStatusRunsQuery.data?.runs ?? []),
|
|
55
|
-
[sessionStatusRunsQuery.data?.runs]
|
|
56
|
-
);
|
|
57
|
-
const sessionRunStatusByKey = useMemo(() => {
|
|
58
|
-
const next = buildSessionRunStatusByKey(activeRunBySessionKey);
|
|
59
|
-
if (suppressedSessionState) {
|
|
60
|
-
const activeRun = activeRunBySessionKey.get(suppressedSessionState.sessionKey) ?? null;
|
|
61
|
-
if (activeRun && (!suppressedSessionState.runId || activeRun.runId === suppressedSessionState.runId)) {
|
|
62
|
-
next.delete(suppressedSessionState.sessionKey);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return next;
|
|
66
|
-
}, [activeRunBySessionKey, suppressedSessionState]);
|
|
67
|
-
const activeRun = useMemo(() => {
|
|
68
|
-
if (!selectedSessionKey) {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
const run = activeRunBySessionKey.get(selectedSessionKey) ?? null;
|
|
72
|
-
const shouldSuppress = (() => {
|
|
73
|
-
if (!run || !suppressedSessionState) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
if (suppressedSessionState.sessionKey !== selectedSessionKey) {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
return !suppressedSessionState.runId || run.runId === suppressedSessionState.runId;
|
|
80
|
-
})();
|
|
81
|
-
if (shouldSuppress) {
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
return run;
|
|
85
|
-
}, [activeRunBySessionKey, selectedSessionKey, suppressedSessionState]);
|
|
86
|
-
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
if (!activeBackendRunId) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
latestBackendRunIdRef.current = activeBackendRunId;
|
|
92
|
-
}, [activeBackendRunId]);
|
|
93
|
-
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
if (view !== 'chat' || !selectedSessionKey || !activeRun) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (!autoResumeEligibleSessionsRef.current.has(selectedSessionKey)) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (isLocallyRunning) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
if (activeBackendRunId === activeRun.runId) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const resumedRunId = resumedRunBySessionRef.current.get(selectedSessionKey);
|
|
108
|
-
if (resumedRunId === activeRun.runId) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
const completedRunId = completedRunBySessionRef.current.get(selectedSessionKey);
|
|
112
|
-
if (completedRunId && completedRunId === activeRun.runId) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const locallySettledAt = locallySettledAtBySessionRef.current.get(selectedSessionKey);
|
|
116
|
-
if (typeof locallySettledAt === 'number') {
|
|
117
|
-
const requestedAt = Date.parse(activeRun.requestedAt ?? '');
|
|
118
|
-
if (Number.isFinite(requestedAt)) {
|
|
119
|
-
if (requestedAt <= locallySettledAt + 2_000) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
} else if (Date.now() - locallySettledAt <= 8_000) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
resumedRunBySessionRef.current.set(selectedSessionKey, activeRun.runId);
|
|
127
|
-
autoResumeEligibleSessionsRef.current.delete(selectedSessionKey);
|
|
128
|
-
void resumeRun(activeRun);
|
|
129
|
-
}, [activeBackendRunId, activeRun, isLocallyRunning, resumeRun, selectedSessionKey, view]);
|
|
130
|
-
|
|
131
|
-
useEffect(() => {
|
|
132
|
-
if (!selectedSessionKey) {
|
|
133
|
-
resumedRunBySessionRef.current.clear();
|
|
134
|
-
completedRunBySessionRef.current.clear();
|
|
135
|
-
locallySettledAtBySessionRef.current.clear();
|
|
136
|
-
autoResumeEligibleSessionsRef.current.clear();
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (!activeRunBySessionKey.has(selectedSessionKey)) {
|
|
140
|
-
resumedRunBySessionRef.current.delete(selectedSessionKey);
|
|
141
|
-
completedRunBySessionRef.current.delete(selectedSessionKey);
|
|
142
|
-
locallySettledAtBySessionRef.current.delete(selectedSessionKey);
|
|
143
|
-
}
|
|
144
|
-
}, [activeRunBySessionKey, selectedSessionKey]);
|
|
145
|
-
|
|
146
|
-
useEffect(() => {
|
|
147
|
-
const wasRunning = wasLocallyRunningRef.current;
|
|
148
|
-
wasLocallyRunningRef.current = isLocallyRunning;
|
|
149
|
-
if (isLocallyRunning) {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
if (wasRunning && selectedSessionKey) {
|
|
153
|
-
const completedRunId = latestBackendRunIdRef.current?.trim() || activeRunBySessionKey.get(selectedSessionKey)?.runId?.trim();
|
|
154
|
-
if (completedRunId) {
|
|
155
|
-
completedRunBySessionRef.current.set(selectedSessionKey, completedRunId);
|
|
156
|
-
}
|
|
157
|
-
locallySettledAtBySessionRef.current.set(selectedSessionKey, Date.now());
|
|
158
|
-
setSuppressedSessionState({
|
|
159
|
-
sessionKey: selectedSessionKey,
|
|
160
|
-
...(completedRunId ? { runId: completedRunId } : {})
|
|
161
|
-
});
|
|
162
|
-
void sessionStatusRunsQuery.refetch();
|
|
163
|
-
}
|
|
164
|
-
}, [activeRunBySessionKey, isLocallyRunning, selectedSessionKey, sessionStatusRunsQuery]);
|
|
165
|
-
|
|
166
|
-
useEffect(() => {
|
|
167
|
-
if (!suppressedSessionState) {
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
const activeRun = activeRunBySessionKey.get(suppressedSessionState.sessionKey) ?? null;
|
|
171
|
-
if (!activeRun) {
|
|
172
|
-
setSuppressedSessionState(null);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
if (suppressedSessionState.runId && activeRun.runId !== suppressedSessionState.runId) {
|
|
176
|
-
setSuppressedSessionState(null);
|
|
177
|
-
}
|
|
178
|
-
}, [activeRunBySessionKey, suppressedSessionState]);
|
|
179
|
-
|
|
180
|
-
useEffect(() => {
|
|
181
|
-
if (!isLocallyRunning) {
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
if (suppressedSessionState?.sessionKey === selectedSessionKey) {
|
|
185
|
-
setSuppressedSessionState(null);
|
|
186
|
-
}
|
|
187
|
-
}, [isLocallyRunning, selectedSessionKey, suppressedSessionState]);
|
|
188
|
-
|
|
189
|
-
return { sessionRunStatusByKey };
|
|
190
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type { RunMetadataParsers } from '@nextclaw/agent-chat';
|
|
2
|
-
import type { ChatRunView } from '@/api/types';
|
|
3
|
-
import type { NextbotAgentRunMetadata, SendMessageParams } from '@/components/chat/chat-stream/types';
|
|
4
|
-
|
|
5
|
-
export const nextbotParsers: RunMetadataParsers = {
|
|
6
|
-
parseReady: (metadata) => {
|
|
7
|
-
if (metadata.driver !== 'nextbot-stream' || metadata.kind !== 'ready') {
|
|
8
|
-
return null;
|
|
9
|
-
}
|
|
10
|
-
return {
|
|
11
|
-
remoteRunId: typeof metadata.backendRunId === 'string' ? metadata.backendRunId : undefined,
|
|
12
|
-
sessionId: typeof metadata.sessionKey === 'string' ? metadata.sessionKey : undefined,
|
|
13
|
-
stopCapable: typeof metadata.stopSupported === 'boolean' ? metadata.stopSupported : undefined,
|
|
14
|
-
stopReason: typeof metadata.stopReason === 'string' ? metadata.stopReason : undefined
|
|
15
|
-
};
|
|
16
|
-
},
|
|
17
|
-
parseFinal: (metadata) => {
|
|
18
|
-
if (metadata.driver !== 'nextbot-stream' || metadata.kind !== 'final') {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
sessionId: typeof metadata.sessionKey === 'string' ? metadata.sessionKey : undefined,
|
|
23
|
-
hasOutput: Boolean(metadata.hasAssistantSessionEvent)
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export function buildSendMetadata(payload: SendMessageParams, requestedSkills: string[]): NextbotAgentRunMetadata {
|
|
29
|
-
return {
|
|
30
|
-
driver: 'nextbot-stream',
|
|
31
|
-
mode: 'send',
|
|
32
|
-
payload,
|
|
33
|
-
requestedSkills
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function buildResumeMetadata(run: ChatRunView): NextbotAgentRunMetadata {
|
|
38
|
-
const fromEventIndex =
|
|
39
|
-
Number.isFinite(run.eventCount) && run.eventCount > 0
|
|
40
|
-
? Math.max(0, Math.trunc(run.eventCount))
|
|
41
|
-
: undefined;
|
|
42
|
-
return {
|
|
43
|
-
driver: 'nextbot-stream',
|
|
44
|
-
mode: 'resume',
|
|
45
|
-
runId: run.runId!,
|
|
46
|
-
...(typeof fromEventIndex === 'number' ? { fromEventIndex } : {}),
|
|
47
|
-
sessionKey: run.sessionKey,
|
|
48
|
-
...(run.agentId ? { agentId: run.agentId } : {}),
|
|
49
|
-
stopSupported: run.stopSupported,
|
|
50
|
-
...(run.stopReason ? { stopReason: run.stopReason } : {})
|
|
51
|
-
};
|
|
52
|
-
}
|