@jini-ai/chat 0.6.6 → 0.6.10
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 +129 -1
- package/dist/core/accepted-input.d.ts +54 -0
- package/dist/core/accepted-input.js +165 -0
- package/dist/core/agent-plugin-chat-state.d.ts +43 -0
- package/dist/core/agent-plugin-chat-state.js +48 -0
- package/dist/core/assistant-client/a2ui-action-poster.js +32 -28
- package/dist/core/assistant-client/messages.d.ts +1 -1
- package/dist/core/assistant-client/messages.js +2 -2
- package/dist/core/assistant-client/transport.d.ts +2 -2
- package/dist/core/assistant-client/transport.js +187 -98
- package/dist/core/assistant-content.d.ts +2 -1
- package/dist/core/assistant-content.js +9 -11
- package/dist/core/browser/page-actions.js +4 -2
- package/dist/core/composer-history.d.ts +16 -9
- package/dist/core/composer-history.js +28 -16
- package/dist/core/conversation-title.d.ts +3 -0
- package/dist/core/conversation-title.js +3 -0
- package/dist/core/durable-projection.js +20 -13
- package/dist/core/durable-runs/continuation.d.ts +4 -0
- package/dist/core/durable-runs/continuation.js +31 -6
- package/dist/core/durable-runs/ledger-contracts.d.ts +2 -1
- package/dist/core/durable-runs/ports.d.ts +6 -2
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/messages.d.ts +2 -0
- package/dist/core/partial-json.d.ts +6 -7
- package/dist/core/partial-json.js +81 -47
- package/dist/core/persistence/title.js +4 -2
- package/dist/core/question-answer.d.ts +23 -0
- package/dist/core/question-answer.js +66 -0
- package/dist/core/question-form/format.d.ts +6 -6
- package/dist/core/question-form/format.js +16 -17
- package/dist/core/question-form/normalize.d.ts +18 -0
- package/dist/core/question-form/normalize.js +112 -122
- package/dist/core/question-form/scan.js +141 -203
- package/dist/core/run-activity.js +62 -49
- package/dist/core/run-events/entry.d.ts +10 -1
- package/dist/core/run-events/entry.js +117 -87
- package/dist/core/todos.d.ts +1 -1
- package/dist/core/todos.js +60 -85
- package/dist/core/tool-events.js +14 -12
- package/dist/core/transcript.js +62 -16
- package/dist/core/turn-usage.js +54 -32
- package/dist/core/user-text-redaction.js +3 -0
- package/dist/core/util/manifest.js +77 -82
- package/dist/core/util/markdown-context.js +51 -53
- package/dist/core/util/parser.js +136 -144
- package/dist/core/util/pointer.js +16 -16
- package/dist/core/util/strip.js +57 -67
- package/dist/core/util/validate.js +15 -17
- package/dist/embed-widget/browser/session-store.js +21 -12
- package/dist/embed-widget/transports/fetch-sse.js +108 -95
- package/dist/embed-widget/vanilla/index.js +81 -70
- package/dist/react/agent-bridge/frontend-session-bridge.js +80 -52
- package/dist/react/components/AttachmentPreviewModal.js +25 -1
- package/dist/react/components/ChatSettingsDialog.d.ts +13 -0
- package/dist/react/components/ChatSettingsDialog.hooks.d.ts +115 -0
- package/dist/react/components/ChatSettingsDialog.hooks.js +40 -0
- package/dist/react/components/ChatSettingsDialog.js +72 -0
- package/dist/react/components/ChatSettingsSegmented.d.ts +18 -0
- package/dist/react/components/ChatSettingsSegmented.js +11 -0
- package/dist/react/components/Composer.d.ts +5 -1
- package/dist/react/components/Composer.js +126 -82
- package/dist/react/components/ConversationList.js +25 -39
- package/dist/react/components/ConversationTitleEditor.d.ts +27 -0
- package/dist/react/components/ConversationTitleEditor.js +54 -0
- package/dist/react/components/Icon.d.ts +1 -1
- package/dist/react/components/Icon.js +25 -45
- package/dist/react/components/Markdown.js +134 -123
- package/dist/react/components/McpUiSurfaceCard.d.ts +2 -2
- package/dist/react/components/McpUiSurfaceCard.js +30 -9
- package/dist/react/components/MessageList.d.ts +13 -13
- package/dist/react/components/MessageList.hooks.d.ts +6 -1
- package/dist/react/components/MessageList.hooks.js +19 -3
- package/dist/react/components/MessageList.js +34 -10
- package/dist/react/components/MessageRow.d.ts +1 -1
- package/dist/react/components/MessageRow.js +75 -47
- package/dist/react/components/MessageRow.usage.js +16 -11
- package/dist/react/components/MidRunUserMessage.js +16 -3
- package/dist/react/components/QuestionForm.js +148 -76
- package/dist/react/components/QuestionsPanel.js +1 -1
- package/dist/react/components/TodoCard.js +12 -6
- package/dist/react/components/ToolCallGroup.d.ts +4 -3
- package/dist/react/components/ToolCallGroup.js +7 -5
- package/dist/react/components/ToolCard.js +47 -40
- package/dist/react/components/composer-discovery.js +6 -1
- package/dist/react/components/mcp-ui-surface-state.d.ts +6 -11
- package/dist/react/components/mcp-ui-surface-state.js +23 -8
- package/dist/react/ext-event-renderer-registry.d.ts +8 -0
- package/dist/react/ext-event-renderer-registry.js +5 -2
- package/dist/react/features/chat-pane/components/AgentPluginUsage.d.ts +7 -0
- package/dist/react/features/chat-pane/components/AgentPluginUsage.js +45 -0
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +55 -19
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +192 -74
- package/dist/react/features/chat-pane/components/RuntimeDropUp.d.ts +29 -0
- package/dist/react/features/chat-pane/components/RuntimeDropUp.js +45 -0
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +80 -47
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +44 -40
- package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +6 -6
- package/dist/react/features/chat-pane/create-mid-run-message-poster.js +8 -8
- package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +3 -3
- package/dist/react/features/chat-pane/create-typed-answer-poster.js +3 -3
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +6 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +11 -5
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +12 -7
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +340 -149
- package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +7 -6
- package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +28 -10
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.d.ts +3 -3
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +60 -12
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.d.ts +49 -0
- package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +143 -0
- package/dist/react/features/chat-pane/model-options.d.ts +14 -0
- package/dist/react/features/chat-pane/model-options.js +45 -25
- package/dist/react/features/chat-pane/rules.d.ts +8 -9
- package/dist/react/features/chat-pane/rules.js +84 -46
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +152 -36
- package/dist/react/features/chat-pane/types.d.ts +19 -9
- package/dist/react/hooks/assistant-chats.js +12 -5
- package/dist/react/hooks/composer-draft-cache.js +116 -118
- package/dist/react/hooks/composer-history-storage.js +5 -5
- package/dist/react/hooks/fab-position.js +7 -4
- package/dist/react/hooks/useAnsweredCards.d.ts +21 -4
- package/dist/react/hooks/useAnsweredCards.js +38 -11
- package/dist/react/hooks/useArtifactStream.js +28 -22
- package/dist/react/hooks/useComposer.d.ts +20 -0
- package/dist/react/hooks/useComposer.js +22 -9
- package/dist/react/hooks/useComposerHistory.hooks.d.ts +6 -1
- package/dist/react/hooks/useComposerHistory.hooks.js +39 -19
- package/dist/react/hooks/useComposerHistoryKeys.hooks.js +24 -14
- package/dist/react/hooks/useConversation.d.ts +7 -0
- package/dist/react/hooks/useConversation.js +23 -10
- package/dist/react/hooks/useExtEventGroups.js +39 -39
- package/dist/react/hooks/useQuestionForms.js +21 -26
- package/dist/react/hooks/useRunStream.js +10 -2
- package/dist/react/index.d.ts +7 -1
- package/dist/react/index.js +9 -0
- package/dist/react/message-blocks.d.ts +6 -4
- package/dist/react/message-blocks.js +94 -72
- package/dist/react/slots.d.ts +2 -2
- package/dist/react/tool-call-display.d.ts +12 -6
- package/dist/react/tool-call-display.js +53 -24
- package/dist/server/durable-runs/recover.js +132 -80
- package/dist/server/run-finalizer/entry.js +25 -15
- package/dist/store/legacy/sqlite/conversations.d.ts +11 -11
- package/dist/store/legacy/sqlite/conversations.js +69 -98
- package/dist/store/legacy/sqlite/messages.d.ts +10 -2
- package/dist/store/legacy/sqlite/messages.js +113 -71
- package/dist/store/runtime/chat-history-store.js +9 -3
- package/dist/store/runtime/durable-run-store.d.ts +2 -2
- package/dist/store/runtime/durable-run-store.js +165 -117
- package/dist/store/runtime/history-tools.js +118 -82
- package/dist/store/runtime/run-ledger.d.ts +4 -1
- package/dist/store/runtime/run-ledger.js +56 -16
- package/dist/store/sql/cursor.js +38 -18
- package/dist/store/sql/store.d.ts +18 -0
- package/dist/store/sql/store.js +46 -15
- package/package.json +18 -16
|
@@ -31,6 +31,14 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
31
31
|
import { useComposerHistory } from './useComposerHistory.hooks.js';
|
|
32
32
|
import { readCachedAttachments, readCachedDraft, rememberCachedDraft, writeCachedAttachments, writeCachedDraft, } from './composer-draft-cache.js';
|
|
33
33
|
import { cacheAttachmentPreviewSource } from './attachment-preview-cache.js';
|
|
34
|
+
/**
|
|
35
|
+
* The real window/document pair, or `null` when this runtime has no `window` — a non-DOM React
|
|
36
|
+
* renderer still runs effects, and subscribing there would throw.
|
|
37
|
+
* @complexity Time/space: O(1).
|
|
38
|
+
*/
|
|
39
|
+
export function browserPageLifecycle() {
|
|
40
|
+
return typeof window === 'undefined' ? null : { window, document };
|
|
41
|
+
}
|
|
34
42
|
const EMPTY_MENTION = { open: false, query: '', results: [] };
|
|
35
43
|
/**
|
|
36
44
|
* How long typing must pause before the draft reaches `localStorage` and the host's persistence
|
|
@@ -44,9 +52,12 @@ export const DRAFT_PERSIST_DEBOUNCE_MS = 800;
|
|
|
44
52
|
* One pending durable draft write at a time, trailing-edge debounced. Flushes on unmount and when
|
|
45
53
|
* the page is hidden (`pagehide`, or `visibilitychange` to hidden — the last event a mobile browser
|
|
46
54
|
* reliably delivers before discarding the tab).
|
|
55
|
+
* @param pageLifecycle The page-exit event sources, captured at mount; `undefined` resolves the
|
|
56
|
+
* browser's own, `null` subscribes to nothing (unmount still flushes).
|
|
47
57
|
* @complexity Time/space: O(1) per call.
|
|
48
58
|
*/
|
|
49
|
-
function useDeferredDraftWrite() {
|
|
59
|
+
function useDeferredDraftWrite(pageLifecycle) {
|
|
60
|
+
const [lifecycle] = useState(() => (pageLifecycle === undefined ? browserPageLifecycle() : pageLifecycle));
|
|
50
61
|
const pendingRef = useRef(null);
|
|
51
62
|
const cancel = useCallback(() => {
|
|
52
63
|
const pending = pendingRef.current;
|
|
@@ -73,20 +84,21 @@ function useDeferredDraftWrite() {
|
|
|
73
84
|
pendingRef.current = { timer, write };
|
|
74
85
|
}, []);
|
|
75
86
|
useEffect(() => {
|
|
76
|
-
if (
|
|
87
|
+
if (lifecycle === null)
|
|
77
88
|
return flush;
|
|
89
|
+
const { window: page, document: doc } = lifecycle;
|
|
78
90
|
const onVisibilityChange = () => {
|
|
79
|
-
if (
|
|
91
|
+
if (doc.visibilityState === 'hidden')
|
|
80
92
|
flush();
|
|
81
93
|
};
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
page.addEventListener('pagehide', flush);
|
|
95
|
+
doc.addEventListener('visibilitychange', onVisibilityChange);
|
|
84
96
|
return () => {
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
page.removeEventListener('pagehide', flush);
|
|
98
|
+
doc.removeEventListener('visibilitychange', onVisibilityChange);
|
|
87
99
|
flush();
|
|
88
100
|
};
|
|
89
|
-
}, [flush]);
|
|
101
|
+
}, [flush, lifecycle]);
|
|
90
102
|
return useMemo(() => ({ schedule, flush, cancel }), [schedule, flush, cancel]);
|
|
91
103
|
}
|
|
92
104
|
export function useComposer(options = {}) {
|
|
@@ -95,7 +107,7 @@ export function useComposer(options = {}) {
|
|
|
95
107
|
const [attachments, setAttachments] = useState([]);
|
|
96
108
|
const [agent, setAgent] = useState(options.initialAgent);
|
|
97
109
|
const [mention, setMention] = useState(EMPTY_MENTION);
|
|
98
|
-
const deferredWrite = useDeferredDraftWrite();
|
|
110
|
+
const deferredWrite = useDeferredDraftWrite(options.pageLifecycle);
|
|
99
111
|
const commitDraft = useCallback((next) => {
|
|
100
112
|
setDraftState(next);
|
|
101
113
|
rememberCachedDraft({ conversationId: conversationId, draft: next });
|
|
@@ -115,6 +127,7 @@ export function useComposer(options = {}) {
|
|
|
115
127
|
const history = useComposerHistory({ setRecalledDraft: setDraftState }, {
|
|
116
128
|
...(options.historyMessages === undefined ? {} : { messages: options.historyMessages }),
|
|
117
129
|
...(options.historyScope === undefined ? {} : { scope: options.historyScope }),
|
|
130
|
+
...(options.historyCap === undefined ? {} : { cap: options.historyCap }),
|
|
118
131
|
...(options.historyStorage === undefined ? {} : { storage: options.historyStorage }),
|
|
119
132
|
...(conversationId === undefined ? {} : { conversationId }),
|
|
120
133
|
});
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
import { type AcceptedChatInput } from '../../core/accepted-input.js';
|
|
1
2
|
import type { ChatMessage } from '../../core/messages.js';
|
|
2
3
|
import { type ComposerHistoryKeyInput, type ComposerHistoryStoragePort } from '../../core/composer-history.js';
|
|
3
4
|
export interface ComposerHistoryController {
|
|
5
|
+
record: (required: {
|
|
6
|
+
inputs: readonly AcceptedChatInput[];
|
|
7
|
+
}, optional?: {}) => void;
|
|
4
8
|
navigate: (input: ComposerHistoryKeyInput, optional?: {}) => boolean;
|
|
5
9
|
edit: (required: {}, optional?: {}) => void;
|
|
6
10
|
}
|
|
7
11
|
export declare function useComposerHistory({ setRecalledDraft }: {
|
|
8
12
|
setRecalledDraft: (text: string) => void;
|
|
9
|
-
}, { messages, scope, storage, conversationId }?: {
|
|
13
|
+
}, { messages, scope, storage, conversationId, cap }?: {
|
|
14
|
+
cap?: number;
|
|
10
15
|
messages?: readonly ChatMessage[];
|
|
11
16
|
scope?: string;
|
|
12
17
|
storage?: ComposerHistoryStoragePort;
|
|
@@ -1,36 +1,42 @@
|
|
|
1
|
+
import { acceptedInputsFromMessages } from '../../core/accepted-input.js';
|
|
1
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
3
|
import { composerHistoryKeyAction, createComposerHistoryState, mergeComposerHistory, normalizeComposerHistory, transitionComposerHistory, } from '../../core/composer-history.js';
|
|
3
4
|
import { browserComposerHistoryStorage } from './composer-history-storage.js';
|
|
4
5
|
const EMPTY_MESSAGES = [];
|
|
5
|
-
export function useComposerHistory({ setRecalledDraft }, { messages = EMPTY_MESSAGES, scope = 'default', storage = browserComposerHistoryStorage, conversationId } = {}) {
|
|
6
|
+
export function useComposerHistory({ setRecalledDraft }, { messages = EMPTY_MESSAGES, scope = 'default', storage = browserComposerHistoryStorage, conversationId, cap = 100 } = {}) {
|
|
6
7
|
const browseRef = useRef(createComposerHistoryState({}));
|
|
7
8
|
const [recent, setRecent] = useState([]);
|
|
8
9
|
const hydratedPortRef = useRef(null);
|
|
9
10
|
const [hydratedScope, setHydratedScope] = useState(null);
|
|
10
11
|
const pendingRef = useRef([]);
|
|
12
|
+
const pendingBaselineRef = useRef([]);
|
|
11
13
|
const conversationRef = useRef(conversationId);
|
|
12
14
|
const scopeRef = useRef(scope);
|
|
13
|
-
const
|
|
15
|
+
const inputs = useMemo(() => acceptedInputsFromMessages({ messages }), [messages]);
|
|
16
|
+
const seenRef = useRef(new Set(inputs.flatMap(input => [input.id, ...(input.aliases ?? [])])));
|
|
14
17
|
const writeQueueRef = useRef(Promise.resolve());
|
|
15
|
-
const current =
|
|
16
|
-
const entries = mergeComposerHistory({ recent, current });
|
|
18
|
+
const current = inputs.map(input => input.text);
|
|
19
|
+
const entries = pendingRef.current.length > 0 ? normalizeComposerHistory({ entries: recent }, { cap }) : mergeComposerHistory({ recent, current }, { cap });
|
|
17
20
|
useEffect(() => {
|
|
18
21
|
let cancelled = false;
|
|
19
22
|
pendingRef.current = [];
|
|
23
|
+
pendingBaselineRef.current = [];
|
|
20
24
|
hydratedPortRef.current = null;
|
|
21
25
|
setRecent([]);
|
|
22
26
|
setHydratedScope(null);
|
|
23
27
|
browseRef.current = createComposerHistoryState({});
|
|
24
28
|
// Read after this hook's outstanding writes so an injected async store cannot return stale
|
|
25
29
|
// entries when a scope is revisited. A remounted hook's default storage writes synchronously.
|
|
26
|
-
void writeQueueRef.current.then(() => storage.read({ scope }, {})).catch(() => []).then((loaded) => {
|
|
30
|
+
void writeQueueRef.current.then(() => storage.read({ scope }, cap === 100 ? {} : { cap })).catch(() => []).then((loaded) => {
|
|
27
31
|
if (cancelled)
|
|
28
32
|
return;
|
|
29
33
|
// Bootstrap an empty recent store from the already-sent current transcript. This
|
|
30
34
|
// makes opening a new chat useful immediately after installing recall; opening an
|
|
31
35
|
// older transcript later does not reorder an established recent list.
|
|
32
36
|
const seed = loaded.length > 0 ? loaded : current;
|
|
33
|
-
|
|
37
|
+
const baseline = pendingRef.current.length > 0
|
|
38
|
+
? mergeComposerHistory({ recent: seed, current: pendingBaselineRef.current }, { cap }) : seed;
|
|
39
|
+
setRecent(normalizeComposerHistory({ entries: [...baseline, ...pendingRef.current] }, { cap }));
|
|
34
40
|
hydratedPortRef.current = storage;
|
|
35
41
|
setHydratedScope(scope);
|
|
36
42
|
});
|
|
@@ -42,31 +48,45 @@ export function useComposerHistory({ setRecalledDraft }, { messages = EMPTY_MESS
|
|
|
42
48
|
scopeRef.current = scope;
|
|
43
49
|
if (adopted)
|
|
44
50
|
return;
|
|
51
|
+
pendingRef.current = [];
|
|
52
|
+
pendingBaselineRef.current = [];
|
|
45
53
|
browseRef.current = createComposerHistoryState({});
|
|
46
54
|
// A conversation's existing turns are available for navigation, but merely opening old
|
|
47
55
|
// conversations must not reorder the cross-conversation recent list.
|
|
48
|
-
seenRef.current = new Set(
|
|
56
|
+
seenRef.current = new Set(inputs.flatMap(input => [input.id, ...(input.aliases ?? [])]));
|
|
49
57
|
// messages is deliberately absent: newly appended turns are handled below, not reseeded.
|
|
50
58
|
}, [conversationId, scope]);
|
|
51
|
-
|
|
59
|
+
/** All acceptance paths, including replay observations, claim identities before writing recall. */
|
|
60
|
+
const record = useCallback(({ inputs: accepted }, _options = {}) => {
|
|
52
61
|
const added = [];
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
seenRef.current.
|
|
62
|
+
const newIds = new Set();
|
|
63
|
+
for (const input of accepted) {
|
|
64
|
+
const ids = [input.id, ...(input.aliases ?? [])];
|
|
65
|
+
const seen = ids.some(id => seenRef.current.has(id));
|
|
66
|
+
for (const id of ids)
|
|
67
|
+
seenRef.current.add(id);
|
|
68
|
+
if (!seen) {
|
|
69
|
+
added.push(input.text);
|
|
70
|
+
for (const id of ids)
|
|
71
|
+
newIds.add(id);
|
|
72
|
+
}
|
|
57
73
|
}
|
|
58
74
|
if (added.length === 0)
|
|
59
75
|
return;
|
|
60
|
-
pendingRef.current.
|
|
61
|
-
pendingRef.current = normalizeComposerHistory({ entries: pendingRef.current });
|
|
62
|
-
|
|
63
|
-
|
|
76
|
+
const firstAcceptance = pendingRef.current.length === 0;
|
|
77
|
+
pendingRef.current = normalizeComposerHistory({ entries: [...pendingRef.current, ...added] }, { cap });
|
|
78
|
+
const before = inputs.filter(input => !newIds.has(input.id)).map(input => input.text);
|
|
79
|
+
if (firstAcceptance)
|
|
80
|
+
pendingBaselineRef.current = before;
|
|
81
|
+
setRecent(previous => normalizeComposerHistory({ entries: [...(firstAcceptance ? mergeComposerHistory({ recent: previous, current: before }, { cap }) : previous), ...added] }, { cap }));
|
|
82
|
+
}, [cap, inputs]);
|
|
83
|
+
useEffect(() => { record({ inputs }); }, [inputs, record]);
|
|
64
84
|
useEffect(() => {
|
|
65
85
|
if (hydratedScope !== scope || hydratedPortRef.current !== storage)
|
|
66
86
|
return;
|
|
67
87
|
// Serialize writes; a slow earlier write must not overwrite a later prompt.
|
|
68
|
-
writeQueueRef.current = writeQueueRef.current.then(() => storage.write({ scope, entries: recent }, {})).catch(() => { });
|
|
69
|
-
}, [hydratedScope, recent, scope, storage]);
|
|
88
|
+
writeQueueRef.current = writeQueueRef.current.then(() => storage.write({ scope, entries: normalizeComposerHistory({ entries: recent }, { cap }) }, cap === 100 ? {} : { cap })).catch(() => { });
|
|
89
|
+
}, [hydratedScope, recent, scope, storage, cap]);
|
|
70
90
|
const navigate = useCallback((input, _optional = {}) => {
|
|
71
91
|
const action = composerHistoryKeyAction(input);
|
|
72
92
|
if (action === null)
|
|
@@ -85,6 +105,6 @@ export function useComposerHistory({ setRecalledDraft }, { messages = EMPTY_MESS
|
|
|
85
105
|
// the original browsing snapshot are never edited in place.
|
|
86
106
|
browseRef.current = createComposerHistoryState({});
|
|
87
107
|
}, []);
|
|
88
|
-
return useMemo(() => ({ navigate, edit }), [navigate, edit]);
|
|
108
|
+
return useMemo(() => ({ navigate, edit, record }), [navigate, edit, record]);
|
|
89
109
|
}
|
|
90
110
|
//# sourceMappingURL=useComposerHistory.hooks.js.map
|
|
@@ -37,28 +37,38 @@ function visualLineBounds({ textarea }) {
|
|
|
37
37
|
mirror.remove();
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
+
function placeCaret(textarea, { placement }) {
|
|
41
|
+
const offset = placement === 'start' ? 0 : textarea.value.length;
|
|
42
|
+
textarea.setSelectionRange(offset, offset);
|
|
43
|
+
}
|
|
44
|
+
function hasModifier(event) {
|
|
45
|
+
return event.shiftKey || event.ctrlKey || event.altKey || event.metaKey;
|
|
46
|
+
}
|
|
47
|
+
/** Keys another handler or an IME already owns, or that an open popup should get instead. */
|
|
48
|
+
function isClaimedElsewhere(event, { popupOpen }) {
|
|
49
|
+
return event.defaultPrevented || popupOpen || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;
|
|
50
|
+
}
|
|
51
|
+
function isHistoryKey(event, { popupOpen }) {
|
|
52
|
+
if (hasModifier(event) || isClaimedElsewhere(event, { popupOpen }))
|
|
53
|
+
return false;
|
|
54
|
+
return ['ArrowUp', 'ArrowDown', 'Escape'].includes(event.key);
|
|
55
|
+
}
|
|
40
56
|
export function useComposerHistoryKeys({ composer, textareaRef }, { popupOpen = false } = {}) {
|
|
41
57
|
const pendingCaretRef = useRef(null);
|
|
42
58
|
// A duplicate history entry can equal the current value, so setDraft may bail out.
|
|
43
59
|
// Still commit the caret request now rather than carrying it into the next real edit.
|
|
44
60
|
const [, requestCaretCommit] = useReducer((revision) => revision + 1, 0);
|
|
45
61
|
useLayoutEffect(() => {
|
|
46
|
-
if (!pendingCaretRef.current)
|
|
47
|
-
return;
|
|
48
62
|
const placement = pendingCaretRef.current;
|
|
63
|
+
if (!placement)
|
|
64
|
+
return;
|
|
49
65
|
pendingCaretRef.current = null;
|
|
50
66
|
const textarea = textareaRef.current;
|
|
51
|
-
if (textarea)
|
|
52
|
-
|
|
53
|
-
textarea.setSelectionRange(offset, offset);
|
|
54
|
-
}
|
|
67
|
+
if (textarea)
|
|
68
|
+
placeCaret(textarea, { placement });
|
|
55
69
|
});
|
|
56
70
|
return (event) => {
|
|
57
|
-
if (event
|
|
58
|
-
return false;
|
|
59
|
-
if (event.defaultPrevented || popupOpen || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229)
|
|
60
|
-
return false;
|
|
61
|
-
if (!['ArrowUp', 'ArrowDown', 'Escape'].includes(event.key))
|
|
71
|
+
if (!isHistoryKey(event, { popupOpen }))
|
|
62
72
|
return false;
|
|
63
73
|
const textarea = event.currentTarget;
|
|
64
74
|
const handled = composer.history?.navigate({
|
|
@@ -78,11 +88,11 @@ export function useComposerHistoryKeys({ composer, textareaRef }, { popupOpen =
|
|
|
78
88
|
// that commit clamps it to the OLD draft's length and breaks multiline Down gating.
|
|
79
89
|
// Up stays on the first line of even a multiline recall so repeated Up can browse
|
|
80
90
|
// older entries. Down/Escape go to the end, including when restoring the draft.
|
|
81
|
-
|
|
91
|
+
const placement = event.key === 'ArrowUp' ? 'start' : 'end';
|
|
92
|
+
pendingCaretRef.current = placement;
|
|
82
93
|
requestCaretCommit();
|
|
83
94
|
// Recalling an identical string may not cause a render; the existing value is safe.
|
|
84
|
-
|
|
85
|
-
textarea.setSelectionRange(offset, offset);
|
|
95
|
+
placeCaret(textarea, { placement });
|
|
86
96
|
}
|
|
87
97
|
return handled;
|
|
88
98
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AcceptedChatInput } from '../../core/accepted-input.js';
|
|
1
2
|
import { type UserTextRedactionOptions } from '../../core/user-text-redaction.js';
|
|
2
3
|
import type { ChatAttachment, ChatMessage } from '../../core/index.js';
|
|
3
4
|
import type { ChatTransport, RunContext } from '../../core/index.js';
|
|
@@ -18,6 +19,12 @@ export interface UseConversationOptions extends UserTextRedactionOptions {
|
|
|
18
19
|
createMessageId?: () => string;
|
|
19
20
|
}
|
|
20
21
|
export interface SendMessageOptions {
|
|
22
|
+
/** Queue flushes reuse the input receipts recorded when they were queued. */
|
|
23
|
+
acceptedInputs?: AcceptedChatInput[];
|
|
24
|
+
/** Called after transport acceptance; rejection never consumes the draft or recall slot. */
|
|
25
|
+
onAccepted?: (input: {
|
|
26
|
+
inputs: AcceptedChatInput[];
|
|
27
|
+
}) => void;
|
|
21
28
|
/** Non-secret signal from a composer that already sanitized the content before this boundary. */
|
|
22
29
|
secretRedaction?: ChatMessage['secretRedaction'];
|
|
23
30
|
attachments?: ChatAttachment[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { acceptedInputUserEvent, projectAcceptedInputEvents } from '../../core/accepted-input.js';
|
|
1
2
|
import { redactUserHistory, redactUserMessage } from '../../core/user-text-redaction.js';
|
|
2
3
|
import { preserveAssistantContent, mergeRunEvents } from '../../core/durable-projection.js';
|
|
3
4
|
/**
|
|
@@ -22,6 +23,18 @@ function defaultCreateMessageId() {
|
|
|
22
23
|
fallbackIdCounter += 1;
|
|
23
24
|
return `msg-${Date.now()}-${fallbackIdCounter}`;
|
|
24
25
|
}
|
|
26
|
+
function pendingUserMessage({ content, sendOptions, createMessageId }, options = {}) {
|
|
27
|
+
return redactUserMessage({ message: {
|
|
28
|
+
id: createMessageId(),
|
|
29
|
+
role: 'user',
|
|
30
|
+
inputPending: true,
|
|
31
|
+
...(sendOptions.acceptedInputs ? { events: sendOptions.acceptedInputs.map(input => acceptedInputUserEvent({ input }, { receipt: 'accepted' })) } : {}),
|
|
32
|
+
content,
|
|
33
|
+
createdAt: Date.now(),
|
|
34
|
+
...(sendOptions.secretRedaction ? { secretRedaction: sendOptions.secretRedaction } : {}),
|
|
35
|
+
...(sendOptions.attachments ? { attachments: sendOptions.attachments } : {}),
|
|
36
|
+
} }, options).message;
|
|
37
|
+
}
|
|
25
38
|
export function useConversation(options) {
|
|
26
39
|
const { transport, conversationId = null, agentId, createMessageId = defaultCreateMessageId } = options;
|
|
27
40
|
const [messages, setMessagesState] = useState(() => redactUserHistory({ history: options.initialMessages ?? [] }, { ...(options.redactUserText ? { redactUserText: options.redactUserText } : {}) }));
|
|
@@ -149,7 +162,7 @@ export function useConversation(options) {
|
|
|
149
162
|
return {
|
|
150
163
|
...m,
|
|
151
164
|
...(nextRunId !== undefined ? { runId: nextRunId } : {}),
|
|
152
|
-
events: mergeRunEvents({ saved: m.events, incoming: run.events }, {}),
|
|
165
|
+
events: projectAcceptedInputEvents({ events: mergeRunEvents({ saved: m.events, incoming: run.events }, {}) }),
|
|
153
166
|
// Replayed completion prefixes cannot erase a saved interrupted segment.
|
|
154
167
|
content: preserveAssistantContent({ saved: m.content, incoming: assistantContentFromEvents({ events: run.events }) }, {}),
|
|
155
168
|
runStatus,
|
|
@@ -169,14 +182,7 @@ export function useConversation(options) {
|
|
|
169
182
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
170
183
|
}, [run.events, run.status, run.runId]);
|
|
171
184
|
const sendMessage = useCallback(async (content, sendOptions = {}) => {
|
|
172
|
-
const userMessage =
|
|
173
|
-
id: createMessageId(),
|
|
174
|
-
role: 'user',
|
|
175
|
-
content,
|
|
176
|
-
createdAt: Date.now(),
|
|
177
|
-
...(sendOptions.secretRedaction ? { secretRedaction: sendOptions.secretRedaction } : {}),
|
|
178
|
-
...(sendOptions.attachments ? { attachments: sendOptions.attachments } : {}),
|
|
179
|
-
} }, options).message;
|
|
185
|
+
const userMessage = pendingUserMessage({ content, sendOptions, createMessageId }, options);
|
|
180
186
|
const resolvedAgentId = sendOptions.agentId ?? agentId;
|
|
181
187
|
const assistantMessage = {
|
|
182
188
|
id: createMessageId(),
|
|
@@ -197,7 +203,7 @@ export function useConversation(options) {
|
|
|
197
203
|
// (host stuck-chat investigation, 2026-09-27).
|
|
198
204
|
setMessagesState((prev) => [...prev, userMessage, assistantMessage]);
|
|
199
205
|
setScrollIntent(true);
|
|
200
|
-
await run.start({
|
|
206
|
+
const accepted = await run.start({
|
|
201
207
|
assistantMessageId: assistantMessage.id,
|
|
202
208
|
history,
|
|
203
209
|
...(resolvedAgentId !== undefined ? { agentId: resolvedAgentId } : {}),
|
|
@@ -205,6 +211,13 @@ export function useConversation(options) {
|
|
|
205
211
|
...(sendOptions.attachments !== undefined ? { attachments: sendOptions.attachments } : {}),
|
|
206
212
|
...(sendOptions.context !== undefined ? { context: sendOptions.context } : {}),
|
|
207
213
|
});
|
|
214
|
+
if (!accepted) {
|
|
215
|
+
setMessagesState(prev => prev.filter(message => message.id !== userMessage.id && message.id !== assistantMessage.id));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const inputs = sendOptions.acceptedInputs ?? [{ id: userMessage.id, text: userMessage.content }];
|
|
219
|
+
setMessagesState(prev => prev.map(message => message.id === userMessage.id ? { ...message, inputPending: false } : message));
|
|
220
|
+
sendOptions.onAccepted?.({ inputs });
|
|
208
221
|
}, [agentId, conversationId, createMessageId, run, options.redactUserText, options.onSecretRedacted]);
|
|
209
222
|
const retry = useCallback(async (assistantMessageId) => {
|
|
210
223
|
const idx = messagesRef.current.findIndex((m) => m.id === assistantMessageId);
|
|
@@ -11,51 +11,51 @@
|
|
|
11
11
|
* chrome for them).
|
|
12
12
|
*/
|
|
13
13
|
import { useMemo } from 'react';
|
|
14
|
+
function addExtEvent(state, { name, data }) {
|
|
15
|
+
const slot = state.slotOf(name, data);
|
|
16
|
+
let group = state.bySlot.get(slot);
|
|
17
|
+
if (!group) {
|
|
18
|
+
group = { name, slot, events: [] };
|
|
19
|
+
state.bySlot.set(slot, group);
|
|
20
|
+
const owner = [...state.openCalls].at(-1);
|
|
21
|
+
if (owner !== undefined)
|
|
22
|
+
state.ownerOf.set(group, { id: owner[0], ...owner[1] });
|
|
23
|
+
}
|
|
24
|
+
group.events.push(data);
|
|
25
|
+
}
|
|
26
|
+
function visitEvent(state, ev) {
|
|
27
|
+
if (ev.kind === 'tool_use') {
|
|
28
|
+
state.openCalls.set(ev.id, { name: ev.name, input: ev.input });
|
|
29
|
+
}
|
|
30
|
+
else if (ev.kind === 'tool_result') {
|
|
31
|
+
state.openCalls.delete(ev.toolUseId);
|
|
32
|
+
state.results.set(ev.toolUseId, { content: ev.content, isError: ev.isError });
|
|
33
|
+
}
|
|
34
|
+
else if (ev.kind === 'ext') {
|
|
35
|
+
addExtEvent(state, ev);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function attachCalls({ bySlot, ownerOf, results }) {
|
|
39
|
+
const order = [...bySlot.values()];
|
|
40
|
+
for (const group of order) {
|
|
41
|
+
const owner = ownerOf.get(group);
|
|
42
|
+
if (owner === undefined)
|
|
43
|
+
continue;
|
|
44
|
+
const result = results.get(owner.id);
|
|
45
|
+
group.call = { id: owner.id, name: owner.name, input: owner.input, ...(result === undefined ? {} : { result }) };
|
|
46
|
+
}
|
|
47
|
+
return order;
|
|
48
|
+
}
|
|
14
49
|
/**
|
|
15
50
|
* @param slotOf - Which render slot an event belongs to. Defaults to its `name`, the one-slot-per-name
|
|
16
51
|
* grouping this hook always had; `MessageRow` passes the registry's `extEventSlot`.
|
|
17
52
|
*/
|
|
18
53
|
export function useExtEventGroups({ events }, { slotOf = (name) => name } = {}) {
|
|
19
54
|
return useMemo(() => {
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const openCalls = new Map();
|
|
25
|
-
const ownerOf = new Map();
|
|
26
|
-
const results = new Map();
|
|
27
|
-
for (const ev of events ?? []) {
|
|
28
|
-
if (ev.kind === 'tool_use') {
|
|
29
|
-
openCalls.set(ev.id, { name: ev.name, input: ev.input });
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
if (ev.kind === 'tool_result') {
|
|
33
|
-
openCalls.delete(ev.toolUseId);
|
|
34
|
-
results.set(ev.toolUseId, { content: ev.content, isError: ev.isError });
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (ev.kind !== 'ext')
|
|
38
|
-
continue;
|
|
39
|
-
const slot = slotOf(ev.name, ev.data);
|
|
40
|
-
let group = bySlot.get(slot);
|
|
41
|
-
if (!group) {
|
|
42
|
-
group = { name: ev.name, slot, events: [] };
|
|
43
|
-
bySlot.set(slot, group);
|
|
44
|
-
order.push(group);
|
|
45
|
-
const owner = [...openCalls].at(-1);
|
|
46
|
-
if (owner !== undefined)
|
|
47
|
-
ownerOf.set(group, { id: owner[0], ...owner[1] });
|
|
48
|
-
}
|
|
49
|
-
group.events.push(ev.data);
|
|
50
|
-
}
|
|
51
|
-
for (const group of order) {
|
|
52
|
-
const owner = ownerOf.get(group);
|
|
53
|
-
if (owner === undefined)
|
|
54
|
-
continue;
|
|
55
|
-
const result = results.get(owner.id);
|
|
56
|
-
group.call = { name: owner.name, input: owner.input, ...(result === undefined ? {} : { result }) };
|
|
57
|
-
}
|
|
58
|
-
return order;
|
|
55
|
+
const state = { slotOf, bySlot: new Map(), openCalls: new Map(), ownerOf: new Map(), results: new Map() };
|
|
56
|
+
for (const ev of events ?? [])
|
|
57
|
+
visitEvent(state, ev);
|
|
58
|
+
return attachCalls(state);
|
|
59
59
|
}, [events, slotOf]);
|
|
60
60
|
}
|
|
61
61
|
//# sourceMappingURL=useExtEventGroups.js.map
|
|
@@ -17,33 +17,28 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { useCallback, useMemo } from 'react';
|
|
19
19
|
import { findFirstQuestionForm, formOptionValueForLabel, formatFormAnswers } from '../../core/index.js';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!found)
|
|
31
|
-
continue;
|
|
32
|
-
const nextMessage = messages[i + 1];
|
|
33
|
-
const submittedAnswers = nextMessage && nextMessage.role === 'user' ? parseSubmittedAnswers({ form: found.form, userMessageContent: nextMessage.content }) ?? undefined : undefined;
|
|
34
|
-
const interactive = submittedAnswers === undefined && i === messages.length - 1;
|
|
35
|
-
out.push({ form: found.form, raw: found.raw, messageId: message.id, interactive, submittedAnswers });
|
|
36
|
-
}
|
|
37
|
-
return out;
|
|
38
|
-
}, [messages]);
|
|
39
|
-
const activeForm = useMemo(() => {
|
|
40
|
-
for (let i = forms.length - 1; i >= 0; i -= 1) {
|
|
41
|
-
const form = forms[i];
|
|
42
|
-
if (form?.interactive)
|
|
43
|
-
return form;
|
|
44
|
-
}
|
|
20
|
+
/**
|
|
21
|
+
* The question-form on `messages[index]`, or `null` when that message is not
|
|
22
|
+
* an assistant message or carries no form. Answers come from the message right
|
|
23
|
+
* after it, and only when that one is a user reply.
|
|
24
|
+
*/
|
|
25
|
+
function parseFormAt(messages, { index }) {
|
|
26
|
+
// Only called with an index `flatMap` is visiting (it skips holes), so the
|
|
27
|
+
// element is always defined.
|
|
28
|
+
const message = messages[index];
|
|
29
|
+
if (message.role !== 'assistant')
|
|
45
30
|
return null;
|
|
46
|
-
}
|
|
31
|
+
const found = findFirstQuestionForm({ input: message.content });
|
|
32
|
+
if (!found)
|
|
33
|
+
return null;
|
|
34
|
+
const nextMessage = messages[index + 1];
|
|
35
|
+
const submittedAnswers = nextMessage?.role === 'user' ? parseSubmittedAnswers({ form: found.form, userMessageContent: nextMessage.content }) ?? undefined : undefined;
|
|
36
|
+
const interactive = submittedAnswers === undefined && index === messages.length - 1;
|
|
37
|
+
return { form: found.form, raw: found.raw, messageId: message.id, interactive, submittedAnswers };
|
|
38
|
+
}
|
|
39
|
+
export function useQuestionForms({ messages }) {
|
|
40
|
+
const forms = useMemo(() => (messages ?? []).flatMap((_, index, all) => parseFormAt(all, { index }) ?? []), [messages]);
|
|
41
|
+
const activeForm = useMemo(() => forms.findLast((form) => form.interactive) ?? null, [forms]);
|
|
47
42
|
const buildSubmission = useCallback((form, answers) => formatFormAnswers({ form: form, answers: answers }), []);
|
|
48
43
|
return { forms, activeForm, buildSubmission };
|
|
49
44
|
}
|
|
@@ -51,6 +51,9 @@ const INITIAL_STATE = {
|
|
|
51
51
|
};
|
|
52
52
|
export function useRunStream({ transport }) {
|
|
53
53
|
const [state, setState] = useState(INITIAL_STATE);
|
|
54
|
+
// Acceptance/checkpoints can precede React's next commit. Cancellation needs the live id,
|
|
55
|
+
// even when the Stop handler still belongs to the render that showed a pending start.
|
|
56
|
+
const runIdRef = useRef(null);
|
|
54
57
|
const subscriptionAbortRef = useRef(null);
|
|
55
58
|
const cancelAbortRef = useRef(null);
|
|
56
59
|
// Bumped on every start/reattach/reset. A handler closes over the
|
|
@@ -81,6 +84,7 @@ export function useRunStream({ transport }) {
|
|
|
81
84
|
onCheckpoint: (message) => {
|
|
82
85
|
if (isStale())
|
|
83
86
|
return;
|
|
87
|
+
runIdRef.current = message.runId ?? runIdRef.current;
|
|
84
88
|
const statuses = { queued: 'streaming', running: 'streaming', succeeded: 'done', failed: 'error', canceled: 'canceled' };
|
|
85
89
|
setState((prev) => ({ ...prev, runId: message.runId ?? prev.runId,
|
|
86
90
|
status: message.runStatus ? statuses[message.runStatus] : prev.status,
|
|
@@ -115,6 +119,7 @@ export function useRunStream({ transport }) {
|
|
|
115
119
|
const start = useCallback(async (input) => {
|
|
116
120
|
teardownSubscription();
|
|
117
121
|
const generation = ++generationRef.current;
|
|
122
|
+
runIdRef.current = null;
|
|
118
123
|
const subscriptionAbort = new AbortController();
|
|
119
124
|
const cancelAbort = new AbortController();
|
|
120
125
|
subscriptionAbortRef.current = subscriptionAbort;
|
|
@@ -129,6 +134,7 @@ export function useRunStream({ transport }) {
|
|
|
129
134
|
}
|
|
130
135
|
return { runId };
|
|
131
136
|
}
|
|
137
|
+
runIdRef.current = runId;
|
|
132
138
|
setState((prev) => ({ ...prev, runId }));
|
|
133
139
|
return { runId };
|
|
134
140
|
}
|
|
@@ -145,6 +151,7 @@ export function useRunStream({ transport }) {
|
|
|
145
151
|
const reattach = useCallback(async (runId, initialEvents = []) => {
|
|
146
152
|
teardownSubscription();
|
|
147
153
|
const generation = ++generationRef.current;
|
|
154
|
+
runIdRef.current = runId;
|
|
148
155
|
const subscriptionAbort = new AbortController();
|
|
149
156
|
const cancelAbort = new AbortController();
|
|
150
157
|
subscriptionAbortRef.current = subscriptionAbort;
|
|
@@ -165,7 +172,7 @@ export function useRunStream({ transport }) {
|
|
|
165
172
|
}, [makeHandlers, teardownSubscription]);
|
|
166
173
|
const cancel = useCallback(() => {
|
|
167
174
|
cancelAbortRef.current?.abort();
|
|
168
|
-
const runId =
|
|
175
|
+
const runId = runIdRef.current;
|
|
169
176
|
// Recorded BEFORE the bump, so it names the generation currently in flight. When `runId` is
|
|
170
177
|
// already known the `stopRun` below does the work and this entry is simply never consumed;
|
|
171
178
|
// when it is not, this is the only thing that will ever stop that run.
|
|
@@ -176,9 +183,10 @@ export function useRunStream({ transport }) {
|
|
|
176
183
|
setState((prev) => ({ ...prev, status: 'canceled' }));
|
|
177
184
|
if (runId)
|
|
178
185
|
runStopRunHostEffect('transport.stopRun (cancel)', () => transport.stopRun(runId));
|
|
179
|
-
}, [
|
|
186
|
+
}, [teardownSubscription, transport]);
|
|
180
187
|
const reset = useCallback(() => {
|
|
181
188
|
generationRef.current += 1;
|
|
189
|
+
runIdRef.current = null;
|
|
182
190
|
teardownSubscription();
|
|
183
191
|
setState(INITIAL_STATE);
|
|
184
192
|
}, [teardownSubscription]);
|
package/dist/react/index.d.ts
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
* chat/core vocabulary. Every export is explicit: adding or renaming a name here is a public API
|
|
4
4
|
* review, so internal module exports cannot silently become public. Do not use export *.
|
|
5
5
|
*/
|
|
6
|
+
export { ChatSettingsDialog } from './components/ChatSettingsDialog.js';
|
|
7
|
+
export { ChatSettingsSegmented } from './components/ChatSettingsSegmented.js';
|
|
8
|
+
export { useChatSettingsDialog } from './components/ChatSettingsDialog.hooks.js';
|
|
9
|
+
export type { ChatPermissionsPort, ChatSettingsSection } from './components/ChatSettingsDialog.hooks.js';
|
|
6
10
|
export type { ChatTransport, FeedbackChange, OnFeedback, ReattachRunOptions, RunContext, RunHandlers, StartRunInput, UserTextRedaction, } from '../core/index.js';
|
|
7
11
|
export { RendererRegistry, } from './artifact-types.js';
|
|
8
12
|
export type { ArtifactFile, ArtifactRenderContext, ArtifactRenderMatch, ArtifactRenderer, } from './artifact-types.js';
|
|
@@ -25,7 +29,7 @@ export type { RunStreamState, RunStreamStatus, StartRunOptions, UseRunStreamResu
|
|
|
25
29
|
export { useConversation, } from './hooks/useConversation.js';
|
|
26
30
|
export type { SendMessageOptions, UseConversationOptions, UseConversationResult, } from './hooks/useConversation.js';
|
|
27
31
|
export { useComposer, } from './hooks/useComposer.js';
|
|
28
|
-
export type { ComposerDraftPersistence, MentionPopoverState, UseComposerOptions, UseComposerResult, } from './hooks/useComposer.js';
|
|
32
|
+
export type { ComposerDraftPersistence, ComposerPageLifecycle, MentionPopoverState, UseComposerOptions, UseComposerResult, } from './hooks/useComposer.js';
|
|
29
33
|
export { useToolTimeline, } from './hooks/useToolTimeline.js';
|
|
30
34
|
export type { ToolTimelineRow, UseToolTimelineOptions, UseToolTimelineResult, } from './hooks/useToolTimeline.js';
|
|
31
35
|
export { useExtEventGroups, } from './hooks/useExtEventGroups.js';
|
|
@@ -97,6 +101,8 @@ export type { TodoCardProps } from './components/TodoCard.js';
|
|
|
97
101
|
* `leadingAccessory` slot; it needs no changes to `ChatPane` itself.
|
|
98
102
|
*/
|
|
99
103
|
export { ConversationList } from './components/ConversationList.js';
|
|
104
|
+
export { ConversationTitle, ConversationTitleEditor } from './components/ConversationTitleEditor.js';
|
|
105
|
+
export type { ConversationTitleProps, ConversationTitleEditorProps } from './components/ConversationTitleEditor.js';
|
|
100
106
|
export type { ConversationListProps, ConversationListItem } from './components/ConversationList.js';
|
|
101
107
|
/**
|
|
102
108
|
* The conversation an operator last had open, per caller identity — so a host's switcher lands
|
package/dist/react/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jini-ai/chat/react — headless hooks, presentational components and slots over framework-free
|
|
3
|
+
* chat/core vocabulary. Every export is explicit: adding or renaming a name here is a public API
|
|
4
|
+
* review, so internal module exports cannot silently become public. Do not use export *.
|
|
5
|
+
*/
|
|
6
|
+
export { ChatSettingsDialog } from './components/ChatSettingsDialog.js';
|
|
7
|
+
export { ChatSettingsSegmented } from './components/ChatSettingsSegmented.js';
|
|
8
|
+
export { useChatSettingsDialog } from './components/ChatSettingsDialog.hooks.js';
|
|
1
9
|
export { RendererRegistry, } from './artifact-types.js';
|
|
2
10
|
export { createToolRendererRegistry, clearToolRenderers, getToolRenderer, registerToolRenderer, } from './tool-renderer-registry.js';
|
|
3
11
|
export { createExtEventRendererRegistry, clearExtEventRenderers, extEventSlot, getExtEventRenderer, registerExtEventRenderer, } from './ext-event-renderer-registry.js';
|
|
@@ -89,6 +97,7 @@ export { TodoCard } from './components/TodoCard.js';
|
|
|
89
97
|
* `leadingAccessory` slot; it needs no changes to `ChatPane` itself.
|
|
90
98
|
*/
|
|
91
99
|
export { ConversationList } from './components/ConversationList.js';
|
|
100
|
+
export { ConversationTitle, ConversationTitleEditor } from './components/ConversationTitleEditor.js';
|
|
92
101
|
/**
|
|
93
102
|
* The conversation an operator last had open, per caller identity — so a host's switcher lands
|
|
94
103
|
* back on it after a reload instead of an empty new chat. See the module doc for the contract.
|
|
@@ -85,6 +85,10 @@ export type MessageBlock<Row> = {
|
|
|
85
85
|
readonly text: string;
|
|
86
86
|
readonly key: string;
|
|
87
87
|
};
|
|
88
|
+
type SlotOf = (ev: {
|
|
89
|
+
readonly name: string;
|
|
90
|
+
readonly data: unknown;
|
|
91
|
+
}) => string;
|
|
88
92
|
/**
|
|
89
93
|
* Rebuilds an assistant message as ordered blocks, or returns `null` if that cannot be done
|
|
90
94
|
* losslessly.
|
|
@@ -107,10 +111,8 @@ export declare function interleaveMessageBlocks<Row extends {
|
|
|
107
111
|
content: string;
|
|
108
112
|
rows: readonly Row[];
|
|
109
113
|
}, { slotOf, includeThinking }?: {
|
|
110
|
-
slotOf?:
|
|
111
|
-
readonly name: string;
|
|
112
|
-
readonly data: unknown;
|
|
113
|
-
}) => string;
|
|
114
|
+
slotOf?: SlotOf;
|
|
114
115
|
includeThinking?: boolean;
|
|
115
116
|
}): MessageBlock<Row>[] | null;
|
|
117
|
+
export {};
|
|
116
118
|
//# sourceMappingURL=message-blocks.d.ts.map
|