@nextclaw/ui 0.8.0 → 0.9.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 +24 -0
- package/dist/assets/ChannelsList-DhvjpZcs.js +1 -0
- package/dist/assets/ChatPage-B8VBaMQm.js +38 -0
- package/dist/assets/{DocBrowser-DDX2HMXW.js → DocBrowser-LpzGe8An.js} +1 -1
- package/dist/assets/{LogoBadge-J53F_3JA.js → LogoBadge-Be4lktJN.js} +1 -1
- package/dist/assets/{MarketplacePage-0BZ4bza0.js → MarketplacePage-Cx9AI3_h.js} +3 -3
- package/dist/assets/{ModelConfig-Wzq9wGHV.js → ModelConfig-DuImUHIX.js} +1 -1
- package/dist/assets/ProvidersList-Ccleg25k.js +1 -0
- package/dist/assets/{RuntimeConfig-N771_AM6.js → RuntimeConfig-C6iqpJR_.js} +1 -1
- package/dist/assets/{SearchConfig-DVt5QVa_.js → SearchConfig-Dvp1TAXu.js} +1 -1
- package/dist/assets/{SecretsConfig-CkwauPa8.js → SecretsConfig-D5Ymlvt9.js} +1 -1
- package/dist/assets/{SessionsConfig-C3mnHzkZ.js → SessionsConfig-CIA_jA1P.js} +2 -2
- package/dist/assets/{chat-message-pxr79GDs.js → chat-message-B60Fh9kI.js} +1 -1
- package/dist/assets/index-BiPDnzv0.js +8 -0
- package/dist/assets/index-C8GsgIUn.css +1 -0
- package/dist/assets/{index-GdpEEKnz.js → index-CPDASUXh.js} +1 -1
- package/dist/assets/{label-CmksBHgc.js → label-D4fGx6Wb.js} +1 -1
- package/dist/assets/{page-layout-Db0GbnhS.js → page-layout-twy8gmBE.js} +1 -1
- package/dist/assets/popover-DYbYpt1j.js +1 -0
- package/dist/assets/{security-config-CjLFME5Q.js → security-config-BcIZ4rpb.js} +1 -1
- package/dist/assets/skeleton-DypBy7jp.js +1 -0
- package/dist/assets/{switch-C24d-UJU.js → switch-DqA6r5XR.js} +1 -1
- package/dist/assets/tabs-custom-C6enKKs1.js +1 -0
- package/dist/assets/{useConfirmDialog-BeP35LcG.js → useConfirmDialog-CHBf5Of7.js} +1 -1
- package/dist/assets/{vendor-psXJBy9u.js → vendor-DKBNiC31.js} +1 -1
- package/dist/index.html +3 -3
- package/package.json +6 -6
- package/src/api/config.ts +9 -38
- package/src/api/ncp-session.ts +50 -0
- package/src/api/types.ts +1 -0
- package/src/components/chat/ChatConversationPanel.test.tsx +65 -0
- package/src/components/chat/ChatConversationPanel.tsx +21 -12
- package/src/components/chat/ChatSidebar.test.tsx +203 -0
- package/src/components/chat/ChatSidebar.tsx +97 -7
- package/src/components/chat/adapters/chat-message.adapter.test.ts +132 -82
- package/src/components/chat/adapters/chat-message.adapter.ts +27 -9
- package/src/components/chat/chat-composer-state.ts +53 -0
- package/src/components/chat/chat-page-data.ts +30 -1
- package/src/components/chat/chat-page-runtime.test.ts +181 -0
- package/src/components/chat/chat-page-runtime.ts +101 -15
- package/src/components/chat/chat-session-preference-sync.test.ts +62 -0
- package/src/components/chat/chat-session-preference-sync.ts +75 -0
- package/src/components/chat/chat-stream/types.ts +3 -0
- package/src/components/chat/containers/chat-input-bar.container.tsx +12 -63
- package/src/components/chat/containers/chat-message-list.container.tsx +31 -27
- package/src/components/chat/legacy/LegacyChatPage.tsx +25 -0
- package/src/components/chat/managers/chat-input.manager.ts +48 -13
- package/src/components/chat/managers/chat-session-list.manager.test.ts +39 -0
- package/src/components/chat/managers/chat-session-list.manager.ts +9 -3
- package/src/components/chat/ncp/NcpChatPage.tsx +53 -13
- package/src/components/chat/ncp/ncp-chat-input.manager.ts +48 -12
- package/src/components/chat/ncp/ncp-chat-page-data.ts +34 -2
- package/src/components/chat/ncp/ncp-chat-thread.manager.ts +1 -1
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +27 -1
- package/src/components/chat/ncp/ncp-session-adapter.ts +20 -0
- package/src/components/chat/presenter/chat-presenter-context.tsx +2 -0
- package/src/components/chat/stores/chat-input.store.ts +4 -0
- package/src/components/chat/stores/chat-thread.store.ts +2 -0
- package/src/components/chat/useChatSessionTypeState.test.tsx +58 -0
- package/src/components/chat/useChatSessionTypeState.ts +25 -8
- package/src/hooks/use-ncp-chat-session-types.ts +11 -0
- package/src/hooks/useConfig.ts +2 -4
- package/src/hooks/useMarketplace.ts +7 -4
- package/src/hooks/useWebSocket.ts +23 -2
- package/dist/assets/ChannelsList-DBcoVJRW.js +0 -1
- package/dist/assets/ChatPage-CD3cxyyM.js +0 -37
- package/dist/assets/ProvidersList-kwzRS8_M.js +0 -1
- package/dist/assets/index-BIvFMkN4.js +0 -1
- package/dist/assets/index-CzkY1reu.js +0 -8
- package/dist/assets/index-RZ0kHHRI.css +0 -1
- package/dist/assets/skeleton-CkpQeVWN.js +0 -1
- package/dist/assets/tabs-custom-D89bh-fc.js +0 -1
|
@@ -1,14 +1,26 @@
|
|
|
1
|
+
import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
|
|
1
2
|
import type { SetStateAction } from 'react';
|
|
2
3
|
import type { ThinkingLevel } from '@/api/types';
|
|
4
|
+
import { updateNcpSession } from '@/api/ncp-session';
|
|
5
|
+
import {
|
|
6
|
+
createChatComposerNodesFromDraft,
|
|
7
|
+
createInitialChatComposerNodes,
|
|
8
|
+
deriveChatComposerDraft,
|
|
9
|
+
deriveSelectedSkillsFromComposer,
|
|
10
|
+
syncComposerSkills
|
|
11
|
+
} from '@/components/chat/chat-composer-state';
|
|
3
12
|
import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
|
|
4
13
|
import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
|
|
5
14
|
import type { ChatInputSnapshot } from '@/components/chat/stores/chat-input.store';
|
|
6
15
|
import type { ChatStreamActionsManager } from '@/components/chat/managers/chat-stream-actions.manager';
|
|
7
16
|
import type { ChatUiManager } from '@/components/chat/managers/chat-ui.manager';
|
|
17
|
+
import { ChatSessionPreferenceSync } from '@/components/chat/chat-session-preference-sync';
|
|
8
18
|
import type { ChatModelOption } from '@/components/chat/chat-input.types';
|
|
9
19
|
import { normalizeSessionType } from '@/components/chat/useChatSessionTypeState';
|
|
10
20
|
|
|
11
21
|
export class NcpChatInputManager {
|
|
22
|
+
private readonly sessionPreferenceSync = new ChatSessionPreferenceSync(updateNcpSession);
|
|
23
|
+
|
|
12
24
|
constructor(
|
|
13
25
|
private uiManager: ChatUiManager,
|
|
14
26
|
private streamActionsManager: ChatStreamActionsManager,
|
|
@@ -32,6 +44,17 @@ export class NcpChatInputManager {
|
|
|
32
44
|
return next;
|
|
33
45
|
};
|
|
34
46
|
|
|
47
|
+
private isSameStringArray = (left: string[], right: string[]): boolean =>
|
|
48
|
+
left.length === right.length && left.every((value, index) => value === right[index]);
|
|
49
|
+
|
|
50
|
+
private syncComposerSnapshot = (nodes: ChatComposerNode[]) => {
|
|
51
|
+
useChatInputStore.getState().setSnapshot({
|
|
52
|
+
composerNodes: nodes,
|
|
53
|
+
draft: deriveChatComposerDraft(nodes),
|
|
54
|
+
selectedSkills: deriveSelectedSkillsFromComposer(nodes)
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
35
58
|
syncSnapshot = (patch: Partial<ChatInputSnapshot>) => {
|
|
36
59
|
if (!this.hasSnapshotChanges(patch)) {
|
|
37
60
|
return;
|
|
@@ -42,8 +65,8 @@ export class NcpChatInputManager {
|
|
|
42
65
|
Object.prototype.hasOwnProperty.call(patch, 'selectedModel') ||
|
|
43
66
|
Object.prototype.hasOwnProperty.call(patch, 'selectedThinkingLevel')
|
|
44
67
|
) {
|
|
45
|
-
const
|
|
46
|
-
this.reconcileThinkingForModel(
|
|
68
|
+
const { selectedModel } = useChatInputStore.getState().snapshot;
|
|
69
|
+
this.reconcileThinkingForModel(selectedModel);
|
|
47
70
|
}
|
|
48
71
|
};
|
|
49
72
|
|
|
@@ -53,7 +76,16 @@ export class NcpChatInputManager {
|
|
|
53
76
|
if (value === prev) {
|
|
54
77
|
return;
|
|
55
78
|
}
|
|
56
|
-
|
|
79
|
+
this.syncComposerSnapshot(createChatComposerNodesFromDraft(value));
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
setComposerNodes = (next: SetStateAction<ChatComposerNode[]>) => {
|
|
83
|
+
const prev = useChatInputStore.getState().snapshot.composerNodes;
|
|
84
|
+
const value = this.resolveUpdateValue(prev, next);
|
|
85
|
+
if (Object.is(value, prev)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.syncComposerSnapshot(value);
|
|
57
89
|
};
|
|
58
90
|
|
|
59
91
|
setPendingSessionType = (next: SetStateAction<string>) => {
|
|
@@ -72,13 +104,12 @@ export class NcpChatInputManager {
|
|
|
72
104
|
if (!message) {
|
|
73
105
|
return;
|
|
74
106
|
}
|
|
75
|
-
const requestedSkills = inputSnapshot
|
|
107
|
+
const { selectedSkills: requestedSkills, composerNodes } = inputSnapshot;
|
|
76
108
|
const sessionKey = sessionSnapshot.selectedSessionKey ?? this.getDraftSessionId();
|
|
77
109
|
if (!sessionSnapshot.selectedSessionKey) {
|
|
78
110
|
this.uiManager.goToSession(sessionKey, { replace: true });
|
|
79
111
|
}
|
|
80
|
-
this.
|
|
81
|
-
this.setSelectedSkills([]);
|
|
112
|
+
this.setComposerNodes(createInitialChatComposerNodes());
|
|
82
113
|
await this.streamActionsManager.sendMessage({
|
|
83
114
|
message,
|
|
84
115
|
sessionKey,
|
|
@@ -88,7 +119,8 @@ export class NcpChatInputManager {
|
|
|
88
119
|
thinkingLevel: inputSnapshot.selectedThinkingLevel ?? undefined,
|
|
89
120
|
stopSupported: true,
|
|
90
121
|
requestedSkills,
|
|
91
|
-
restoreDraftOnError: true
|
|
122
|
+
restoreDraftOnError: true,
|
|
123
|
+
composerNodes
|
|
92
124
|
});
|
|
93
125
|
};
|
|
94
126
|
|
|
@@ -125,20 +157,23 @@ export class NcpChatInputManager {
|
|
|
125
157
|
};
|
|
126
158
|
|
|
127
159
|
setSelectedSkills = (next: SetStateAction<string[]>) => {
|
|
128
|
-
const
|
|
160
|
+
const snapshot = useChatInputStore.getState().snapshot;
|
|
161
|
+
const { selectedSkills: prev } = snapshot;
|
|
129
162
|
const value = this.resolveUpdateValue(prev, next);
|
|
130
|
-
if (
|
|
163
|
+
if (this.isSameStringArray(value, prev)) {
|
|
131
164
|
return;
|
|
132
165
|
}
|
|
133
|
-
|
|
166
|
+
this.syncComposerSnapshot(syncComposerSkills(snapshot.composerNodes, value, snapshot.skillRecords));
|
|
134
167
|
};
|
|
135
168
|
|
|
136
169
|
selectModel = (value: string) => {
|
|
137
170
|
this.setSelectedModel(value);
|
|
171
|
+
this.sessionPreferenceSync.syncSelectedSessionPreferences();
|
|
138
172
|
};
|
|
139
173
|
|
|
140
174
|
selectThinkingLevel = (value: ThinkingLevel) => {
|
|
141
175
|
this.setSelectedThinkingLevel(value);
|
|
176
|
+
this.sessionPreferenceSync.syncSelectedSessionPreferences();
|
|
142
177
|
};
|
|
143
178
|
|
|
144
179
|
selectSkills = (next: string[]) => {
|
|
@@ -165,8 +200,9 @@ export class NcpChatInputManager {
|
|
|
165
200
|
private reconcileThinkingForModel(model: string): void {
|
|
166
201
|
const snapshot = useChatInputStore.getState().snapshot;
|
|
167
202
|
const modelOption = snapshot.modelOptions.find((option) => option.value === model);
|
|
168
|
-
const
|
|
169
|
-
|
|
203
|
+
const { selectedThinkingLevel } = snapshot;
|
|
204
|
+
const nextThinking = this.resolveThinkingForModel(modelOption, selectedThinkingLevel);
|
|
205
|
+
if (nextThinking !== selectedThinkingLevel) {
|
|
170
206
|
useChatInputStore.getState().setSnapshot({ selectedThinkingLevel: nextThinking });
|
|
171
207
|
}
|
|
172
208
|
}
|
|
@@ -7,12 +7,17 @@ import {
|
|
|
7
7
|
readNcpSessionPreferredThinking
|
|
8
8
|
} from '@/components/chat/ncp/ncp-session-adapter';
|
|
9
9
|
import { useChatSessionTypeState } from '@/components/chat/useChatSessionTypeState';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
resolveSelectedModelValue,
|
|
12
|
+
resolveRecentSessionPreferredModel,
|
|
13
|
+
useSyncSelectedModel
|
|
14
|
+
} from '@/components/chat/chat-page-runtime';
|
|
11
15
|
import {
|
|
12
16
|
useConfig,
|
|
13
17
|
useConfigMeta,
|
|
14
18
|
useNcpSessions
|
|
15
19
|
} from '@/hooks/useConfig';
|
|
20
|
+
import { useNcpChatSessionTypes } from '@/hooks/use-ncp-chat-session-types';
|
|
16
21
|
import { useMarketplaceInstalled } from '@/hooks/useMarketplace';
|
|
17
22
|
import { buildProviderModelCatalog, composeProviderModel, resolveModelThinkingCapability } from '@/lib/provider-models';
|
|
18
23
|
|
|
@@ -36,6 +41,7 @@ export function useNcpChatPageData(params: UseNcpChatPageDataParams) {
|
|
|
36
41
|
const configQuery = useConfig();
|
|
37
42
|
const configMetaQuery = useConfigMeta();
|
|
38
43
|
const sessionsQuery = useNcpSessions({ limit: 200 });
|
|
44
|
+
const sessionTypesQuery = useNcpChatSessionTypes();
|
|
39
45
|
const installedSkillsQuery = useMarketplaceInstalled('skill');
|
|
40
46
|
const isProviderStateResolved =
|
|
41
47
|
(configQuery.isFetched || configQuery.isSuccess) &&
|
|
@@ -107,20 +113,45 @@ export function useNcpChatPageData(params: UseNcpChatPageDataParams) {
|
|
|
107
113
|
selectedSessionKey: params.selectedSessionKey,
|
|
108
114
|
pendingSessionType: params.pendingSessionType,
|
|
109
115
|
setPendingSessionType: params.setPendingSessionType,
|
|
110
|
-
sessionTypesData:
|
|
116
|
+
sessionTypesData: sessionTypesQuery.data
|
|
111
117
|
});
|
|
118
|
+
const recentSessionPreferredModel = useMemo(
|
|
119
|
+
() =>
|
|
120
|
+
resolveRecentSessionPreferredModel({
|
|
121
|
+
sessions: allSessions,
|
|
122
|
+
selectedSessionKey: params.selectedSessionKey,
|
|
123
|
+
sessionType: sessionTypeState.selectedSessionType
|
|
124
|
+
}),
|
|
125
|
+
[allSessions, params.selectedSessionKey, sessionTypeState.selectedSessionType]
|
|
126
|
+
);
|
|
112
127
|
|
|
113
128
|
useSyncSelectedModel({
|
|
114
129
|
modelOptions,
|
|
130
|
+
selectedSessionKey: params.selectedSessionKey,
|
|
115
131
|
selectedSessionPreferredModel: selectedSession?.preferredModel,
|
|
132
|
+
fallbackPreferredModel: recentSessionPreferredModel,
|
|
116
133
|
defaultModel: configQuery.data?.agents.defaults.model,
|
|
117
134
|
setSelectedModel: params.setSelectedModel
|
|
118
135
|
});
|
|
119
136
|
|
|
137
|
+
const hydratedSessionModel = useMemo(
|
|
138
|
+
() =>
|
|
139
|
+
resolveSelectedModelValue({
|
|
140
|
+
currentSelectedModel: '',
|
|
141
|
+
modelOptions,
|
|
142
|
+
selectedSessionPreferredModel: selectedSession?.preferredModel,
|
|
143
|
+
fallbackPreferredModel: recentSessionPreferredModel,
|
|
144
|
+
defaultModel: configQuery.data?.agents.defaults.model,
|
|
145
|
+
preferSessionPreferredModel: true
|
|
146
|
+
}),
|
|
147
|
+
[configQuery.data?.agents.defaults.model, modelOptions, recentSessionPreferredModel, selectedSession?.preferredModel]
|
|
148
|
+
);
|
|
149
|
+
|
|
120
150
|
return {
|
|
121
151
|
configQuery,
|
|
122
152
|
configMetaQuery,
|
|
123
153
|
sessionsQuery,
|
|
154
|
+
sessionTypesQuery,
|
|
124
155
|
installedSkillsQuery,
|
|
125
156
|
isProviderStateResolved,
|
|
126
157
|
modelOptions,
|
|
@@ -128,6 +159,7 @@ export function useNcpChatPageData(params: UseNcpChatPageDataParams) {
|
|
|
128
159
|
sessions,
|
|
129
160
|
skillRecords,
|
|
130
161
|
selectedSession,
|
|
162
|
+
hydratedSessionModel,
|
|
131
163
|
selectedSessionThinkingLevel,
|
|
132
164
|
...sessionTypeState
|
|
133
165
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { deleteNcpSession as deleteNcpSessionApi } from '@/api/
|
|
1
|
+
import { deleteNcpSession as deleteNcpSessionApi } from '@/api/ncp-session';
|
|
2
2
|
import type { ChatSessionListManager } from '@/components/chat/managers/chat-session-list.manager';
|
|
3
3
|
import type { ChatStreamActionsManager } from '@/components/chat/managers/chat-stream-actions.manager';
|
|
4
4
|
import type { ChatUiManager } from '@/components/chat/managers/chat-ui.manager';
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
adaptNcpSessionSummary,
|
|
3
|
+
buildNcpSessionRunStatusByKey,
|
|
4
|
+
readNcpSessionPreferredThinking
|
|
5
|
+
} from '@/components/chat/ncp/ncp-session-adapter';
|
|
2
6
|
import type { NcpSessionSummaryView } from '@/api/types';
|
|
3
7
|
|
|
4
8
|
function createSummary(partial: Partial<NcpSessionSummaryView> = {}): NcpSessionSummaryView {
|
|
@@ -47,3 +51,25 @@ describe('readNcpSessionPreferredThinking', () => {
|
|
|
47
51
|
expect(thinking).toBe('high');
|
|
48
52
|
});
|
|
49
53
|
});
|
|
54
|
+
|
|
55
|
+
describe('buildNcpSessionRunStatusByKey', () => {
|
|
56
|
+
it('marks the active local session as running before the server summary catches up', () => {
|
|
57
|
+
const statuses = buildNcpSessionRunStatusByKey({
|
|
58
|
+
summaries: [createSummary({ sessionId: 'ncp-session-1', status: 'idle' })],
|
|
59
|
+
activeSessionId: 'ncp-session-1',
|
|
60
|
+
isLocallyRunning: true
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(statuses.get('ncp-session-1')).toBe('running');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('keeps persisted running sessions marked as running', () => {
|
|
67
|
+
const statuses = buildNcpSessionRunStatusByKey({
|
|
68
|
+
summaries: [createSummary({ sessionId: 'ncp-session-2', status: 'running' })],
|
|
69
|
+
activeSessionId: null,
|
|
70
|
+
isLocallyRunning: false
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(statuses.get('ncp-session-2')).toBe('running');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ToolInvocationStatus, type UIMessage } from '@nextclaw/agent-chat';
|
|
2
2
|
import type { NcpMessagePart } from '@nextclaw/ncp';
|
|
3
3
|
import type { NcpMessageView, NcpSessionSummaryView, SessionEntryView, ThinkingLevel } from '@/api/types';
|
|
4
|
+
import type { SessionRunStatus } from '@/lib/session-run-status';
|
|
4
5
|
|
|
5
6
|
const THINKING_LEVEL_SET = new Set<string>(['off', 'minimal', 'low', 'medium', 'high', 'adaptive', 'xhigh']);
|
|
6
7
|
|
|
@@ -167,6 +168,7 @@ export function adaptNcpMessageToUiMessage(message: NcpMessageView): UIMessage {
|
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
export function adaptNcpMessagesToUiMessages(messages: readonly NcpMessageView[]): UIMessage[] {
|
|
171
|
+
console.log('[adaptNcpMessagesToUiMessages]', { messages });
|
|
170
172
|
return messages.map(adaptNcpMessageToUiMessage);
|
|
171
173
|
}
|
|
172
174
|
|
|
@@ -189,6 +191,24 @@ export function adaptNcpSessionSummaries(summaries: NcpSessionSummaryView[]): Se
|
|
|
189
191
|
return summaries.map(adaptNcpSessionSummary);
|
|
190
192
|
}
|
|
191
193
|
|
|
194
|
+
export function buildNcpSessionRunStatusByKey(params: {
|
|
195
|
+
summaries: readonly NcpSessionSummaryView[];
|
|
196
|
+
activeSessionId?: string | null;
|
|
197
|
+
isLocallyRunning?: boolean;
|
|
198
|
+
}): Map<string, SessionRunStatus> {
|
|
199
|
+
const map = new Map<string, SessionRunStatus>();
|
|
200
|
+
for (const summary of params.summaries) {
|
|
201
|
+
if (summary.status === 'running') {
|
|
202
|
+
map.set(summary.sessionId, 'running');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const activeSessionId = readOptionalString(params.activeSessionId);
|
|
206
|
+
if (params.isLocallyRunning && activeSessionId) {
|
|
207
|
+
map.set(activeSessionId, 'running');
|
|
208
|
+
}
|
|
209
|
+
return map;
|
|
210
|
+
}
|
|
211
|
+
|
|
192
212
|
export function createNcpSessionId(): string {
|
|
193
213
|
return `ncp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
194
214
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
|
|
1
2
|
import { createContext, useContext } from 'react';
|
|
2
3
|
import type { ReactNode } from 'react';
|
|
3
4
|
import type { SetStateAction } from 'react';
|
|
@@ -11,6 +12,7 @@ import type { ThinkingLevel } from '@/api/types';
|
|
|
11
12
|
export type ChatInputManagerLike = {
|
|
12
13
|
syncSnapshot: (patch: Record<string, unknown>) => void;
|
|
13
14
|
setDraft: (next: SetStateAction<string>) => void;
|
|
15
|
+
setComposerNodes: (next: SetStateAction<ChatComposerNode[]>) => void;
|
|
14
16
|
setPendingSessionType: (next: SetStateAction<string>) => void;
|
|
15
17
|
send: () => Promise<void>;
|
|
16
18
|
stop: () => Promise<void>;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
|
+
import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
|
|
2
3
|
import type { MarketplaceInstalledRecord } from '@/api/types';
|
|
3
4
|
import type { ThinkingLevel } from '@/api/types';
|
|
4
5
|
import type { ChatModelOption } from '@/components/chat/chat-input.types';
|
|
6
|
+
import { createInitialChatComposerNodes } from '@/components/chat/chat-composer-state';
|
|
5
7
|
|
|
6
8
|
export type ChatInputSnapshot = {
|
|
7
9
|
isProviderStateResolved: boolean;
|
|
10
|
+
composerNodes: ChatComposerNode[];
|
|
8
11
|
draft: string;
|
|
9
12
|
pendingSessionType: string;
|
|
10
13
|
defaultSessionType: string;
|
|
@@ -33,6 +36,7 @@ type ChatInputStore = {
|
|
|
33
36
|
|
|
34
37
|
const initialSnapshot: ChatInputSnapshot = {
|
|
35
38
|
isProviderStateResolved: false,
|
|
39
|
+
composerNodes: createInitialChatComposerNodes(),
|
|
36
40
|
draft: '',
|
|
37
41
|
pendingSessionType: 'native',
|
|
38
42
|
defaultSessionType: 'native',
|
|
@@ -8,6 +8,7 @@ export type ChatThreadSnapshot = {
|
|
|
8
8
|
modelOptions: ChatModelOption[];
|
|
9
9
|
sessionTypeUnavailable: boolean;
|
|
10
10
|
sessionTypeUnavailableMessage?: string | null;
|
|
11
|
+
sessionTypeLabel?: string | null;
|
|
11
12
|
selectedSessionKey: string | null;
|
|
12
13
|
sessionDisplayName?: string;
|
|
13
14
|
canDeleteSession: boolean;
|
|
@@ -29,6 +30,7 @@ const initialSnapshot: ChatThreadSnapshot = {
|
|
|
29
30
|
modelOptions: [],
|
|
30
31
|
sessionTypeUnavailable: false,
|
|
31
32
|
sessionTypeUnavailableMessage: null,
|
|
33
|
+
sessionTypeLabel: null,
|
|
32
34
|
selectedSessionKey: null,
|
|
33
35
|
sessionDisplayName: undefined,
|
|
34
36
|
canDeleteSession: false,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { resolveSessionTypeLabel, useChatSessionTypeState } from '@/components/chat/useChatSessionTypeState';
|
|
4
|
+
|
|
5
|
+
vi.mock('@/lib/i18n', () => ({
|
|
6
|
+
t: (key: string) => key
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
describe('useChatSessionTypeState', () => {
|
|
10
|
+
it('formats non-native runtime labels generically when no explicit label is provided', () => {
|
|
11
|
+
expect(resolveSessionTypeLabel('workspace-agent')).toBe('Workspace Agent');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('preserves an explicitly selected draft session type instead of resetting to the default', () => {
|
|
15
|
+
const setPendingSessionType = vi.fn();
|
|
16
|
+
|
|
17
|
+
const { result } = renderHook(() =>
|
|
18
|
+
useChatSessionTypeState({
|
|
19
|
+
selectedSession: null,
|
|
20
|
+
selectedSessionKey: null,
|
|
21
|
+
pendingSessionType: 'codex-sdk',
|
|
22
|
+
setPendingSessionType,
|
|
23
|
+
sessionTypesData: {
|
|
24
|
+
defaultType: 'native',
|
|
25
|
+
options: [
|
|
26
|
+
{ value: 'native', label: 'Native' },
|
|
27
|
+
{ value: 'codex-sdk', label: 'Codex' }
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(result.current.selectedSessionType).toBe('codex-sdk');
|
|
34
|
+
expect(setPendingSessionType).not.toHaveBeenCalled();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('hydrates the draft session type from the runtime default when no explicit type exists', () => {
|
|
38
|
+
const setPendingSessionType = vi.fn();
|
|
39
|
+
|
|
40
|
+
renderHook(() =>
|
|
41
|
+
useChatSessionTypeState({
|
|
42
|
+
selectedSession: null,
|
|
43
|
+
selectedSessionKey: null,
|
|
44
|
+
pendingSessionType: '',
|
|
45
|
+
setPendingSessionType,
|
|
46
|
+
sessionTypesData: {
|
|
47
|
+
defaultType: 'codex-sdk',
|
|
48
|
+
options: [
|
|
49
|
+
{ value: 'native', label: 'Native' },
|
|
50
|
+
{ value: 'codex-sdk', label: 'Codex' }
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
expect(setPendingSessionType).toHaveBeenCalledWith('codex-sdk');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useMemo } from 'react';
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
2
2
|
import type { Dispatch, SetStateAction } from 'react';
|
|
3
3
|
import type { SessionEntryView } from '@/api/types';
|
|
4
4
|
import { t } from '@/lib/i18n';
|
|
@@ -33,13 +33,16 @@ export function resolveSessionTypeLabel(sessionType: string, fallbackLabel?: str
|
|
|
33
33
|
if (sessionType === 'native') {
|
|
34
34
|
return t('chatSessionTypeNative');
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
const normalizedFallback = fallbackLabel?.trim();
|
|
37
|
+
if (normalizedFallback) {
|
|
38
|
+
return normalizedFallback;
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
return sessionType
|
|
41
|
+
.trim()
|
|
42
|
+
.split(/[-_]+/g)
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
45
|
+
.join(' ') || sessionType;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
function buildSessionTypeOptions(
|
|
@@ -113,6 +116,7 @@ export function useChatSessionTypeState(params: UseChatSessionTypeStateParams):
|
|
|
113
116
|
() => normalizeSessionType(sessionTypesData?.defaultType ?? DEFAULT_SESSION_TYPE),
|
|
114
117
|
[sessionTypesData?.defaultType]
|
|
115
118
|
);
|
|
119
|
+
const lastAutoPendingSessionTypeRef = useRef<string | null>(null);
|
|
116
120
|
const selectedSessionType = useMemo(
|
|
117
121
|
() => normalizeSessionType(selectedSession?.sessionType ?? pendingSessionType ?? defaultSessionType),
|
|
118
122
|
[defaultSessionType, pendingSessionType, selectedSession?.sessionType]
|
|
@@ -122,8 +126,21 @@ export function useChatSessionTypeState(params: UseChatSessionTypeStateParams):
|
|
|
122
126
|
if (selectedSessionKey) {
|
|
123
127
|
return;
|
|
124
128
|
}
|
|
129
|
+
const rawPending = typeof pendingSessionType === 'string' ? pendingSessionType.trim() : '';
|
|
130
|
+
const normalizedPending = normalizeSessionType(pendingSessionType);
|
|
131
|
+
const shouldFollowDefault =
|
|
132
|
+
rawPending.length === 0 ||
|
|
133
|
+
lastAutoPendingSessionTypeRef.current === normalizedPending ||
|
|
134
|
+
(lastAutoPendingSessionTypeRef.current === null && normalizedPending === DEFAULT_SESSION_TYPE);
|
|
135
|
+
if (!shouldFollowDefault) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
lastAutoPendingSessionTypeRef.current = defaultSessionType;
|
|
139
|
+
if (normalizedPending === defaultSessionType) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
125
142
|
setPendingSessionType(defaultSessionType);
|
|
126
|
-
}, [defaultSessionType, selectedSessionKey, setPendingSessionType]);
|
|
143
|
+
}, [defaultSessionType, pendingSessionType, selectedSessionKey, setPendingSessionType]);
|
|
127
144
|
|
|
128
145
|
const canEditSessionType = !selectedSessionKey || Boolean(selectedSession?.sessionTypeMutable);
|
|
129
146
|
const availableSessionTypeSet = useMemo(
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query';
|
|
2
|
+
import { fetchNcpChatSessionTypes } from '@/api/config';
|
|
3
|
+
|
|
4
|
+
export function useNcpChatSessionTypes() {
|
|
5
|
+
return useQuery({
|
|
6
|
+
queryKey: ['ncp-session-types'],
|
|
7
|
+
queryFn: fetchNcpChatSessionTypes,
|
|
8
|
+
staleTime: 10_000,
|
|
9
|
+
retry: false
|
|
10
|
+
});
|
|
11
|
+
}
|
package/src/hooks/useConfig.ts
CHANGED
|
@@ -19,11 +19,8 @@ import {
|
|
|
19
19
|
executeConfigAction,
|
|
20
20
|
fetchSessions,
|
|
21
21
|
fetchSessionHistory,
|
|
22
|
-
fetchNcpSessions,
|
|
23
|
-
fetchNcpSessionMessages,
|
|
24
22
|
updateSession,
|
|
25
23
|
deleteSession,
|
|
26
|
-
deleteNcpSession,
|
|
27
24
|
sendChatTurn,
|
|
28
25
|
fetchChatRun,
|
|
29
26
|
fetchChatRuns,
|
|
@@ -34,6 +31,7 @@ import {
|
|
|
34
31
|
setCronJobEnabled,
|
|
35
32
|
runCronJob
|
|
36
33
|
} from '@/api/config';
|
|
34
|
+
import { deleteNcpSession, fetchNcpSessionMessages, fetchNcpSessions } from '@/api/ncp-session';
|
|
37
35
|
import { toast } from 'sonner';
|
|
38
36
|
import { t } from '@/lib/i18n';
|
|
39
37
|
|
|
@@ -390,7 +388,7 @@ export function useChatRuns(params?: {
|
|
|
390
388
|
if (params?.isLocallyRunning) {
|
|
391
389
|
return 800;
|
|
392
390
|
}
|
|
393
|
-
const data = query.state
|
|
391
|
+
const { data } = query.state;
|
|
394
392
|
const hasActiveRuns = Array.isArray(data?.runs) && data.runs.length > 0;
|
|
395
393
|
return hasActiveRuns ? 800 : false;
|
|
396
394
|
},
|
|
@@ -61,8 +61,10 @@ export function useInstallMarketplaceItem() {
|
|
|
61
61
|
mutationFn: (request: MarketplaceInstallRequest) => installMarketplaceItem(request),
|
|
62
62
|
onSuccess: (result) => {
|
|
63
63
|
queryClient.invalidateQueries({ queryKey: ['marketplace-installed', result.type] });
|
|
64
|
-
queryClient.
|
|
65
|
-
|
|
64
|
+
queryClient.invalidateQueries({ queryKey: ['marketplace-items'] });
|
|
65
|
+
if (result.type === 'plugin') {
|
|
66
|
+
queryClient.invalidateQueries({ queryKey: ['ncp-session-types'] });
|
|
67
|
+
}
|
|
66
68
|
const fallback = result.type === 'plugin'
|
|
67
69
|
? t('marketplaceInstallSuccessPlugin')
|
|
68
70
|
: t('marketplaceInstallSuccessSkill');
|
|
@@ -82,8 +84,9 @@ export function useManageMarketplaceItem() {
|
|
|
82
84
|
onSuccess: (result) => {
|
|
83
85
|
queryClient.invalidateQueries({ queryKey: ['marketplace-installed', result.type] });
|
|
84
86
|
queryClient.invalidateQueries({ queryKey: ['marketplace-items'] });
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
if (result.type === 'plugin') {
|
|
88
|
+
queryClient.invalidateQueries({ queryKey: ['ncp-session-types'] });
|
|
89
|
+
}
|
|
87
90
|
const fallback = result.action === 'enable'
|
|
88
91
|
? t('marketplaceEnableSuccess')
|
|
89
92
|
: result.action === 'disable'
|
|
@@ -73,11 +73,25 @@ export function useWebSocket(queryClient?: QueryClient) {
|
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
queryClient.invalidateQueries({ queryKey: ['sessions'] });
|
|
76
|
+
queryClient.invalidateQueries({ queryKey: ['ncp-sessions'] });
|
|
76
77
|
if (sessionKey && sessionKey.trim().length > 0) {
|
|
77
78
|
queryClient.invalidateQueries({ queryKey: ['session-history', sessionKey.trim()] });
|
|
79
|
+
queryClient.invalidateQueries({ queryKey: ['ncp-session-messages', sessionKey.trim()] });
|
|
78
80
|
return;
|
|
79
81
|
}
|
|
80
82
|
queryClient.invalidateQueries({ queryKey: ['session-history'] });
|
|
83
|
+
queryClient.invalidateQueries({ queryKey: ['ncp-session-messages'] });
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const shouldInvalidateConfigQuery = (configPath: string) => {
|
|
87
|
+
const normalized = configPath.trim().toLowerCase();
|
|
88
|
+
if (!normalized) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
if (normalized.startsWith('plugins') || normalized.startsWith('skills')) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
81
95
|
};
|
|
82
96
|
|
|
83
97
|
setConnectionStatus('connecting');
|
|
@@ -98,13 +112,20 @@ export function useWebSocket(queryClient?: QueryClient) {
|
|
|
98
112
|
});
|
|
99
113
|
|
|
100
114
|
client.on('config.updated', (event) => {
|
|
115
|
+
const payload = event.payload as { path?: unknown } | undefined;
|
|
116
|
+
const configPath = typeof payload?.path === 'string' ? payload.path : '';
|
|
101
117
|
// Trigger refetch of config
|
|
102
|
-
if (queryClient) {
|
|
118
|
+
if (queryClient && shouldInvalidateConfigQuery(configPath)) {
|
|
103
119
|
queryClient.invalidateQueries({ queryKey: ['config'] });
|
|
104
120
|
}
|
|
105
|
-
if (
|
|
121
|
+
if (configPath.startsWith('session')) {
|
|
106
122
|
invalidateSessionQueries();
|
|
107
123
|
}
|
|
124
|
+
if (configPath.startsWith('plugins')) {
|
|
125
|
+
queryClient?.invalidateQueries({ queryKey: ['ncp-session-types'] });
|
|
126
|
+
queryClient?.invalidateQueries({ queryKey: ['marketplace-installed', 'plugin'] });
|
|
127
|
+
queryClient?.invalidateQueries({ queryKey: ['marketplace-items'] });
|
|
128
|
+
}
|
|
108
129
|
});
|
|
109
130
|
|
|
110
131
|
client.on('run.updated', (event) => {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as v,j as a,a5 as Z,F as ee,e as T,K as ae,ah as te,aR as se,aS as ne,aT as le,z as re,s as oe,a8 as ce,v as ie}from"./vendor-psXJBy9u.js";import{t as e,c as I,Y as me,u as q,a as $,b as H,_ as pe,$ as de,I as D,S as be,e as ue,f as xe,g as ye,h as ge,B as E}from"./index-CzkY1reu.js";import{L as he}from"./label-CmksBHgc.js";import{S as fe}from"./switch-C24d-UJU.js";import{L as K,S as J}from"./LogoBadge-J53F_3JA.js";import{h as _}from"./config-hints-CApS3K_7.js";import{c as we,b as ve,a as je,C as ke}from"./config-layout-BHnOoweL.js";import{T as Se}from"./tabs-custom-D89bh-fc.js";import{P as Ce,a as Ne}from"./page-layout-Db0GbnhS.js";function Pe({value:t,onChange:m,className:i,placeholder:r=""}){const[o,u]=v.useState(""),d=x=>{x.key==="Enter"&&o.trim()?(x.preventDefault(),m([...t,o.trim()]),u("")):x.key==="Backspace"&&!o&&t.length>0&&m(t.slice(0,-1))},g=x=>{m(t.filter((j,h)=>h!==x))};return a.jsxs("div",{className:I("flex flex-wrap gap-2 p-2 border rounded-md min-h-[42px]",i),children:[t.map((x,j)=>a.jsxs("span",{className:"inline-flex items-center gap-1 px-2 py-1 bg-primary text-primary-foreground rounded text-sm",children:[x,a.jsx("button",{type:"button",onClick:()=>g(j),className:"hover:text-red-300 transition-colors",children:a.jsx(Z,{className:"h-3 w-3"})})]},j)),a.jsx("input",{type:"text",value:o,onChange:x=>u(x.target.value),onKeyDown:d,className:"flex-1 outline-none min-w-[100px] bg-transparent text-sm",placeholder:r||e("enterTag")})]})}function z(t){var r,o;const m=me();return((r=t.tutorialUrls)==null?void 0:r[m])||((o=t.tutorialUrls)==null?void 0:o.default)||t.tutorialUrl}const Ie={telegram:"telegram.svg",slack:"slack.svg",discord:"discord.svg",whatsapp:"whatsapp.svg",qq:"qq.svg",feishu:"feishu.svg",dingtalk:"dingtalk.svg",wecom:"wecom.svg",mochat:"mochat.svg",email:"email.svg"};function Fe(t,m){const i=m.toLowerCase(),r=t[i];return r?`/logos/${r}`:null}function Y(t){return Fe(Ie,t)}const R=[{value:"pairing",label:"pairing"},{value:"allowlist",label:"allowlist"},{value:"open",label:"open"},{value:"disabled",label:"disabled"}],B=[{value:"open",label:"open"},{value:"allowlist",label:"allowlist"},{value:"disabled",label:"disabled"}],Te=[{value:"off",label:"off"},{value:"partial",label:"partial"},{value:"block",label:"block"},{value:"progress",label:"progress"}],De=t=>t.includes("token")||t.includes("secret")||t.includes("password")?a.jsx(ae,{className:"h-3.5 w-3.5 text-gray-500"}):t.includes("url")||t.includes("host")?a.jsx(te,{className:"h-3.5 w-3.5 text-gray-500"}):t.includes("email")||t.includes("mail")?a.jsx(se,{className:"h-3.5 w-3.5 text-gray-500"}):t.includes("id")||t.includes("from")?a.jsx(ne,{className:"h-3.5 w-3.5 text-gray-500"}):t==="enabled"||t==="consentGranted"?a.jsx(le,{className:"h-3.5 w-3.5 text-gray-500"}):a.jsx(re,{className:"h-3.5 w-3.5 text-gray-500"});function G(){return{telegram:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"token",type:"password",label:e("botToken")},{name:"allowFrom",type:"tags",label:e("allowFrom")},{name:"proxy",type:"text",label:e("proxy")},{name:"accountId",type:"text",label:e("accountId")},{name:"dmPolicy",type:"select",label:e("dmPolicy"),options:R},{name:"groupPolicy",type:"select",label:e("groupPolicy"),options:B},{name:"groupAllowFrom",type:"tags",label:e("groupAllowFrom")},{name:"requireMention",type:"boolean",label:e("requireMention")},{name:"mentionPatterns",type:"tags",label:e("mentionPatterns")},{name:"groups",type:"json",label:e("groupRulesJson")}],discord:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"token",type:"password",label:e("botToken")},{name:"allowBots",type:"boolean",label:e("allowBotMessages")},{name:"allowFrom",type:"tags",label:e("allowFrom")},{name:"gatewayUrl",type:"text",label:e("gatewayUrl")},{name:"intents",type:"number",label:e("intents")},{name:"proxy",type:"text",label:e("proxy")},{name:"mediaMaxMb",type:"number",label:e("attachmentMaxSizeMb")},{name:"streaming",type:"select",label:e("streamingMode"),options:Te},{name:"draftChunk",type:"json",label:e("draftChunkingJson")},{name:"textChunkLimit",type:"number",label:e("textChunkLimit")},{name:"accountId",type:"text",label:e("accountId")},{name:"dmPolicy",type:"select",label:e("dmPolicy"),options:R},{name:"groupPolicy",type:"select",label:e("groupPolicy"),options:B},{name:"groupAllowFrom",type:"tags",label:e("groupAllowFrom")},{name:"requireMention",type:"boolean",label:e("requireMention")},{name:"mentionPatterns",type:"tags",label:e("mentionPatterns")},{name:"groups",type:"json",label:e("groupRulesJson")}],whatsapp:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"bridgeUrl",type:"text",label:e("bridgeUrl")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],feishu:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"appId",type:"text",label:e("appId")},{name:"appSecret",type:"password",label:e("appSecret")},{name:"encryptKey",type:"password",label:e("encryptKey")},{name:"verificationToken",type:"password",label:e("verificationToken")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],dingtalk:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"clientId",type:"text",label:e("clientId")},{name:"clientSecret",type:"password",label:e("clientSecret")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],wecom:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"corpId",type:"text",label:e("corpId")},{name:"agentId",type:"text",label:e("agentId")},{name:"secret",type:"password",label:e("secret")},{name:"token",type:"password",label:e("token")},{name:"callbackPort",type:"number",label:e("callbackPort")},{name:"callbackPath",type:"text",label:e("callbackPath")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],slack:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"mode",type:"text",label:e("mode")},{name:"webhookPath",type:"text",label:e("webhookPath")},{name:"allowBots",type:"boolean",label:e("allowBotMessages")},{name:"botToken",type:"password",label:e("botToken")},{name:"appToken",type:"password",label:e("appToken")}],email:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"consentGranted",type:"boolean",label:e("consentGranted")},{name:"imapHost",type:"text",label:e("imapHost")},{name:"imapPort",type:"number",label:e("imapPort")},{name:"imapUsername",type:"text",label:e("imapUsername")},{name:"imapPassword",type:"password",label:e("imapPassword")},{name:"fromAddress",type:"email",label:e("fromAddress")}],mochat:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"baseUrl",type:"text",label:e("baseUrl")},{name:"clawToken",type:"password",label:e("clawToken")},{name:"agentUserId",type:"text",label:e("agentUserId")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],qq:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"appId",type:"text",label:e("appId")},{name:"secret",type:"password",label:e("appSecret")},{name:"markdownSupport",type:"boolean",label:e("markdownSupport")},{name:"allowFrom",type:"tags",label:e("allowFrom")}]}}function A(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function V(t,m){const i={...t};for(const[r,o]of Object.entries(m)){const u=i[r];if(A(u)&&A(o)){i[r]=V(u,o);continue}i[r]=o}return i}function Ae(t,m){const i=t.split("."),r={};let o=r;for(let u=0;u<i.length-1;u+=1){const d=i[u];o[d]={},o=o[d]}return o[i[i.length-1]]=m,r}function Le({channelName:t}){var O,U;const{data:m}=q(),{data:i}=$(),{data:r}=H(),o=pe(),u=de(),[d,g]=v.useState({}),[x,j]=v.useState({}),[h,f]=v.useState(null),k=t?m==null?void 0:m.channels[t]:null,w=t?G()[t]??[]:[],c=r==null?void 0:r.uiHints,p=t?`channels.${t}`:null,S=((O=r==null?void 0:r.actions)==null?void 0:O.filter(s=>s.scope===p))??[],C=t&&(((U=_(`channels.${t}`,c))==null?void 0:U.label)??t),P=i==null?void 0:i.channels.find(s=>s.name===t),F=P?z(P):void 0;v.useEffect(()=>{if(k){g({...k});const s={};(t?G()[t]??[]:[]).filter(l=>l.type==="json").forEach(l=>{const y=k[l.name];s[l.name]=JSON.stringify(y??{},null,2)}),j(s)}else g({}),j({})},[k,t]);const N=(s,n)=>{g(l=>({...l,[s]:n}))},L=s=>{if(s.preventDefault(),!t)return;const n={...d};for(const l of w){if(l.type!=="password")continue;const y=n[l.name];(typeof y!="string"||y.length===0)&&delete n[l.name]}for(const l of w){if(l.type!=="json")continue;const y=x[l.name]??"";try{n[l.name]=y.trim()?JSON.parse(y):{}}catch{T.error(`${e("invalidJson")}: ${l.name}`);return}}o.mutate({channel:t,data:n})},Q=s=>{if(!s||!t)return;const n=s.channels;if(!A(n))return;const l=n[t];A(l)&&g(y=>V(y,l))},W=async s=>{if(!(!t||!p)){f(s.id);try{let n={...d};s.saveBeforeRun&&(n={...n,...s.savePatch??{}},g(n),await o.mutateAsync({channel:t,data:n}));const l=await u.mutateAsync({actionId:s.id,data:{scope:p,draftConfig:Ae(p,n)}});Q(l.patch),l.ok?T.success(l.message||e("success")):T.error(l.message||e("error"))}catch(n){const l=n instanceof Error?n.message:String(n);T.error(`${e("error")}: ${l}`)}finally{f(null)}}};if(!t||!P||!k)return a.jsx("div",{className:we,children:a.jsxs("div",{children:[a.jsx("h3",{className:"text-base font-semibold text-gray-900",children:e("channelsSelectTitle")}),a.jsx("p",{className:"mt-2 text-sm text-gray-500",children:e("channelsSelectDescription")})]})});const M=!!k.enabled;return a.jsxs("div",{className:ve,children:[a.jsx("div",{className:"border-b border-gray-100 px-6 py-5",children:a.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[a.jsxs("div",{className:"min-w-0",children:[a.jsxs("div",{className:"flex items-center gap-3",children:[a.jsx(K,{name:t,src:Y(t),className:I("h-9 w-9 rounded-lg border",M?"border-primary/30 bg-white":"border-gray-200/70 bg-white"),imgClassName:"h-5 w-5 object-contain",fallback:a.jsx("span",{className:"text-sm font-semibold uppercase text-gray-500",children:t[0]})}),a.jsx("h3",{className:"truncate text-lg font-semibold text-gray-900 capitalize",children:C})]}),a.jsx("p",{className:"mt-2 text-sm text-gray-500",children:e("channelsFormDescription")}),F&&a.jsxs("a",{href:F,className:"mt-2 inline-flex items-center gap-1.5 text-xs text-primary transition-colors hover:text-primary-hover",children:[a.jsx(ee,{className:"h-3.5 w-3.5"}),e("channelsGuideTitle")]})]}),a.jsx(J,{status:M?"active":"inactive",label:M?e("statusActive"):e("statusInactive")})]})}),a.jsxs("form",{onSubmit:L,className:"flex min-h-0 flex-1 flex-col",children:[a.jsx("div",{className:"min-h-0 flex-1 space-y-6 overflow-y-auto overscroll-contain px-6 py-5",children:w.map(s=>{const n=t?_(`channels.${t}.${s.name}`,c):void 0,l=(n==null?void 0:n.label)??s.label,y=n==null?void 0:n.placeholder;return a.jsxs("div",{className:"space-y-2.5",children:[a.jsxs(he,{htmlFor:s.name,className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[De(s.name),l]}),s.type==="boolean"&&a.jsxs("div",{className:"flex items-center justify-between rounded-xl bg-gray-50 p-3",children:[a.jsx("span",{className:"text-sm text-gray-500",children:d[s.name]?e("enabled"):e("disabled")}),a.jsx(fe,{id:s.name,checked:d[s.name]||!1,onCheckedChange:b=>N(s.name,b),className:"data-[state=checked]:bg-emerald-500"})]}),(s.type==="text"||s.type==="email")&&a.jsx(D,{id:s.name,type:s.type,value:d[s.name]||"",onChange:b=>N(s.name,b.target.value),placeholder:y,className:"rounded-xl"}),s.type==="password"&&a.jsx(D,{id:s.name,type:"password",value:d[s.name]||"",onChange:b=>N(s.name,b.target.value),placeholder:y??e("leaveBlankToKeepUnchanged"),className:"rounded-xl"}),s.type==="number"&&a.jsx(D,{id:s.name,type:"number",value:d[s.name]||0,onChange:b=>N(s.name,parseInt(b.target.value,10)||0),placeholder:y,className:"rounded-xl"}),s.type==="tags"&&a.jsx(Pe,{value:d[s.name]||[],onChange:b=>N(s.name,b)}),s.type==="select"&&a.jsxs(be,{value:d[s.name]||"",onValueChange:b=>N(s.name,b),children:[a.jsx(ue,{className:"rounded-xl",children:a.jsx(xe,{})}),a.jsx(ye,{children:(s.options??[]).map(b=>a.jsx(ge,{value:b.value,children:b.label},b.value))})]}),s.type==="json"&&a.jsx("textarea",{id:s.name,value:x[s.name]??"{}",onChange:b=>j(X=>({...X,[s.name]:b.target.value})),className:"min-h-[120px] w-full resize-none rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-mono"})]},s.name)})}),a.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3 border-t border-gray-100 px-6 py-4",children:[a.jsx("div",{className:"flex flex-wrap items-center gap-2",children:S.filter(s=>s.trigger==="manual").map(s=>a.jsx(E,{type:"button",onClick:()=>W(s),disabled:o.isPending||!!h,variant:"secondary",children:h===s.id?e("connecting"):s.title},s.id))}),a.jsx(E,{type:"submit",disabled:o.isPending||!!h,children:o.isPending?e("saving"):e("save")})]})]})]})}const Me={telegram:"channelDescTelegram",slack:"channelDescSlack",email:"channelDescEmail",webhook:"channelDescWebhook",discord:"channelDescDiscord",feishu:"channelDescFeishu"};function He(){const{data:t}=q(),{data:m}=$(),{data:i}=H(),[r,o]=v.useState("enabled"),[u,d]=v.useState(),[g,x]=v.useState(""),j=i==null?void 0:i.uiHints,h=m==null?void 0:m.channels,f=t==null?void 0:t.channels,k=[{id:"enabled",label:e("channelsTabEnabled"),count:(h??[]).filter(c=>{var p;return(p=f==null?void 0:f[c.name])==null?void 0:p.enabled}).length},{id:"all",label:e("channelsTabAll"),count:(h??[]).length}],w=v.useMemo(()=>{const c=g.trim().toLowerCase();return(h??[]).filter(p=>{var C;const S=((C=f==null?void 0:f[p.name])==null?void 0:C.enabled)||!1;return r==="enabled"?S:!0}).filter(p=>c?(p.displayName||p.name).toLowerCase().includes(c)||p.name.toLowerCase().includes(c):!0)},[r,f,h,g]);return v.useEffect(()=>{if(w.length===0){d(void 0);return}w.some(p=>p.name===u)||d(w[0].name)},[w,u]),!t||!m?a.jsx("div",{className:"p-8 text-gray-400",children:e("channelsLoading")}):a.jsxs(Ce,{className:"xl:flex xl:h-full xl:min-h-0 xl:flex-col xl:pb-0",children:[a.jsx(Ne,{title:e("channelsPageTitle"),description:e("channelsPageDescription")}),a.jsxs("div",{className:I(ke,"xl:min-h-0 xl:flex-1"),children:[a.jsxs("section",{className:je,children:[a.jsx("div",{className:"border-b border-gray-100 px-4 pt-4",children:a.jsx(Se,{tabs:k,activeTab:r,onChange:o,className:"mb-0"})}),a.jsx("div",{className:"border-b border-gray-100 px-4 py-3",children:a.jsxs("div",{className:"relative",children:[a.jsx(oe,{className:"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400"}),a.jsx(D,{value:g,onChange:c=>x(c.target.value),placeholder:e("channelsFilterPlaceholder"),className:"h-10 rounded-xl pl-9"})]})}),a.jsxs("div",{className:"min-h-0 flex-1 space-y-2 overflow-y-auto overscroll-contain p-3",children:[w.map(c=>{const p=t.channels[c.name],S=(p==null?void 0:p.enabled)||!1,C=_(`channels.${c.name}`,j),P=z(c),F=(C==null?void 0:C.help)||e(Me[c.name]||"channelDescriptionDefault"),N=u===c.name;return a.jsx("button",{type:"button",onClick:()=>d(c.name),className:I("w-full rounded-xl border p-2.5 text-left transition-all",N?"border-primary/30 bg-primary-50/40 shadow-sm":"border-gray-200/70 bg-white hover:border-gray-300 hover:bg-gray-50/70"),children:a.jsxs("div",{className:"flex items-start justify-between gap-3",children:[a.jsxs("div",{className:"flex min-w-0 items-center gap-3",children:[a.jsx(K,{name:c.name,src:Y(c.name),className:I("h-10 w-10 rounded-lg border",S?"border-primary/30 bg-white":"border-gray-200/70 bg-white"),imgClassName:"h-5 w-5 object-contain",fallback:a.jsx("span",{className:"text-sm font-semibold uppercase text-gray-500",children:c.name[0]})}),a.jsxs("div",{className:"min-w-0",children:[a.jsx("p",{className:"truncate text-sm font-semibold text-gray-900",children:c.displayName||c.name}),a.jsx("p",{className:"line-clamp-1 text-[11px] text-gray-500",children:F})]})]}),a.jsxs("div",{className:"flex items-center gap-2",children:[P&&a.jsx("a",{href:P,onClick:L=>L.stopPropagation(),className:"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-300 transition-colors hover:bg-gray-100/70 hover:text-gray-500",title:e("channelsGuideTitle"),children:a.jsx(ce,{className:"h-3.5 w-3.5"})}),a.jsx(J,{status:S?"active":"inactive",label:S?e("statusActive"):e("statusInactive"),className:"min-w-[56px] justify-center"})]})]})},c.name)}),w.length===0&&a.jsxs("div",{className:"flex h-full min-h-[220px] flex-col items-center justify-center rounded-xl border border-dashed border-gray-200 bg-gray-50/70 py-10 text-center",children:[a.jsx("div",{className:"mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-white",children:a.jsx(ie,{className:"h-5 w-5 text-gray-300"})}),a.jsx("p",{className:"text-sm font-medium text-gray-700",children:e("channelsNoMatch")})]})]})]}),a.jsx(Le,{channelName:u})]})]})}export{He as ChannelsList};
|