@fromsko/chat-ui 0.0.0-stage → 0.1.6
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/LICENSE +21 -0
- package/README.md +100 -2
- package/lib/adapters/event-bus.d.ts +80 -0
- package/lib/adapters/event-bus.js +73 -0
- package/lib/adapters/index.d.ts +3 -0
- package/lib/adapters/index.js +3 -0
- package/lib/adapters/labels.d.ts +6 -0
- package/lib/adapters/labels.js +17 -0
- package/lib/adapters/stream-bridge.d.ts +9 -0
- package/lib/adapters/stream-bridge.js +7 -0
- package/lib/blocks/cards/AskCard.d.ts +8 -0
- package/lib/blocks/cards/AskCard.js +97 -0
- package/lib/blocks/cards/CompactionCard.d.ts +5 -0
- package/lib/blocks/cards/CompactionCard.js +5 -0
- package/lib/blocks/cards/ContextUsage.d.ts +14 -0
- package/lib/blocks/cards/ContextUsage.js +136 -0
- package/lib/blocks/cards/DisplayCard.d.ts +7 -0
- package/lib/blocks/cards/DisplayCard.js +27 -0
- package/lib/blocks/cards/MemoryPill.d.ts +5 -0
- package/lib/blocks/cards/MemoryPill.js +5 -0
- package/lib/blocks/cards/OutputCard.d.ts +5 -0
- package/lib/blocks/cards/OutputCard.js +13 -0
- package/lib/blocks/cards/PermissionCard.d.ts +21 -0
- package/lib/blocks/cards/PermissionCard.js +26 -0
- package/lib/blocks/cards/SpringCheck.d.ts +9 -0
- package/lib/blocks/cards/SpringCheck.js +17 -0
- package/lib/blocks/cards/TodoCard.d.ts +19 -0
- package/lib/blocks/cards/TodoCard.js +28 -0
- package/lib/blocks/cards/index.d.ts +10 -0
- package/lib/blocks/cards/index.js +10 -0
- package/lib/blocks/composer/Composer.d.ts +66 -0
- package/lib/blocks/composer/Composer.js +73 -0
- package/lib/blocks/composer/ComposerAttachments.d.ts +17 -0
- package/lib/blocks/composer/ComposerAttachments.js +10 -0
- package/lib/blocks/composer/ComposerGrip.d.ts +1 -0
- package/lib/blocks/composer/ComposerGrip.js +39 -0
- package/lib/blocks/composer/ComposerInput.d.ts +18 -0
- package/lib/blocks/composer/ComposerInput.js +60 -0
- package/lib/blocks/composer/ComposerMenus.d.ts +10 -0
- package/lib/blocks/composer/ComposerMenus.js +78 -0
- package/lib/blocks/composer/ComposerToolbar.d.ts +27 -0
- package/lib/blocks/composer/ComposerToolbar.js +16 -0
- package/lib/blocks/composer/MentionPicker.d.ts +13 -0
- package/lib/blocks/composer/MentionPicker.js +71 -0
- package/lib/blocks/composer/PromptPop.d.ts +7 -0
- package/lib/blocks/composer/PromptPop.js +40 -0
- package/lib/blocks/composer/QueuedPrompts.d.ts +14 -0
- package/lib/blocks/composer/QueuedPrompts.js +21 -0
- package/lib/blocks/composer/SlingButton.d.ts +10 -0
- package/lib/blocks/composer/SlingButton.js +112 -0
- package/lib/blocks/composer/index.d.ts +7 -0
- package/lib/blocks/composer/index.js +1 -0
- package/lib/blocks/conversation/ChatEmptyScene.d.ts +4 -0
- package/lib/blocks/conversation/ChatEmptyScene.js +21 -0
- package/lib/blocks/conversation/ChatHeader.d.ts +13 -0
- package/lib/blocks/conversation/ChatHeader.js +20 -0
- package/lib/blocks/conversation/ChatPane.d.ts +90 -0
- package/lib/blocks/conversation/ChatPane.js +60 -0
- package/lib/blocks/conversation/Conversation.d.ts +11 -0
- package/lib/blocks/conversation/Conversation.js +41 -0
- package/lib/blocks/conversation/LineSidebar.d.ts +14 -0
- package/lib/blocks/conversation/LineSidebar.js +42 -0
- package/lib/blocks/conversation/index.d.ts +5 -0
- package/lib/blocks/conversation/index.js +5 -0
- package/lib/blocks/index.d.ts +9 -0
- package/lib/blocks/index.js +9 -0
- package/lib/blocks/message/DayDivider.d.ts +10 -0
- package/lib/blocks/message/DayDivider.js +29 -0
- package/lib/blocks/message/DiffView.d.ts +5 -0
- package/lib/blocks/message/DiffView.js +18 -0
- package/lib/blocks/message/MermaidDiagram.d.ts +4 -0
- package/lib/blocks/message/MermaidDiagram.js +43 -0
- package/lib/blocks/message/Message.d.ts +17 -0
- package/lib/blocks/message/Message.js +45 -0
- package/lib/blocks/message/MessageActionBar.d.ts +18 -0
- package/lib/blocks/message/MessageActionBar.js +15 -0
- package/lib/blocks/message/MessageArtifacts.d.ts +13 -0
- package/lib/blocks/message/MessageArtifacts.js +9 -0
- package/lib/blocks/message/MessageAttachments.d.ts +5 -0
- package/lib/blocks/message/MessageAttachments.js +28 -0
- package/lib/blocks/message/MessageBubble.d.ts +11 -0
- package/lib/blocks/message/MessageBubble.js +31 -0
- package/lib/blocks/message/MessageRow.d.ts +20 -0
- package/lib/blocks/message/MessageRow.js +49 -0
- package/lib/blocks/message/MessageStream.d.ts +23 -0
- package/lib/blocks/message/MessageStream.js +80 -0
- package/lib/blocks/message/MessageTrace.d.ts +16 -0
- package/lib/blocks/message/MessageTrace.js +39 -0
- package/lib/blocks/message/PartView.d.ts +10 -0
- package/lib/blocks/message/PartView.js +29 -0
- package/lib/blocks/message/code-text.d.ts +15 -0
- package/lib/blocks/message/code-text.js +17 -0
- package/lib/blocks/message/index.d.ts +12 -0
- package/lib/blocks/message/index.js +12 -0
- package/lib/blocks/reasoning/Reasoning.d.ts +7 -0
- package/lib/blocks/reasoning/Reasoning.js +26 -0
- package/lib/blocks/reasoning/ReasoningBlock.d.ts +6 -0
- package/lib/blocks/reasoning/ReasoningBlock.js +5 -0
- package/lib/blocks/reasoning/index.d.ts +2 -0
- package/lib/blocks/reasoning/index.js +2 -0
- package/lib/blocks/selection/SelectionActions.d.ts +12 -0
- package/lib/blocks/selection/SelectionActions.js +29 -0
- package/lib/blocks/selection/SelectionBar.d.ts +10 -0
- package/lib/blocks/selection/SelectionBar.js +6 -0
- package/lib/blocks/selection/index.d.ts +2 -0
- package/lib/blocks/selection/index.js +2 -0
- package/lib/blocks/settings/DisplaySettingsPanel.d.ts +5 -0
- package/lib/blocks/settings/DisplaySettingsPanel.js +33 -0
- package/lib/blocks/settings/SquishSwitch.d.ts +8 -0
- package/lib/blocks/settings/SquishSwitch.js +21 -0
- package/lib/blocks/settings/index.d.ts +2 -0
- package/lib/blocks/settings/index.js +2 -0
- package/lib/blocks/status/LatticeLoader.d.ts +12 -0
- package/lib/blocks/status/LatticeLoader.js +22 -0
- package/lib/blocks/status/TypingIndicator.d.ts +5 -0
- package/lib/blocks/status/TypingIndicator.js +5 -0
- package/lib/blocks/status/index.d.ts +2 -0
- package/lib/blocks/status/index.js +2 -0
- package/lib/blocks/tool/SandboxCard.d.ts +5 -0
- package/lib/blocks/tool/SandboxCard.js +10 -0
- package/lib/blocks/tool/ToolCard.d.ts +10 -0
- package/lib/blocks/tool/ToolCard.js +32 -0
- package/lib/blocks/tool/ToolGroup.d.ts +6 -0
- package/lib/blocks/tool/ToolGroup.js +23 -0
- package/lib/blocks/tool/exec-envelope.d.ts +7 -0
- package/lib/blocks/tool/exec-envelope.js +43 -0
- package/lib/blocks/tool/index.d.ts +4 -0
- package/lib/blocks/tool/index.js +4 -0
- package/lib/domain/ask.d.ts +14 -0
- package/lib/domain/ask.js +50 -0
- package/lib/domain/attachments.d.ts +16 -0
- package/lib/domain/attachments.js +126 -0
- package/lib/domain/attribution.d.ts +4 -0
- package/lib/domain/attribution.js +11 -0
- package/lib/domain/composer-palette.d.ts +15 -0
- package/lib/domain/composer-palette.js +131 -0
- package/lib/domain/index.d.ts +12 -0
- package/lib/domain/index.js +12 -0
- package/lib/domain/mention.d.ts +17 -0
- package/lib/domain/mention.js +95 -0
- package/lib/domain/message-blocks.d.ts +29 -0
- package/lib/domain/message-blocks.js +63 -0
- package/lib/domain/message-menu.d.ts +4 -0
- package/lib/domain/message-menu.js +46 -0
- package/lib/domain/queue.d.ts +23 -0
- package/lib/domain/queue.js +52 -0
- package/lib/domain/reducer.d.ts +137 -0
- package/lib/domain/reducer.js +442 -0
- package/lib/domain/selection-actions.d.ts +13 -0
- package/lib/domain/selection-actions.js +56 -0
- package/lib/domain/selection-gates.d.ts +6 -0
- package/lib/domain/selection-gates.js +36 -0
- package/lib/domain/slash-commands.d.ts +16 -0
- package/lib/domain/slash-commands.js +142 -0
- package/lib/hooks/index.d.ts +8 -0
- package/lib/hooks/index.js +8 -0
- package/lib/hooks/useComposerPalette.d.ts +35 -0
- package/lib/hooks/useComposerPalette.js +120 -0
- package/lib/hooks/useComposerSubmit.d.ts +31 -0
- package/lib/hooks/useComposerSubmit.js +135 -0
- package/lib/hooks/useInputHistory.d.ts +14 -0
- package/lib/hooks/useInputHistory.js +52 -0
- package/lib/hooks/usePendingAsk.d.ts +11 -0
- package/lib/hooks/usePendingAsk.js +33 -0
- package/lib/hooks/usePendingPermissions.d.ts +10 -0
- package/lib/hooks/usePendingPermissions.js +26 -0
- package/lib/hooks/useQueuedPrompts.d.ts +12 -0
- package/lib/hooks/useQueuedPrompts.js +91 -0
- package/lib/hooks/useSelectionPop.d.ts +13 -0
- package/lib/hooks/useSelectionPop.js +130 -0
- package/lib/hooks/useStreamBridge.d.ts +1 -0
- package/lib/hooks/useStreamBridge.js +23 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.js +10 -0
- package/lib/primitives/Badge.d.ts +7 -0
- package/lib/primitives/Badge.js +13 -0
- package/lib/primitives/Button.d.ts +9 -0
- package/lib/primitives/Button.js +21 -0
- package/lib/primitives/Collapsible.d.ts +12 -0
- package/lib/primitives/Collapsible.js +14 -0
- package/lib/primitives/Dialog.d.ts +10 -0
- package/lib/primitives/Dialog.js +19 -0
- package/lib/primitives/DropdownMenu.d.ts +28 -0
- package/lib/primitives/DropdownMenu.js +37 -0
- package/lib/primitives/Empty.d.ts +8 -0
- package/lib/primitives/Empty.js +6 -0
- package/lib/primitives/Input.d.ts +3 -0
- package/lib/primitives/Input.js +6 -0
- package/lib/primitives/Kbd.d.ts +2 -0
- package/lib/primitives/Kbd.js +5 -0
- package/lib/primitives/Popover.d.ts +15 -0
- package/lib/primitives/Popover.js +32 -0
- package/lib/primitives/ScrollArea.d.ts +5 -0
- package/lib/primitives/ScrollArea.js +5 -0
- package/lib/primitives/Separator.d.ts +5 -0
- package/lib/primitives/Separator.js +5 -0
- package/lib/primitives/Spinner.d.ts +5 -0
- package/lib/primitives/Spinner.js +5 -0
- package/lib/primitives/Textarea.d.ts +3 -0
- package/lib/primitives/Textarea.js +6 -0
- package/lib/primitives/Tooltip.d.ts +8 -0
- package/lib/primitives/Tooltip.js +7 -0
- package/lib/primitives/index.d.ts +14 -0
- package/lib/primitives/index.js +14 -0
- package/lib/reactbits/Motion.d.ts +24 -0
- package/lib/reactbits/Motion.js +52 -0
- package/lib/reactbits/index.d.ts +1 -0
- package/lib/reactbits/index.js +1 -0
- package/lib/stores/chat-store.d.ts +33 -0
- package/lib/stores/chat-store.js +93 -0
- package/lib/stores/display-store.d.ts +15 -0
- package/lib/stores/display-store.js +116 -0
- package/lib/stores/index.d.ts +3 -0
- package/lib/stores/index.js +3 -0
- package/lib/stores/theme-store.d.ts +8 -0
- package/lib/stores/theme-store.js +29 -0
- package/lib/tokens/index.d.ts +21 -0
- package/lib/tokens/index.js +66 -0
- package/lib/types/agent.d.ts +62 -0
- package/lib/types/agent.js +1 -0
- package/lib/types/callbacks.d.ts +55 -0
- package/lib/types/callbacks.js +1 -0
- package/lib/types/composer.d.ts +116 -0
- package/lib/types/composer.js +1 -0
- package/lib/types/conversation.d.ts +54 -0
- package/lib/types/conversation.js +1 -0
- package/lib/types/display.d.ts +25 -0
- package/lib/types/display.js +40 -0
- package/lib/types/index.d.ts +8 -0
- package/lib/types/index.js +8 -0
- package/lib/types/message.d.ts +100 -0
- package/lib/types/message.js +1 -0
- package/lib/types/selection.d.ts +60 -0
- package/lib/types/selection.js +1 -0
- package/lib/types/theme.d.ts +100 -0
- package/lib/types/theme.js +1 -0
- package/lib/utils/clipboard.d.ts +4 -0
- package/lib/utils/clipboard.js +41 -0
- package/lib/utils/cn.d.ts +2 -0
- package/lib/utils/cn.js +14 -0
- package/lib/utils/index.d.ts +3 -0
- package/lib/utils/index.js +3 -0
- package/lib/utils/storage.d.ts +11 -0
- package/lib/utils/storage.js +48 -0
- package/package.json +93 -4
- package/src/styles/blocks.css +2439 -0
- package/src/styles/index.css +3 -0
- package/src/tokens/theme.css +464 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Eraser, FileText, Languages, MessageSquarePlus, Play, Settings, Square, } from "lucide-react";
|
|
2
|
+
export const BUILT_IN_SLASH_COMMANDS = [
|
|
3
|
+
{
|
|
4
|
+
id: "stop",
|
|
5
|
+
label: "停止生成",
|
|
6
|
+
description: "停止当前在飞回合",
|
|
7
|
+
trigger: "/stop",
|
|
8
|
+
kind: "action",
|
|
9
|
+
icon: Square,
|
|
10
|
+
source: "builtin",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: "clear",
|
|
14
|
+
label: "清空输入",
|
|
15
|
+
description: "清空输入框草稿",
|
|
16
|
+
trigger: "/clear",
|
|
17
|
+
kind: "action",
|
|
18
|
+
icon: Eraser,
|
|
19
|
+
source: "builtin",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "new",
|
|
23
|
+
label: "新建会话",
|
|
24
|
+
description: "开启一段新对话",
|
|
25
|
+
trigger: "/new",
|
|
26
|
+
kind: "action",
|
|
27
|
+
icon: MessageSquarePlus,
|
|
28
|
+
source: "builtin",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "model",
|
|
32
|
+
label: "模型设置",
|
|
33
|
+
description: "打开资料面板切换模型",
|
|
34
|
+
trigger: "/model",
|
|
35
|
+
kind: "action",
|
|
36
|
+
icon: Settings,
|
|
37
|
+
source: "builtin",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "summarize",
|
|
41
|
+
label: "总结",
|
|
42
|
+
description: "总结下方内容,先给结论再列要点",
|
|
43
|
+
trigger: "/summary",
|
|
44
|
+
kind: "insert",
|
|
45
|
+
icon: FileText,
|
|
46
|
+
source: "builtin",
|
|
47
|
+
insert: "请总结以下内容,先给结论再列要点:\n",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "translate",
|
|
51
|
+
label: "翻译",
|
|
52
|
+
description: "翻译成中文并保留专有名词",
|
|
53
|
+
trigger: "/translate",
|
|
54
|
+
kind: "insert",
|
|
55
|
+
icon: Languages,
|
|
56
|
+
source: "builtin",
|
|
57
|
+
insert: "请把下面的内容翻译成中文,保留专有名词:\n",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "explain",
|
|
61
|
+
label: "解释",
|
|
62
|
+
description: "逐段解释并标注关键概念",
|
|
63
|
+
trigger: "/explain",
|
|
64
|
+
kind: "insert",
|
|
65
|
+
icon: Play,
|
|
66
|
+
source: "builtin",
|
|
67
|
+
insert: "请逐段解释下面的内容,并标注关键概念:\n",
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
export function filterSlashCommands(commands, query) {
|
|
71
|
+
const q = query.trim().toLowerCase();
|
|
72
|
+
if (q.length === 0)
|
|
73
|
+
return [...commands];
|
|
74
|
+
return commands.filter((c) => c.trigger.toLowerCase().includes(q) ||
|
|
75
|
+
c.id.toLowerCase().includes(q) ||
|
|
76
|
+
c.label.toLowerCase().includes(q) ||
|
|
77
|
+
c.description.toLowerCase().includes(q));
|
|
78
|
+
}
|
|
79
|
+
export function findSlashCommand(commands, id) {
|
|
80
|
+
return commands.find((c) => c.id === id);
|
|
81
|
+
}
|
|
82
|
+
export function mergeSlashCommands(builtin, ...extras) {
|
|
83
|
+
const seen = new Set();
|
|
84
|
+
const out = [];
|
|
85
|
+
for (const group of [builtin, ...extras]) {
|
|
86
|
+
for (const c of group) {
|
|
87
|
+
const key = `${c.source ?? "builtin"}:${c.id}`;
|
|
88
|
+
if (seen.has(key))
|
|
89
|
+
continue;
|
|
90
|
+
seen.add(key);
|
|
91
|
+
out.push(c);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
export function expandCommandTemplate(template, args) {
|
|
97
|
+
if (template.includes("$ARGUMENTS")) {
|
|
98
|
+
return template.replaceAll("$ARGUMENTS", args);
|
|
99
|
+
}
|
|
100
|
+
return args.length > 0 ? `${template}${args}` : template;
|
|
101
|
+
}
|
|
102
|
+
export function expandSlashDraft(commands, draft) {
|
|
103
|
+
const trimmed = draft.trimStart();
|
|
104
|
+
if (!trimmed.startsWith("/"))
|
|
105
|
+
return null;
|
|
106
|
+
const firstSpace = trimmed.search(/\s/);
|
|
107
|
+
const token = firstSpace === -1 ? trimmed : trimmed.slice(0, firstSpace);
|
|
108
|
+
const rest = firstSpace === -1 ? "" : trimmed.slice(firstSpace + 1).trim();
|
|
109
|
+
const cmd = commands.find((c) => c.trigger === token);
|
|
110
|
+
if (!cmd || cmd.kind !== "insert" || !cmd.insert)
|
|
111
|
+
return null;
|
|
112
|
+
return expandCommandTemplate(cmd.insert, rest);
|
|
113
|
+
}
|
|
114
|
+
export function planSlashCommandPick(params) {
|
|
115
|
+
const { value, tokenStart, caret, trigger, template } = params;
|
|
116
|
+
const leading = value.slice(0, tokenStart);
|
|
117
|
+
if (leading.trim().length === 0) {
|
|
118
|
+
return {
|
|
119
|
+
start: tokenStart,
|
|
120
|
+
end: caret,
|
|
121
|
+
insert: `${trigger} `,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const args = value.slice(caret);
|
|
125
|
+
return {
|
|
126
|
+
start: tokenStart,
|
|
127
|
+
end: value.length,
|
|
128
|
+
insert: expandCommandTemplate(template, args.trim()),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export function slashCommandAvailable(command, ctx) {
|
|
132
|
+
if (command.kind === "insert")
|
|
133
|
+
return Boolean(command.insert);
|
|
134
|
+
switch (command.id) {
|
|
135
|
+
case "stop":
|
|
136
|
+
return ctx.streaming;
|
|
137
|
+
case "clear":
|
|
138
|
+
return ctx.hasDraft;
|
|
139
|
+
default:
|
|
140
|
+
return ctx.wiredActions.includes(command.id);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { useInputHistory, pushInputHistory, nextHistoryIndex, INPUT_HISTORY_LIMIT, type InputHistoryApi, type HistoryDirection, } from "./useInputHistory";
|
|
2
|
+
export { useQueuedPrompts, decideSubmit, flushEdge, MAX_QUEUED_PROMPTS, QUEUED_FULL_NOTICE, type QueuedPromptsApi, } from "./useQueuedPrompts";
|
|
3
|
+
export { useComposerPalette, bumpPaletteItemUse, planPaletteSync, planEnterSkillLevel, planPaletteEscape, type ComposerPalette, } from "./useComposerPalette";
|
|
4
|
+
export { useComposerSubmit, planMentionPick, planSkillTokenPick, type ComposerSubmitApi, type ComposerSubmitWiring, } from "./useComposerSubmit";
|
|
5
|
+
export { useSelectionPop, selectionPopPosition, type SelectionPopState, } from "./useSelectionPop";
|
|
6
|
+
export { usePendingAsk, isBatchAsk, askPlaceholder, ASK_SKIP_SENTINEL, mergePendingAsk, dropPendingAsk, batchAnswersOf, type PendingAskApi, } from "./usePendingAsk";
|
|
7
|
+
export { usePendingPermissions, mergePendingById, type PendingPermissionsApi, } from "./usePendingPermissions";
|
|
8
|
+
export { useStreamBridge } from "./useStreamBridge";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { useInputHistory, pushInputHistory, nextHistoryIndex, INPUT_HISTORY_LIMIT, } from "./useInputHistory";
|
|
2
|
+
export { useQueuedPrompts, decideSubmit, flushEdge, MAX_QUEUED_PROMPTS, QUEUED_FULL_NOTICE, } from "./useQueuedPrompts";
|
|
3
|
+
export { useComposerPalette, bumpPaletteItemUse, planPaletteSync, planEnterSkillLevel, planPaletteEscape, } from "./useComposerPalette";
|
|
4
|
+
export { useComposerSubmit, planMentionPick, planSkillTokenPick, } from "./useComposerSubmit";
|
|
5
|
+
export { useSelectionPop, selectionPopPosition, } from "./useSelectionPop";
|
|
6
|
+
export { usePendingAsk, isBatchAsk, askPlaceholder, ASK_SKIP_SENTINEL, mergePendingAsk, dropPendingAsk, batchAnswersOf, } from "./usePendingAsk";
|
|
7
|
+
export { usePendingPermissions, mergePendingById, } from "./usePendingPermissions";
|
|
8
|
+
export { useStreamBridge } from "./useStreamBridge";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Agent } from "../types/agent";
|
|
2
|
+
import type { ComposerTrigger, PaletteFileEntry, PaletteItem, SlashCommand, TriggerToken } from "../types/composer";
|
|
3
|
+
interface PaletteState {
|
|
4
|
+
open: boolean;
|
|
5
|
+
token: TriggerToken | null;
|
|
6
|
+
index: number;
|
|
7
|
+
selectedAgent: Agent | null;
|
|
8
|
+
level1Token: TriggerToken | null;
|
|
9
|
+
}
|
|
10
|
+
export declare function planPaletteSync(prev: PaletteState, token: TriggerToken | null): PaletteState;
|
|
11
|
+
export declare function planEnterSkillLevel(agent: Agent, caret: number, level1Token: TriggerToken): PaletteState;
|
|
12
|
+
export declare function planPaletteEscape(prev: PaletteState): PaletteState;
|
|
13
|
+
export interface ComposerPalette {
|
|
14
|
+
open: boolean;
|
|
15
|
+
trigger: ComposerTrigger | null;
|
|
16
|
+
token: TriggerToken | null;
|
|
17
|
+
items: PaletteItem[];
|
|
18
|
+
index: number;
|
|
19
|
+
selectedAgent: Agent | null;
|
|
20
|
+
sync: (field: HTMLTextAreaElement, next: string) => void;
|
|
21
|
+
move: (delta: number) => void;
|
|
22
|
+
enterSkillLevel: (agent: Agent, caret: number) => void;
|
|
23
|
+
escape: () => void;
|
|
24
|
+
close: () => void;
|
|
25
|
+
}
|
|
26
|
+
export declare function bumpPaletteItemUse(item: PaletteItem): void;
|
|
27
|
+
export declare function useComposerPalette(params: {
|
|
28
|
+
members: Agent[];
|
|
29
|
+
streaming: boolean;
|
|
30
|
+
hasDraft: boolean;
|
|
31
|
+
wiredActions: readonly string[];
|
|
32
|
+
extraCommands?: readonly SlashCommand[];
|
|
33
|
+
files?: readonly PaletteFileEntry[];
|
|
34
|
+
}): ComposerPalette;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { buildAgentSkillItems, buildPaletteItems, detectTriggerToken, moveIndex, paletteRecencyId, rankByRecency, } from "../domain/composer-palette";
|
|
3
|
+
import { BUILT_IN_SLASH_COMMANDS, mergeSlashCommands, slashCommandAvailable, } from "../domain/slash-commands";
|
|
4
|
+
import { LS_KEYS, loadJSON, saveJSON } from "../utils/storage";
|
|
5
|
+
const CLOSED = {
|
|
6
|
+
open: false,
|
|
7
|
+
token: null,
|
|
8
|
+
index: 0,
|
|
9
|
+
selectedAgent: null,
|
|
10
|
+
level1Token: null,
|
|
11
|
+
};
|
|
12
|
+
export function planPaletteSync(prev, token) {
|
|
13
|
+
if (prev.selectedAgent && token && token.trigger === "/") {
|
|
14
|
+
return { ...prev, token, index: 0 };
|
|
15
|
+
}
|
|
16
|
+
if (!token)
|
|
17
|
+
return prev.open ? CLOSED : prev;
|
|
18
|
+
return {
|
|
19
|
+
open: true,
|
|
20
|
+
token,
|
|
21
|
+
index: 0,
|
|
22
|
+
selectedAgent: null,
|
|
23
|
+
level1Token: null,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function planEnterSkillLevel(agent, caret, level1Token) {
|
|
27
|
+
return {
|
|
28
|
+
open: true,
|
|
29
|
+
token: { trigger: "/", start: caret, query: "" },
|
|
30
|
+
index: 0,
|
|
31
|
+
selectedAgent: agent,
|
|
32
|
+
level1Token,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function planPaletteEscape(prev) {
|
|
36
|
+
if (prev.selectedAgent && prev.level1Token) {
|
|
37
|
+
return {
|
|
38
|
+
open: true,
|
|
39
|
+
token: prev.level1Token,
|
|
40
|
+
index: 0,
|
|
41
|
+
selectedAgent: null,
|
|
42
|
+
level1Token: null,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return CLOSED;
|
|
46
|
+
}
|
|
47
|
+
function getPaletteRecencyIds() {
|
|
48
|
+
return loadJSON(LS_KEYS.paletteRecency, []);
|
|
49
|
+
}
|
|
50
|
+
export function bumpPaletteItemUse(item) {
|
|
51
|
+
try {
|
|
52
|
+
const id = paletteRecencyId(item);
|
|
53
|
+
const rest = getPaletteRecencyIds().filter((x) => x !== id);
|
|
54
|
+
saveJSON(LS_KEYS.paletteRecency, [id, ...rest].slice(0, 50));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function useComposerPalette(params) {
|
|
61
|
+
const { members, streaming, hasDraft, wiredActions, extraCommands, files } = params;
|
|
62
|
+
const [state, setState] = useState(CLOSED);
|
|
63
|
+
const commands = useMemo(() => {
|
|
64
|
+
const merged = mergeSlashCommands(BUILT_IN_SLASH_COMMANDS, extraCommands ?? []);
|
|
65
|
+
return merged.filter((c) => slashCommandAvailable(c, { streaming, hasDraft, wiredActions }));
|
|
66
|
+
}, [extraCommands, streaming, hasDraft, wiredActions]);
|
|
67
|
+
const skillCommands = useMemo(() => commands.filter((c) => c.source === "skill"), [commands]);
|
|
68
|
+
const items = useMemo(() => {
|
|
69
|
+
if (!state.token)
|
|
70
|
+
return [];
|
|
71
|
+
if (state.selectedAgent) {
|
|
72
|
+
return buildAgentSkillItems(state.selectedAgent, skillCommands, state.token.query);
|
|
73
|
+
}
|
|
74
|
+
const raw = buildPaletteItems(state.token, {
|
|
75
|
+
members,
|
|
76
|
+
commands,
|
|
77
|
+
files,
|
|
78
|
+
});
|
|
79
|
+
return rankByRecency(raw, getPaletteRecencyIds());
|
|
80
|
+
}, [state.token, state.selectedAgent, members, commands, skillCommands, files]);
|
|
81
|
+
const index = items.length === 0 ? 0 : Math.min(state.index, items.length - 1);
|
|
82
|
+
const open = state.open && items.length > 0;
|
|
83
|
+
const stateRef = useRef(state);
|
|
84
|
+
stateRef.current = state;
|
|
85
|
+
const sync = useCallback((field, _next) => {
|
|
86
|
+
const caret = field.selectionStart ?? 0;
|
|
87
|
+
const token = detectTriggerToken(field.value, caret);
|
|
88
|
+
setState((prev) => planPaletteSync(prev, token));
|
|
89
|
+
}, []);
|
|
90
|
+
const move = useCallback((delta) => {
|
|
91
|
+
setState((prev) => {
|
|
92
|
+
const length = items.length;
|
|
93
|
+
return { ...prev, index: moveIndex(prev.index, delta, length) };
|
|
94
|
+
});
|
|
95
|
+
}, [items.length]);
|
|
96
|
+
const enterSkillLevel = useCallback((agent, caret) => {
|
|
97
|
+
setState((prev) => {
|
|
98
|
+
if (!prev.token)
|
|
99
|
+
return prev;
|
|
100
|
+
return planEnterSkillLevel(agent, caret, prev.token);
|
|
101
|
+
});
|
|
102
|
+
}, []);
|
|
103
|
+
const escape = useCallback(() => {
|
|
104
|
+
setState((prev) => planPaletteEscape(prev));
|
|
105
|
+
}, []);
|
|
106
|
+
const close = useCallback(() => setState(CLOSED), []);
|
|
107
|
+
return {
|
|
108
|
+
open,
|
|
109
|
+
trigger: state.token?.trigger ?? null,
|
|
110
|
+
token: state.token,
|
|
111
|
+
items,
|
|
112
|
+
index,
|
|
113
|
+
selectedAgent: state.selectedAgent,
|
|
114
|
+
sync,
|
|
115
|
+
move,
|
|
116
|
+
enterSkillLevel,
|
|
117
|
+
escape,
|
|
118
|
+
close,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
import type { Attachment } from "../types/message";
|
|
3
|
+
import type { ComposerSubmitApi, QueuedPromptsApi, SlashCommand } from "../types/composer";
|
|
4
|
+
import type { ComposerPalette } from "./useComposerPalette";
|
|
5
|
+
export type { ComposerSubmitApi };
|
|
6
|
+
export interface ComposerSubmitWiring {
|
|
7
|
+
value: string;
|
|
8
|
+
onChange: (v: string) => void;
|
|
9
|
+
fieldRef: RefObject<HTMLTextAreaElement | null>;
|
|
10
|
+
palette: ComposerPalette;
|
|
11
|
+
streaming: boolean;
|
|
12
|
+
queued: QueuedPromptsApi;
|
|
13
|
+
sendWithAttachments?: (text: string, deliver: (text: string, attachments?: Attachment[]) => void) => void;
|
|
14
|
+
onSend: (text: string, attachments?: Attachment[]) => void;
|
|
15
|
+
onStop?: () => void;
|
|
16
|
+
onNotice?: (message: string) => void;
|
|
17
|
+
onNewConversation?: () => void;
|
|
18
|
+
onOpenProfile?: () => void;
|
|
19
|
+
slashExpand?: (draft: string) => string | null;
|
|
20
|
+
slashExtra?: readonly SlashCommand[];
|
|
21
|
+
skillCommands?: readonly SlashCommand[];
|
|
22
|
+
}
|
|
23
|
+
export declare function planMentionPick(value: string, tokenStart: number, caret: number, agentName: string): {
|
|
24
|
+
value: string;
|
|
25
|
+
caret: number;
|
|
26
|
+
};
|
|
27
|
+
export declare function planSkillTokenPick(value: string, tokenStart: number, caret: number, trigger: string): {
|
|
28
|
+
value: string;
|
|
29
|
+
caret: number;
|
|
30
|
+
};
|
|
31
|
+
export declare function useComposerSubmit(wiring: ComposerSubmitWiring): ComposerSubmitApi;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { bumpPaletteItemUse } from "./useComposerPalette";
|
|
3
|
+
import { buildAgentSkillItems } from "../domain/composer-palette";
|
|
4
|
+
import { planSlashCommandPick } from "../domain/slash-commands";
|
|
5
|
+
function insertAt(value, start, end, insert) {
|
|
6
|
+
return {
|
|
7
|
+
value: value.slice(0, start) + insert + value.slice(end),
|
|
8
|
+
caret: start + insert.length,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function planMentionPick(value, tokenStart, caret, agentName) {
|
|
12
|
+
return insertAt(value, tokenStart, caret, `@${agentName} `);
|
|
13
|
+
}
|
|
14
|
+
export function planSkillTokenPick(value, tokenStart, caret, trigger) {
|
|
15
|
+
return insertAt(value, tokenStart, caret, `/${trigger} `);
|
|
16
|
+
}
|
|
17
|
+
export function useComposerSubmit(wiring) {
|
|
18
|
+
const { value, onChange, fieldRef, palette, streaming, queued, sendWithAttachments, onSend, onNotice, onNewConversation, onOpenProfile, slashExpand, } = wiring;
|
|
19
|
+
const setCaret = (pos) => {
|
|
20
|
+
requestAnimationFrame(() => {
|
|
21
|
+
const el = fieldRef.current;
|
|
22
|
+
if (!el)
|
|
23
|
+
return;
|
|
24
|
+
el.focus();
|
|
25
|
+
el.setSelectionRange(pos, pos);
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const apply = (next) => {
|
|
29
|
+
onChange(next.value);
|
|
30
|
+
setCaret(next.caret);
|
|
31
|
+
palette.close();
|
|
32
|
+
};
|
|
33
|
+
const deliver = (text, attachments) => {
|
|
34
|
+
onSend(text, attachments);
|
|
35
|
+
palette.close();
|
|
36
|
+
};
|
|
37
|
+
const pick = useCallback((item) => {
|
|
38
|
+
const token = palette.token;
|
|
39
|
+
bumpPaletteItemUse(item);
|
|
40
|
+
const caret = fieldRef.current?.selectionStart ?? value.length;
|
|
41
|
+
if (item.kind === "command") {
|
|
42
|
+
const cmd = item.command;
|
|
43
|
+
if (palette.selectedAgent) {
|
|
44
|
+
apply(planSkillTokenPick(value, token?.start ?? caret, caret, cmd.trigger.replace(/^\//, "")));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (cmd.kind === "action") {
|
|
48
|
+
if (token) {
|
|
49
|
+
onChange(value.slice(0, token.start) + value.slice(caret));
|
|
50
|
+
}
|
|
51
|
+
palette.close();
|
|
52
|
+
if (cmd.id === "stop")
|
|
53
|
+
wiring.onStop?.();
|
|
54
|
+
if (cmd.id === "clear")
|
|
55
|
+
onChange("");
|
|
56
|
+
if (cmd.id === "new")
|
|
57
|
+
onNewConversation?.();
|
|
58
|
+
if (cmd.id === "model")
|
|
59
|
+
onOpenProfile?.();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (token) {
|
|
63
|
+
const plan = planSlashCommandPick({
|
|
64
|
+
value,
|
|
65
|
+
tokenStart: token.start,
|
|
66
|
+
caret,
|
|
67
|
+
trigger: cmd.trigger,
|
|
68
|
+
template: cmd.insert ?? "",
|
|
69
|
+
});
|
|
70
|
+
apply(insertAt(value, plan.start, plan.end, plan.insert));
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (item.kind === "file") {
|
|
75
|
+
if (token)
|
|
76
|
+
apply(insertAt(value, token.start, caret, `${item.path} `));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (item.kind === "agent") {
|
|
80
|
+
if (!token)
|
|
81
|
+
return;
|
|
82
|
+
const next = planMentionPick(value, token.start, caret, item.agent.name);
|
|
83
|
+
onChange(next.value);
|
|
84
|
+
setCaret(next.caret);
|
|
85
|
+
const skills = buildAgentSkillItems(item.agent, wiring.skillCommands ?? [], "");
|
|
86
|
+
if (skills.length > 0) {
|
|
87
|
+
palette.enterSkillLevel(item.agent, next.caret);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
palette.close();
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (token)
|
|
95
|
+
apply(insertAt(value, token.start, caret, "@所有人 "));
|
|
96
|
+
}, [value, palette, fieldRef, onChange, wiring.skillCommands]);
|
|
97
|
+
const submit = useCallback(() => {
|
|
98
|
+
const expanded = slashExpand?.(value) ?? value;
|
|
99
|
+
if (sendWithAttachments) {
|
|
100
|
+
sendWithAttachments(expanded, deliver);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
deliver(expanded);
|
|
104
|
+
}
|
|
105
|
+
onChange("");
|
|
106
|
+
palette.close();
|
|
107
|
+
}, [value, slashExpand, sendWithAttachments, onChange, palette]);
|
|
108
|
+
const submitOrQueue = useCallback(() => {
|
|
109
|
+
const text = value;
|
|
110
|
+
if (text.trim().length === 0 && !sendWithAttachments)
|
|
111
|
+
return;
|
|
112
|
+
if (!streaming) {
|
|
113
|
+
submit();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const outcome = queued.enqueue(slashExpand?.(text) ?? text);
|
|
117
|
+
if (outcome === "queued" || outcome === "not-streaming") {
|
|
118
|
+
onChange("");
|
|
119
|
+
palette.close();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
onNotice?.("待发队列已满,请先处理");
|
|
123
|
+
}, [
|
|
124
|
+
value,
|
|
125
|
+
streaming,
|
|
126
|
+
submit,
|
|
127
|
+
queued,
|
|
128
|
+
slashExpand,
|
|
129
|
+
onChange,
|
|
130
|
+
palette,
|
|
131
|
+
onNotice,
|
|
132
|
+
sendWithAttachments,
|
|
133
|
+
]);
|
|
134
|
+
return { pick, submitOrQueue };
|
|
135
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const INPUT_HISTORY_LIMIT = 20;
|
|
2
|
+
export type HistoryDirection = "up" | "down";
|
|
3
|
+
export declare function pushInputHistory(history: readonly string[], entry: string): string[];
|
|
4
|
+
export declare function nextHistoryIndex(current: number, direction: HistoryDirection, length: number): number;
|
|
5
|
+
export interface InputHistoryApi {
|
|
6
|
+
items: readonly string[];
|
|
7
|
+
handleArrowUp: () => boolean;
|
|
8
|
+
handleArrowDown: () => boolean;
|
|
9
|
+
save: (entry: string) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function useInputHistory({ value, onChange, }: {
|
|
12
|
+
value: string;
|
|
13
|
+
onChange: (v: string) => void;
|
|
14
|
+
}): InputHistoryApi;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { LS_KEYS, loadJSON, saveJSON } from "../utils/storage";
|
|
3
|
+
export const INPUT_HISTORY_LIMIT = 20;
|
|
4
|
+
export function pushInputHistory(history, entry) {
|
|
5
|
+
const trimmed = entry.trim();
|
|
6
|
+
if (trimmed.length === 0)
|
|
7
|
+
return [...history];
|
|
8
|
+
const rest = history.filter((h) => h !== trimmed);
|
|
9
|
+
return [trimmed, ...rest].slice(0, INPUT_HISTORY_LIMIT);
|
|
10
|
+
}
|
|
11
|
+
export function nextHistoryIndex(current, direction, length) {
|
|
12
|
+
if (length === 0)
|
|
13
|
+
return -1;
|
|
14
|
+
if (direction === "up") {
|
|
15
|
+
return current < 0 ? 0 : Math.min(current, length - 1);
|
|
16
|
+
}
|
|
17
|
+
return current <= 0 ? -1 : current - 1;
|
|
18
|
+
}
|
|
19
|
+
export function useInputHistory({ value, onChange, }) {
|
|
20
|
+
const [history, setHistory] = useState(() => loadJSON(LS_KEYS.inputHistory, []));
|
|
21
|
+
const indexRef = useRef(-1);
|
|
22
|
+
const draftRef = useRef("");
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
saveJSON(LS_KEYS.inputHistory, history);
|
|
25
|
+
}, [history]);
|
|
26
|
+
const handleArrowUp = () => {
|
|
27
|
+
if (indexRef.current < 0 && value.trim().length > 0)
|
|
28
|
+
return false;
|
|
29
|
+
const next = nextHistoryIndex(indexRef.current, "up", history.length);
|
|
30
|
+
if (next < 0)
|
|
31
|
+
return true;
|
|
32
|
+
if (indexRef.current < 0)
|
|
33
|
+
draftRef.current = value;
|
|
34
|
+
indexRef.current = next;
|
|
35
|
+
onChange(history[next]);
|
|
36
|
+
return true;
|
|
37
|
+
};
|
|
38
|
+
const handleArrowDown = () => {
|
|
39
|
+
if (indexRef.current < 0)
|
|
40
|
+
return false;
|
|
41
|
+
const next = nextHistoryIndex(indexRef.current, "down", history.length);
|
|
42
|
+
indexRef.current = next;
|
|
43
|
+
onChange(next < 0 ? draftRef.current : history[next]);
|
|
44
|
+
return true;
|
|
45
|
+
};
|
|
46
|
+
const save = (entry) => {
|
|
47
|
+
setHistory((prev) => pushInputHistory(prev, entry));
|
|
48
|
+
indexRef.current = -1;
|
|
49
|
+
draftRef.current = "";
|
|
50
|
+
};
|
|
51
|
+
return { items: history, handleArrowUp, handleArrowDown, save };
|
|
52
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PendingAsk } from "../types/conversation";
|
|
2
|
+
export { ASK_SKIP_SENTINEL, isBatchAsk, askPlaceholder, mergePendingAsk, dropPendingAsk, firstPendingAsk, clampAskIndex, stashAnswer, batchAnswersOf, } from "../domain/ask";
|
|
3
|
+
export interface PendingAskApi {
|
|
4
|
+
pending: PendingAsk | null;
|
|
5
|
+
queued: number;
|
|
6
|
+
push: (ask: PendingAsk) => void;
|
|
7
|
+
clearTurn: (turnId: string | null) => void;
|
|
8
|
+
respond: (callId: string, answer: string) => void;
|
|
9
|
+
skip: (callId: string) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function usePendingAsk(): PendingAskApi;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { dropPendingAsk, firstPendingAsk, mergePendingAsk, ASK_SKIP_SENTINEL, } from "../domain/ask";
|
|
3
|
+
import { getStreamBridge } from "../adapters/stream-bridge";
|
|
4
|
+
export { ASK_SKIP_SENTINEL, isBatchAsk, askPlaceholder, mergePendingAsk, dropPendingAsk, firstPendingAsk, clampAskIndex, stashAnswer, batchAnswersOf, } from "../domain/ask";
|
|
5
|
+
export function usePendingAsk() {
|
|
6
|
+
const [queue, setQueue] = useState([]);
|
|
7
|
+
const push = useCallback((ask) => {
|
|
8
|
+
setQueue((prev) => mergePendingAsk(prev, ask));
|
|
9
|
+
}, []);
|
|
10
|
+
const clearTurn = useCallback((turnId) => {
|
|
11
|
+
setQueue((prev) => turnId === null ? [] : prev.filter((a) => a.turnId !== turnId));
|
|
12
|
+
}, []);
|
|
13
|
+
const respond = useCallback((callId, answer) => {
|
|
14
|
+
setQueue((prev) => {
|
|
15
|
+
const ask = prev.find((a) => a.callId === callId);
|
|
16
|
+
if (ask) {
|
|
17
|
+
void getStreamBridge()?.respondAsk?.(ask.turnId, ask.callId, answer);
|
|
18
|
+
}
|
|
19
|
+
return dropPendingAsk(prev, callId);
|
|
20
|
+
});
|
|
21
|
+
}, []);
|
|
22
|
+
const skip = useCallback((callId) => {
|
|
23
|
+
respond(callId, ASK_SKIP_SENTINEL);
|
|
24
|
+
}, [respond]);
|
|
25
|
+
return useMemo(() => ({
|
|
26
|
+
pending: firstPendingAsk(queue),
|
|
27
|
+
queued: Math.max(0, queue.length - 1),
|
|
28
|
+
push,
|
|
29
|
+
clearTurn,
|
|
30
|
+
respond,
|
|
31
|
+
skip,
|
|
32
|
+
}), [queue, push, clearTurn, respond, skip]);
|
|
33
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PendingPermission } from "../types/conversation";
|
|
2
|
+
export declare function mergePendingById(prev: PendingPermission[], incoming: PendingPermission[]): PendingPermission[];
|
|
3
|
+
export interface PendingPermissionsApi {
|
|
4
|
+
visible: PendingPermission[];
|
|
5
|
+
push: (permission: PendingPermission) => void;
|
|
6
|
+
decide: (permissionId: string) => void;
|
|
7
|
+
respond: (permissionId: string, approved: boolean, remembered: boolean) => void;
|
|
8
|
+
setActiveRequestIds: (ids: ReadonlySet<string>) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function usePendingPermissions(): PendingPermissionsApi;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { getStreamBridge } from "../adapters/stream-bridge";
|
|
3
|
+
export function mergePendingById(prev, incoming) {
|
|
4
|
+
const ids = new Set(prev.map((p) => p.permissionId));
|
|
5
|
+
return [...prev, ...incoming.filter((p) => !ids.has(p.permissionId))];
|
|
6
|
+
}
|
|
7
|
+
export function usePendingPermissions() {
|
|
8
|
+
const [all, setAll] = useState([]);
|
|
9
|
+
const [activeRequestIds, setActiveRequestIds] = useState(new Set());
|
|
10
|
+
const push = useCallback((permission) => {
|
|
11
|
+
setAll((prev) => mergePendingById(prev, [permission]));
|
|
12
|
+
}, []);
|
|
13
|
+
const decide = useCallback((permissionId) => {
|
|
14
|
+
setAll((prev) => prev.filter((p) => p.permissionId !== permissionId));
|
|
15
|
+
}, []);
|
|
16
|
+
const respond = useCallback((permissionId, approved, remembered) => {
|
|
17
|
+
setAll((prev) => {
|
|
18
|
+
const found = prev.find((p) => p.permissionId === permissionId);
|
|
19
|
+
void getStreamBridge()?.respondPermission?.(permissionId, approved, remembered);
|
|
20
|
+
void found;
|
|
21
|
+
return prev.filter((p) => p.permissionId !== permissionId);
|
|
22
|
+
});
|
|
23
|
+
}, []);
|
|
24
|
+
const visible = useMemo(() => all.filter((p) => !p.requestId || activeRequestIds.has(p.requestId)), [all, activeRequestIds]);
|
|
25
|
+
return { visible, push, decide, respond, setActiveRequestIds };
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Attachment } from "../types/message";
|
|
2
|
+
import type { ConversationStatus } from "../types/conversation";
|
|
3
|
+
import type { QueuedPromptsApi } from "../types/composer";
|
|
4
|
+
export type { QueuedPromptsApi };
|
|
5
|
+
export { decideSubmit, flushEdge, enqueuePrompt, removePrompt, movePrompt, takeHeadPrompt, prependPrompt, nextQueuedId, MAX_QUEUED_PROMPTS, QUEUED_FULL_NOTICE, } from "../domain/queue";
|
|
6
|
+
export declare function useQueuedPrompts(params: {
|
|
7
|
+
conversationId: string;
|
|
8
|
+
status: ConversationStatus;
|
|
9
|
+
onSend: (text: string, attachments?: Attachment[]) => void;
|
|
10
|
+
onStop?: () => void;
|
|
11
|
+
onNotice?: (message: string) => void;
|
|
12
|
+
}): QueuedPromptsApi;
|