@nextclaw/ui 0.11.0 → 0.11.1
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 +8 -2
- package/dist/assets/{ChannelsList-BqsOYnXz.js → ChannelsList-CVPqrxns.js} +4 -4
- package/dist/assets/ChatPage-BO1VUrAY.js +37 -0
- package/dist/assets/{DocBrowser-BmL0QXBZ.js → DocBrowser-FBwg8iji.js} +1 -1
- package/dist/assets/{LogoBadge-C1HiPZPf.js → LogoBadge-BCmJfRT8.js} +1 -1
- package/dist/assets/MarketplacePage-DWxXUOCx.js +49 -0
- package/dist/assets/{McpMarketplacePage-CLHFnNBd.js → McpMarketplacePage-Bth9X_hu.js} +2 -2
- package/dist/assets/{ModelConfig-LQSR58tc.js → ModelConfig-PkSp_ioc.js} +1 -1
- package/dist/assets/ProvidersList-DVDge8wa.js +1 -0
- package/dist/assets/RemoteAccessPage-BVkzfEaL.js +1 -0
- package/dist/assets/RuntimeConfig-ByJs3khh.js +1 -0
- package/dist/assets/{SearchConfig-Chzo_JGs.js → SearchConfig-KZUAqYJN.js} +1 -1
- package/dist/assets/{SecretsConfig-CEIbjZYA.js → SecretsConfig-qwB_Y_Ka.js} +2 -2
- package/dist/assets/SessionsConfig-CGCl4UTr.js +2 -0
- package/dist/assets/index-CrilScMo.css +1 -0
- package/dist/assets/{index-j6A_-1b6.js → index-D41ntvb7.js} +6 -6
- package/dist/assets/{label-GACO2RzW.js → label-7JEFhkur.js} +1 -1
- package/dist/assets/ncp-session-adapter-BOqhkrc-.js +1 -0
- package/dist/assets/{page-layout-DjXaK3A3.js → page-layout-B7q511TE.js} +1 -1
- package/dist/assets/popover-CywJGmPr.js +1 -0
- package/dist/assets/security-config-zi2UxN5r.js +1 -0
- package/dist/assets/skeleton-qUJZQ03S.js +1 -0
- package/dist/assets/{status-dot-IWEBezqb.js → status-dot-BilwNdTT.js} +1 -1
- package/dist/assets/{switch-DCHAJSrA.js → switch-BLp2Pno1.js} +1 -1
- package/dist/assets/tabs-custom-CgIdQMGC.js +1 -0
- package/dist/assets/useConfirmDialog-BitswAkv.js +1 -0
- package/dist/assets/{vendor-CNhxtHCf.js → vendor-D_JxmsLV.js} +87 -87
- package/dist/index.html +3 -3
- package/package.json +4 -4
- 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/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/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/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-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,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
|
-
}
|