@jini-ai/chat 0.3.11 → 0.5.1
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 +130 -0
- package/README.md +152 -6
- package/dist/core/ag-ui/entry.d.ts +53 -0
- package/dist/core/ag-ui/entry.js +135 -0
- package/dist/core/agentic/chat-capabilities.d.ts +1 -1
- package/dist/core/agentic/index.d.ts +1 -1
- package/dist/core/agentic/index.js +1 -1
- package/dist/core/assistant-content.d.ts +6 -2
- package/dist/core/assistant-content.js +2 -2
- package/dist/core/compact-events.d.ts +3 -1
- package/dist/core/compact-events.js +1 -1
- package/dist/core/events.d.ts +1 -1
- package/dist/core/events.js +1 -1
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +3 -3
- package/dist/core/messages.d.ts +4 -2
- package/dist/core/messages.js +2 -2
- package/dist/core/partial-json.d.ts +6 -2
- package/dist/core/partial-json.js +3 -3
- package/dist/core/persistence/index.d.ts +1 -1
- package/dist/core/persistence/index.js +1 -1
- package/dist/core/persistence/ports.d.ts +2 -123
- package/dist/core/persistence/title.d.ts +3 -1
- package/dist/core/persistence/title.js +1 -1
- package/dist/core/question-form/format.d.ts +16 -7
- package/dist/core/question-form/format.js +3 -3
- package/dist/core/question-form/normalize.d.ts +7 -2
- package/dist/core/question-form/normalize.js +5 -5
- package/dist/core/question-form/scan.d.ts +18 -6
- package/dist/core/question-form/scan.js +14 -14
- package/dist/core/run-activity.d.ts +16 -4
- package/dist/core/run-activity.js +11 -11
- package/dist/core/run-events/entry.d.ts +121 -0
- package/dist/core/run-events/entry.js +290 -0
- package/dist/core/todos.d.ts +20 -8
- package/dist/core/todos.js +11 -11
- package/dist/core/tool-events.d.ts +3 -1
- package/dist/core/tool-events.js +1 -1
- package/dist/core/tool-output.d.ts +3 -1
- package/dist/core/tool-output.js +1 -1
- package/dist/core/tools.d.ts +14 -3
- package/dist/core/tools.js +3 -3
- package/dist/core/transcript.d.ts +13 -5
- package/dist/core/transcript.js +5 -5
- package/dist/core/util/manifest.d.ts +9 -3
- package/dist/core/util/manifest.js +3 -3
- package/dist/core/util/markdown-context.d.ts +11 -3
- package/dist/core/util/markdown-context.js +3 -3
- package/dist/core/util/markup-attributes.d.ts +3 -1
- package/dist/core/util/markup-attributes.js +1 -1
- package/dist/core/util/parser.d.ts +9 -5
- package/dist/core/util/parser.js +13 -13
- package/dist/core/util/recover.d.ts +6 -2
- package/dist/core/util/recover.js +6 -6
- package/dist/core/util/strip.d.ts +19 -5
- package/dist/core/util/strip.js +17 -17
- package/dist/core/util/validate.d.ts +3 -1
- package/dist/core/util/validate.js +1 -1
- package/dist/embed-widget/browser/page-actions.d.ts +121 -0
- package/dist/embed-widget/browser/page-actions.js +113 -0
- package/dist/embed-widget/browser/session-store.d.ts +44 -0
- package/dist/embed-widget/browser/session-store.js +103 -0
- package/dist/embed-widget/react/embed.d.ts +45 -0
- package/dist/embed-widget/react/embed.js +87 -0
- package/dist/embed-widget/react/header.d.ts +21 -0
- package/dist/embed-widget/react/header.js +18 -0
- package/dist/embed-widget/transports/fetch-sse.d.ts +75 -0
- package/dist/embed-widget/transports/fetch-sse.js +175 -0
- package/dist/react/agent-bridge/frontend-session-bridge.d.ts +15 -3
- package/dist/react/agent-bridge/frontend-session-bridge.js +13 -6
- package/dist/react/artifact-types.d.ts +3 -3
- package/dist/react/artifact-types.js +3 -3
- package/dist/react/components/A2uiSurfaceCard.js +34 -13
- package/dist/react/components/AttachmentPreviewModal.hooks.d.ts +7 -2
- package/dist/react/components/AttachmentPreviewModal.hooks.js +5 -5
- package/dist/react/components/AttachmentPreviewModal.js +2 -2
- package/dist/react/components/AttachmentTray.d.ts +3 -1
- package/dist/react/components/AttachmentTray.js +2 -2
- package/dist/react/components/ChatFab.js +1 -1
- package/dist/react/components/Composer.js +9 -9
- package/dist/react/components/ConversationList.js +1 -1
- package/dist/react/components/McpUiSurfaceCard.d.ts +4 -2
- package/dist/react/components/McpUiSurfaceCard.js +5 -5
- package/dist/react/components/MessageList.js +1 -1
- package/dist/react/components/MessageRow.js +9 -9
- package/dist/react/components/QuestionForm.js +5 -5
- package/dist/react/components/ToolCard.js +9 -9
- package/dist/react/components/composer-discovery.d.ts +29 -8
- package/dist/react/components/composer-discovery.js +10 -10
- package/dist/react/ext-event-renderer-registry.d.ts +15 -5
- package/dist/react/ext-event-renderer-registry.js +4 -4
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.d.ts +3 -1
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +3 -3
- package/dist/react/features/chat-pane/components/ChatPane.js +43 -43
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.d.ts +4 -1
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +5 -35
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.d.ts +5 -2
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +1 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +38 -10
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +15 -15
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +31 -31
- package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.d.ts +3 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.js +3 -3
- package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.d.ts +4 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.js +1 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +30 -30
- package/dist/react/features/chat-pane/hooks/useChatPaneWorkingDirectory.hooks.js +57 -57
- package/dist/react/features/chat-pane/rules.d.ts +13 -4
- package/dist/react/features/chat-pane/rules.js +5 -5
- package/dist/react/features/chat-pane/types.d.ts +1 -1
- package/dist/react/hooks/attachment-preview-cache.d.ts +7 -2
- package/dist/react/hooks/attachment-preview-cache.js +2 -2
- package/dist/react/hooks/composer-draft-cache.d.ts +17 -5
- package/dist/react/hooks/composer-draft-cache.js +6 -6
- package/dist/react/hooks/useArtifactStream.d.ts +5 -1
- package/dist/react/hooks/useArtifactStream.js +3 -3
- package/dist/react/hooks/useChatFabDrag.d.ts +9 -4
- package/dist/react/hooks/useChatFabDrag.js +4 -4
- package/dist/react/hooks/useComposer.js +8 -8
- package/dist/react/hooks/useConversation.js +56 -4
- package/dist/react/hooks/useExtEventGroups.d.ts +5 -1
- package/dist/react/hooks/useExtEventGroups.js +1 -1
- package/dist/react/hooks/useLatestOperation.d.ts +8 -3
- package/dist/react/hooks/useLatestOperation.js +4 -4
- package/dist/react/hooks/usePinnedTodos.d.ts +3 -1
- package/dist/react/hooks/usePinnedTodos.js +3 -3
- package/dist/react/hooks/useQuestionForms.d.ts +7 -2
- package/dist/react/hooks/useQuestionForms.js +7 -7
- package/dist/react/hooks/useRunActivity.d.ts +6 -1
- package/dist/react/hooks/useRunActivity.js +3 -3
- package/dist/react/hooks/useRunStream.d.ts +3 -1
- package/dist/react/hooks/useRunStream.js +1 -1
- package/dist/react/hooks/useToolTimeline.d.ts +3 -1
- package/dist/react/hooks/useToolTimeline.js +3 -3
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +6 -6
- package/dist/react/message-blocks.d.ts +10 -4
- package/dist/react/message-blocks.js +2 -2
- package/dist/react/styles/reference.css +3 -3
- package/dist/react/tool-renderer-registry.d.ts +8 -3
- package/dist/react/tool-renderer-registry.js +3 -3
- package/dist/react/util/defined-props.d.ts +3 -1
- package/dist/react/util/defined-props.js +1 -1
- package/dist/server/run-finalizer/entry.d.ts +47 -0
- package/dist/server/run-finalizer/entry.js +156 -0
- package/dist/server/run-finalizer/ports.d.ts +52 -0
- package/dist/store/contracts/index.d.ts +5 -0
- package/dist/store/contracts/index.js +5 -0
- package/dist/store/errors.d.ts +12 -0
- package/dist/store/errors.js +18 -0
- package/dist/store/index.d.ts +4 -0
- package/dist/store/index.js +2 -0
- package/dist/store/legacy/index.d.ts +3 -0
- package/dist/store/legacy/index.js +2 -0
- package/dist/store/legacy/sqlite/conversations.d.ts +129 -0
- package/dist/store/legacy/sqlite/conversations.js +274 -0
- package/dist/store/legacy/sqlite/index.d.ts +6 -0
- package/dist/store/legacy/sqlite/index.js +5 -0
- package/dist/store/legacy/sqlite/messages.d.ts +156 -0
- package/dist/store/legacy/sqlite/messages.js +246 -0
- package/dist/store/legacy/sqlite/schema.d.ts +3 -0
- package/dist/store/legacy/sqlite/schema.js +40 -0
- package/dist/store/pglite/index.d.ts +21 -0
- package/dist/store/pglite/index.js +17 -0
- package/dist/store/ports.d.ts +121 -0
- package/dist/store/ports.js +2 -0
- package/dist/store/postgres/index.d.ts +21 -0
- package/dist/store/postgres/index.js +17 -0
- package/dist/store/sql/boundary.d.ts +28 -0
- package/dist/store/sql/boundary.js +32 -0
- package/dist/store/sql/cursor.d.ts +24 -0
- package/dist/store/sql/cursor.js +49 -0
- package/dist/store/sql/maintenance.d.ts +9 -0
- package/dist/store/sql/maintenance.js +16 -0
- package/dist/store/sql/store.d.ts +22 -0
- package/dist/store/sql/store.js +340 -0
- package/dist/store/sql/tables.d.ts +34 -0
- package/dist/store/sql/tables.js +2 -0
- package/dist/store/sqlite/index.d.ts +36 -0
- package/dist/store/sqlite/index.js +29 -0
- package/dist/store/sqlite/schema.d.ts +49 -0
- package/dist/store/sqlite/schema.js +80 -0
- package/package.json +211 -14
- package/dist/react/features/model-picker/components/CredentialStatusBadge.d.ts +0 -7
- package/dist/react/features/model-picker/components/CredentialStatusBadge.js +0 -20
- package/dist/react/features/model-picker/components/ModelPicker.d.ts +0 -9
- package/dist/react/features/model-picker/components/ModelPicker.js +0 -34
- package/dist/react/features/model-picker/constants.d.ts +0 -6
- package/dist/react/features/model-picker/constants.js +0 -9
- package/dist/react/features/model-picker/dependencies.d.ts +0 -11
- package/dist/react/features/model-picker/dependencies.js +0 -10
- package/dist/react/features/model-picker/hooks/useModelPicker.hooks.d.ts +0 -27
- package/dist/react/features/model-picker/hooks/useModelPicker.hooks.js +0 -62
- package/dist/react/features/model-picker/index.d.ts +0 -32
- package/dist/react/features/model-picker/index.js +0 -29
- package/dist/react/features/model-picker/ports.d.ts +0 -11
- package/dist/react/features/model-picker/rules.d.ts +0 -26
- package/dist/react/features/model-picker/rules.js +0 -84
- package/dist/react/features/model-picker/types.d.ts +0 -38
- package/dist/react/features/model-picker/types.js +0 -2
- package/dist/react/poc-mcp-ui-official/PocMcpUiCard.d.ts +0 -9
- package/dist/react/poc-mcp-ui-official/PocMcpUiCard.js +0 -29
- package/dist/react/poc-mcp-ui-official/create-poc-resource.d.ts +0 -30
- package/dist/react/poc-mcp-ui-official/create-poc-resource.js +0 -44
- /package/dist/{react/features/model-picker → server/run-finalizer}/ports.js +0 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/** Default JSON wire request is an explicit host choice, never a hidden endpoint default. O(history). */
|
|
2
|
+
export function buildJsonChatRequest({ message, history }) {
|
|
3
|
+
return { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message, history }) };
|
|
4
|
+
}
|
|
5
|
+
/** Maps the text/error/end/directive JSON protocol with required directive vocabulary and copy. O(data). */
|
|
6
|
+
export function mapJsonChatSseFrame({ frame, directiveEventName, streamError }) {
|
|
7
|
+
if (!frame.data)
|
|
8
|
+
return {};
|
|
9
|
+
const data = JSON.parse(frame.data);
|
|
10
|
+
const obj = typeof data === 'object' && data !== null ? data : {};
|
|
11
|
+
if (frame.event === 'text') {
|
|
12
|
+
if (typeof obj.delta !== 'string')
|
|
13
|
+
throw new Error('Invalid text frame');
|
|
14
|
+
return { event: { kind: 'text', text: obj.delta } };
|
|
15
|
+
}
|
|
16
|
+
if (frame.event === 'error')
|
|
17
|
+
return { error: new Error(typeof obj.message === 'string' && obj.message ? obj.message : streamError) };
|
|
18
|
+
if (frame.event === 'end')
|
|
19
|
+
return { done: true };
|
|
20
|
+
if (frame.event === directiveEventName)
|
|
21
|
+
return { event: { kind: 'ext', name: directiveEventName, data } };
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
async function* responseChunks(body, signal) {
|
|
25
|
+
const reader = body.getReader();
|
|
26
|
+
const cancel = () => { void reader.cancel().catch(() => { }); };
|
|
27
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
28
|
+
if (signal.aborted)
|
|
29
|
+
cancel();
|
|
30
|
+
try {
|
|
31
|
+
for (;;) {
|
|
32
|
+
const { done, value } = await reader.read();
|
|
33
|
+
if (done)
|
|
34
|
+
return;
|
|
35
|
+
yield value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
signal.removeEventListener('abort', cancel);
|
|
40
|
+
try {
|
|
41
|
+
await reader.cancel();
|
|
42
|
+
}
|
|
43
|
+
catch { /* Already closed or aborted. */ }
|
|
44
|
+
reader.releaseLock();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates a cancellable, one-POST-per-run transport with no reattachable server state.
|
|
49
|
+
* Uses the host-bound SSE decoder rather than implementing another parser. Every run
|
|
50
|
+
* owns its controller, listeners and event log; cancellation and end settle once and
|
|
51
|
+
* prevent late events. Input and cancelSignal aborts release readers/listeners, and frames after
|
|
52
|
+
* end cannot mutate the settled reply. A later settlement clears an earlier pending directive.
|
|
53
|
+
* Default request bytes retain the {message, history} wire shape; a host requestBuilder may carry
|
|
54
|
+
* required agent identity differently. No browser module imports a Node-backed runtime barrel.
|
|
55
|
+
* The returned positional methods implement the existing ChatTransport.
|
|
56
|
+
* @complexity O(history + streamed bytes + emitted events); O(emitted events + decoder buffer) space.
|
|
57
|
+
*/
|
|
58
|
+
export function createFetchSseTransport(args, options = {}) {
|
|
59
|
+
for (const limit of [args.maxHistoryMessages, args.maxHistoryMessageChars]) {
|
|
60
|
+
if (!Number.isSafeInteger(limit) || limit < 0)
|
|
61
|
+
throw new Error('Invalid history limit');
|
|
62
|
+
}
|
|
63
|
+
if (!args.endpoint || !args.agentId)
|
|
64
|
+
throw new Error('Endpoint and agent identity are required');
|
|
65
|
+
const activeRuns = new Map();
|
|
66
|
+
const observe = (runId, phase) => {
|
|
67
|
+
try {
|
|
68
|
+
options.onLifecycle?.({ runId, phase });
|
|
69
|
+
}
|
|
70
|
+
catch { /* Telemetry cannot break a run. */ }
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
async startRun(input, handlers) {
|
|
74
|
+
let latestIndex = input.history.length - 1;
|
|
75
|
+
while (latestIndex >= 0 && input.history[latestIndex]?.role !== 'user')
|
|
76
|
+
latestIndex -= 1;
|
|
77
|
+
const message = input.history[latestIndex]?.content;
|
|
78
|
+
if (!message)
|
|
79
|
+
throw new Error(args.copy.noUserMessage);
|
|
80
|
+
const history = (args.maxHistoryMessages === 0 ? [] : input.history.slice(Math.max(0, latestIndex - args.maxHistoryMessages), latestIndex))
|
|
81
|
+
.filter(m => m.content.trim().length > 0)
|
|
82
|
+
.map(m => ({ role: m.role, content: m.content.slice(0, args.maxHistoryMessageChars) }));
|
|
83
|
+
// Build before registering a run, so a synchronous host-builder failure leaks no state.
|
|
84
|
+
const request = args.requestBuilder({ endpoint: args.endpoint, agentId: args.agentId, message, history, input });
|
|
85
|
+
const runId = args.ids({});
|
|
86
|
+
if (!runId || activeRuns.has(runId))
|
|
87
|
+
throw new Error('Run ID must be unique and nonempty');
|
|
88
|
+
const controller = options.createAbortController?.({}) ?? new AbortController();
|
|
89
|
+
activeRuns.set(runId, controller);
|
|
90
|
+
const collected = [];
|
|
91
|
+
let settled = false;
|
|
92
|
+
const signals = [input.signal, ...(input.cancelSignal ? [input.cancelSignal] : [])];
|
|
93
|
+
const finish = () => {
|
|
94
|
+
if (settled)
|
|
95
|
+
return;
|
|
96
|
+
settled = true;
|
|
97
|
+
activeRuns.delete(runId);
|
|
98
|
+
for (const signal of signals)
|
|
99
|
+
signal.removeEventListener('abort', abort);
|
|
100
|
+
controller.signal.removeEventListener('abort', onAborted);
|
|
101
|
+
observe(runId, 'settled');
|
|
102
|
+
handlers.onDone([...collected]);
|
|
103
|
+
};
|
|
104
|
+
const onAborted = () => { observe(runId, 'aborted'); finish(); };
|
|
105
|
+
const abort = () => controller.abort();
|
|
106
|
+
controller.signal.addEventListener('abort', onAborted, { once: true });
|
|
107
|
+
for (const signal of signals)
|
|
108
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
109
|
+
observe(runId, 'started');
|
|
110
|
+
if (signals.some(signal => signal.aborted))
|
|
111
|
+
abort();
|
|
112
|
+
void (async () => {
|
|
113
|
+
try {
|
|
114
|
+
if (settled)
|
|
115
|
+
return;
|
|
116
|
+
const response = await args.fetch(args.endpoint, { ...request, signal: controller.signal });
|
|
117
|
+
if (settled) {
|
|
118
|
+
try {
|
|
119
|
+
await response.body?.cancel();
|
|
120
|
+
}
|
|
121
|
+
catch { /* closed body */ }
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
const body = await response.json().catch(() => null);
|
|
126
|
+
if (settled)
|
|
127
|
+
return;
|
|
128
|
+
const error = typeof body === 'object' && body !== null && 'error' in body && typeof body.error === 'string' && body.error
|
|
129
|
+
? body.error : args.copy.requestFailed({ status: response.status });
|
|
130
|
+
observe(runId, 'error');
|
|
131
|
+
handlers.onError(new Error(error));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!response.body)
|
|
135
|
+
throw new Error(args.copy.missingBody);
|
|
136
|
+
for await (const frame of args.decode({ source: responseChunks(response.body, controller.signal) })) {
|
|
137
|
+
if (settled)
|
|
138
|
+
break;
|
|
139
|
+
const result = args.frameMapper({ frame });
|
|
140
|
+
if (result.event) {
|
|
141
|
+
collected.push(result.event);
|
|
142
|
+
handlers.onEvent(result.event);
|
|
143
|
+
}
|
|
144
|
+
if (settled)
|
|
145
|
+
break;
|
|
146
|
+
if (result.error) {
|
|
147
|
+
observe(runId, 'error');
|
|
148
|
+
handlers.onError(result.error);
|
|
149
|
+
}
|
|
150
|
+
if (settled)
|
|
151
|
+
break;
|
|
152
|
+
if (result.done) {
|
|
153
|
+
finish();
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (!settled && !controller.signal.aborted) {
|
|
160
|
+
observe(runId, 'error');
|
|
161
|
+
handlers.onError(error instanceof Error ? error : new Error(String(error)));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
finish();
|
|
166
|
+
}
|
|
167
|
+
})();
|
|
168
|
+
return { runId };
|
|
169
|
+
},
|
|
170
|
+
async reattachRun(_runId, handlers) { handlers.onDone([]); },
|
|
171
|
+
async fetchRunStatus() { return null; },
|
|
172
|
+
async stopRun(runId) { activeRuns.get(runId)?.abort(); },
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=fetch-sse.js.map
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { type PageDriver } from '@jini-ai/agentic';
|
|
2
2
|
import type { ChatPaneAgentBridgeAccess, ChatPaneAgentToolAction } from '../features/chat-pane/types.js';
|
|
3
3
|
export interface FrontendSessionBridgeOptions {
|
|
4
|
-
/** Daemon origin. Defaults to same-origin, which is how the dev server proxies `/api`. */
|
|
5
|
-
readonly baseUrl?: string;
|
|
6
4
|
/** Supplying one claims the six `page.*` verbs. Omit for a chat-only surface. */
|
|
7
5
|
readonly pageDriver?: PageDriver;
|
|
8
6
|
/**
|
|
@@ -15,6 +13,20 @@ export interface FrontendSessionBridgeOptions {
|
|
|
15
13
|
/** Reported for failures that are nobody's tool call: stream errors, malformed frames. */
|
|
16
14
|
readonly onError?: (error: unknown) => void;
|
|
17
15
|
}
|
|
16
|
+
/** The host supplies its origin and stream transport; native fetch defaults may be overridden. */
|
|
17
|
+
export interface FrontendSessionBridgeArgs {
|
|
18
|
+
/** Pass an empty string for the same-origin proxy used by a browser host. */
|
|
19
|
+
readonly baseUrl: string;
|
|
20
|
+
readonly openStream: (args: {
|
|
21
|
+
url: string;
|
|
22
|
+
}) => EventSource;
|
|
23
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
24
|
+
readonly request?: (args: {
|
|
25
|
+
url: string;
|
|
26
|
+
init: RequestInit;
|
|
27
|
+
timeoutMs: number;
|
|
28
|
+
}) => Promise<Response>;
|
|
29
|
+
}
|
|
18
30
|
export interface FrontendSessionBridge {
|
|
19
31
|
/** Hand this to `ChatPane`'s `agentControl.bridgeAccess`. */
|
|
20
32
|
readonly bridgeAccess: ChatPaneAgentBridgeAccess;
|
|
@@ -41,5 +53,5 @@ export interface FrontendSessionBridge {
|
|
|
41
53
|
* @param options - What this page can serve and where the daemon is.
|
|
42
54
|
* @returns The pane's bridge, the attach result, and a teardown.
|
|
43
55
|
*/
|
|
44
|
-
export declare function createFrontendSessionBridge(options?: FrontendSessionBridgeOptions): FrontendSessionBridge;
|
|
56
|
+
export declare function createFrontendSessionBridge({ baseUrl: base, openStream, request, fetch }: FrontendSessionBridgeArgs, options?: FrontendSessionBridgeOptions): FrontendSessionBridge;
|
|
45
57
|
//# sourceMappingURL=frontend-session-bridge.d.ts.map
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
* {@link FrontendSessionBridge.bindToken} answers "what is valid now", and only the latter
|
|
35
35
|
* belongs in a run request.
|
|
36
36
|
*/
|
|
37
|
-
import { FETCH_TIMEOUT_MS, fetchWithTimeout } from '@jini-ai/platform/fetch-with-timeout';
|
|
38
37
|
import { CHAT_CAPABILITIES } from '../../core/index.js';
|
|
39
38
|
import { PAGE_CAPABILITIES, executePageCapability } from '@jini-ai/agentic';
|
|
40
39
|
/** How many settled invocation ids to remember for replay suppression. */
|
|
@@ -59,9 +58,13 @@ function errorMessage(error) {
|
|
|
59
58
|
* @param options - What this page can serve and where the daemon is.
|
|
60
59
|
* @returns The pane's bridge, the attach result, and a teardown.
|
|
61
60
|
*/
|
|
62
|
-
export function createFrontendSessionBridge(options = {}) {
|
|
63
|
-
const base = options.baseUrl ?? '';
|
|
61
|
+
export function createFrontendSessionBridge({ baseUrl: base, openStream, request, fetch }, options = {}) {
|
|
64
62
|
const capabilities = claimedCapabilities(options);
|
|
63
|
+
const send = request ?? (({ url, init, timeoutMs }) => {
|
|
64
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
65
|
+
const signal = init.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
66
|
+
return (fetch ?? globalThis.fetch)(url, { ...init, signal });
|
|
67
|
+
});
|
|
65
68
|
let sessionId;
|
|
66
69
|
/** Replaced on every reattach; a promise resolved once could never carry this. */
|
|
67
70
|
let currentBindToken;
|
|
@@ -79,11 +82,15 @@ export function createFrontendSessionBridge(options = {}) {
|
|
|
79
82
|
/** Invocation ids already run here, so a stream reconnect cannot double-click a button. */
|
|
80
83
|
const executed = new Set();
|
|
81
84
|
const query = capabilities.map((id) => `capability=${encodeURIComponent(id)}`).join('&');
|
|
82
|
-
const source =
|
|
85
|
+
const source = openStream({ url: `${base}/api/frontend-sessions/stream?${query}` });
|
|
83
86
|
async function respond(invocationId, body) {
|
|
84
87
|
if (sessionId === undefined)
|
|
85
88
|
throw new Error('the surface is not attached yet');
|
|
86
|
-
const response = await
|
|
89
|
+
const response = await send({
|
|
90
|
+
url: `${base}/api/frontend-sessions/${encodeURIComponent(sessionId)}/responses`,
|
|
91
|
+
init: { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ invocationId, ...body }) },
|
|
92
|
+
timeoutMs: 15_000,
|
|
93
|
+
});
|
|
87
94
|
if (!response.ok)
|
|
88
95
|
throw new Error(`answering "${invocationId}" failed: ${response.status}`);
|
|
89
96
|
}
|
|
@@ -94,7 +101,7 @@ export function createFrontendSessionBridge(options = {}) {
|
|
|
94
101
|
let output;
|
|
95
102
|
try {
|
|
96
103
|
if (options.pageDriver !== undefined && action.capabilityId.startsWith('page.')) {
|
|
97
|
-
output = await executePageCapability(options.pageDriver, action.capabilityId, action.input);
|
|
104
|
+
output = await executePageCapability({ driver: options.pageDriver, capabilityId: action.capabilityId, input: action.input });
|
|
98
105
|
}
|
|
99
106
|
else {
|
|
100
107
|
const entry = Object.entries(options.executors ?? {})
|
|
@@ -42,8 +42,8 @@ export interface ArtifactRenderMatch {
|
|
|
42
42
|
file: ArtifactFile;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* A minimal ordered registry of `ArtifactRenderer`s:
|
|
46
|
-
* first-matched (later registrations
|
|
45
|
+
* A minimal ordered registry of `ArtifactRenderer`s: newest-registered,
|
|
46
|
+
* first-matched (later registrations override earlier matches by registering
|
|
47
47
|
* before resolution, mirroring the tool-renderer registry's "last writer
|
|
48
48
|
* wins on the same id" convention is intentionally NOT used here — artifact
|
|
49
49
|
* renderers are matched by predicate, not by name, so ordering is the
|
|
@@ -53,7 +53,7 @@ export declare class RendererRegistry {
|
|
|
53
53
|
private readonly renderers;
|
|
54
54
|
/** Registers `renderer`, returning an unregister handle. */
|
|
55
55
|
register(renderer: ArtifactRenderer): () => void;
|
|
56
|
-
/** The
|
|
56
|
+
/** The newest registered renderer whose `canRender` accepts `ctx`, or `null`. */
|
|
57
57
|
resolve(ctx: ArtifactRenderContext): ArtifactRenderMatch | null;
|
|
58
58
|
/** Visible mainly for tests. */
|
|
59
59
|
list(): readonly ArtifactRenderer[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A minimal ordered registry of `ArtifactRenderer`s:
|
|
3
|
-
* first-matched (later registrations
|
|
2
|
+
* A minimal ordered registry of `ArtifactRenderer`s: newest-registered,
|
|
3
|
+
* first-matched (later registrations override earlier matches by registering
|
|
4
4
|
* before resolution, mirroring the tool-renderer registry's "last writer
|
|
5
5
|
* wins on the same id" convention is intentionally NOT used here — artifact
|
|
6
6
|
* renderers are matched by predicate, not by name, so ordering is the
|
|
@@ -17,7 +17,7 @@ export class RendererRegistry {
|
|
|
17
17
|
this.renderers.splice(idx, 1);
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
/** The
|
|
20
|
+
/** The newest registered renderer whose `canRender` accepts `ctx`, or `null`. */
|
|
21
21
|
resolve(ctx) {
|
|
22
22
|
for (const renderer of this.renderers) {
|
|
23
23
|
if (renderer.canRender(ctx))
|
|
@@ -32,12 +32,16 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
32
32
|
* surfaces as a visible, honest notice instead of vanishing or hitting the wrong endpoint.
|
|
33
33
|
*/
|
|
34
34
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
35
|
-
import { createA2uiInterpreter, createLabCatalog } from '@jini-ai/agentic/a2ui';
|
|
35
|
+
import { createA2uiInterpreter, createLabCatalog, DynamicValueSchema } from '@jini-ai/agentic/a2ui';
|
|
36
|
+
import { createSystemClock } from '@jini-ai/core/primitives';
|
|
36
37
|
import { buildA2uiCatalogFromRegistry } from '@jini-ai/ui/a2ui';
|
|
37
38
|
import { DEFAULT_INTERACTIVE_UI_REGISTRY } from '@jini-ai/ui/interactive-ui';
|
|
38
39
|
import { useT } from '../hooks/context.js';
|
|
39
40
|
/** Every agent→renderer message shape carries `surfaceId` under exactly one of these keys. */
|
|
40
41
|
const SURFACE_ID_KEYS = ['createSurface', 'updateComponents', 'updateDataModel', 'deleteSurface'];
|
|
42
|
+
// Preserve the interpreter's former module-wide sequence so separate cards cannot reuse an
|
|
43
|
+
// action ID in the same millisecond; the wall-clock suffix keeps the existing wire format.
|
|
44
|
+
let actionIdCounter = 0;
|
|
41
45
|
function extractSurfaceId(message) {
|
|
42
46
|
if (typeof message !== 'object' || message === null)
|
|
43
47
|
return undefined;
|
|
@@ -54,7 +58,7 @@ function RenderComponent({ interpreter, surfaceId, componentId, ancestors, onAct
|
|
|
54
58
|
if (ancestors.has(componentId)) {
|
|
55
59
|
return (_jsxs("span", { className: "a2ui-placeholder a2ui-placeholder-cycle", "data-a2ui-status": "cycle", "data-a2ui-component-id": componentId, children: ["\u27F2 circular reference at \u201C", componentId, "\u201D"] }));
|
|
56
60
|
}
|
|
57
|
-
const surface = interpreter.getSurface(surfaceId);
|
|
61
|
+
const surface = interpreter.getSurface({ surfaceId });
|
|
58
62
|
const component = surface?.components.get(componentId);
|
|
59
63
|
if (!component) {
|
|
60
64
|
return (_jsxs("span", { className: "a2ui-placeholder a2ui-placeholder-missing", "data-a2ui-status": "missing", "data-a2ui-component-id": componentId, children: ["\u26A0 missing component \u201C", componentId, "\u201D"] }));
|
|
@@ -63,7 +67,9 @@ function RenderComponent({ interpreter, surfaceId, componentId, ancestors, onAct
|
|
|
63
67
|
nextAncestors.add(componentId);
|
|
64
68
|
const childProps = { interpreter, surfaceId, ancestors: nextAncestors, onAction };
|
|
65
69
|
function resolveText(value) {
|
|
66
|
-
|
|
70
|
+
// Text props already passed the catalog schema. Parsing preserves that wire type at this
|
|
71
|
+
// Record<string, unknown> boundary without asserting a shape the compiler cannot verify.
|
|
72
|
+
const result = interpreter.resolve({ surfaceId, value: DynamicValueSchema.parse(value) });
|
|
67
73
|
if (!result.ok)
|
|
68
74
|
return `⚠ unresolved (${result.reason})`;
|
|
69
75
|
return typeof result.value === 'string' ? result.value : JSON.stringify(result.value);
|
|
@@ -104,7 +110,7 @@ function RenderComponent({ interpreter, surfaceId, componentId, ancestors, onAct
|
|
|
104
110
|
// `buildA2uiCatalogFromRegistry` call below. Resolved against the SAME registry
|
|
105
111
|
// `@jini-ai/ui`'s own `A2uiSurfaceRenderer` uses, so a type addable to the catalog and
|
|
106
112
|
// renderable there is addable and renderable here too.
|
|
107
|
-
const entry = DEFAULT_INTERACTIVE_UI_REGISTRY.resolveById(component.component);
|
|
113
|
+
const entry = DEFAULT_INTERACTIVE_UI_REGISTRY.resolveById({ id: component.component });
|
|
108
114
|
if (!entry) {
|
|
109
115
|
return (_jsxs("span", { className: "a2ui-placeholder", "data-a2ui-status": "unrenderable-type", children: ["\u26A0 no renderer yet for component type \u201C", component.component, "\u201D"] }));
|
|
110
116
|
}
|
|
@@ -137,15 +143,26 @@ export function A2uiSurfaceCard({ events, runId, onAgentAction }) {
|
|
|
137
143
|
const t = useT();
|
|
138
144
|
// Merges `DEFAULT_INTERACTIVE_UI_REGISTRY` (shadcn/recharts) into the lab catalog, keyed under
|
|
139
145
|
// the SAME `catalogId` the lab catalog already uses — so a tool built against
|
|
140
|
-
// `createLabCatalog().catalogId` (e.g. `demo-a2ui-tool.ts`) still validates unchanged, and now
|
|
146
|
+
// `createLabCatalog({}).catalogId` (e.g. `demo-a2ui-tool.ts`) still validates unchanged, and now
|
|
141
147
|
// ALSO gains every registry component as a real, addable/removable catalog entry.
|
|
142
148
|
const catalog = useMemo(() => {
|
|
143
|
-
const base = createLabCatalog();
|
|
144
|
-
return buildA2uiCatalogFromRegistry(DEFAULT_INTERACTIVE_UI_REGISTRY, base.catalogId, { base });
|
|
149
|
+
const base = createLabCatalog({});
|
|
150
|
+
return buildA2uiCatalogFromRegistry({ registry: DEFAULT_INTERACTIVE_UI_REGISTRY, catalogId: base.catalogId }, { base });
|
|
145
151
|
}, []);
|
|
146
152
|
const interpreterRef = useRef(null);
|
|
147
|
-
if (!interpreterRef.current)
|
|
148
|
-
|
|
153
|
+
if (!interpreterRef.current) {
|
|
154
|
+
const clock = createSystemClock();
|
|
155
|
+
interpreterRef.current = createA2uiInterpreter({
|
|
156
|
+
catalog,
|
|
157
|
+
clock,
|
|
158
|
+
ids: {
|
|
159
|
+
next: (_required) => {
|
|
160
|
+
actionIdCounter += 1;
|
|
161
|
+
return `a2ui-action-${actionIdCounter}-${clock.nowMs().toString(36)}`;
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
149
166
|
const interpreter = interpreterRef.current;
|
|
150
167
|
const [, forceRender] = useState(0);
|
|
151
168
|
const appliedCountRef = useRef(0);
|
|
@@ -157,7 +174,11 @@ export function A2uiSurfaceCard({ events, runId, onAgentAction }) {
|
|
|
157
174
|
const [pendingAgentAction, setPendingAgentAction] = useState(null);
|
|
158
175
|
const [localActionResult, setLocalActionResult] = useState(null);
|
|
159
176
|
const [deliveryFailureNotice, setDeliveryFailureNotice] = useState(null);
|
|
160
|
-
useEffect(() =>
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
const unsubscribe = interpreter.subscribe({ listener: () => forceRender((n) => n + 1) });
|
|
179
|
+
// React invokes cleanup with no arguments; the interpreter's port takes a required bag.
|
|
180
|
+
return () => unsubscribe({});
|
|
181
|
+
}, [interpreter]);
|
|
161
182
|
useEffect(() => {
|
|
162
183
|
while (appliedCountRef.current < events.length) {
|
|
163
184
|
const message = events[appliedCountRef.current];
|
|
@@ -165,7 +186,7 @@ export function A2uiSurfaceCard({ events, runId, onAgentAction }) {
|
|
|
165
186
|
const surfaceId = extractSurfaceId(message);
|
|
166
187
|
if (surfaceId)
|
|
167
188
|
surfaceIdRef.current = surfaceId;
|
|
168
|
-
const result = interpreter.applyAgentMessage(message);
|
|
189
|
+
const result = interpreter.applyAgentMessage({ raw: message });
|
|
169
190
|
if (result.unattributedViolation) {
|
|
170
191
|
setRefusalNotice(result.unattributedViolation);
|
|
171
192
|
}
|
|
@@ -200,13 +221,13 @@ export function A2uiSurfaceCard({ events, runId, onAgentAction }) {
|
|
|
200
221
|
// the same way `handleAction`'s own (non-effect) call site already gets one on every render.
|
|
201
222
|
}, [events, interpreter, onAgentAction, runId]);
|
|
202
223
|
const surfaceId = surfaceIdRef.current;
|
|
203
|
-
const root = surfaceId ? interpreter.getRoot(surfaceId) : undefined;
|
|
224
|
+
const root = surfaceId ? interpreter.getRoot({ surfaceId }) : undefined;
|
|
204
225
|
function handleAction(componentId) {
|
|
205
226
|
// `surfaceId` is non-null here by construction, not by luck: this callback is only ever reached
|
|
206
227
|
// from a `Button` inside the rendered tree, and nothing renders until `root` resolves — which
|
|
207
228
|
// the `!root` early return below only permits when `surfaceId` was already truthy. Asserted
|
|
208
229
|
// rather than re-checked, exactly as the `RenderComponent` call site below already does.
|
|
209
|
-
const built = interpreter.buildAction(surfaceId, componentId);
|
|
230
|
+
const built = interpreter.buildAction({ surfaceId: surfaceId, componentId });
|
|
210
231
|
if (!built.ok) {
|
|
211
232
|
setRefusalNotice(built.reason);
|
|
212
233
|
return;
|
|
@@ -42,7 +42,9 @@ export type AttachmentPreviewStatus = 'image' | 'text' | 'unsupported';
|
|
|
42
42
|
/** Hard cap on rendered text-preview characters, so a large attached log/CSV cannot hang the tab painting one giant `<pre>`. */
|
|
43
43
|
export declare const MAX_TEXT_PREVIEW_CHARS = 200000;
|
|
44
44
|
/** Exported for the component's own render branch, which needs the same answer without re-deriving it from `status`. */
|
|
45
|
-
export declare function looksLikeImageAttachment(attachment
|
|
45
|
+
export declare function looksLikeImageAttachment({ attachment }: {
|
|
46
|
+
attachment: Pick<ChatAttachment, 'kind' | 'name'>;
|
|
47
|
+
}): boolean;
|
|
46
48
|
export interface AttachmentTextPreview {
|
|
47
49
|
content: string;
|
|
48
50
|
truncated: boolean;
|
|
@@ -66,5 +68,8 @@ export interface AttachmentPreviewController {
|
|
|
66
68
|
* read on mount (bounded in practice by the upload route's own 20 MB per-attachment cap, and by
|
|
67
69
|
* {@link MAX_TEXT_PREVIEW_CHARS} for what actually renders).
|
|
68
70
|
*/
|
|
69
|
-
export declare function useAttachmentPreviewModal(attachment
|
|
71
|
+
export declare function useAttachmentPreviewModal({ attachment, onClose }: {
|
|
72
|
+
attachment: ChatAttachment;
|
|
73
|
+
onClose: () => void;
|
|
74
|
+
}): AttachmentPreviewController;
|
|
70
75
|
//# sourceMappingURL=AttachmentPreviewModal.hooks.d.ts.map
|
|
@@ -50,7 +50,7 @@ function extensionOf(name) {
|
|
|
50
50
|
return dot === -1 ? '' : name.slice(dot + 1).toLowerCase();
|
|
51
51
|
}
|
|
52
52
|
/** Exported for the component's own render branch, which needs the same answer without re-deriving it from `status`. */
|
|
53
|
-
export function looksLikeImageAttachment(attachment) {
|
|
53
|
+
export function looksLikeImageAttachment({ attachment }) {
|
|
54
54
|
return attachment.kind === 'image' || IMAGE_PREVIEW_EXTENSIONS.has(extensionOf(attachment.name));
|
|
55
55
|
}
|
|
56
56
|
function looksLikeTextAttachment(name) {
|
|
@@ -89,15 +89,15 @@ function canCreateObjectUrl() {
|
|
|
89
89
|
* read on mount (bounded in practice by the upload route's own 20 MB per-attachment cap, and by
|
|
90
90
|
* {@link MAX_TEXT_PREVIEW_CHARS} for what actually renders).
|
|
91
91
|
*/
|
|
92
|
-
export function useAttachmentPreviewModal(attachment, onClose) {
|
|
92
|
+
export function useAttachmentPreviewModal({ attachment, onClose }) {
|
|
93
93
|
const dialogRef = useRef(null);
|
|
94
94
|
const triggerRef = useRef(null);
|
|
95
95
|
const [imageFailed, setImageFailed] = useState(false);
|
|
96
96
|
const [imageUrl, setImageUrl] = useState(undefined);
|
|
97
97
|
const [text, setText] = useState(undefined);
|
|
98
98
|
const [textLoading, setTextLoading] = useState(false);
|
|
99
|
-
const cachedFile = getAttachmentPreviewSource(attachment.path);
|
|
100
|
-
const attemptImage = !imageFailed && cachedFile !== undefined && looksLikeImageAttachment(attachment) && canCreateObjectUrl();
|
|
99
|
+
const cachedFile = getAttachmentPreviewSource({ path: attachment.path });
|
|
100
|
+
const attemptImage = !imageFailed && cachedFile !== undefined && looksLikeImageAttachment({ attachment: attachment }) && canCreateObjectUrl();
|
|
101
101
|
const attemptText = !attemptImage && cachedFile !== undefined && looksLikeTextAttachment(attachment.name);
|
|
102
102
|
const status = attemptImage ? 'image' : attemptText ? 'text' : 'unsupported';
|
|
103
103
|
// Object URL: created/revoked in an effect (not `useMemo`) so a React 18 Strict Mode double-render
|
|
@@ -192,7 +192,7 @@ export function useAttachmentPreviewModal(attachment, onClose) {
|
|
|
192
192
|
onImageError: () => setImageFailed(true),
|
|
193
193
|
textLoading,
|
|
194
194
|
text,
|
|
195
|
-
sizeLabel: formatAttachmentSize(attachment.size),
|
|
195
|
+
sizeLabel: formatAttachmentSize({ size: attachment.size }),
|
|
196
196
|
handleNativeCancel,
|
|
197
197
|
handleBackdropClick,
|
|
198
198
|
};
|
|
@@ -12,7 +12,7 @@ import { Icon } from './Icon.js';
|
|
|
12
12
|
import { looksLikeImageAttachment, useAttachmentPreviewModal } from './AttachmentPreviewModal.hooks.js';
|
|
13
13
|
export function AttachmentPreviewModal({ attachment, onClose }) {
|
|
14
14
|
const t = useT();
|
|
15
|
-
const c = useAttachmentPreviewModal(attachment, onClose);
|
|
16
|
-
return (_jsxs("dialog", { ref: c.dialogRef, className: "jini-attachment-preview-dialog", role: "dialog", "aria-modal": "true", "aria-label": t('{name} preview', { name: attachment.name }), onCancel: c.handleNativeCancel, onClick: c.handleBackdropClick, children: [_jsxs("div", { className: "jini-attachment-preview-header", children: [_jsx("span", { className: "jini-attachment-preview-name", title: attachment.name, children: attachment.name }), _jsx("button", { type: "button", className: "jini-attachment-preview-close", onClick: onClose, title: t('Close'), "aria-label": t('Close'), children: _jsx(Icon, { name: "close", size: 16 }) })] }), _jsx("div", { className: "jini-attachment-preview-body", children: c.status === 'image' && c.imageUrl ? (_jsx("img", { src: c.imageUrl, alt: attachment.name, className: "jini-attachment-preview-image", onError: c.onImageError })) : c.status === 'text' ? (c.textLoading ? (_jsx("div", { className: "jini-attachment-preview-loading", children: t('Loading…') })) : (_jsxs(_Fragment, { children: [_jsx("pre", { className: "jini-attachment-preview-text", children: c.text?.content ?? '' }), c.text?.truncated ? (_jsx("div", { className: "jini-attachment-preview-truncated", children: t('Preview truncated — this file is larger than the preview limit.') })) : null] }))) : (_jsxs("div", { className: "jini-attachment-preview-meta", children: [_jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Name') }), _jsx("span", { children: attachment.name })] }), _jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Type') }), _jsx("span", { children: looksLikeImageAttachment(attachment) ? t('Image') : t('File') })] }), c.sizeLabel ? (_jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Size') }), _jsx("span", { children: c.sizeLabel })] })) : null, _jsx("div", { className: "jini-attachment-preview-meta-note", children: t('A preview is not available for this file.') })] })) })] }));
|
|
15
|
+
const c = useAttachmentPreviewModal({ attachment: attachment, onClose: onClose });
|
|
16
|
+
return (_jsxs("dialog", { ref: c.dialogRef, className: "jini-attachment-preview-dialog", role: "dialog", "aria-modal": "true", "aria-label": t('{name} preview', { name: attachment.name }), onCancel: c.handleNativeCancel, onClick: c.handleBackdropClick, children: [_jsxs("div", { className: "jini-attachment-preview-header", children: [_jsx("span", { className: "jini-attachment-preview-name", title: attachment.name, children: attachment.name }), _jsx("button", { type: "button", className: "jini-attachment-preview-close", onClick: onClose, title: t('Close'), "aria-label": t('Close'), children: _jsx(Icon, { name: "close", size: 16 }) })] }), _jsx("div", { className: "jini-attachment-preview-body", children: c.status === 'image' && c.imageUrl ? (_jsx("img", { src: c.imageUrl, alt: attachment.name, className: "jini-attachment-preview-image", onError: c.onImageError })) : c.status === 'text' ? (c.textLoading ? (_jsx("div", { className: "jini-attachment-preview-loading", children: t('Loading…') })) : (_jsxs(_Fragment, { children: [_jsx("pre", { className: "jini-attachment-preview-text", children: c.text?.content ?? '' }), c.text?.truncated ? (_jsx("div", { className: "jini-attachment-preview-truncated", children: t('Preview truncated — this file is larger than the preview limit.') })) : null] }))) : (_jsxs("div", { className: "jini-attachment-preview-meta", children: [_jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Name') }), _jsx("span", { children: attachment.name })] }), _jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Type') }), _jsx("span", { children: looksLikeImageAttachment({ attachment: attachment }) ? t('Image') : t('File') })] }), c.sizeLabel ? (_jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Size') }), _jsx("span", { children: c.sizeLabel })] })) : null, _jsx("div", { className: "jini-attachment-preview-meta-note", children: t('A preview is not available for this file.') })] })) })] }));
|
|
17
17
|
}
|
|
18
18
|
//# sourceMappingURL=AttachmentPreviewModal.js.map
|
|
@@ -2,5 +2,7 @@ import type { AttachmentTraySlot } from '../slots.js';
|
|
|
2
2
|
export type AttachmentTrayProps = AttachmentTraySlot;
|
|
3
3
|
export declare function AttachmentTray({ attachments, onRemove, renderItem }: AttachmentTrayProps): import("react").JSX.Element | null;
|
|
4
4
|
/** Formats optional byte counts for the compact composer attachment chip. */
|
|
5
|
-
export declare function formatAttachmentSize(size
|
|
5
|
+
export declare function formatAttachmentSize({ size }: {
|
|
6
|
+
size: number | undefined;
|
|
7
|
+
}): string | null;
|
|
6
8
|
//# sourceMappingURL=AttachmentTray.d.ts.map
|
|
@@ -8,11 +8,11 @@ export function AttachmentTray({ attachments, onRemove, renderItem }) {
|
|
|
8
8
|
return (_jsx("div", { className: "jini-attachment-tray", "data-testid": "composer-attachment-tray", children: attachments.map((a) => (_jsxs("div", { className: "jini-attachment-chip", "data-testid": "attachment-chip", "data-attachment-kind": a.kind, "data-attachment-name": a.name, children: [renderItem ? renderItem(a) : _jsx(DefaultAttachmentChip, { attachment: a }), _jsx("button", { type: "button", className: "jini-attachment-remove", onClick: () => onRemove(a.path), title: t('Remove {name}', { name: a.name }), "aria-label": t('Remove {name}', { name: a.name }), children: _jsx(Icon, { name: "close", size: 12 }) })] }, a.path))) }));
|
|
9
9
|
}
|
|
10
10
|
function DefaultAttachmentChip({ attachment }) {
|
|
11
|
-
const size = formatAttachmentSize(attachment.size);
|
|
11
|
+
const size = formatAttachmentSize({ size: attachment.size });
|
|
12
12
|
return (_jsxs("span", { className: "jini-attachment-chip-body", children: [_jsx("span", { className: `jini-attachment-chip-icon is-${attachment.kind}`, children: _jsx(Icon, { name: attachment.kind === 'image' ? 'image' : 'file', size: 15 }) }), _jsxs("span", { className: "jini-attachment-chip-copy", children: [_jsx("span", { className: "jini-attachment-chip-name", title: attachment.name, children: attachment.name }), size ? _jsx("small", { className: "jini-attachment-chip-size", children: size }) : null] })] }));
|
|
13
13
|
}
|
|
14
14
|
/** Formats optional byte counts for the compact composer attachment chip. */
|
|
15
|
-
export function formatAttachmentSize(size) {
|
|
15
|
+
export function formatAttachmentSize({ size }) {
|
|
16
16
|
if (size === undefined || !Number.isFinite(size) || size < 0)
|
|
17
17
|
return null;
|
|
18
18
|
if (size < 1_024)
|
|
@@ -8,7 +8,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
8
8
|
*/
|
|
9
9
|
import { useChatFabDrag } from '../hooks/useChatFabDrag.js';
|
|
10
10
|
export function ChatFab({ open, onToggle, label = 'chat' }) {
|
|
11
|
-
const { buttonRef, position, onPointerDown, onPointerMove, onPointerUp, onPointerCancel } = useChatFabDrag(onToggle);
|
|
11
|
+
const { buttonRef, position, onPointerDown, onPointerMove, onPointerUp, onPointerCancel } = useChatFabDrag({ onClick: onToggle });
|
|
12
12
|
return (_jsx("button", { ref: buttonRef, type: "button", className: `chat-fab${open ? ' chat-fab-open' : ''}`,
|
|
13
13
|
// Not `onClick`: the pointer gesture already decides click-versus-drag, and keeping both
|
|
14
14
|
// would fire the toggle twice on a tap.
|
|
@@ -90,8 +90,8 @@ export function Composer({ composer, onSend, disabled = false, sendDisabled = fa
|
|
|
90
90
|
const resolvedPlaceholder = placeholder ?? t('Send a message…');
|
|
91
91
|
const discoveryGroups = slots?.discoveryGroups ?? [];
|
|
92
92
|
const hasDiscoveryItems = discoveryGroups.some((group) => group.items.length > 0);
|
|
93
|
-
const slashQuery = parseComposerSlashQuery(composer.draft);
|
|
94
|
-
const slashMatches = slashQuery === null ? [] : filterComposerDiscovery(discoveryGroups, slashQuery);
|
|
93
|
+
const slashQuery = parseComposerSlashQuery({ draft: composer.draft });
|
|
94
|
+
const slashMatches = slashQuery === null ? [] : filterComposerDiscovery({ groups: discoveryGroups, query: slashQuery });
|
|
95
95
|
const slashOpen = slashMatches.length > 0 && dismissedSlashDraft !== composer.draft;
|
|
96
96
|
/**
|
|
97
97
|
* A bare "/" intentionally matches every item (`matchesFuzzyCommand`'s own doc) so the palette
|
|
@@ -167,14 +167,14 @@ export function Composer({ composer, onSend, disabled = false, sendDisabled = fa
|
|
|
167
167
|
function update() {
|
|
168
168
|
const rect = composerRoot.getBoundingClientRect();
|
|
169
169
|
if (discoveryMenuOpen)
|
|
170
|
-
setDiscoveryMenuPosition(composerDiscoveryMenuPosition(rect));
|
|
170
|
+
setDiscoveryMenuPosition(composerDiscoveryMenuPosition({ composerRect: rect }));
|
|
171
171
|
if (slashOpen)
|
|
172
|
-
setSlashMenuPosition(composerSlashMenuPosition(rect));
|
|
172
|
+
setSlashMenuPosition(composerSlashMenuPosition({ composerRect: rect }));
|
|
173
173
|
// Shares the discovery menu's own anchor math (same left inset off `.jini-composer`) rather
|
|
174
174
|
// than measuring the working-dir button's own rect — one less measurement to keep in sync,
|
|
175
175
|
// and the two popovers already never open at once (each is gated on its own trigger).
|
|
176
176
|
if (workdirOpen)
|
|
177
|
-
setWorkdirPosition(composerDiscoveryMenuPosition(rect));
|
|
177
|
+
setWorkdirPosition(composerDiscoveryMenuPosition({ composerRect: rect }));
|
|
178
178
|
}
|
|
179
179
|
update();
|
|
180
180
|
window.addEventListener('scroll', update, true);
|
|
@@ -222,7 +222,7 @@ export function Composer({ composer, onSend, disabled = false, sendDisabled = fa
|
|
|
222
222
|
const match = slashMatches[index];
|
|
223
223
|
if (!match)
|
|
224
224
|
return;
|
|
225
|
-
const resolution = resolveComposerSlashInvocation(composer.draft, match.item);
|
|
225
|
+
const resolution = resolveComposerSlashInvocation({ draft: composer.draft, item: match.item });
|
|
226
226
|
if (!resolution)
|
|
227
227
|
return;
|
|
228
228
|
if (resolution.type === 'complete') {
|
|
@@ -235,7 +235,7 @@ export function Composer({ composer, onSend, disabled = false, sendDisabled = fa
|
|
|
235
235
|
}
|
|
236
236
|
let expectedDraft = composer.draft;
|
|
237
237
|
if (!match.item.command) {
|
|
238
|
-
expectedDraft = replaceComposerSlashTrigger(composer.draft, match.item.insertText ?? match.item.label);
|
|
238
|
+
expectedDraft = replaceComposerSlashTrigger({ draft: composer.draft, insertText: match.item.insertText ?? match.item.label });
|
|
239
239
|
composer.setDraft(expectedDraft);
|
|
240
240
|
}
|
|
241
241
|
setDismissedSlashDraft(null);
|
|
@@ -243,7 +243,7 @@ export function Composer({ composer, onSend, disabled = false, sendDisabled = fa
|
|
|
243
243
|
restoreComposerFocus();
|
|
244
244
|
}
|
|
245
245
|
function selectPlusItem(item) {
|
|
246
|
-
const expectedDraft = item.insertText ? appendComposerDiscovery(composer.draft, item.insertText) : composer.draft;
|
|
246
|
+
const expectedDraft = item.insertText ? appendComposerDiscovery({ draft: composer.draft, insertText: item.insertText }) : composer.draft;
|
|
247
247
|
if (item.insertText)
|
|
248
248
|
composer.setDraft(expectedDraft);
|
|
249
249
|
setDiscoveryMenuOpen(false);
|
|
@@ -271,7 +271,7 @@ export function Composer({ composer, onSend, disabled = false, sendDisabled = fa
|
|
|
271
271
|
if (event.nativeEvent.isComposing)
|
|
272
272
|
return;
|
|
273
273
|
if (slashOpen) {
|
|
274
|
-
const action = resolveComposerSlashKeyAction(event.key, event.shiftKey);
|
|
274
|
+
const action = resolveComposerSlashKeyAction({ key: event.key, shiftKey: event.shiftKey });
|
|
275
275
|
if (action.type !== 'none') {
|
|
276
276
|
event.preventDefault();
|
|
277
277
|
if (action.type === 'move') {
|
|
@@ -6,7 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
* into `ChatPane`'s `leadingAccessory` slot — it needs no changes to `ChatPane` itself.
|
|
7
7
|
*
|
|
8
8
|
* Storage-agnostic by construction. It takes data and callbacks and owns nothing durable, so a
|
|
9
|
-
* host backs it with `@jini-ai/sqlite`'s `ChatHistoryStore`, an HTTP endpoint, or an in-memory
|
|
9
|
+
* host backs it with `@jini-ai/sqlite-chat`'s `ChatHistoryStore`, an HTTP endpoint, or an in-memory
|
|
10
10
|
* array without this file knowing the difference.
|
|
11
11
|
*
|
|
12
12
|
* Search is a **client-side filter by default**, and delegates to `onSearch` when a host provides
|
|
@@ -29,10 +29,12 @@ export declare function McpUiSurfaceCard({ events, sandboxProxyUrl, onToolCall,
|
|
|
29
29
|
* The ext-event slot key for MCP-UI: one slot per `ui://` URI. Each resource is its own card, so a
|
|
30
30
|
* run's second card renders where it arrived instead of inside the first card's slot, while a
|
|
31
31
|
* re-emitted resource with the same URI still updates its card in place. Pass it as
|
|
32
|
-
* `registerExtEventRenderer(MCP_UI_EXT_EVENT_NAME, renderer, { slotKey: mcpUiSurfaceSlotKey })`
|
|
32
|
+
* `registerExtEventRenderer({ name: MCP_UI_EXT_EVENT_NAME, renderer }, { slotKey: mcpUiSurfaceSlotKey })`
|
|
33
33
|
* when registering a custom MCP-UI renderer; {@link registerMcpUiSurfaceRenderer} already does.
|
|
34
34
|
*/
|
|
35
|
-
export declare function mcpUiSurfaceSlotKey(data
|
|
35
|
+
export declare function mcpUiSurfaceSlotKey({ data }: {
|
|
36
|
+
data: unknown;
|
|
37
|
+
}): string | undefined;
|
|
36
38
|
/**
|
|
37
39
|
* Registers {@link McpUiSurfaceCard} against the ext-event registry.
|
|
38
40
|
*
|