@nextclaw/ui 0.14.1 → 0.14.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 +29 -0
- package/dist/assets/{api-DPcvCLfa.js → api-a14tR_z1.js} +7 -7
- package/dist/assets/channels-list-page-CFGkm4DT.js +8 -0
- package/dist/assets/chat-page-CEdavJXc.js +105 -0
- package/dist/assets/{config-split-page-BCJhqdK9.js → config-split-page-B5b9B0uE.js} +1 -1
- package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-ClH_Ec7n.js} +1 -1
- package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-DPNDnVpk.js} +1 -1
- package/dist/assets/{dist-DPrgcEC0.js → dist-BtCS2Ucq.js} +1 -1
- package/dist/assets/{dist-DsE4OsSS.js → dist-CLGGLm8b.js} +1 -1
- package/dist/assets/doc-browser-5nMWYKBD.js +1 -0
- package/dist/assets/doc-browser-BJRLhztv.js +1 -0
- package/dist/assets/doc-browser-context-D4JwMMxK.js +1 -0
- package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-CtJXjIol.js} +1 -1
- package/dist/assets/{external-link-C_dmG-WJ.js → external-link-CdJiG13p.js} +1 -1
- package/dist/assets/index-BDN1zzMQ.js +105 -0
- package/dist/assets/index-g3uSZXoc.css +1 -0
- package/dist/assets/mcp-marketplace-page-BU0kIjhR.js +40 -0
- package/dist/assets/mcp-marketplace-page-sMNFSq_T.js +1 -0
- package/dist/assets/middleware-CkFT3R4v.js +1 -0
- package/dist/assets/model-config-page-D6hYXROd.js +1 -0
- package/dist/assets/plus-DPjb0piO.js +1 -0
- package/dist/assets/provider-scoped-model-input-DAX2HgmR.js +1 -0
- package/dist/assets/providers-config-page-BzAeaACA.js +1 -0
- package/dist/assets/{react-sZz5HSCm.js → react-C6cZgP44.js} +1 -1
- package/dist/assets/remote-B0G80uPr.js +1 -0
- package/dist/assets/rotate-cw-DnEB1Rju.js +1 -0
- package/dist/assets/runtime-config-page-C5xKaV3i.js +1 -0
- package/dist/assets/{save-MzjoiHri.js → save-DKA0x4zk.js} +1 -1
- package/dist/assets/{search-B29dE4GN.js → search-BNlcoj5c.js} +1 -1
- package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-Cq9uJ-N8.js} +1 -1
- package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page-CxSbZLBo.js} +2 -2
- package/dist/assets/{select-BKmh05hd.js → select-CvTlDZTQ.js} +1 -1
- package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-8ffF2y2C.js} +1 -1
- package/dist/assets/use-config-DmT0o1gv.js +1 -0
- package/dist/assets/x-BiSSpgqB.js +1 -0
- package/dist/index.html +15 -14
- package/package.json +9 -9
- package/src/app/components/app-presenter-provider.tsx +11 -2
- package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
- package/src/app/components/layout/app-layout.tsx +18 -4
- package/src/app/components/layout/sidebar-items.tsx +194 -71
- package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
- package/src/app/components/layout/sidebar.tsx +340 -83
- package/src/app/managers/viewport-layout.manager.ts +10 -0
- package/src/app/stores/viewport-layout.store.ts +39 -3
- package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
- package/src/features/agents/components/agents-page.tsx +10 -6
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
- package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +14 -15
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +18 -18
- package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +16 -14
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +80 -7
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +82 -25
- package/src/features/chat/components/layout/chat-sidebar.tsx +59 -92
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +289 -0
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +427 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +204 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
- package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +32 -17
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
- package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
- package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
- package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
- package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
- package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
- package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +93 -12
- package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
- package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
- package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +2 -2
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
- package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
- package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
- package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
- package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
- package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
- package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
- package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -2
- package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +2 -2
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +2 -2
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
- package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
- package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
- package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
- package/src/features/chat/index.ts +0 -1
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
- package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
- package/src/features/chat/managers/chat-thread.manager.ts +108 -34
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
- package/src/features/chat/presenters/chat.presenter.ts +2 -19
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
- package/src/features/chat/stores/chat-thread.store.ts +42 -26
- package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
- package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
- package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
- package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
- package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
- package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
- package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
- package/src/shared/lib/ui-document-title/index.ts +50 -21
- package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
- package/dist/assets/chat-page-BBnNJoKG.js +0 -105
- package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
- package/dist/assets/doc-browser-DgQedeAn.js +0 -1
- package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
- package/dist/assets/index-7o1WdUbU.js +0 -103
- package/dist/assets/index-DQWmEWzk.css +0 -1
- package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
- package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
- package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
- package/dist/assets/plus-Bnt56oqG.js +0 -1
- package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
- package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
- package/dist/assets/remote-B0LMs8u7.js +0 -1
- package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
- package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
- package/dist/assets/use-config-DZkBWIdL.js +0 -1
- package/dist/assets/x-CW2KO-Hz.js +0 -1
- package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
- package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
- package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
- package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
- package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
- package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
- package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
- package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
- package/src/features/chat/managers/chat-input.manager.ts +0 -430
- package/src/features/chat/managers/chat-run.manager.ts +0 -110
- package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
- package/src/features/chat/stores/chat-input.store.ts +0 -104
- /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
|
@@ -1,39 +1,54 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
1
|
+
import { useMemo, type SetStateAction } from 'react';
|
|
2
|
+
|
|
2
3
|
import type { NcpSessionSummaryView, SessionSkillEntryView } from '@/shared/lib/api';
|
|
3
4
|
import { t } from '@/shared/lib/i18n';
|
|
4
|
-
import {
|
|
5
|
+
import { normalizeSessionProjectRootValue } from '@/shared/lib/session-project';
|
|
6
|
+
import {
|
|
7
|
+
useNcpSessionSkills,
|
|
8
|
+
} from '@/features/chat/features/ncp/hooks/use-ncp-session-queries';
|
|
9
|
+
import { useNcpChatProviderStateResolved } from '@/features/chat/features/ncp/hooks/use-ncp-chat-derived-state';
|
|
5
10
|
import {
|
|
6
11
|
buildNcpChatProviderModelOptions,
|
|
7
12
|
filterNcpChatModelOptionsBySessionType,
|
|
8
13
|
} from '@/features/chat/features/ncp/utils/ncp-chat-query-derived.utils';
|
|
9
|
-
import { useNcpChatProviderStateResolved } from '@/features/chat/features/ncp/hooks/use-ncp-chat-derived-state';
|
|
10
|
-
import { useChatSessionTypeState } from '@/features/chat/features/session-type/hooks/use-chat-session-type-state';
|
|
11
14
|
import { adaptNcpSessionSummaries } from '@/features/chat/features/session/utils/ncp-session-adapter.utils';
|
|
12
15
|
import { resolveRecentSessionPreferredValue } from '@/features/chat/features/session/utils/chat-session-preference-governance.utils';
|
|
16
|
+
import { useChatSessionTypeState } from '@/features/chat/features/session-type/hooks/use-chat-session-type-state';
|
|
13
17
|
import { chatRecentModelsManager } from '@/features/chat/managers/chat-recent-models.manager';
|
|
14
|
-
import type { ChatInputSnapshot } from '@/features/chat/stores/chat-input.store';
|
|
15
18
|
import { useChatQueryStore } from '@/features/chat/stores/ncp-chat-query.store';
|
|
16
|
-
|
|
17
|
-
import {
|
|
19
|
+
|
|
20
|
+
import type { SessionConversationInputSnapshot } from './use-session-conversation-input-state';
|
|
18
21
|
|
|
19
22
|
const EMPTY_SESSION_SKILL_RECORDS: SessionSkillEntryView[] = [];
|
|
20
23
|
const EMPTY_NCP_SESSION_SUMMARIES: NcpSessionSummaryView[] = [];
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
type UseSessionConversationInputQueryParams = {
|
|
26
|
+
readonly sessionKey: string | null;
|
|
27
|
+
readonly inputSnapshot: SessionConversationInputSnapshot;
|
|
28
|
+
readonly setPendingSessionType: (sessionType: SetStateAction<string>) => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function useSessionConversationInputQuery(params: UseSessionConversationInputQueryParams) {
|
|
32
|
+
const {
|
|
33
|
+
sessionKey,
|
|
34
|
+
inputSnapshot,
|
|
35
|
+
setPendingSessionType,
|
|
36
|
+
} = params;
|
|
37
|
+
const selectedSessionKey = sessionKey ?? null;
|
|
27
38
|
const isProviderStateResolved = useNcpChatProviderStateResolved();
|
|
28
39
|
const querySnapshot = useChatQueryStore((state) => state.snapshot);
|
|
29
40
|
const config = querySnapshot.configQuery?.data ?? null;
|
|
30
41
|
const sessionSummaries =
|
|
31
42
|
querySnapshot.sessionsQuery?.data?.sessions ?? EMPTY_NCP_SESSION_SUMMARIES;
|
|
43
|
+
const sessionSkillsQuery = useNcpSessionSkills({
|
|
44
|
+
sessionId: selectedSessionKey?.trim() || 'draft-session',
|
|
45
|
+
projectRoot: normalizeSessionProjectRootValue(inputSnapshot.pendingProjectRoot),
|
|
46
|
+
});
|
|
32
47
|
const skillRecords =
|
|
33
|
-
|
|
48
|
+
sessionSkillsQuery.data?.records ?? EMPTY_SESSION_SKILL_RECORDS;
|
|
34
49
|
const isSkillsLoading = Boolean(
|
|
35
|
-
|
|
36
|
-
|
|
50
|
+
sessionSkillsQuery.isLoading ||
|
|
51
|
+
sessionSkillsQuery.isFetching,
|
|
37
52
|
);
|
|
38
53
|
const sessions = useMemo(
|
|
39
54
|
() => adaptNcpSessionSummaries(sessionSummaries),
|
|
@@ -45,8 +60,8 @@ export function useChatInputBarQueryState(snapshot: ChatInputSnapshot) {
|
|
|
45
60
|
);
|
|
46
61
|
const sessionTypeState = useChatSessionTypeState({
|
|
47
62
|
selectedSession,
|
|
48
|
-
pendingSessionType:
|
|
49
|
-
setPendingSessionType
|
|
63
|
+
pendingSessionType: inputSnapshot.pendingSessionType,
|
|
64
|
+
setPendingSessionType,
|
|
50
65
|
sessionTypesData: querySnapshot.sessionTypesQuery?.data ?? null,
|
|
51
66
|
});
|
|
52
67
|
const providerModelOptions = useMemo(
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState, type SetStateAction } from 'react';
|
|
2
|
+
import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
|
|
3
|
+
import type { NcpDraftAttachment } from '@nextclaw/ncp-react';
|
|
4
|
+
|
|
5
|
+
import type { ThinkingLevel } from '@/shared/lib/api';
|
|
6
|
+
import { DEFAULT_SESSION_TYPE } from '@/features/chat/features/session-type/utils/chat-session-type.utils';
|
|
7
|
+
import { createChatComposerNodesFromDraft } from '@/features/chat/features/input/utils/chat-composer-state.utils';
|
|
8
|
+
|
|
9
|
+
type SessionConversationInputStateValue = string | null | undefined;
|
|
10
|
+
type SessionConversationSkillSelection = {
|
|
11
|
+
readonly ref: string;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type SessionConversationComposerState = {
|
|
16
|
+
readonly text: string;
|
|
17
|
+
readonly nodes: readonly ChatComposerNode[];
|
|
18
|
+
readonly selectedSkills: readonly string[];
|
|
19
|
+
readonly skillRecords: readonly SessionConversationSkillSelection[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type SessionConversationInputSnapshot = SessionConversationComposerState & {
|
|
23
|
+
readonly attachments: readonly NcpDraftAttachment[];
|
|
24
|
+
readonly selectedModel: SessionConversationInputStateValue;
|
|
25
|
+
readonly selectedThinkingLevel: ThinkingLevel | null;
|
|
26
|
+
readonly pendingSessionType: string;
|
|
27
|
+
readonly selectedSessionType: string | null;
|
|
28
|
+
readonly pendingProjectRoot: string | null;
|
|
29
|
+
readonly composerFocusRequestId: number;
|
|
30
|
+
readonly sendError: string | null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type SessionConversationInputPatch =
|
|
34
|
+
Partial<SessionConversationInputSnapshot> |
|
|
35
|
+
((snapshot: SessionConversationInputSnapshot) => Partial<SessionConversationInputSnapshot>);
|
|
36
|
+
|
|
37
|
+
export type SessionConversationInputActions = {
|
|
38
|
+
readonly update: (patch: SessionConversationInputPatch) => void;
|
|
39
|
+
readonly syncComposer: (composer: SessionConversationComposerState) => void;
|
|
40
|
+
readonly resetComposer: () => void;
|
|
41
|
+
readonly restoreComposer: (
|
|
42
|
+
composer: SessionConversationComposerState & {
|
|
43
|
+
readonly attachments?: readonly NcpDraftAttachment[];
|
|
44
|
+
},
|
|
45
|
+
) => void;
|
|
46
|
+
readonly applyPromptSuggestion: (prompt: string) => void;
|
|
47
|
+
readonly requestComposerFocusAtEnd: () => void;
|
|
48
|
+
readonly consumeComposerFocusRequest: () => void;
|
|
49
|
+
readonly setAttachments: (attachments: readonly NcpDraftAttachment[]) => void;
|
|
50
|
+
readonly addAttachments: (attachments: readonly NcpDraftAttachment[]) => readonly NcpDraftAttachment[];
|
|
51
|
+
readonly removeAttachment: (attachmentId: string) => void;
|
|
52
|
+
readonly setSelectedModel: (model: SessionConversationInputStateValue) => void;
|
|
53
|
+
readonly setSelectedThinkingLevel: (level: ThinkingLevel | null) => void;
|
|
54
|
+
readonly setPendingSessionType: (sessionType: SetStateAction<string>) => void;
|
|
55
|
+
readonly setPendingProjectRoot: (projectRoot: string | null) => void;
|
|
56
|
+
readonly setSelectedSkills: (
|
|
57
|
+
selectedSkills: readonly string[],
|
|
58
|
+
skillRecords: readonly SessionConversationSkillSelection[],
|
|
59
|
+
) => void;
|
|
60
|
+
readonly setSendError: (message: string | null) => void;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const EMPTY_COMPOSER_STATE: SessionConversationComposerState = {
|
|
64
|
+
text: '',
|
|
65
|
+
nodes: [],
|
|
66
|
+
selectedSkills: [],
|
|
67
|
+
skillRecords: [],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const createInitialInputSnapshot = (): SessionConversationInputSnapshot => ({
|
|
71
|
+
...EMPTY_COMPOSER_STATE,
|
|
72
|
+
attachments: [],
|
|
73
|
+
selectedModel: undefined,
|
|
74
|
+
selectedThinkingLevel: null,
|
|
75
|
+
pendingSessionType: DEFAULT_SESSION_TYPE,
|
|
76
|
+
selectedSessionType: DEFAULT_SESSION_TYPE,
|
|
77
|
+
pendingProjectRoot: null,
|
|
78
|
+
composerFocusRequestId: 0,
|
|
79
|
+
sendError: null,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const normalizeSessionType = (sessionType: string | null | undefined): string => {
|
|
83
|
+
const trimmed = sessionType?.trim();
|
|
84
|
+
return trimmed ? trimmed : DEFAULT_SESSION_TYPE;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const buildAttachmentKey = (attachment: NcpDraftAttachment): string => {
|
|
88
|
+
return [
|
|
89
|
+
attachment.assetUri ?? '',
|
|
90
|
+
attachment.url ?? '',
|
|
91
|
+
attachment.name,
|
|
92
|
+
attachment.mimeType,
|
|
93
|
+
String(attachment.sizeBytes),
|
|
94
|
+
attachment.contentBase64 ?? '',
|
|
95
|
+
].join(':');
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const mergeAttachments = (
|
|
99
|
+
current: readonly NcpDraftAttachment[],
|
|
100
|
+
next: readonly NcpDraftAttachment[],
|
|
101
|
+
): readonly NcpDraftAttachment[] => {
|
|
102
|
+
const seen = new Set(current.map(buildAttachmentKey));
|
|
103
|
+
const merged = [...current];
|
|
104
|
+
next.forEach((attachment) => {
|
|
105
|
+
const key = buildAttachmentKey(attachment);
|
|
106
|
+
if (!seen.has(key)) {
|
|
107
|
+
seen.add(key);
|
|
108
|
+
merged.push(attachment);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return merged;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const useSessionConversationInputState = () => {
|
|
115
|
+
const [snapshot, setSnapshot] = useState<SessionConversationInputSnapshot>(createInitialInputSnapshot);
|
|
116
|
+
|
|
117
|
+
const update = useCallback((patch: SessionConversationInputPatch) => {
|
|
118
|
+
setSnapshot((current) => {
|
|
119
|
+
const resolvedPatch = typeof patch === 'function' ? patch(current) : patch;
|
|
120
|
+
return {
|
|
121
|
+
...current,
|
|
122
|
+
...resolvedPatch,
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
}, []);
|
|
126
|
+
|
|
127
|
+
const syncComposer = useCallback((composer: SessionConversationComposerState) => {
|
|
128
|
+
update({
|
|
129
|
+
text: composer.text,
|
|
130
|
+
nodes: composer.nodes,
|
|
131
|
+
selectedSkills: composer.selectedSkills,
|
|
132
|
+
skillRecords: composer.skillRecords,
|
|
133
|
+
sendError: null,
|
|
134
|
+
});
|
|
135
|
+
}, [update]);
|
|
136
|
+
|
|
137
|
+
const resetComposer = useCallback(() => {
|
|
138
|
+
update({
|
|
139
|
+
...EMPTY_COMPOSER_STATE,
|
|
140
|
+
attachments: [],
|
|
141
|
+
sendError: null,
|
|
142
|
+
});
|
|
143
|
+
}, [update]);
|
|
144
|
+
|
|
145
|
+
const restoreComposer = useCallback((
|
|
146
|
+
composer: SessionConversationComposerState & {
|
|
147
|
+
readonly attachments?: readonly NcpDraftAttachment[];
|
|
148
|
+
},
|
|
149
|
+
) => {
|
|
150
|
+
update({
|
|
151
|
+
...composer,
|
|
152
|
+
});
|
|
153
|
+
}, [update]);
|
|
154
|
+
|
|
155
|
+
const applyPromptSuggestion = useCallback((prompt: string) => {
|
|
156
|
+
update({
|
|
157
|
+
text: prompt,
|
|
158
|
+
nodes: createChatComposerNodesFromDraft(prompt),
|
|
159
|
+
selectedSkills: [],
|
|
160
|
+
skillRecords: [],
|
|
161
|
+
sendError: null,
|
|
162
|
+
composerFocusRequestId: Date.now(),
|
|
163
|
+
});
|
|
164
|
+
}, [update]);
|
|
165
|
+
|
|
166
|
+
const requestComposerFocusAtEnd = useCallback(() => {
|
|
167
|
+
update((current) => ({
|
|
168
|
+
composerFocusRequestId: current.composerFocusRequestId + 1,
|
|
169
|
+
}));
|
|
170
|
+
}, [update]);
|
|
171
|
+
|
|
172
|
+
const consumeComposerFocusRequest = useCallback(() => {
|
|
173
|
+
update({ composerFocusRequestId: 0 });
|
|
174
|
+
}, [update]);
|
|
175
|
+
|
|
176
|
+
const setAttachments = useCallback((attachments: readonly NcpDraftAttachment[]) => {
|
|
177
|
+
update({ attachments });
|
|
178
|
+
}, [update]);
|
|
179
|
+
|
|
180
|
+
const addAttachments = useCallback((attachments: readonly NcpDraftAttachment[]) => {
|
|
181
|
+
if (attachments.length === 0) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
const existingKeys = new Set(snapshot.attachments.map(buildAttachmentKey));
|
|
185
|
+
const nextAttachments = mergeAttachments(snapshot.attachments, attachments);
|
|
186
|
+
const insertedAttachments = nextAttachments.filter(
|
|
187
|
+
(attachment) => !existingKeys.has(buildAttachmentKey(attachment)),
|
|
188
|
+
);
|
|
189
|
+
if (insertedAttachments.length > 0) {
|
|
190
|
+
update({ attachments: nextAttachments });
|
|
191
|
+
}
|
|
192
|
+
return insertedAttachments;
|
|
193
|
+
}, [snapshot.attachments, update]);
|
|
194
|
+
|
|
195
|
+
const removeAttachment = useCallback((attachmentId: string) => {
|
|
196
|
+
update((current) => ({
|
|
197
|
+
attachments: current.attachments.filter((attachment) => attachment.id !== attachmentId),
|
|
198
|
+
}));
|
|
199
|
+
}, [update]);
|
|
200
|
+
|
|
201
|
+
const setSelectedModel = useCallback((model: SessionConversationInputStateValue) => {
|
|
202
|
+
update({ selectedModel: model });
|
|
203
|
+
}, [update]);
|
|
204
|
+
|
|
205
|
+
const setSelectedThinkingLevel = useCallback((level: ThinkingLevel | null) => {
|
|
206
|
+
update({ selectedThinkingLevel: level });
|
|
207
|
+
}, [update]);
|
|
208
|
+
|
|
209
|
+
const setPendingSessionType = useCallback((sessionType: SetStateAction<string>) => {
|
|
210
|
+
update((current) => {
|
|
211
|
+
const nextSessionType = typeof sessionType === 'function'
|
|
212
|
+
? sessionType(current.pendingSessionType)
|
|
213
|
+
: sessionType;
|
|
214
|
+
const normalizedSessionType = normalizeSessionType(nextSessionType);
|
|
215
|
+
return {
|
|
216
|
+
pendingSessionType: normalizedSessionType,
|
|
217
|
+
selectedSessionType: normalizedSessionType,
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
}, [update]);
|
|
221
|
+
|
|
222
|
+
const setPendingProjectRoot = useCallback((projectRoot: string | null) => {
|
|
223
|
+
update({ pendingProjectRoot: projectRoot });
|
|
224
|
+
}, [update]);
|
|
225
|
+
|
|
226
|
+
const setSelectedSkills = useCallback((
|
|
227
|
+
selectedSkills: readonly string[],
|
|
228
|
+
skillRecords: readonly SessionConversationSkillSelection[],
|
|
229
|
+
) => {
|
|
230
|
+
update({
|
|
231
|
+
selectedSkills,
|
|
232
|
+
skillRecords,
|
|
233
|
+
sendError: null,
|
|
234
|
+
});
|
|
235
|
+
}, [update]);
|
|
236
|
+
|
|
237
|
+
const setSendError = useCallback((message: string | null) => {
|
|
238
|
+
update({ sendError: message });
|
|
239
|
+
}, [update]);
|
|
240
|
+
|
|
241
|
+
const actions = useMemo<SessionConversationInputActions>(() => ({
|
|
242
|
+
update,
|
|
243
|
+
syncComposer,
|
|
244
|
+
resetComposer,
|
|
245
|
+
restoreComposer,
|
|
246
|
+
applyPromptSuggestion,
|
|
247
|
+
requestComposerFocusAtEnd,
|
|
248
|
+
consumeComposerFocusRequest,
|
|
249
|
+
setAttachments,
|
|
250
|
+
addAttachments,
|
|
251
|
+
removeAttachment,
|
|
252
|
+
setSelectedModel,
|
|
253
|
+
setSelectedThinkingLevel,
|
|
254
|
+
setPendingSessionType,
|
|
255
|
+
setPendingProjectRoot,
|
|
256
|
+
setSelectedSkills,
|
|
257
|
+
setSendError,
|
|
258
|
+
}), [
|
|
259
|
+
update,
|
|
260
|
+
syncComposer,
|
|
261
|
+
resetComposer,
|
|
262
|
+
restoreComposer,
|
|
263
|
+
applyPromptSuggestion,
|
|
264
|
+
requestComposerFocusAtEnd,
|
|
265
|
+
consumeComposerFocusRequest,
|
|
266
|
+
setAttachments,
|
|
267
|
+
addAttachments,
|
|
268
|
+
removeAttachment,
|
|
269
|
+
setSelectedModel,
|
|
270
|
+
setSelectedThinkingLevel,
|
|
271
|
+
setPendingSessionType,
|
|
272
|
+
setPendingProjectRoot,
|
|
273
|
+
setSelectedSkills,
|
|
274
|
+
setSendError,
|
|
275
|
+
]);
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
inputSnapshot: snapshot,
|
|
279
|
+
inputActions: actions,
|
|
280
|
+
};
|
|
281
|
+
};
|
package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { t } from '@/shared/lib/i18n';
|
|
2
|
+
import {
|
|
3
|
+
buildModelToolbarSelect,
|
|
4
|
+
buildSkillPickerModel,
|
|
5
|
+
buildThinkingToolbarSelect,
|
|
6
|
+
type ChatModelRecord,
|
|
7
|
+
type ChatSkillRecord,
|
|
8
|
+
type ChatThinkingLevel,
|
|
9
|
+
} from '@/features/chat/features/input/utils/chat-input-bar.utils';
|
|
10
|
+
|
|
11
|
+
function buildThinkingLabels(): Record<ChatThinkingLevel, string> {
|
|
12
|
+
return {
|
|
13
|
+
off: t('chatThinkingLevelOff'),
|
|
14
|
+
minimal: t('chatThinkingLevelMinimal'),
|
|
15
|
+
low: t('chatThinkingLevelLow'),
|
|
16
|
+
medium: t('chatThinkingLevelMedium'),
|
|
17
|
+
high: t('chatThinkingLevelHigh'),
|
|
18
|
+
adaptive: t('chatThinkingLevelAdaptive'),
|
|
19
|
+
xhigh: t('chatThinkingLevelXhigh'),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type ToolbarSelectBuildParams = {
|
|
24
|
+
readonly allModelsLabel: string;
|
|
25
|
+
readonly favoriteModelLabel: string;
|
|
26
|
+
readonly favoriteModelValues: string[];
|
|
27
|
+
readonly favoriteModelsLabel: string;
|
|
28
|
+
readonly hasModelOptions: boolean;
|
|
29
|
+
readonly isModelOptionsLoading: boolean;
|
|
30
|
+
readonly modelRecords: ChatModelRecord[];
|
|
31
|
+
readonly modelSearchEmptyLabel: string;
|
|
32
|
+
readonly modelSearchPlaceholder: string;
|
|
33
|
+
readonly onFavoriteModelToggle: (value: string, favorite: boolean) => void;
|
|
34
|
+
readonly onModelChange: (value: string) => void;
|
|
35
|
+
readonly onThinkingChange: (value: ChatThinkingLevel | null) => void;
|
|
36
|
+
readonly recentModelValues: string[];
|
|
37
|
+
readonly recentModelsLabel: string;
|
|
38
|
+
readonly selectedModel: string;
|
|
39
|
+
readonly selectedThinkingLevel: ChatThinkingLevel | null;
|
|
40
|
+
readonly thinkingDefaultLevel: ChatThinkingLevel | null;
|
|
41
|
+
readonly thinkingSupportedLevels: ChatThinkingLevel[];
|
|
42
|
+
readonly unfavoriteModelLabel: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export function buildSessionConversationToolbarSelects(params: ToolbarSelectBuildParams) {
|
|
46
|
+
const {
|
|
47
|
+
allModelsLabel,
|
|
48
|
+
favoriteModelLabel,
|
|
49
|
+
favoriteModelValues,
|
|
50
|
+
favoriteModelsLabel,
|
|
51
|
+
hasModelOptions,
|
|
52
|
+
isModelOptionsLoading,
|
|
53
|
+
modelRecords,
|
|
54
|
+
modelSearchEmptyLabel,
|
|
55
|
+
modelSearchPlaceholder,
|
|
56
|
+
onFavoriteModelToggle,
|
|
57
|
+
onModelChange,
|
|
58
|
+
onThinkingChange,
|
|
59
|
+
recentModelValues,
|
|
60
|
+
recentModelsLabel,
|
|
61
|
+
selectedModel,
|
|
62
|
+
selectedThinkingLevel,
|
|
63
|
+
thinkingDefaultLevel,
|
|
64
|
+
thinkingSupportedLevels,
|
|
65
|
+
unfavoriteModelLabel,
|
|
66
|
+
} = params;
|
|
67
|
+
return [
|
|
68
|
+
buildModelToolbarSelect({
|
|
69
|
+
modelOptions: modelRecords,
|
|
70
|
+
favoriteModelValues,
|
|
71
|
+
recentModelValues,
|
|
72
|
+
selectedModel,
|
|
73
|
+
isModelOptionsLoading,
|
|
74
|
+
hasModelOptions,
|
|
75
|
+
onFavoriteToggle: onFavoriteModelToggle,
|
|
76
|
+
onValueChange: onModelChange,
|
|
77
|
+
texts: {
|
|
78
|
+
modelSelectPlaceholder: t('chatSelectModel'),
|
|
79
|
+
modelNoOptionsLabel: t('chatModelNoOptions'),
|
|
80
|
+
modelSearchPlaceholder,
|
|
81
|
+
modelSearchEmptyLabel,
|
|
82
|
+
favoriteModelsLabel,
|
|
83
|
+
favoriteModelLabel,
|
|
84
|
+
unfavoriteModelLabel,
|
|
85
|
+
recentModelsLabel,
|
|
86
|
+
allModelsLabel,
|
|
87
|
+
},
|
|
88
|
+
}),
|
|
89
|
+
buildThinkingToolbarSelect({
|
|
90
|
+
supportedLevels: thinkingSupportedLevels,
|
|
91
|
+
selectedThinkingLevel,
|
|
92
|
+
defaultThinkingLevel: thinkingDefaultLevel,
|
|
93
|
+
onValueChange: onThinkingChange,
|
|
94
|
+
texts: {
|
|
95
|
+
thinkingLabels: buildThinkingLabels(),
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
].filter((item): item is NonNullable<typeof item> => item !== null);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function buildSessionConversationSkillPicker(params: {
|
|
102
|
+
readonly allSkillsLabel: string;
|
|
103
|
+
readonly isSkillsLoading: boolean;
|
|
104
|
+
readonly onSelectedKeysChange: (keys: string[]) => void;
|
|
105
|
+
readonly recentSkillGroupValues: string[];
|
|
106
|
+
readonly recentSkillValues: string[];
|
|
107
|
+
readonly recentSkillsLabel: string;
|
|
108
|
+
readonly skillRecords: ChatSkillRecord[];
|
|
109
|
+
readonly selectedSkills: readonly string[];
|
|
110
|
+
}) {
|
|
111
|
+
const {
|
|
112
|
+
allSkillsLabel,
|
|
113
|
+
isSkillsLoading,
|
|
114
|
+
onSelectedKeysChange,
|
|
115
|
+
recentSkillGroupValues,
|
|
116
|
+
recentSkillValues,
|
|
117
|
+
recentSkillsLabel,
|
|
118
|
+
selectedSkills,
|
|
119
|
+
skillRecords,
|
|
120
|
+
} = params;
|
|
121
|
+
return buildSkillPickerModel({
|
|
122
|
+
skillRecords,
|
|
123
|
+
recentSkillValues,
|
|
124
|
+
groupedRecentSkillValues: recentSkillGroupValues,
|
|
125
|
+
selectedSkills: [...selectedSkills],
|
|
126
|
+
isLoading: isSkillsLoading,
|
|
127
|
+
onSelectedKeysChange,
|
|
128
|
+
texts: {
|
|
129
|
+
title: t('chatSkillsPickerTitle'),
|
|
130
|
+
searchPlaceholder: t('chatSkillsPickerSearchPlaceholder'),
|
|
131
|
+
emptyLabel: t('chatSkillsPickerEmpty'),
|
|
132
|
+
loadingLabel: t('sessionsLoading'),
|
|
133
|
+
manageLabel: t('chatSkillsPickerManage'),
|
|
134
|
+
recentSkillsLabel,
|
|
135
|
+
allSkillsLabel,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function resolveThinkingForConversationModel(
|
|
141
|
+
modelOption: ChatModelRecord | undefined,
|
|
142
|
+
current: ChatThinkingLevel | null,
|
|
143
|
+
): ChatThinkingLevel | null {
|
|
144
|
+
const capability = modelOption?.thinkingCapability;
|
|
145
|
+
if (!capability || capability.supported.length === 0) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
if (current === 'off') {
|
|
149
|
+
return 'off';
|
|
150
|
+
}
|
|
151
|
+
if (current && capability.supported.includes(current)) {
|
|
152
|
+
return current;
|
|
153
|
+
}
|
|
154
|
+
if (capability.default && capability.supported.includes(capability.default)) {
|
|
155
|
+
return capability.default;
|
|
156
|
+
}
|
|
157
|
+
return 'off';
|
|
158
|
+
}
|
package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import { expect, it, vi } from 'vitest';
|
|
3
|
+
import { useChatInputSurfaceState } from '@/features/chat/features/input/hooks/use-chat-input-surface-state';
|
|
4
|
+
|
|
5
|
+
vi.mock('@/features/panel-apps', () => ({
|
|
6
|
+
usePanelApps: () => ({
|
|
7
|
+
data: { entries: [] },
|
|
8
|
+
isFetching: false,
|
|
9
|
+
isLoading: false,
|
|
10
|
+
}),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
function createHookParams() {
|
|
14
|
+
return {
|
|
15
|
+
commands: [],
|
|
16
|
+
isSkillsLoading: false,
|
|
17
|
+
itemTexts: {
|
|
18
|
+
slashTexts: {
|
|
19
|
+
noSkillDescription: 'No description',
|
|
20
|
+
slashSkillScopeLabel: 'Scope',
|
|
21
|
+
slashSkillSpecLabel: 'Spec',
|
|
22
|
+
slashSkillSubtitle: 'Skill',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
language: 'zh' as const,
|
|
26
|
+
onSelectSkill: vi.fn(),
|
|
27
|
+
recentSkillValues: [],
|
|
28
|
+
skillRecords: [],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
it('keeps equivalent input surface trigger updates from rerendering the composer owner', () => {
|
|
33
|
+
let renderCount = 0;
|
|
34
|
+
const trigger = {
|
|
35
|
+
end: 4,
|
|
36
|
+
key: 'slash',
|
|
37
|
+
marker: '/',
|
|
38
|
+
query: 'xxx',
|
|
39
|
+
start: 0,
|
|
40
|
+
};
|
|
41
|
+
const { result } = renderHook(() => {
|
|
42
|
+
renderCount += 1;
|
|
43
|
+
return useChatInputSurfaceState(createHookParams());
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(renderCount).toBe(1);
|
|
47
|
+
|
|
48
|
+
act(() => result.current.setInputSurfaceTrigger(trigger));
|
|
49
|
+
expect(renderCount).toBe(2);
|
|
50
|
+
|
|
51
|
+
act(() => result.current.setInputSurfaceTrigger({ ...trigger }));
|
|
52
|
+
expect(renderCount).toBe(2);
|
|
53
|
+
|
|
54
|
+
act(() => result.current.setInputSurfaceTrigger({ ...trigger, end: 3, query: 'xx' }));
|
|
55
|
+
expect(renderCount).toBe(3);
|
|
56
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
resolveChatInputSurfaceState,
|
|
4
|
+
type ChatInputSurfaceTrigger,
|
|
5
|
+
} from '@nextclaw/agent-chat-ui';
|
|
6
|
+
import { usePanelApps } from '@/features/panel-apps';
|
|
7
|
+
import { t, type I18nLanguage } from '@/shared/lib/i18n';
|
|
8
|
+
import { createPanelAppReferenceInputSurfacePlugin, PANEL_APP_REFERENCE_TRIGGER_SPEC } from '@/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils';
|
|
9
|
+
import {
|
|
10
|
+
createSlashCommandInputSurfacePlugin,
|
|
11
|
+
type ChatSlashCommandDescriptor,
|
|
12
|
+
} from '@/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils';
|
|
13
|
+
import type { ChatInputBarAdapterTexts, ChatSkillRecord } from '@/features/chat/types/chat-input-bar.types';
|
|
14
|
+
|
|
15
|
+
export function useChatInputSurfaceState(params: {
|
|
16
|
+
commands: readonly ChatSlashCommandDescriptor[];
|
|
17
|
+
isSkillsLoading: boolean;
|
|
18
|
+
itemTexts: {
|
|
19
|
+
slashTexts: Pick<
|
|
20
|
+
ChatInputBarAdapterTexts,
|
|
21
|
+
'slashSkillSubtitle' | 'slashSkillSpecLabel' | 'slashSkillScopeLabel' | 'noSkillDescription'
|
|
22
|
+
>;
|
|
23
|
+
};
|
|
24
|
+
language: I18nLanguage;
|
|
25
|
+
onSelectSkill: (skillRef: string) => void;
|
|
26
|
+
recentSkillValues: readonly string[];
|
|
27
|
+
skillRecords: readonly ChatSkillRecord[];
|
|
28
|
+
}) {
|
|
29
|
+
const {
|
|
30
|
+
isSkillsLoading,
|
|
31
|
+
itemTexts,
|
|
32
|
+
language,
|
|
33
|
+
onSelectSkill,
|
|
34
|
+
recentSkillValues,
|
|
35
|
+
skillRecords,
|
|
36
|
+
commands,
|
|
37
|
+
} = params;
|
|
38
|
+
const [inputSurfaceTrigger, setInputSurfaceTriggerState] = useState<ChatInputSurfaceTrigger | null>(null);
|
|
39
|
+
const inputSurfaceTriggerSignatureRef = useRef('null');
|
|
40
|
+
const setInputSurfaceTrigger = useCallback((nextTrigger: ChatInputSurfaceTrigger | null): void => {
|
|
41
|
+
const nextSignature = JSON.stringify(nextTrigger ? [nextTrigger.key, nextTrigger.marker, nextTrigger.query, nextTrigger.start, nextTrigger.end] : null);
|
|
42
|
+
if (inputSurfaceTriggerSignatureRef.current === nextSignature) return;
|
|
43
|
+
inputSurfaceTriggerSignatureRef.current = nextSignature;
|
|
44
|
+
setInputSurfaceTriggerState(nextTrigger);
|
|
45
|
+
}, []);
|
|
46
|
+
const panelApps = usePanelApps({ enabled: inputSurfaceTrigger?.key === PANEL_APP_REFERENCE_TRIGGER_SPEC.key });
|
|
47
|
+
|
|
48
|
+
const inputSurfacePlugins = useMemo(
|
|
49
|
+
() => [
|
|
50
|
+
createSlashCommandInputSurfacePlugin({
|
|
51
|
+
commands,
|
|
52
|
+
itemTexts: itemTexts.slashTexts,
|
|
53
|
+
menuTexts: {
|
|
54
|
+
loadingLabel: t('chatSlashLoading', language),
|
|
55
|
+
sectionLabel: t('chatSlashSection', language),
|
|
56
|
+
emptyLabel: t('chatSlashNoResult', language),
|
|
57
|
+
hintLabel: t('chatSlashHint', language),
|
|
58
|
+
itemHintLabel: t('chatSlashSkillHint', language)
|
|
59
|
+
},
|
|
60
|
+
labels: {
|
|
61
|
+
commandHintLabel: t('chatSlashCommandHint', language),
|
|
62
|
+
commandSectionLabel: t('chatSlashSectionCommands', language),
|
|
63
|
+
commandSubtitle: t('chatSlashTypeCommand', language),
|
|
64
|
+
skillHintLabel: t('chatSlashSkillHint', language),
|
|
65
|
+
skillSectionLabel: t('chatSlashSectionSkills', language)
|
|
66
|
+
},
|
|
67
|
+
onSelectSkill
|
|
68
|
+
}),
|
|
69
|
+
createPanelAppReferenceInputSurfacePlugin({
|
|
70
|
+
itemTexts: {
|
|
71
|
+
appIdLabel: t('chatPanelAppReferenceAppId', language),
|
|
72
|
+
fileLabel: t('chatPanelAppReferenceFile', language),
|
|
73
|
+
noDescriptionLabel: t('chatPanelAppReferenceNoDescription', language),
|
|
74
|
+
subtitle: t('chatPanelAppReferenceType', language)
|
|
75
|
+
},
|
|
76
|
+
menuTexts: {
|
|
77
|
+
loadingLabel: t('chatPanelAppReferenceLoading', language),
|
|
78
|
+
sectionLabel: t('chatPanelAppReferenceSection', language),
|
|
79
|
+
emptyLabel: t('chatPanelAppReferenceNoResult', language),
|
|
80
|
+
hintLabel: t('chatPanelAppReferenceHint', language),
|
|
81
|
+
itemHintLabel: t('chatPanelAppReferenceItemHint', language)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
commands,
|
|
87
|
+
itemTexts.slashTexts,
|
|
88
|
+
language,
|
|
89
|
+
onSelectSkill
|
|
90
|
+
]
|
|
91
|
+
);
|
|
92
|
+
const inputSurfaceState = useMemo(
|
|
93
|
+
() => resolveChatInputSurfaceState({
|
|
94
|
+
data: {
|
|
95
|
+
isPanelAppsLoading: panelApps.isLoading || panelApps.isFetching,
|
|
96
|
+
isSkillsLoading,
|
|
97
|
+
panelApps: panelApps.data?.entries ?? [],
|
|
98
|
+
recentSkillValues,
|
|
99
|
+
skillRecords
|
|
100
|
+
},
|
|
101
|
+
plugins: inputSurfacePlugins,
|
|
102
|
+
trigger: inputSurfaceTrigger
|
|
103
|
+
}),
|
|
104
|
+
[
|
|
105
|
+
inputSurfacePlugins,
|
|
106
|
+
inputSurfaceTrigger,
|
|
107
|
+
panelApps.data?.entries,
|
|
108
|
+
panelApps.isFetching,
|
|
109
|
+
panelApps.isLoading,
|
|
110
|
+
isSkillsLoading,
|
|
111
|
+
recentSkillValues,
|
|
112
|
+
skillRecords
|
|
113
|
+
]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return { inputSurfaceState, setInputSurfaceTrigger };
|
|
117
|
+
}
|