@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
|
@@ -10,25 +10,49 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
10
10
|
* scroll-anchoring state lives in `useMessageListAutoScroll`
|
|
11
11
|
* (`MessageList.hooks.ts`); see that file for why it is presentational.
|
|
12
12
|
*/
|
|
13
|
-
import { memo } from 'react';
|
|
13
|
+
import { memo, useMemo } from 'react';
|
|
14
|
+
import { projectAcceptedInputMessages } from '../../core/accepted-input.js';
|
|
14
15
|
import { isTerminalRunStatus } from '../../core/index.js';
|
|
15
16
|
import { useT } from '../hooks/context.js';
|
|
16
17
|
import { useMessageListAutoScroll, useConversationUsage } from './MessageList.hooks.js';
|
|
17
18
|
import { MessageRow } from './MessageRow.js';
|
|
19
|
+
import { SentAttachmentButton } from './SentAttachment.js';
|
|
20
|
+
import { AttachmentPreviewModal } from './AttachmentPreviewModal.js';
|
|
21
|
+
import { useAttachmentGallery } from './AttachmentGallery.hooks.js';
|
|
22
|
+
function renderMessageRow({ message, isLast, isStreaming, usageSummary }, { activeQuestionFormMessageId, questionFormSubmittedAnswersByMessageId, onQuestionFormSubmit, ...rowProps } = {}) {
|
|
23
|
+
const runStreaming = isLast && isStreaming && !isTerminalRunStatus({ status: message.runStatus });
|
|
24
|
+
return (_jsx(MessageRow, { message: message, ...rowProps, ...(usageSummary === undefined ? {} : { usageSummary }), runStreaming: runStreaming, runSucceeded: message.runStatus === 'succeeded', questionFormInteractive: message.id === activeQuestionFormMessageId, ...(questionFormSubmittedAnswersByMessageId?.[message.id] !== undefined ? { questionFormSubmittedAnswers: questionFormSubmittedAnswersByMessageId[message.id] } : {}), ...(onQuestionFormSubmit !== undefined ? { onQuestionFormSubmit: (text, answers) => onQuestionFormSubmit(message.id, text, answers) } : {}) }, message.id));
|
|
25
|
+
}
|
|
18
26
|
/**
|
|
19
27
|
* Memoized: the pane re-renders on every composer keystroke, and re-rendering a long transcript
|
|
20
28
|
* (hundreds of tool rows, MCP-UI cards, markdown) per key made typing lag (owner report
|
|
21
|
-
* 2026-10-08). Callers must pass stable props — `ChatPane` memoizes `
|
|
29
|
+
* 2026-10-08). Callers must pass stable props — `ChatPane` memoizes `pendingPrompts` and
|
|
22
30
|
* `projectFileNames` for exactly this.
|
|
23
31
|
*/
|
|
24
|
-
export const MessageList = memo(function MessageList({ messages, isStreaming = false, scrollIntent = false, onScrolled,
|
|
25
|
-
const
|
|
32
|
+
export const MessageList = memo(function MessageList({ messages: sourceMessages, isStreaming = false, scrollIntent = false, onScrolled, pendingPrompt, pendingPrompts, ...rowProps }) {
|
|
33
|
+
const messages = useMemo(() => projectAcceptedInputMessages({ messages: sourceMessages }), [sourceMessages]);
|
|
26
34
|
const usageByMessage = useConversationUsage({ messages });
|
|
27
|
-
const { containerRef, onScroll } = useMessageListAutoScroll({ messages, scrollIntent }, onScrolled !== undefined ? { onScrolled } : {});
|
|
28
|
-
return (_jsxs("div", { className: "jini-message-list", ref: containerRef, onScroll: onScroll, "data-agent-element": "chat-transcript", "data-agent-role": "list", "data-agent-label": "The conversation transcript, oldest message first", children: [messages.map((message) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
const { containerRef, onScroll, awayFromLatest, jumpToLatest } = useMessageListAutoScroll({ messages, scrollIntent }, onScrolled !== undefined ? { onScrolled } : {});
|
|
36
|
+
return (_jsxs("div", { className: "jini-message-list", ref: containerRef, onScroll: onScroll, "data-agent-element": "chat-transcript", "data-agent-role": "list", "data-agent-label": "The conversation transcript, oldest message first", children: [messages.map((message) => renderMessageRow({
|
|
37
|
+
message,
|
|
38
|
+
isLast: message.id === messages[messages.length - 1]?.id,
|
|
39
|
+
isStreaming,
|
|
40
|
+
usageSummary: usageByMessage.get(message.id),
|
|
41
|
+
}, rowProps)), (pendingPrompts ?? (pendingPrompt ? [pendingPrompt] : [])).map((prompt, index) => (_jsx(QueuedMessage, { prompt: prompt, renderAttachment: rowProps.renderAttachment }, prompt.id ?? index))), awayFromLatest && _jsx(JumpToLatest, { onJump: jumpToLatest })] }));
|
|
33
42
|
});
|
|
43
|
+
/**
|
|
44
|
+
* Sticky, zero-height last child of the scroll container, so the button floats centred just above
|
|
45
|
+
* the composer without a wrapper element changing the pane's flex layout.
|
|
46
|
+
*/
|
|
47
|
+
function JumpToLatest({ onJump }) {
|
|
48
|
+
const t = useT();
|
|
49
|
+
return (_jsx("div", { className: "jini-message-list__jump", children: _jsxs("button", { type: "button", className: "jini-message-list__jump-btn", onClick: onJump, children: [_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("path", { d: "M12 5v14" }), _jsx("path", { d: "m19 12-7 7-7-7" })] }), t('Jump to latest')] }) }));
|
|
50
|
+
}
|
|
51
|
+
/** One cancelable queue receipt; attachment rendering reuses the sent-message preview owner. */
|
|
52
|
+
function QueuedMessage({ prompt, renderAttachment }) {
|
|
53
|
+
const t = useT();
|
|
54
|
+
const attachments = prompt.attachments ?? [];
|
|
55
|
+
const gallery = useAttachmentGallery({ attachments });
|
|
56
|
+
return (_jsxs("div", { className: "jini-chat-pane__queued", role: "status", "aria-label": t('Message queued — not yet sent, will send once the current run finishes'), "data-testid": "chat-pane-queued", "data-agent-element": "chat-message-pending", "data-agent-role": "region", "data-agent-label": "A message queued to send once the current run finishes", children: [_jsx("div", { children: t('Queued — sends when this reply finishes') }), _jsx("div", { className: "jini-chat-pane__queued-text", children: prompt.text }), attachments.length > 0 && _jsx("div", { className: "jini-message-attachments", children: attachments.map(attachment => _jsx(SentAttachmentButton, { attachment: attachment, onOpen: gallery.open, renderAttachment: renderAttachment }, attachment.path)) }), gallery.opened !== null && _jsx(AttachmentPreviewModal, { attachment: gallery.opened, onClose: gallery.close, navigation: gallery.navigation }), _jsx("div", { className: "jini-chat-pane__queued-actions", children: _jsx("button", { type: "button", className: "jini-chat-pane__queued-cancel", onClick: () => prompt.onCancel(), title: t('Cancel queued message'), children: t('Cancel') }) })] }));
|
|
57
|
+
}
|
|
34
58
|
//# sourceMappingURL=MessageList.js.map
|
|
@@ -63,7 +63,7 @@ export interface MessageRowProps {
|
|
|
63
63
|
* included (a host's "Done line" display choice, 2026-10-11). */
|
|
64
64
|
showUsageLine?: boolean;
|
|
65
65
|
/** How tool calls show (`../tool-call-display.ts`): every card (`'show'`, the default), one
|
|
66
|
-
* fold-up line per turn (`'collapsed'`), or
|
|
66
|
+
* fold-up line per turn (`'collapsed'`), or compact unrecovered failures (`'hidden'`). */
|
|
67
67
|
toolCallDisplay?: ToolCallDisplay;
|
|
68
68
|
/** Show the model's reasoning (`thinking` events) as a fold-up block where it happened. Off by
|
|
69
69
|
* default — the transcript never showed it before this option. Needs the interleaved layout; a
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { projectAcceptedInputEvents } from '../../core/accepted-input.js';
|
|
3
|
+
import { MidRunUserMessage, MID_RUN_USER_MESSAGE_EXT_EVENT_NAME } from './MidRunUserMessage.js';
|
|
2
4
|
import { terminalMessageNotice, continuingRunNotice } from '../../core/durable-projection.js';
|
|
3
5
|
import { usageLayout, usageLineMeetsActions, useMessageUsage, useUsageSummary } from './MessageRow.usage.js';
|
|
4
6
|
/**
|
|
@@ -51,8 +53,8 @@ import { usageLayout, usageLineMeetsActions, useMessageUsage, useUsageSummary }
|
|
|
51
53
|
* not appear until the whole message is done, not merely until its current
|
|
52
54
|
* chunk stopped arriving.
|
|
53
55
|
*/
|
|
54
|
-
import React, { memo } from 'react';
|
|
55
|
-
import { isAwaitingAnswer, isTerminalRunStatus, splitOnQuestionForms, stripArtifact } from '../../core/index.js';
|
|
56
|
+
import React, { memo, useMemo } from 'react';
|
|
57
|
+
import { assistantContentFromEvents, isAwaitingAnswer, isTerminalRunStatus, splitOnQuestionForms, stripArtifact } from '../../core/index.js';
|
|
56
58
|
import { useToolTimeline } from '../hooks/useToolTimeline.js';
|
|
57
59
|
import { useExtEventGroups } from '../hooks/useExtEventGroups.js';
|
|
58
60
|
import { useCopyToClipboard } from '../hooks/useCopyToClipboard.js';
|
|
@@ -116,16 +118,25 @@ function UsageSummary({ usage, showTotal }) {
|
|
|
116
118
|
return null;
|
|
117
119
|
return (_jsxs("div", { className: "jini-message-usage", title: title, role: "note", "aria-label": ariaLabel, children: [_jsx("span", { className: "jini-message-usage-dot", "aria-hidden": true, children: "\u25CF" }), t('Done'), " \u00B7 ", text] }));
|
|
118
120
|
}
|
|
119
|
-
function messageBlocks({
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
function messageBlocks({ message, rows, registry, content = message.content }, { toolCallDisplay = 'show', includeThinking = false } = {}) {
|
|
122
|
+
const blocks = interleaveMessageBlocks({ events: message.events, content, rows }, { slotOf: (ev) => registry.slot({ name: ev.name, data: ev.data }), includeThinking });
|
|
123
|
+
return blocks ? applyToolCallDisplay({ blocks, mode: toolCallDisplay }, { runFailed: message.runStatus === 'failed' }) : null;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The flat layout's tool groups. Even when host text prevents interleaving, event ordering can still
|
|
127
|
+
* prove tool recovery and reply boundaries, so hidden mode reconstructs the blocks from the events —
|
|
128
|
+
* only for this display decision; the host's text is never replaced. Otherwise one flat list.
|
|
129
|
+
* @complexity O(n) in events + rows.
|
|
130
|
+
*/
|
|
131
|
+
function flatToolGroups({ message, rows, registry, displayBlocks }, { toolCallDisplay }) {
|
|
132
|
+
if (toolCallDisplay === 'hidden') {
|
|
133
|
+
const recovery = displayBlocks
|
|
134
|
+
?? messageBlocks({ message, rows, registry, content: assistantContentFromEvents({ events: message.events ?? [] }) }, { toolCallDisplay });
|
|
135
|
+
if (recovery)
|
|
136
|
+
return recovery.flatMap((block) => (block.kind === 'tools' ? [{ rows: block.rows, key: block.key }] : []));
|
|
137
|
+
}
|
|
138
|
+
const flat = toolRowsForDisplay({ rows, mode: toolCallDisplay }, { runFailed: message.runStatus === 'failed' });
|
|
139
|
+
return flat.length > 0 ? [{ rows: flat, key: 'tools' }] : [];
|
|
129
140
|
}
|
|
130
141
|
/** The model's reasoning, folded under a "Thinking" summary so it never crowds the answer. */
|
|
131
142
|
function ThinkingBlock({ text }) {
|
|
@@ -164,50 +175,66 @@ const NO_ATTACHMENTS = [];
|
|
|
164
175
|
* re-renders per composer keystroke) only re-renders rows whose props actually changed. Messages
|
|
165
176
|
* are replaced, never mutated, so identity is a sound change signal.
|
|
166
177
|
*/
|
|
167
|
-
export const MessageRow = memo(function MessageRow({
|
|
178
|
+
export const MessageRow = memo(function MessageRow({ showUsageTotal = true, showUsageLine = true, toolCallDisplay = 'show', showThinking = false, runStreaming = false, runSucceeded = false, questionFormInteractive = false, ...props }) {
|
|
179
|
+
const message = useMemo(() => props.message.events === undefined ? props.message : {
|
|
180
|
+
...props.message, events: projectAcceptedInputEvents({ events: props.message.events }),
|
|
181
|
+
}, [props.message]);
|
|
182
|
+
if (message.role === 'user')
|
|
183
|
+
return _jsx(UserMessageRow, { ...props, message: message });
|
|
184
|
+
return _jsx(AssistantMessageRow, { ...props, message: message, showUsageTotal, showUsageLine, toolCallDisplay, showThinking, runStreaming, runSucceeded, questionFormInteractive });
|
|
185
|
+
});
|
|
186
|
+
function UserMessageRow({ message, renderAttachment }) {
|
|
168
187
|
const t = useT();
|
|
169
|
-
const registry = useExtEventRendererRegistry();
|
|
170
|
-
const messageUsage = useMessageUsage({ message, ...(usageSummary === undefined ? {} : { summary: usageSummary }) });
|
|
171
|
-
const timeline = useToolTimeline({ events: message.events }, { runStreaming, runSucceeded });
|
|
172
|
-
const extGroups = useExtEventGroups({ events: message.events }, { slotOf: (name, data) => registry.slot({ name, data }) });
|
|
173
|
-
const answeredCards = useAnsweredCards({ slotOf: registry.slot });
|
|
174
188
|
// Which attachment chip (if any) this row's preview modal is open for. Row-local, not
|
|
175
189
|
// module-level: two attachments in the same message can never be "open" at once, but two
|
|
176
190
|
// different messages opening their own attachments independently is fine and expected.
|
|
177
191
|
// Previous/next steps through this message's images only.
|
|
178
192
|
const gallery = useAttachmentGallery({ attachments: message.attachments ?? NO_ATTACHMENTS });
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
193
|
+
return (_jsxs("div", { className: "jini-message jini-message-user", "data-message-id": message.id, "data-agent-element": `chat-message-${message.id}`, "data-agent-role": "region", "data-agent-label": "A message from the user", children: [message.attachments && message.attachments.length > 0 ? (_jsx("div", { className: "jini-message-attachments", children: message.attachments.map((a) => (_jsx(SentAttachmentButton, { attachment: a, onOpen: gallery.open, renderAttachment: renderAttachment }, a.path))) })) : null, _jsx("div", { className: "jini-message-content", children: message.content }), _jsx("div", { className: "jini-message-actions jini-message-actions--user", children: _jsx(CopyMessageButton, { text: message.content, label: t('Copy message') }) }), gallery.opened ? (_jsx(AttachmentPreviewModal, { attachment: gallery.opened, onClose: gallery.close, navigation: gallery.navigation })) : null] }));
|
|
194
|
+
}
|
|
195
|
+
function AssistantMessageRow({ message, usageSummary, showUsageTotal, showUsageLine, toolCallDisplay, showThinking, runStreaming, runSucceeded, questionFormInteractive, questionFormSubmittedAnswers, onQuestionFormSubmit, projectFileNames, onRequestOpenFile, }) {
|
|
196
|
+
const registry = useExtEventRendererRegistry();
|
|
197
|
+
const messageUsage = useMessageUsage({ message, ...(usageSummary === undefined ? {} : { summary: usageSummary }) });
|
|
198
|
+
const timeline = useToolTimeline({ events: message.events }, { runStreaming, runSucceeded });
|
|
199
|
+
const extGroups = useExtEventGroups({ events: message.events }, { slotOf: (name, data) => registry.slot({ name, data }) });
|
|
200
|
+
const answeredCards = useAnsweredCards({ slotOf: registry.slot, events: message.events ?? [], groups: extGroups, runStreaming, runStatus: message.runStatus });
|
|
182
201
|
const terminalNotice = terminalMessageNotice({ message }, {});
|
|
183
202
|
const visibleContent = stripArtifact({ content: message.content });
|
|
184
|
-
const
|
|
185
|
-
|
|
203
|
+
const displayBlocks = messageBlocks({ message, rows: timeline.rows, registry }, { toolCallDisplay, includeThinking: showThinking });
|
|
204
|
+
// Artifact stripping trims/replaces text, invalidating the event offsets for rendering. Keep the
|
|
205
|
+
// flat layout there; displayBlocks still supplies the recovery decision from the original events.
|
|
206
|
+
const blocks = visibleContent === message.content ? displayBlocks : null;
|
|
186
207
|
const pending = isPendingWithNoContent(message, visibleContent, timeline.rows.length);
|
|
187
208
|
const runInProgress = isRunInProgress(message.runStatus);
|
|
188
209
|
const awaitingAnswer = messageAwaitingAnswer({ runStreaming, runInProgress, events: message.events }, { isAnsweredCard: answeredCards.isAnsweredCard });
|
|
189
210
|
const usage = usageLayout({ usage: messageUsage, interleaved: blocks !== null, runInProgress }, { runStreaming });
|
|
190
211
|
const footerUsage = showUsageLine ? usage.footer : undefined;
|
|
191
212
|
const renderToolCard = (row) => (_jsx(ToolCard, { use: row.use, result: row.result, runStreaming: runStreaming, runSucceeded: runSucceeded, ...(projectFileNames !== undefined ? { projectFileNames } : {}), ...(onRequestOpenFile !== undefined ? { onRequestOpenFile } : {}) }, row.id));
|
|
192
|
-
//
|
|
193
|
-
const renderTools = (rows, key) => (toolCallDisplay
|
|
194
|
-
? _jsx(ToolCallGroup, { rows: rows, renderRow: renderToolCard }, key)
|
|
213
|
+
// Hidden mode reuses the same disclosure and card renderer for unrecovered failures.
|
|
214
|
+
const renderTools = (rows, key) => (toolCallDisplay !== 'show'
|
|
215
|
+
? _jsx(ToolCallGroup, { rows: rows, renderRow: renderToolCard, failureOnly: toolCallDisplay === 'hidden' }, key)
|
|
195
216
|
: _jsx("div", { className: "jini-message-tools", children: rows.map(renderToolCard) }, key));
|
|
196
|
-
const
|
|
217
|
+
const flatTools = flatToolGroups({ message, rows: timeline.rows, registry, displayBlocks }, { toolCallDisplay });
|
|
197
218
|
const renderExtGroup = (group) => {
|
|
198
219
|
const renderer = registry.get({ name: group.name });
|
|
220
|
+
if (!renderer && group.name === MID_RUN_USER_MESSAGE_EXT_EVENT_NAME)
|
|
221
|
+
return _jsx(MidRunUserMessage, { events: group.events });
|
|
199
222
|
if (!renderer)
|
|
200
223
|
return null;
|
|
224
|
+
const answerText = answeredCards.answerText(group.slot);
|
|
201
225
|
const node = renderer({
|
|
202
226
|
name: group.name,
|
|
203
227
|
events: group.events,
|
|
204
|
-
runStreaming,
|
|
228
|
+
runStreaming: answeredCards.runStreaming,
|
|
205
229
|
runSucceeded,
|
|
206
230
|
awaitingAnswer,
|
|
207
231
|
runId: message.runId,
|
|
208
232
|
...(group.call === undefined ? {} : { call: group.call }),
|
|
209
233
|
answered: answeredCards.isAnswered(group.slot),
|
|
210
234
|
onAnswered: () => answeredCards.markAnswered(group.slot),
|
|
235
|
+
onAnswerText: (text) => answeredCards.markAnswered(group.slot, text),
|
|
236
|
+
...(answeredCards.isRecorded(group.slot) ? { answerRecorded: true } : {}),
|
|
237
|
+
...(answerText === undefined ? {} : { answerText }),
|
|
211
238
|
});
|
|
212
239
|
if (!node)
|
|
213
240
|
return null;
|
|
@@ -218,28 +245,29 @@ export const MessageRow = memo(function MessageRow({ message, usageSummary, show
|
|
|
218
245
|
_jsx(ExtEventErrorBoundary, { name: group.name, children: _jsx("div", { children: node }) }, `${group.slot}:${group.events.length}`));
|
|
219
246
|
};
|
|
220
247
|
const renderTextSegments = (text, keyPrefix) => splitOnQuestionForms({ input: text }).map((segment, i) => segment.kind === 'text' ? (segment.text.trim() ? (_jsx("div", { className: "jini-message-content", children: _jsx(Markdown, { children: segment.text }) }, `${keyPrefix}-${i}`)) : null) : (_jsx(QuestionForm, { form: segment.form, interactive: questionFormInteractive, ...(questionFormSubmittedAnswers !== undefined ? { submittedAnswers: questionFormSubmittedAnswers } : {}), ...(onQuestionFormSubmit !== undefined ? { onSubmit: onQuestionFormSubmit } : {}) }, `${keyPrefix}-${i}`)));
|
|
248
|
+
const renderBlock = (block) => {
|
|
249
|
+
if (block.kind === 'text') {
|
|
250
|
+
return _jsx(React.Fragment, { children: renderTextSegments(block.text, block.key) }, block.key);
|
|
251
|
+
}
|
|
252
|
+
if (block.kind === 'reply-end') {
|
|
253
|
+
const reply = usage.replies[block.index];
|
|
254
|
+
return reply && showUsageLine ? _jsx(UsageSummary, { usage: reply, showTotal: showUsageTotal }, block.key) : null;
|
|
255
|
+
}
|
|
256
|
+
if (block.kind === 'tools')
|
|
257
|
+
return renderTools(block.rows, block.key);
|
|
258
|
+
if (block.kind === 'thinking')
|
|
259
|
+
return _jsx(ThinkingBlock, { text: block.text }, block.key);
|
|
260
|
+
const group = extGroups.find((g) => g.slot === block.slot);
|
|
261
|
+
if (!group)
|
|
262
|
+
return null;
|
|
263
|
+
return (_jsx("div", { className: "jini-message-ext-events", children: renderExtGroup(group) }, block.key));
|
|
264
|
+
};
|
|
221
265
|
return (_jsxs("div", { className: usageLineMeetsActions({ footer: footerUsage, terminalNotice, pending, runInProgress }, { showTotal: showUsageTotal })
|
|
222
266
|
? 'jini-message jini-message-assistant jini-message-assistant--usage-actions'
|
|
223
267
|
: 'jini-message jini-message-assistant', "data-message-id": message.id, "data-run-status": message.runStatus, "data-agent-element": `chat-message-${message.id}`, "data-agent-role": "region", "data-agent-label": "A reply from the assistant", children: [message.agentName ? _jsx("div", { className: "jini-message-agent", children: message.agentName }) : null, blocks
|
|
224
|
-
? blocks.map(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
if (block.kind === 'reply-end') {
|
|
229
|
-
const reply = usage.replies[block.index];
|
|
230
|
-
return reply && showUsageLine ? _jsx(UsageSummary, { usage: reply, showTotal: showUsageTotal }, block.key) : null;
|
|
231
|
-
}
|
|
232
|
-
if (block.kind === 'tools')
|
|
233
|
-
return renderTools(block.rows, block.key);
|
|
234
|
-
if (block.kind === 'thinking')
|
|
235
|
-
return _jsx(ThinkingBlock, { text: block.text }, block.key);
|
|
236
|
-
const group = extGroups.find((g) => g.slot === block.slot);
|
|
237
|
-
if (!group)
|
|
238
|
-
return null;
|
|
239
|
-
return (_jsx("div", { className: "jini-message-ext-events", children: renderExtGroup(group) }, block.key));
|
|
240
|
-
})
|
|
241
|
-
: (_jsxs(_Fragment, { children: [segments.map((segment, i) => segment.kind === 'text' ? (segment.text.trim() ? (_jsx("div", { className: "jini-message-content", children: _jsx(Markdown, { children: segment.text }) }, i)) : null) : (_jsx(QuestionForm, { form: segment.form, interactive: questionFormInteractive, ...(questionFormSubmittedAnswers !== undefined ? { submittedAnswers: questionFormSubmittedAnswers } : {}), ...(onQuestionFormSubmit !== undefined ? { onSubmit: onQuestionFormSubmit } : {}) }, i))), flatToolRows.length > 0 ? renderTools(flatToolRows) : null, extGroups.length > 0 ? _jsx("div", { className: "jini-message-ext-events", children: extGroups.map(renderExtGroup) }) : null] })), _jsx(AssistantMessageFooter, { message, usageEvent: footerUsage, showUsageTotal, terminalNotice, pending, runInProgress, runStreaming, visibleContent, isAnsweredCard: answeredCards.isAnsweredCard })] }));
|
|
242
|
-
});
|
|
268
|
+
? blocks.map(renderBlock)
|
|
269
|
+
: (_jsxs(_Fragment, { children: [renderTextSegments(visibleContent, 'text'), flatTools.map((group) => renderTools(group.rows, group.key)), extGroups.length > 0 ? _jsx("div", { className: "jini-message-ext-events", children: extGroups.map(renderExtGroup) }) : null] })), _jsx(AssistantMessageFooter, { message, usageEvent: footerUsage, showUsageTotal, terminalNotice, pending, runInProgress, runStreaming, visibleContent, isAnsweredCard: answeredCards.isAnsweredCard })] }));
|
|
270
|
+
}
|
|
243
271
|
/**
|
|
244
272
|
* The live "what is it doing" line under a running reply (see `core/run-activity.ts`). Replaces the
|
|
245
273
|
* old "Thinking…" placeholder, which vanished for good as soon as the first tool row appeared.
|
|
@@ -33,19 +33,24 @@ export function usageLineMeetsActions({ footer, terminalNotice, pending, runInPr
|
|
|
33
33
|
* the current turn's cost is unknown. `showTotal: false` turns the total off. */
|
|
34
34
|
export function useUsageSummary({ usage }, { showTotal = true } = {}) {
|
|
35
35
|
const t = useT();
|
|
36
|
-
const parts = []
|
|
37
|
-
|
|
38
|
-
const seconds = Math.round(usage.durationMs / 1000);
|
|
39
|
-
parts.push(seconds >= 60 ? `${Math.floor(seconds / 60)}m ${seconds % 60}s` : `${seconds}s`);
|
|
40
|
-
}
|
|
41
|
-
if (usage.costUsd !== undefined) {
|
|
42
|
-
parts.push(formatUsageCost({ costUsd: usage.costUsd, ...(showTotal ? { totalCostUsd: usage.totalCostUsd ?? usage.costUsd } : {}) }));
|
|
43
|
-
}
|
|
44
|
-
else if (showTotal && usage.totalCostUsd !== undefined) {
|
|
45
|
-
parts.push(`total ${formatUsageCost({ costUsd: usage.totalCostUsd })}`);
|
|
46
|
-
}
|
|
36
|
+
const parts = [usageDuration({ durationMs: usage.durationMs }), usageCost({ usage }, { showTotal })]
|
|
37
|
+
.filter((part) => part !== undefined);
|
|
47
38
|
const text = parts.join(' · ');
|
|
48
39
|
const title = usage.outputTokens === undefined ? undefined : t('{n} out', { n: usage.outputTokens });
|
|
49
40
|
return { text, title, ariaLabel: title === undefined ? undefined : `${t('Done')} · ${text} · ${title}` };
|
|
50
41
|
}
|
|
42
|
+
function usageDuration({ durationMs }, _options = {}) {
|
|
43
|
+
if (durationMs === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
const seconds = Math.round(durationMs / 1000);
|
|
46
|
+
return seconds >= 60 ? `${Math.floor(seconds / 60)}m ${seconds % 60}s` : `${seconds}s`;
|
|
47
|
+
}
|
|
48
|
+
function usageCost({ usage }, { showTotal = true } = {}) {
|
|
49
|
+
if (usage.costUsd !== undefined) {
|
|
50
|
+
return formatUsageCost({ costUsd: usage.costUsd, ...(showTotal ? { totalCostUsd: usage.totalCostUsd ?? usage.costUsd } : {}) });
|
|
51
|
+
}
|
|
52
|
+
if (showTotal && usage.totalCostUsd !== undefined)
|
|
53
|
+
return `total ${formatUsageCost({ costUsd: usage.totalCostUsd })}`;
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
51
56
|
//# sourceMappingURL=MessageRow.usage.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* @module MidRunUserMessage
|
|
4
4
|
*
|
|
@@ -10,6 +10,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
10
10
|
* It is an event of the assistant turn, not a `ChatMessage` of its own, because the agent folded it
|
|
11
11
|
* into the turn it was already running — there is no separate reply to hang off it.
|
|
12
12
|
*/
|
|
13
|
+
import { acceptedInputEvent } from '../../core/accepted-input.js';
|
|
14
|
+
import { SentAttachmentButton } from './SentAttachment.js';
|
|
15
|
+
import { AttachmentPreviewModal } from './AttachmentPreviewModal.js';
|
|
16
|
+
import { useAttachmentGallery } from './AttachmentGallery.hooks.js';
|
|
13
17
|
import { MID_RUN_USER_MESSAGE_EVENT_NAME } from '../../core/turn-usage.js';
|
|
14
18
|
import { registerExtEventRenderer } from '../ext-event-renderer-registry.js';
|
|
15
19
|
/** The ext-event name the daemon's mid-run `user_message` run event arrives under. One source with
|
|
@@ -25,10 +29,19 @@ export function midRunUserMessageSlotKey({ data }) {
|
|
|
25
29
|
}
|
|
26
30
|
/** The bubble for one mid-run message — the same classes as a sent user message, so it reads as one. */
|
|
27
31
|
export function MidRunUserMessage({ events }) {
|
|
28
|
-
const
|
|
32
|
+
const data = events.at(-1);
|
|
33
|
+
const text = field(data, 'text');
|
|
29
34
|
if (text === undefined)
|
|
30
35
|
return null;
|
|
31
|
-
|
|
36
|
+
const receipt = acceptedInputEvent({ event: { kind: 'ext', name: MID_RUN_USER_MESSAGE_EVENT_NAME, data } });
|
|
37
|
+
// MOVE: A single live delivery may contain several queued inputs; display their original receipts.
|
|
38
|
+
const inputs = receipt?.data.inputs ?? [{ id: field(data, 'id') ?? 'mid-run', text }];
|
|
39
|
+
return _jsx(_Fragment, { children: inputs.map(input => _jsx(MidRunInput, { input: input }, input.id)) });
|
|
40
|
+
}
|
|
41
|
+
function MidRunInput({ input }) {
|
|
42
|
+
const attachments = input.attachments ?? [];
|
|
43
|
+
const gallery = useAttachmentGallery({ attachments });
|
|
44
|
+
return (_jsxs("div", { className: "jini-message jini-message-user jini-message-user--mid-run", "data-agent-role": "region", "data-agent-label": "A message from the user, sent while the agent was working", children: [_jsx("div", { className: "jini-message-content", children: input.text }), attachments.length > 0 && _jsx("div", { className: "jini-message-attachments", children: attachments.map(attachment => _jsx(SentAttachmentButton, { attachment: attachment, onOpen: gallery.open }, attachment.path)) }), gallery.opened !== null && _jsx(AttachmentPreviewModal, { attachment: gallery.opened, onClose: gallery.close, navigation: gallery.navigation })] }));
|
|
32
45
|
}
|
|
33
46
|
/**
|
|
34
47
|
* Registers {@link MidRunUserMessage} against the ext-event registry.
|