@steerable/agent-ui 0.2.3 → 0.2.4
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/dist/cards/ActionSegmentCard.d.ts +18 -0
- package/dist/cards/ActionSegmentCard.d.ts.map +1 -0
- package/dist/cards/ActionSegmentCard.js +26 -0
- package/dist/cards/ActionSegmentCard.js.map +1 -0
- package/dist/cards/AnalysisDocumentCard.d.ts +33 -0
- package/dist/cards/AnalysisDocumentCard.d.ts.map +1 -0
- package/dist/cards/AnalysisDocumentCard.js +11 -0
- package/dist/cards/AnalysisDocumentCard.js.map +1 -0
- package/dist/cards/AskUserQuestionsCard.d.ts +43 -0
- package/dist/cards/AskUserQuestionsCard.d.ts.map +1 -0
- package/dist/cards/AskUserQuestionsCard.js +171 -0
- package/dist/cards/AskUserQuestionsCard.js.map +1 -0
- package/dist/cards/CoverageReportCard.d.ts +41 -0
- package/dist/cards/CoverageReportCard.d.ts.map +1 -0
- package/dist/cards/CoverageReportCard.js +55 -0
- package/dist/cards/CoverageReportCard.js.map +1 -0
- package/dist/cards/OrchestrationPlanCard.d.ts +60 -0
- package/dist/cards/OrchestrationPlanCard.d.ts.map +1 -0
- package/dist/cards/OrchestrationPlanCard.js +103 -0
- package/dist/cards/OrchestrationPlanCard.js.map +1 -0
- package/dist/cards/PlanSelectorCard.d.ts +40 -0
- package/dist/cards/PlanSelectorCard.d.ts.map +1 -0
- package/dist/cards/PlanSelectorCard.js +106 -0
- package/dist/cards/PlanSelectorCard.js.map +1 -0
- package/dist/cards/PlanStepsCard.d.ts +18 -0
- package/dist/cards/PlanStepsCard.d.ts.map +1 -0
- package/dist/cards/PlanStepsCard.js +25 -0
- package/dist/cards/PlanStepsCard.js.map +1 -0
- package/dist/cards/QuizCard.d.ts +44 -0
- package/dist/cards/QuizCard.d.ts.map +1 -0
- package/dist/cards/QuizCard.js +113 -0
- package/dist/cards/QuizCard.js.map +1 -0
- package/dist/cards/ResearchPlanCard.d.ts +23 -0
- package/dist/cards/ResearchPlanCard.d.ts.map +1 -0
- package/dist/cards/ResearchPlanCard.js +50 -0
- package/dist/cards/ResearchPlanCard.js.map +1 -0
- package/dist/cards/SearchSourcesCard.d.ts +29 -0
- package/dist/cards/SearchSourcesCard.d.ts.map +1 -0
- package/dist/cards/SearchSourcesCard.js +83 -0
- package/dist/cards/SearchSourcesCard.js.map +1 -0
- package/dist/cards/SuggestedRepliesCard.d.ts +18 -0
- package/dist/cards/SuggestedRepliesCard.d.ts.map +1 -0
- package/dist/cards/SuggestedRepliesCard.js +14 -0
- package/dist/cards/SuggestedRepliesCard.js.map +1 -0
- package/dist/cards/SummaryMessageCard.d.ts +40 -0
- package/dist/cards/SummaryMessageCard.d.ts.map +1 -0
- package/dist/cards/SummaryMessageCard.js +56 -0
- package/dist/cards/SummaryMessageCard.js.map +1 -0
- package/dist/cards/ThinkingProcessCard.d.ts +46 -0
- package/dist/cards/ThinkingProcessCard.d.ts.map +1 -0
- package/dist/cards/ThinkingProcessCard.js +79 -0
- package/dist/cards/ThinkingProcessCard.js.map +1 -0
- package/dist/cards/ToolExecutionCard.d.ts +21 -0
- package/dist/cards/ToolExecutionCard.d.ts.map +1 -0
- package/dist/cards/ToolExecutionCard.js +65 -0
- package/dist/cards/ToolExecutionCard.js.map +1 -0
- package/dist/cards/icons.d.ts +38 -0
- package/dist/cards/icons.d.ts.map +1 -0
- package/dist/cards/icons.js +87 -0
- package/dist/cards/icons.js.map +1 -0
- package/dist/cards/index.d.ts +38 -0
- package/dist/cards/index.d.ts.map +1 -0
- package/dist/cards/index.js +24 -0
- package/dist/cards/index.js.map +1 -0
- package/dist/cards/types.d.ts +24 -0
- package/dist/cards/types.d.ts.map +1 -0
- package/dist/cards/types.js +7 -0
- package/dist/cards/types.js.map +1 -0
- package/dist/components/AgentSelector.d.ts +23 -0
- package/dist/components/AgentSelector.d.ts.map +1 -0
- package/dist/components/AgentSelector.js +63 -0
- package/dist/components/AgentSelector.js.map +1 -0
- package/dist/components/AgentSelector.stories.d.ts +12 -0
- package/dist/components/AgentSelector.stories.d.ts.map +1 -0
- package/dist/components/AgentSelector.stories.js +95 -0
- package/dist/components/AgentSelector.stories.js.map +1 -0
- package/dist/components/ChatPanel.d.ts +138 -21
- package/dist/components/ChatPanel.d.ts.map +1 -1
- package/dist/components/ChatPanel.js +181 -26
- package/dist/components/ChatPanel.js.map +1 -1
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/state/ChatSessionProvider.d.ts +59 -0
- package/dist/state/ChatSessionProvider.d.ts.map +1 -0
- package/dist/state/ChatSessionProvider.js +71 -0
- package/dist/state/ChatSessionProvider.js.map +1 -0
- package/dist/state/MockChatStreamTransport.d.ts +54 -0
- package/dist/state/MockChatStreamTransport.d.ts.map +1 -0
- package/dist/state/MockChatStreamTransport.js +86 -0
- package/dist/state/MockChatStreamTransport.js.map +1 -0
- package/dist/state/MockChatStreamTransport.stories.d.ts +13 -0
- package/dist/state/MockChatStreamTransport.stories.d.ts.map +1 -0
- package/dist/state/MockChatStreamTransport.stories.js +60 -0
- package/dist/state/MockChatStreamTransport.stories.js.map +1 -0
- package/dist/state/bridgeLegacySSE.d.ts +42 -0
- package/dist/state/bridgeLegacySSE.d.ts.map +1 -0
- package/dist/state/bridgeLegacySSE.js +102 -0
- package/dist/state/bridgeLegacySSE.js.map +1 -0
- package/dist/state/index.d.ts +32 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +24 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/parseSSE.d.ts +50 -0
- package/dist/state/parseSSE.d.ts.map +1 -0
- package/dist/state/parseSSE.js +112 -0
- package/dist/state/parseSSE.js.map +1 -0
- package/dist/state/useChatComposer.d.ts +51 -0
- package/dist/state/useChatComposer.d.ts.map +1 -0
- package/dist/state/useChatComposer.js +82 -0
- package/dist/state/useChatComposer.js.map +1 -0
- package/dist/state/useChatList.d.ts +71 -0
- package/dist/state/useChatList.d.ts.map +1 -0
- package/dist/state/useChatList.js +158 -0
- package/dist/state/useChatList.js.map +1 -0
- package/dist/state/useChatSession.d.ts +29 -0
- package/dist/state/useChatSession.d.ts.map +1 -0
- package/dist/state/useChatSession.js +37 -0
- package/dist/state/useChatSession.js.map +1 -0
- package/dist/state/useScrollLock.d.ts +35 -0
- package/dist/state/useScrollLock.d.ts.map +1 -0
- package/dist/state/useScrollLock.js +74 -0
- package/dist/state/useScrollLock.js.map +1 -0
- package/dist/state/useToolCallStream.d.ts +38 -0
- package/dist/state/useToolCallStream.d.ts.map +1 -0
- package/dist/state/useToolCallStream.js +74 -0
- package/dist/state/useToolCallStream.js.map +1 -0
- package/package.json +10 -2
- package/src/cards/ActionSegmentCard.tsx +60 -0
- package/src/cards/AnalysisDocumentCard.tsx +81 -0
- package/src/cards/AskUserQuestionsCard.tsx +354 -0
- package/src/cards/CoverageReportCard.tsx +215 -0
- package/src/cards/OrchestrationPlanCard.tsx +328 -0
- package/src/cards/PlanSelectorCard.tsx +312 -0
- package/src/cards/PlanStepsCard.tsx +52 -0
- package/src/cards/QuizCard.tsx +309 -0
- package/src/cards/ResearchPlanCard.tsx +169 -0
- package/src/cards/SearchSourcesCard.tsx +244 -0
- package/src/cards/SuggestedRepliesCard.tsx +48 -0
- package/src/cards/SummaryMessageCard.tsx +145 -0
- package/src/cards/ThinkingProcessCard.tsx +155 -0
- package/src/cards/ToolExecutionCard.tsx +152 -0
- package/src/cards/cards.test.tsx +492 -0
- package/src/cards/icons.tsx +249 -0
- package/src/cards/index.ts +53 -0
- package/src/cards/types.ts +27 -0
- package/src/components/AgentSelector.stories.tsx +113 -0
- package/src/components/AgentSelector.test.tsx +70 -0
- package/src/components/AgentSelector.tsx +147 -0
- package/src/components/ChatPanel.test.tsx +130 -0
- package/src/components/ChatPanel.tsx +612 -138
- package/src/components/index.ts +13 -1
- package/src/index.ts +43 -0
- package/src/state/ChatSessionProvider.test.tsx +85 -0
- package/src/state/ChatSessionProvider.tsx +98 -0
- package/src/state/MockChatStreamTransport.stories.tsx +95 -0
- package/src/state/MockChatStreamTransport.test.ts +163 -0
- package/src/state/MockChatStreamTransport.ts +123 -0
- package/src/state/bridgeLegacySSE.test.ts +134 -0
- package/src/state/bridgeLegacySSE.ts +134 -0
- package/src/state/index.ts +82 -0
- package/src/state/parseSSE.test.ts +123 -0
- package/src/state/parseSSE.ts +123 -0
- package/src/state/useChatComposer.test.ts +145 -0
- package/src/state/useChatComposer.ts +117 -0
- package/src/state/useChatList.test.ts +135 -0
- package/src/state/useChatList.ts +217 -0
- package/src/state/useChatSession.test.ts +61 -0
- package/src/state/useChatSession.ts +64 -0
- package/src/state/useScrollLock.test.ts +99 -0
- package/src/state/useScrollLock.ts +105 -0
- package/src/state/useToolCallStream.test.ts +86 -0
- package/src/state/useToolCallStream.ts +110 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `parseSSE` — wire-level Server-Sent-Events parser.
|
|
3
|
+
*
|
|
4
|
+
* Feed it raw text chunks from any source (fetch ReadableStream chunks,
|
|
5
|
+
* Electron IPC `webContents.send` payloads, mocked fixtures, …). It buffers,
|
|
6
|
+
* splits on the `\r?\n\r?\n` frame terminator, and emits one
|
|
7
|
+
* `{ event?, data?, id?, retry? }` per complete frame. JSON parsing of `data`
|
|
8
|
+
* is intentionally out of scope; downstream layers like {@link bridgeLegacySSE}
|
|
9
|
+
* handle that.
|
|
10
|
+
*
|
|
11
|
+
* Replaces deeppath's `SSEParser` and deeppath-agent's `LocalBackendSseParser`
|
|
12
|
+
* with a single dependency-free implementation that compiles to both Node and
|
|
13
|
+
* the browser. Heartbeat-timeout watchdogs (deeppath had a 30 s gate) live on
|
|
14
|
+
* the transport, not here — this class is a pure parser.
|
|
15
|
+
*/
|
|
16
|
+
export class SSEParser {
|
|
17
|
+
options;
|
|
18
|
+
buffer = '';
|
|
19
|
+
completed = false;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
/** Feed one chunk. Safe to call with partial UTF-8 strings (we don't decode). */
|
|
24
|
+
feed(chunk) {
|
|
25
|
+
if (this.completed)
|
|
26
|
+
return;
|
|
27
|
+
this.buffer += chunk;
|
|
28
|
+
const parts = this.buffer.split(/\r?\n\r?\n/);
|
|
29
|
+
this.buffer = parts.pop() ?? '';
|
|
30
|
+
for (const frame of parts) {
|
|
31
|
+
if (frame.length > 0)
|
|
32
|
+
this.processFrame(frame);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Call when the source signals end-of-stream. Flushes any trailing frame. */
|
|
36
|
+
end() {
|
|
37
|
+
if (this.completed)
|
|
38
|
+
return;
|
|
39
|
+
if (this.buffer.trim().length > 0)
|
|
40
|
+
this.processFrame(this.buffer);
|
|
41
|
+
this.buffer = '';
|
|
42
|
+
}
|
|
43
|
+
/** Drop all state and stop emitting. */
|
|
44
|
+
cleanup() {
|
|
45
|
+
this.buffer = '';
|
|
46
|
+
this.completed = true;
|
|
47
|
+
}
|
|
48
|
+
processFrame(raw) {
|
|
49
|
+
const frame = {};
|
|
50
|
+
const dataLines = [];
|
|
51
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
52
|
+
if (line.length === 0)
|
|
53
|
+
continue;
|
|
54
|
+
// Comments per the SSE spec — leading `:` lines.
|
|
55
|
+
if (line.startsWith(':'))
|
|
56
|
+
continue;
|
|
57
|
+
const colon = line.indexOf(':');
|
|
58
|
+
const field = colon === -1 ? line : line.substring(0, colon);
|
|
59
|
+
let value = colon === -1 ? '' : line.substring(colon + 1);
|
|
60
|
+
if (value.startsWith(' '))
|
|
61
|
+
value = value.substring(1);
|
|
62
|
+
switch (field) {
|
|
63
|
+
case 'event':
|
|
64
|
+
frame.event = value;
|
|
65
|
+
break;
|
|
66
|
+
case 'data':
|
|
67
|
+
dataLines.push(value);
|
|
68
|
+
break;
|
|
69
|
+
case 'id':
|
|
70
|
+
frame.id = value;
|
|
71
|
+
break;
|
|
72
|
+
case 'retry': {
|
|
73
|
+
const ms = Number.parseInt(value, 10);
|
|
74
|
+
if (Number.isFinite(ms))
|
|
75
|
+
frame.retry = ms;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
default:
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (dataLines.length === 0 && frame.event === undefined && frame.id === undefined) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (dataLines.length > 0)
|
|
86
|
+
frame.data = dataLines.join('\n');
|
|
87
|
+
if (frame.data === '[DONE]' || frame.data === '"[DONE]"') {
|
|
88
|
+
this.completed = true;
|
|
89
|
+
this.options.onComplete?.();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
this.options.onFrame(frame);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
this.options.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Tolerant JSON parser for SSE `data:` payloads — returns the raw string when
|
|
102
|
+
* the body isn't JSON, which deeppath relies on for bare-text content deltas.
|
|
103
|
+
*/
|
|
104
|
+
export function parseSSEData(data) {
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(data);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return data;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=parseSSE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseSSE.js","sourceRoot":"","sources":["../../src/state/parseSSE.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useChatComposer` — input draft + send/cancel + keyboard glue.
|
|
3
|
+
*
|
|
4
|
+
* Lifted from deeppath-agent's `LocalChatPanel.tsx` (inline `inputValue` /
|
|
5
|
+
* `handleSubmit`) and deeppath's `ChatInput` (cancel handling). The hook is
|
|
6
|
+
* intentionally UI-free: it owns the draft string and the keyboard
|
|
7
|
+
* dispatcher; the visible textarea and send button live in the consuming
|
|
8
|
+
* component.
|
|
9
|
+
*
|
|
10
|
+
* Typical wiring:
|
|
11
|
+
*
|
|
12
|
+
* const composer = useChatComposer({ onSend: send, onCancel: cancel });
|
|
13
|
+
*
|
|
14
|
+
* <textarea
|
|
15
|
+
* value={composer.value}
|
|
16
|
+
* onChange={(e) => composer.setValue(e.target.value)}
|
|
17
|
+
* onKeyDown={composer.handleKeyDown}
|
|
18
|
+
* />
|
|
19
|
+
* <button onClick={composer.submit}>Send</button>
|
|
20
|
+
*/
|
|
21
|
+
export interface UseChatComposerOptions {
|
|
22
|
+
initialValue?: string;
|
|
23
|
+
/** Called with the trimmed value when the user submits a non-empty draft. */
|
|
24
|
+
onSend: (value: string) => void | Promise<void>;
|
|
25
|
+
/** Called when the user wants to cancel the in-flight stream. Optional. */
|
|
26
|
+
onCancel?: () => void;
|
|
27
|
+
/**
|
|
28
|
+
* If true (default), Enter submits and Shift+Enter inserts a newline. Set
|
|
29
|
+
* to false for "Enter inserts newline, Cmd/Ctrl+Enter submits" semantics.
|
|
30
|
+
*/
|
|
31
|
+
enterToSubmit?: boolean;
|
|
32
|
+
/** Whether the composer is currently disabled (no submit / no key handling). */
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Whether the parent reports an in-flight stream. When true, `submit()` is
|
|
36
|
+
* a no-op and Enter triggers `onCancel` instead. Mirrors the cloud product.
|
|
37
|
+
*/
|
|
38
|
+
isStreaming?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface UseChatComposerReturn {
|
|
41
|
+
value: string;
|
|
42
|
+
setValue: (value: string) => void;
|
|
43
|
+
submit: () => Promise<void> | void;
|
|
44
|
+
cancel: () => void;
|
|
45
|
+
clear: () => void;
|
|
46
|
+
handleKeyDown: (e: React.KeyboardEvent<HTMLElement>) => void;
|
|
47
|
+
/** True if `value.trim()` is non-empty and we're not disabled. */
|
|
48
|
+
canSubmit: boolean;
|
|
49
|
+
}
|
|
50
|
+
export declare function useChatComposer(options: UseChatComposerOptions): UseChatComposerReturn;
|
|
51
|
+
//# sourceMappingURL=useChatComposer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useChatComposer.d.ts","sourceRoot":"","sources":["../../src/state/useChatComposer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACnC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,aAAa,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAC7D,kEAAkE;IAClE,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CA8DtF"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useChatComposer` — input draft + send/cancel + keyboard glue.
|
|
3
|
+
*
|
|
4
|
+
* Lifted from deeppath-agent's `LocalChatPanel.tsx` (inline `inputValue` /
|
|
5
|
+
* `handleSubmit`) and deeppath's `ChatInput` (cancel handling). The hook is
|
|
6
|
+
* intentionally UI-free: it owns the draft string and the keyboard
|
|
7
|
+
* dispatcher; the visible textarea and send button live in the consuming
|
|
8
|
+
* component.
|
|
9
|
+
*
|
|
10
|
+
* Typical wiring:
|
|
11
|
+
*
|
|
12
|
+
* const composer = useChatComposer({ onSend: send, onCancel: cancel });
|
|
13
|
+
*
|
|
14
|
+
* <textarea
|
|
15
|
+
* value={composer.value}
|
|
16
|
+
* onChange={(e) => composer.setValue(e.target.value)}
|
|
17
|
+
* onKeyDown={composer.handleKeyDown}
|
|
18
|
+
* />
|
|
19
|
+
* <button onClick={composer.submit}>Send</button>
|
|
20
|
+
*/
|
|
21
|
+
import { useCallback, useRef, useState } from 'react';
|
|
22
|
+
export function useChatComposer(options) {
|
|
23
|
+
const [value, setValue] = useState(options.initialValue ?? '');
|
|
24
|
+
const optionsRef = useRef(options);
|
|
25
|
+
optionsRef.current = options;
|
|
26
|
+
const submit = useCallback(async () => {
|
|
27
|
+
const opts = optionsRef.current;
|
|
28
|
+
if (opts.disabled || opts.isStreaming)
|
|
29
|
+
return;
|
|
30
|
+
const trimmed = value.trim();
|
|
31
|
+
if (!trimmed)
|
|
32
|
+
return;
|
|
33
|
+
// Clear synchronously so the user can keep typing while the request flies.
|
|
34
|
+
setValue('');
|
|
35
|
+
await opts.onSend(trimmed);
|
|
36
|
+
}, [value]);
|
|
37
|
+
const cancel = useCallback(() => {
|
|
38
|
+
optionsRef.current.onCancel?.();
|
|
39
|
+
}, []);
|
|
40
|
+
const clear = useCallback(() => setValue(''), []);
|
|
41
|
+
const handleKeyDown = useCallback((e) => {
|
|
42
|
+
const opts = optionsRef.current;
|
|
43
|
+
if (opts.disabled)
|
|
44
|
+
return;
|
|
45
|
+
const enterSubmits = opts.enterToSubmit ?? true;
|
|
46
|
+
if (e.key === 'Enter') {
|
|
47
|
+
if (enterSubmits && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
if (opts.isStreaming) {
|
|
50
|
+
cancel();
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
void submit();
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!enterSubmits && (e.metaKey || e.ctrlKey)) {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
if (opts.isStreaming)
|
|
60
|
+
cancel();
|
|
61
|
+
else
|
|
62
|
+
void submit();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (e.key === 'Escape' && opts.isStreaming) {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
cancel();
|
|
69
|
+
}
|
|
70
|
+
}, [submit, cancel]);
|
|
71
|
+
const canSubmit = value.trim().length > 0 && !(options.disabled || options.isStreaming);
|
|
72
|
+
return {
|
|
73
|
+
value,
|
|
74
|
+
setValue,
|
|
75
|
+
submit,
|
|
76
|
+
cancel,
|
|
77
|
+
clear,
|
|
78
|
+
handleKeyDown,
|
|
79
|
+
canSubmit,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=useChatComposer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useChatComposer.js","sourceRoot":"","sources":["../../src/state/useChatComposer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAiCtD,MAAM,UAAU,eAAe,CAAC,OAA+B;IAC7D,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAE7B,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACpC,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC;QAChC,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO;QAC9C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,2EAA2E;QAC3E,QAAQ,CAAC,EAAE,CAAC,CAAC;QACb,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;QAC9B,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;IAClC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAElD,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,CAAmC,EAAE,EAAE;QACtC,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC;QAChC,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC;QAChD,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;YACtB,IAAI,YAAY,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;gBAC5D,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;oBACrB,MAAM,EAAE,CAAC;gBACX,CAAC;qBAAM,CAAC;oBACN,KAAK,MAAM,EAAE,CAAC;gBAChB,CAAC;gBACD,OAAO;YACT,CAAC;YACD,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC9C,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,IAAI,IAAI,CAAC,WAAW;oBAAE,MAAM,EAAE,CAAC;;oBAC1B,KAAK,MAAM,EAAE,CAAC;gBACnB,OAAO;YACT,CAAC;QACH,CAAC;QACD,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAC3C,CAAC,CAAC,cAAc,EAAE,CAAC;YACnB,MAAM,EAAE,CAAC;QACX,CAAC;IACH,CAAC,EACD,CAAC,MAAM,EAAE,MAAM,CAAC,CACjB,CAAC;IAEF,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IAExF,OAAO;QACL,KAAK;QACL,QAAQ;QACR,MAAM;QACN,MAAM;QACN,KAAK;QACL,aAAa;QACb,SAAS;KACV,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useChatList` — paginated chat-summary + agent-catalog state for the chat
|
|
3
|
+
* sidebar. Generic in the chat and agent shapes so cloud and local apps reuse
|
|
4
|
+
* the same hook (the cloud schema has extra fields on `Chat`, the local
|
|
5
|
+
* schema has fewer).
|
|
6
|
+
*
|
|
7
|
+
* Lifted from deeppath-agent's `useChatsAndAgents.ts` and deeppath's
|
|
8
|
+
* `useChatUI` chat-list subset. The hook owns:
|
|
9
|
+
* - paginated chat list with "load more" and refresh
|
|
10
|
+
* - the agent catalog (single page for now)
|
|
11
|
+
* - the agent picked for the next new chat
|
|
12
|
+
* - in-place title patching (used by `chat_title_updated` SSE events to
|
|
13
|
+
* avoid clobbering pagination state with a full refresh)
|
|
14
|
+
* - create / delete chat actions, with optimistic patching of the local
|
|
15
|
+
* list.
|
|
16
|
+
*
|
|
17
|
+
* The transport is provided by the consumer so the same hook serves Electron
|
|
18
|
+
* IPC, the cloud REST client, or any other backend.
|
|
19
|
+
*/
|
|
20
|
+
import { type Dispatch, type SetStateAction } from 'react';
|
|
21
|
+
export interface ChatListTransport<TChat, TAgent> {
|
|
22
|
+
listChats: (input: {
|
|
23
|
+
page: number;
|
|
24
|
+
pageSize: number;
|
|
25
|
+
}) => Promise<{
|
|
26
|
+
chats: TChat[];
|
|
27
|
+
hasMore: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
listAgents: () => Promise<TAgent[]>;
|
|
30
|
+
createChat?: (input: {
|
|
31
|
+
agentId?: string;
|
|
32
|
+
}) => Promise<TChat | string>;
|
|
33
|
+
deleteChat?: (chatId: string) => Promise<boolean>;
|
|
34
|
+
/** Tell the hook how to read a chat's id and title. */
|
|
35
|
+
getChatId: (chat: TChat) => string;
|
|
36
|
+
setChatTitle?: (chat: TChat, title: string) => TChat;
|
|
37
|
+
/** Tell the hook how to read an agent's id (for `selectedAgentId`). */
|
|
38
|
+
getAgentId: (agent: TAgent) => string;
|
|
39
|
+
}
|
|
40
|
+
export interface UseChatListOptions<TChat, TAgent> {
|
|
41
|
+
transport: ChatListTransport<TChat, TAgent>;
|
|
42
|
+
/** Defaults to 50. */
|
|
43
|
+
pageSize?: number;
|
|
44
|
+
/** Skip the mount-time fetch (e.g. SSR contexts). */
|
|
45
|
+
skipInitialLoad?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface UseChatListReturn<TChat, TAgent> {
|
|
48
|
+
chats: TChat[];
|
|
49
|
+
agents: TAgent[];
|
|
50
|
+
isLoading: boolean;
|
|
51
|
+
isLoadingMoreChats: boolean;
|
|
52
|
+
hasMoreChats: boolean;
|
|
53
|
+
error: Error | null;
|
|
54
|
+
selectedAgentId: string | null;
|
|
55
|
+
/**
|
|
56
|
+
* Accepts either a plain value or a React-style updater function so
|
|
57
|
+
* adapters can patch the selection without re-reading the current value.
|
|
58
|
+
*/
|
|
59
|
+
setSelectedAgentId: Dispatch<SetStateAction<string | null>>;
|
|
60
|
+
refreshChats: () => Promise<void>;
|
|
61
|
+
refreshAgents: () => Promise<void>;
|
|
62
|
+
loadMoreChats: () => Promise<void>;
|
|
63
|
+
createChat: (input?: {
|
|
64
|
+
agentId?: string;
|
|
65
|
+
}) => Promise<string | null>;
|
|
66
|
+
deleteChat: (chatId: string) => Promise<boolean>;
|
|
67
|
+
/** In-place title patcher — does not hit the server. */
|
|
68
|
+
patchChatTitle: (chatId: string, title: string) => void;
|
|
69
|
+
}
|
|
70
|
+
export declare function useChatList<TChat, TAgent>(options: UseChatListOptions<TChat, TAgent>): UseChatListReturn<TChat, TAgent>;
|
|
71
|
+
//# sourceMappingURL=useChatList.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useChatList.d.ts","sourceRoot":"","sources":["../../src/state/useChatList.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAML,KAAK,QAAQ,EACb,KAAK,cAAc,EACpB,MAAM,OAAO,CAAC;AAEf,MAAM,WAAW,iBAAiB,CAAC,KAAK,EAAE,MAAM;IAC9C,SAAS,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC;QAChE,KAAK,EAAE,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC,CAAC;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC;IACtE,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,uDAAuD;IACvD,SAAS,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,MAAM,CAAC;IACnC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC;IACrD,uEAAuE;IACvE,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC;AAED,MAAM,WAAW,kBAAkB,CAAC,KAAK,EAAE,MAAM;IAC/C,SAAS,EAAE,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC5C,sBAAsB;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB,CAAC,KAAK,EAAE,MAAM;IAC9C,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;OAGG;IACH,kBAAkB,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IAC5D,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACrE,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,wDAAwD;IACxD,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EACvC,OAAO,EAAE,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,GACzC,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CA2IlC"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useChatList` — paginated chat-summary + agent-catalog state for the chat
|
|
3
|
+
* sidebar. Generic in the chat and agent shapes so cloud and local apps reuse
|
|
4
|
+
* the same hook (the cloud schema has extra fields on `Chat`, the local
|
|
5
|
+
* schema has fewer).
|
|
6
|
+
*
|
|
7
|
+
* Lifted from deeppath-agent's `useChatsAndAgents.ts` and deeppath's
|
|
8
|
+
* `useChatUI` chat-list subset. The hook owns:
|
|
9
|
+
* - paginated chat list with "load more" and refresh
|
|
10
|
+
* - the agent catalog (single page for now)
|
|
11
|
+
* - the agent picked for the next new chat
|
|
12
|
+
* - in-place title patching (used by `chat_title_updated` SSE events to
|
|
13
|
+
* avoid clobbering pagination state with a full refresh)
|
|
14
|
+
* - create / delete chat actions, with optimistic patching of the local
|
|
15
|
+
* list.
|
|
16
|
+
*
|
|
17
|
+
* The transport is provided by the consumer so the same hook serves Electron
|
|
18
|
+
* IPC, the cloud REST client, or any other backend.
|
|
19
|
+
*/
|
|
20
|
+
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
21
|
+
export function useChatList(options) {
|
|
22
|
+
const pageSize = options.pageSize ?? 50;
|
|
23
|
+
const transportRef = useRef(options.transport);
|
|
24
|
+
transportRef.current = options.transport;
|
|
25
|
+
const [chats, setChats] = useState([]);
|
|
26
|
+
const [agents, setAgents] = useState([]);
|
|
27
|
+
const [isLoading, setLoading] = useState(!options.skipInitialLoad);
|
|
28
|
+
const [isLoadingMoreChats, setLoadingMore] = useState(false);
|
|
29
|
+
const [hasMoreChats, setHasMore] = useState(false);
|
|
30
|
+
const [error, setError] = useState(null);
|
|
31
|
+
const [selectedAgentId, setSelectedAgentId] = useState(null);
|
|
32
|
+
const pageRef = useRef(1);
|
|
33
|
+
const mountedRef = useRef(true);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
mountedRef.current = true;
|
|
36
|
+
return () => {
|
|
37
|
+
mountedRef.current = false;
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
const refreshChats = useCallback(async () => {
|
|
41
|
+
setLoading(true);
|
|
42
|
+
try {
|
|
43
|
+
const res = await transportRef.current.listChats({ page: 1, pageSize });
|
|
44
|
+
if (!mountedRef.current)
|
|
45
|
+
return;
|
|
46
|
+
setChats(res.chats);
|
|
47
|
+
setHasMore(res.hasMore);
|
|
48
|
+
pageRef.current = 1;
|
|
49
|
+
setError(null);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
if (mountedRef.current)
|
|
53
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
if (mountedRef.current)
|
|
57
|
+
setLoading(false);
|
|
58
|
+
}
|
|
59
|
+
}, [pageSize]);
|
|
60
|
+
const refreshAgents = useCallback(async () => {
|
|
61
|
+
try {
|
|
62
|
+
const list = await transportRef.current.listAgents();
|
|
63
|
+
if (mountedRef.current)
|
|
64
|
+
setAgents(list);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
if (mountedRef.current)
|
|
68
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
69
|
+
}
|
|
70
|
+
}, []);
|
|
71
|
+
const loadMoreChats = useCallback(async () => {
|
|
72
|
+
if (!hasMoreChats || isLoadingMoreChats)
|
|
73
|
+
return;
|
|
74
|
+
setLoadingMore(true);
|
|
75
|
+
try {
|
|
76
|
+
const next = pageRef.current + 1;
|
|
77
|
+
const res = await transportRef.current.listChats({ page: next, pageSize });
|
|
78
|
+
if (!mountedRef.current)
|
|
79
|
+
return;
|
|
80
|
+
setChats((prev) => prev.concat(res.chats));
|
|
81
|
+
setHasMore(res.hasMore);
|
|
82
|
+
pageRef.current = next;
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (mountedRef.current)
|
|
86
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
if (mountedRef.current)
|
|
90
|
+
setLoadingMore(false);
|
|
91
|
+
}
|
|
92
|
+
}, [hasMoreChats, isLoadingMoreChats, pageSize]);
|
|
93
|
+
const createChat = useCallback(async (input) => {
|
|
94
|
+
const t = transportRef.current;
|
|
95
|
+
if (!t.createChat)
|
|
96
|
+
return null;
|
|
97
|
+
const created = await t.createChat({ agentId: input?.agentId ?? selectedAgentId ?? undefined });
|
|
98
|
+
// Either a chat object or a bare id.
|
|
99
|
+
const id = typeof created === 'string' ? created : t.getChatId(created);
|
|
100
|
+
await refreshChats();
|
|
101
|
+
return id;
|
|
102
|
+
}, [refreshChats, selectedAgentId]);
|
|
103
|
+
const deleteChat = useCallback(async (chatId) => {
|
|
104
|
+
const t = transportRef.current;
|
|
105
|
+
if (!t.deleteChat)
|
|
106
|
+
return false;
|
|
107
|
+
const ok = await t.deleteChat(chatId);
|
|
108
|
+
if (ok && mountedRef.current) {
|
|
109
|
+
setChats((prev) => prev.filter((c) => t.getChatId(c) !== chatId));
|
|
110
|
+
}
|
|
111
|
+
return ok;
|
|
112
|
+
}, []);
|
|
113
|
+
const patchChatTitle = useCallback((chatId, title) => {
|
|
114
|
+
const t = transportRef.current;
|
|
115
|
+
if (!t.setChatTitle)
|
|
116
|
+
return;
|
|
117
|
+
setChats((prev) => prev.map((c) => (t.getChatId(c) === chatId ? t.setChatTitle(c, title) : c)));
|
|
118
|
+
}, []);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (options.skipInitialLoad)
|
|
121
|
+
return;
|
|
122
|
+
void refreshChats();
|
|
123
|
+
void refreshAgents();
|
|
124
|
+
// Run once on mount; downstream callers use refresh* explicitly.
|
|
125
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
126
|
+
}, []);
|
|
127
|
+
return useMemo(() => ({
|
|
128
|
+
chats,
|
|
129
|
+
agents,
|
|
130
|
+
isLoading,
|
|
131
|
+
isLoadingMoreChats,
|
|
132
|
+
hasMoreChats,
|
|
133
|
+
error,
|
|
134
|
+
selectedAgentId,
|
|
135
|
+
setSelectedAgentId,
|
|
136
|
+
refreshChats,
|
|
137
|
+
refreshAgents,
|
|
138
|
+
loadMoreChats,
|
|
139
|
+
createChat,
|
|
140
|
+
deleteChat,
|
|
141
|
+
patchChatTitle,
|
|
142
|
+
}), [
|
|
143
|
+
chats,
|
|
144
|
+
agents,
|
|
145
|
+
isLoading,
|
|
146
|
+
isLoadingMoreChats,
|
|
147
|
+
hasMoreChats,
|
|
148
|
+
error,
|
|
149
|
+
selectedAgentId,
|
|
150
|
+
refreshChats,
|
|
151
|
+
refreshAgents,
|
|
152
|
+
loadMoreChats,
|
|
153
|
+
createChat,
|
|
154
|
+
deleteChat,
|
|
155
|
+
patchChatTitle,
|
|
156
|
+
]);
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=useChatList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useChatList.js","sourceRoot":"","sources":["../../src/state/useChatList.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useChatSession` — convenience wrapper that combines `useChatStream` (turn
|
|
3
|
+
* lifecycle) with `useChatComposer` (draft + keys + send). Most consumers want
|
|
4
|
+
* this single hook so the wiring is identical across web-shell, deeppath, and
|
|
5
|
+
* deeppath-agent. Power users still reach for the underlying primitives.
|
|
6
|
+
*
|
|
7
|
+
* const { messages, isStreaming, composer, cancel } = useChatSession({
|
|
8
|
+
* transport,
|
|
9
|
+
* initialMessages,
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* <ChatPanel>
|
|
13
|
+
* <MessageList messages={messages} isStreaming={isStreaming} />
|
|
14
|
+
* <ChatInput composer={composer} onCancel={cancel} />
|
|
15
|
+
* </ChatPanel>
|
|
16
|
+
*/
|
|
17
|
+
import { type UseChatStreamOptions, type UseChatStreamReturn } from '../hooks/useChatStream.js';
|
|
18
|
+
import { type UseChatComposerOptions, type UseChatComposerReturn } from './useChatComposer.js';
|
|
19
|
+
export interface UseChatSessionOptions extends UseChatStreamOptions, Pick<UseChatComposerOptions, 'enterToSubmit' | 'initialValue'> {
|
|
20
|
+
/** Optional metadata callback when submitting a message (e.g. @mentions). */
|
|
21
|
+
buildMetadata?: (value: string) => Record<string, unknown> | undefined;
|
|
22
|
+
/** Force-disable the composer regardless of streaming state. */
|
|
23
|
+
composerDisabled?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface UseChatSessionReturn extends UseChatStreamReturn {
|
|
26
|
+
composer: UseChatComposerReturn;
|
|
27
|
+
}
|
|
28
|
+
export declare function useChatSession(options: UseChatSessionOptions): UseChatSessionReturn;
|
|
29
|
+
//# sourceMappingURL=useChatSession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useChatSession.d.ts","sourceRoot":"","sources":["../../src/state/useChatSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC3B,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,qBACf,SAAQ,oBAAoB,EAC1B,IAAI,CAAC,sBAAsB,EAAE,eAAe,GAAG,cAAc,CAAC;IAChE,6EAA6E;IAC7E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACvE,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAqB,SAAQ,mBAAmB;IAC/D,QAAQ,EAAE,qBAAqB,CAAC;CACjC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,oBAAoB,CAqBnF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useChatSession` — convenience wrapper that combines `useChatStream` (turn
|
|
3
|
+
* lifecycle) with `useChatComposer` (draft + keys + send). Most consumers want
|
|
4
|
+
* this single hook so the wiring is identical across web-shell, deeppath, and
|
|
5
|
+
* deeppath-agent. Power users still reach for the underlying primitives.
|
|
6
|
+
*
|
|
7
|
+
* const { messages, isStreaming, composer, cancel } = useChatSession({
|
|
8
|
+
* transport,
|
|
9
|
+
* initialMessages,
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* <ChatPanel>
|
|
13
|
+
* <MessageList messages={messages} isStreaming={isStreaming} />
|
|
14
|
+
* <ChatInput composer={composer} onCancel={cancel} />
|
|
15
|
+
* </ChatPanel>
|
|
16
|
+
*/
|
|
17
|
+
import { useMemo } from 'react';
|
|
18
|
+
import { useChatStream, } from '../hooks/useChatStream.js';
|
|
19
|
+
import { useChatComposer, } from './useChatComposer.js';
|
|
20
|
+
export function useChatSession(options) {
|
|
21
|
+
const stream = useChatStream(options);
|
|
22
|
+
const composer = useChatComposer({
|
|
23
|
+
initialValue: options.initialValue,
|
|
24
|
+
enterToSubmit: options.enterToSubmit,
|
|
25
|
+
isStreaming: stream.isStreaming,
|
|
26
|
+
disabled: options.composerDisabled,
|
|
27
|
+
onSend: async (value) => {
|
|
28
|
+
await stream.sendUserMessage({
|
|
29
|
+
content: value,
|
|
30
|
+
metadata: options.buildMetadata?.(value),
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
onCancel: stream.cancel,
|
|
34
|
+
});
|
|
35
|
+
return useMemo(() => ({ ...stream, composer }), [stream, composer]);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=useChatSession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useChatSession.js","sourceRoot":"","sources":["../../src/state/useChatSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EACL,aAAa,GAGd,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,eAAe,GAGhB,MAAM,sBAAsB,CAAC;AAe9B,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC3D,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAEtC,MAAM,QAAQ,GAAG,eAAe,CAAC;QAC/B,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,QAAQ,EAAE,OAAO,CAAC,gBAAgB;QAClC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACtB,MAAM,MAAM,CAAC,eAAe,CAAC;gBAC3B,OAAO,EAAE,KAAK;gBACd,QAAQ,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC,KAAK,CAAC;aACzC,CAAC,CAAC;QACL,CAAC;QACD,QAAQ,EAAE,MAAM,CAAC,MAAM;KACxB,CAAC,CAAC;IAEH,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,CAAC,EAC/B,CAAC,MAAM,EAAE,QAAQ,CAAC,CACnB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useScrollLock` — sticky-bottom scrolling for streaming message lists.
|
|
3
|
+
*
|
|
4
|
+
* The hook returns a callback ref to attach to the scroll container and a
|
|
5
|
+
* `scrollToBottom` imperative API. Behaviour:
|
|
6
|
+
* - When the container's scroll position is within `threshold` px of the
|
|
7
|
+
* bottom, the hook treats the user as "anchored" and re-runs
|
|
8
|
+
* `scrollToBottom` whenever the dependency array `revalidateKeys` changes.
|
|
9
|
+
* - As soon as the user scrolls up past `threshold`, the lock releases —
|
|
10
|
+
* the list will no longer auto-scroll until the user scrolls back to the
|
|
11
|
+
* bottom (or `scrollToBottom` is called explicitly, e.g. on send).
|
|
12
|
+
*
|
|
13
|
+
* Uses a callback ref so the listener attaches the moment the element mounts,
|
|
14
|
+
* not after a second effect pass. Matches deeppath's MessageList scroll
|
|
15
|
+
* handling minus the inline MutationObserver+IntersectionObserver soup.
|
|
16
|
+
*/
|
|
17
|
+
export interface UseScrollLockOptions {
|
|
18
|
+
/** Px from the bottom that still counts as "anchored". Default 24. */
|
|
19
|
+
threshold?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Dependencies that, when changed, will trigger an auto-scroll if the user
|
|
22
|
+
* is currently anchored. Pass e.g. `[messages.length, lastMessageContent]`.
|
|
23
|
+
*/
|
|
24
|
+
revalidateKeys?: ReadonlyArray<unknown>;
|
|
25
|
+
/** Scroll smooth vs jump. Defaults to 'auto' (instant). */
|
|
26
|
+
behavior?: ScrollBehavior;
|
|
27
|
+
}
|
|
28
|
+
export interface UseScrollLockReturn<E extends HTMLElement = HTMLDivElement> {
|
|
29
|
+
/** Callback ref — pass directly to `ref={...}` on the scroll container. */
|
|
30
|
+
setRef: (el: E | null) => void;
|
|
31
|
+
isAnchored: boolean;
|
|
32
|
+
scrollToBottom: (behavior?: ScrollBehavior) => void;
|
|
33
|
+
}
|
|
34
|
+
export declare function useScrollLock<E extends HTMLElement = HTMLDivElement>(options?: UseScrollLockOptions): UseScrollLockReturn<E>;
|
|
35
|
+
//# sourceMappingURL=useScrollLock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useScrollLock.d.ts","sourceRoot":"","sources":["../../src/state/useScrollLock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACxC,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,WAAW,GAAG,cAAc;IACzE,2EAA2E;IAC3E,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IAC/B,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,EAAE,CAAC,QAAQ,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;CACrD;AAMD,wBAAgB,aAAa,CAAC,CAAC,SAAS,WAAW,GAAG,cAAc,EAClE,OAAO,GAAE,oBAAyB,GACjC,mBAAmB,CAAC,CAAC,CAAC,CA4DxB"}
|