@k2b/cloud 0.25.0 → 0.27.0
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/package.json +3 -3
- package/src/_internal/capabilities.ts +12 -0
- package/src/_internal/define-app.ts +8 -1
- package/src/_internal/process-identity.ts +7 -1
- package/src/_internal/registry-validation.ts +3 -0
- package/src/_internal/registry.ts +1 -0
- package/src/_internal/runtime-context.ts +1 -0
- package/src/access/GroupCoverage.tsx +175 -0
- package/src/access/PermissionEditor.tsx +119 -99
- package/src/access/messages.ts +30 -0
- package/src/ai/admin.ts +1 -0
- package/src/ai/approval-routes.ts +5 -5
- package/src/ai/browser-code-contracts.ts +14 -2
- package/src/ai/browser.ts +8 -1
- package/src/ai/capabilities.ts +115 -33
- package/src/ai/chat/blocks.tsx +86 -179
- package/src/ai/chat/builtin-tools.tsx +83 -48
- package/src/ai/chat/file-tools.tsx +4 -1
- package/src/ai/chat/live-turn.browser-harness.tsx +44 -0
- package/src/ai/chat/message-actions.tsx +6 -2
- package/src/ai/chat/message-utils.ts +17 -14
- package/src/ai/chat/messages.ts +330 -2
- package/src/ai/chat/presentation.tsx +278 -104
- package/src/ai/chat/tool-groups.ts +55 -35
- package/src/ai/chat/turn-layout.ts +141 -0
- package/src/ai/chat/turn-view.tsx +644 -0
- package/src/ai/client/controller.ts +60 -31
- package/src/ai/client/file-source.ts +20 -3
- package/src/ai/client/projection.ts +42 -6
- package/src/ai/code-mode-skill.ts +27 -27
- package/src/ai/code-runtime-tools.ts +10 -1
- package/src/ai/code-source-contracts.ts +54 -4
- package/src/ai/code-source-tools.ts +10 -3
- package/src/ai/credentials.ts +17 -3
- package/src/ai/data-analysis-skill.ts +2 -2
- package/src/ai/default-tools.ts +2 -2
- package/src/ai/executor.ts +177 -80
- package/src/ai/file-context.ts +14 -2
- package/src/ai/file-tools.ts +17 -3
- package/src/ai/files-store.ts +134 -11
- package/src/ai/grids-skill.ts +2 -2
- package/src/ai/index.ts +7 -0
- package/src/ai/memories.ts +14 -0
- package/src/ai/migrate.ts +125 -0
- package/src/ai/model-request-settings.ts +98 -0
- package/src/ai/protocol.ts +26 -4
- package/src/ai/provider-fetch.ts +67 -15
- package/src/ai/provider-retry.ts +105 -0
- package/src/ai/provider.ts +7 -1
- package/src/ai/quota-provider.ts +16 -7
- package/src/ai/request-headers.ts +117 -0
- package/src/ai/routes.ts +34 -6
- package/src/ai/runtime.ts +1 -1
- package/src/ai/settings.ts +19 -2
- package/src/ai/skill-seeds.ts +31 -3
- package/src/ai/skills.ts +26 -0
- package/src/ai/solid.ts +1 -1
- package/src/ai/store.ts +202 -59
- package/src/ai/stream.ts +182 -37
- package/src/ai/structured.ts +20 -5
- package/src/ai/system-prompt.ts +25 -0
- package/src/ai/timeline.ts +9 -11
- package/src/ai/tool-call-names.ts +45 -0
- package/src/ai/turn-policy.ts +247 -0
- package/src/ai/turn-timing.ts +31 -3
- package/src/ai/types.ts +36 -5
- package/src/api/admin-ai-quotas.ts +36 -1
- package/src/api/admin-core-settings.ts +16 -23
- package/src/api/admin-outgoing-mail.ts +62 -0
- package/src/api/index.ts +2 -0
- package/src/cli/admin/ai-quotas.ts +70 -1
- package/src/cli/admin/index.ts +6 -0
- package/src/cli/admin/outgoing-mail.ts +118 -0
- package/src/contracts/app.ts +2 -0
- package/src/contracts/index.ts +1 -0
- package/src/contracts/outgoing-mail.ts +77 -0
- package/src/contracts/registry.ts +4 -0
- package/src/services/index.ts +3 -0
- package/src/services/notifications/email.ts +16 -26
- package/src/services/outgoing-mail/index.ts +19 -0
- package/src/services/outgoing-mail/store.ts +286 -0
- package/src/services/outgoing-mail/test-send.ts +40 -0
- package/src/services/outgoing-mail/transport.ts +13 -0
- package/src/services/settings/core-settings.ts +1 -38
- package/src/services/settings/store.ts +5 -1
- package/src/shared/ai-model-request-settings.ts +21 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/ai-request-options.ts +185 -0
- package/src/shared/app-presentation.ts +10 -2
- package/src/ssr/admin-navigation.ts +1 -1
- package/src/ssr/platform-messages.ts +2 -0
- package/src/ssr/workspace-navigation.ts +7 -1
- package/src/styles/effects.css +69 -0
package/src/ai/chat/blocks.tsx
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { dates } from "@k2b/stdlib";
|
|
2
2
|
import { mutation } from "@k2b/stdlib/solid";
|
|
3
3
|
import { Button, ButtonLink, Chat, isStructuredDataValue, MarkdownView, SplitButton, StructuredDataPreview, useLocale } from "@k2b/ui";
|
|
4
|
-
import {
|
|
4
|
+
import { createSignal, For, type JSX, Match, Show, Switch, untrack } from "solid-js";
|
|
5
5
|
import type { CapabilityActionReview } from "../../contracts/capabilities";
|
|
6
6
|
import { markdown } from "../../shared";
|
|
7
7
|
import type { AiTurnBlock } from "../protocol";
|
|
8
|
-
import { isRenderableTurnBlock } from "../protocol";
|
|
9
8
|
import { hasSpecializedBuiltinToolView, SpecializedBuiltinToolBlock } from "./builtin-tools";
|
|
10
9
|
import { hasCapabilityTable } from "./capability-result";
|
|
11
10
|
import { CapabilityTablePreview } from "./capability-table";
|
|
@@ -26,8 +25,8 @@ import {
|
|
|
26
25
|
} from "./message-utils";
|
|
27
26
|
import { aiChatMessages } from "./messages";
|
|
28
27
|
import { AssistantMarkdownBlock } from "./primitives";
|
|
29
|
-
import { AiToolActivity
|
|
30
|
-
import {
|
|
28
|
+
import { AiToolActivity } from "./tool-disclosure";
|
|
29
|
+
import { isFailedTool } from "./tool-groups";
|
|
31
30
|
import { CloudCardBlock, CloudSurveyBlock, CloudSurveyResultBlock, CloudTextEditorBlock, CloudTextEditorResultBlock } from "./visual-tools";
|
|
32
31
|
import { FetchFileToolBlock, WebExtractToolBlock, WebSearchToolBlock } from "./web-tools";
|
|
33
32
|
|
|
@@ -78,35 +77,36 @@ function ThinkingBlockView(props: { text: string; streaming?: boolean }) {
|
|
|
78
77
|
);
|
|
79
78
|
}
|
|
80
79
|
|
|
81
|
-
function CompactionBlockView(props: { block: Extract<AiTurnBlock, { kind: "compaction" }> }) {
|
|
80
|
+
export function CompactionBlockView(props: { block: Extract<AiTurnBlock, { kind: "compaction" }> }) {
|
|
82
81
|
const locale = useLocale();
|
|
82
|
+
const t = () => aiChatMessages(locale());
|
|
83
83
|
const status = () => props.block.status;
|
|
84
84
|
const description = () => {
|
|
85
|
-
if (status() === "completed") return
|
|
86
|
-
if (status() === "skipped") return
|
|
87
|
-
if (status() === "failed") return
|
|
88
|
-
return
|
|
85
|
+
if (status() === "completed") return t().compacted;
|
|
86
|
+
if (status() === "skipped") return t().compactionSkipped;
|
|
87
|
+
if (status() === "failed") return t().compactionFailed;
|
|
88
|
+
return t().compacting;
|
|
89
89
|
};
|
|
90
90
|
|
|
91
91
|
return (
|
|
92
|
-
<Show when={status() !== "running"} fallback={<Chat.Activity label=
|
|
92
|
+
<Show when={status() !== "running"} fallback={<Chat.Activity label={t().compacting} icon="ti ti-brain" tone="ai" busy />}>
|
|
93
93
|
<Chat.Activity
|
|
94
|
-
label=
|
|
94
|
+
label={t().showCompaction}
|
|
95
95
|
description={description()}
|
|
96
96
|
icon="ti ti-brain"
|
|
97
97
|
tone={status() === "failed" ? "danger" : "ai"}
|
|
98
98
|
bodyInset={false}
|
|
99
99
|
>
|
|
100
100
|
<div class="w-full min-w-0 rounded-md bg-zinc-100/70 p-2 text-[11px] leading-5 text-secondary [box-shadow:var(--ui-control-recess)] dark:bg-zinc-950/70">
|
|
101
|
-
<Show when={props.block.result} fallback={<p>
|
|
101
|
+
<Show when={props.block.result} fallback={<p>{t().compactionSummary}</p>}>
|
|
102
102
|
{(compactResult) => (
|
|
103
103
|
<dl class="grid grid-cols-2 gap-2">
|
|
104
104
|
<div class="rounded-md bg-white/65 px-2 py-1 dark:bg-white/5">
|
|
105
|
-
<dt class="uppercase tracking-wide text-dimmed">
|
|
105
|
+
<dt class="uppercase tracking-wide text-dimmed">{t().before}</dt>
|
|
106
106
|
<dd class="font-medium text-primary">{compactResult().entriesBefore.toLocaleString(locale())}</dd>
|
|
107
107
|
</div>
|
|
108
108
|
<div class="rounded-md bg-white/65 px-2 py-1 dark:bg-white/5">
|
|
109
|
-
<dt class="uppercase tracking-wide text-dimmed">
|
|
109
|
+
<dt class="uppercase tracking-wide text-dimmed">{t().after}</dt>
|
|
110
110
|
<dd class="font-medium text-primary">{compactResult().entriesAfter.toLocaleString(locale())}</dd>
|
|
111
111
|
</div>
|
|
112
112
|
</dl>
|
|
@@ -158,11 +158,13 @@ function ToolResultDisclosure(props: {
|
|
|
158
158
|
errorLabel?: string;
|
|
159
159
|
errorDescription?: string;
|
|
160
160
|
}) {
|
|
161
|
+
const locale = useLocale();
|
|
162
|
+
const t = () => aiChatMessages(locale());
|
|
161
163
|
return (
|
|
162
164
|
<AiToolActivity
|
|
163
165
|
blockId={props.blockId}
|
|
164
166
|
icon={props.icon ?? (props.isError ? "ti ti-alert-circle" : aiToolIcon(props.toolName))}
|
|
165
|
-
label={props.isError ? (props.errorLabel ??
|
|
167
|
+
label={props.isError ? (props.errorLabel ?? t().toolFailed({ title: props.labelOnError ?? props.name })) : props.name}
|
|
166
168
|
description={props.isError ? props.errorDescription : undefined}
|
|
167
169
|
tone={props.isError ? "danger" : "neutral"}
|
|
168
170
|
accent={props.accent}
|
|
@@ -171,16 +173,18 @@ function ToolResultDisclosure(props: {
|
|
|
171
173
|
>
|
|
172
174
|
<div class="flex w-full min-w-0 flex-col gap-2">
|
|
173
175
|
<Show when={props.args !== undefined}>
|
|
174
|
-
<ToolDetail title=
|
|
176
|
+
<ToolDetail title={t().input} toolName={props.toolName} value={props.args} />
|
|
175
177
|
</Show>
|
|
176
|
-
<ToolDetail title=
|
|
178
|
+
<ToolDetail title={t().response} toolName={props.toolName} value={props.result} />
|
|
177
179
|
</div>
|
|
178
180
|
</AiToolActivity>
|
|
179
181
|
);
|
|
180
182
|
}
|
|
181
183
|
|
|
182
|
-
function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
184
|
+
export function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
183
185
|
const actions = useAiChatActions();
|
|
186
|
+
const locale = useLocale();
|
|
187
|
+
const t = () => aiChatMessages(locale());
|
|
184
188
|
const request = () => ({ turnId: props.turnId, callId: props.block.callId, name: props.block.name });
|
|
185
189
|
const pending = () => props.block.status === "awaiting_approval";
|
|
186
190
|
const actionDisabled = () => actions.actionDisabled?.() ?? false;
|
|
@@ -197,14 +201,17 @@ function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
197
201
|
if (!actionDisabled() && !approval.loading()) void approval.mutate(input);
|
|
198
202
|
};
|
|
199
203
|
const title = () => props.block.presentation?.title ?? displayToolName(props.block.name);
|
|
200
|
-
const ownerName = () => props.block.presentation?.appName ??
|
|
204
|
+
const ownerName = () => props.block.presentation?.appName ?? t().assistant;
|
|
201
205
|
const description = () => {
|
|
202
206
|
const reviewMessage = props.block.approval?.review?.message.trim();
|
|
203
207
|
if (reviewMessage) return reviewMessage;
|
|
204
208
|
const message = props.block.approval?.message?.trim();
|
|
205
209
|
if (!message) return null;
|
|
206
|
-
const duplicateTitle = props.block.presentation ? `${props.block.presentation.appName}: ${props.block.presentation.title}
|
|
207
|
-
const withoutDuplicateTitle =
|
|
210
|
+
const duplicateTitle = props.block.presentation ? `${props.block.presentation.appName}: ${props.block.presentation.title}` : "";
|
|
211
|
+
const withoutDuplicateTitle =
|
|
212
|
+
duplicateTitle && (message === duplicateTitle || message.startsWith(`${duplicateTitle}\n`))
|
|
213
|
+
? message.slice(duplicateTitle.length).trim()
|
|
214
|
+
: message;
|
|
208
215
|
if (/^Review the validated arguments below before running this action\.$/i.test(withoutDuplicateTitle)) return null;
|
|
209
216
|
return withoutDuplicateTitle || null;
|
|
210
217
|
};
|
|
@@ -223,11 +230,11 @@ function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
223
230
|
const reviewLinks = () => props.block.approval?.review?.links ?? [];
|
|
224
231
|
const reviewLinkTitle = (rel: "open" | "edit" | "status" | "preview" | "download") =>
|
|
225
232
|
({
|
|
226
|
-
open:
|
|
227
|
-
edit:
|
|
228
|
-
status:
|
|
229
|
-
preview:
|
|
230
|
-
download:
|
|
233
|
+
open: t().openIn({ app: ownerName() }),
|
|
234
|
+
edit: t().editIn({ app: ownerName() }),
|
|
235
|
+
status: t().statusIn({ app: ownerName() }),
|
|
236
|
+
preview: t().preview,
|
|
237
|
+
download: t().download,
|
|
231
238
|
})[rel];
|
|
232
239
|
const detailData = () => (isStructuredDataValue(props.block.args) ? props.block.args : undefined);
|
|
233
240
|
const appAccent = () => props.block.presentation?.appAccent;
|
|
@@ -237,7 +244,7 @@ function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
237
244
|
<section
|
|
238
245
|
class={`w-full overflow-hidden rounded-xl border border-[var(--k2b-border)] bg-[var(--k2b-surface)] text-sm text-primary ${appAccent() ? "app-accent-scope" : ""}`}
|
|
239
246
|
style={{ "--app-accent": appAccent() }}
|
|
240
|
-
aria-label={
|
|
247
|
+
aria-label={t().approvalRequired({ title: title() })}
|
|
241
248
|
>
|
|
242
249
|
<div class="p-4">
|
|
243
250
|
<div class="flex min-w-0 items-center gap-3">
|
|
@@ -257,7 +264,7 @@ function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
257
264
|
<h3 class="truncate text-sm font-semibold leading-5 text-primary">
|
|
258
265
|
{ownerName()} · {title()}
|
|
259
266
|
</h3>
|
|
260
|
-
<p class="text-xs text-dimmed">
|
|
267
|
+
<p class="text-xs text-dimmed">{t().action}</p>
|
|
261
268
|
</div>
|
|
262
269
|
</div>
|
|
263
270
|
<Show when={reviewLines().length > 0}>
|
|
@@ -300,7 +307,7 @@ function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
300
307
|
class="max-h-72 overflow-auto whitespace-pre-wrap break-words pr-2 font-sans text-xs leading-5 text-secondary"
|
|
301
308
|
role="region"
|
|
302
309
|
tabIndex={0}
|
|
303
|
-
aria-label={
|
|
310
|
+
aria-label={t().contentOf({ label: detail.label })}
|
|
304
311
|
>
|
|
305
312
|
<ReviewDetailValue detail={detail} />
|
|
306
313
|
</pre>
|
|
@@ -321,7 +328,7 @@ function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
321
328
|
data-ai-approval-footer
|
|
322
329
|
>
|
|
323
330
|
<Show when={reviewLinks().length > 0}>
|
|
324
|
-
<nav class="flex flex-wrap gap-1" aria-label={
|
|
331
|
+
<nav class="flex flex-wrap gap-1" aria-label={t().linksFor({ title: title() })}>
|
|
325
332
|
<For each={reviewLinks()}>
|
|
326
333
|
{(link) => (
|
|
327
334
|
<ButtonLink href={link.href} target="_blank" rel="noopener noreferrer" size="xs" variant="ghost">
|
|
@@ -332,24 +339,24 @@ function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
332
339
|
</nav>
|
|
333
340
|
</Show>
|
|
334
341
|
<Show when={approval.error()}>
|
|
335
|
-
<p class="text-xs text-red-700 dark:text-red-300">
|
|
342
|
+
<p class="text-xs text-red-700 dark:text-red-300">{t().approvalFailed}</p>
|
|
336
343
|
</Show>
|
|
337
344
|
<div class="ml-auto shrink-0">
|
|
338
345
|
<Show
|
|
339
346
|
when={pending()}
|
|
340
347
|
fallback={
|
|
341
348
|
<span class="text-xs font-medium text-secondary">
|
|
342
|
-
{title()} · {props.block.status === "rejected" ?
|
|
349
|
+
{title()} · {props.block.status === "rejected" ? t().rejected : t().approved}
|
|
343
350
|
</span>
|
|
344
351
|
}
|
|
345
352
|
>
|
|
346
|
-
<Show when={actions.onApproval} fallback={<span class="text-xs font-medium text-secondary">
|
|
353
|
+
<Show when={actions.onApproval} fallback={<span class="text-xs font-medium text-secondary">{t().approvalUnavailable}</span>}>
|
|
347
354
|
<Show
|
|
348
355
|
when={!actionDisabled()}
|
|
349
356
|
fallback={
|
|
350
357
|
<span class="inline-flex items-center gap-1 text-xs font-medium text-secondary">
|
|
351
358
|
<i class="ti ti-player-stop" aria-hidden="true" />
|
|
352
|
-
|
|
359
|
+
{t().stoppingResponse}
|
|
353
360
|
</span>
|
|
354
361
|
}
|
|
355
362
|
>
|
|
@@ -358,30 +365,30 @@ function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
358
365
|
fallback={
|
|
359
366
|
<span class="inline-flex items-center gap-1 text-xs font-medium text-secondary">
|
|
360
367
|
<i class={`ti ${submitted() ? "ti-check" : "ti-loader-2 animate-spin"}`} aria-hidden="true" />
|
|
361
|
-
{submitted() ?
|
|
368
|
+
{submitted() ? t().submitted : t().submitting}
|
|
362
369
|
</span>
|
|
363
370
|
}
|
|
364
371
|
>
|
|
365
372
|
<div class="flex flex-wrap justify-end gap-1">
|
|
366
373
|
<Button size="xs" variant="ghost" onClick={() => submit({ approved: false })}>
|
|
367
|
-
|
|
374
|
+
{t().reject}
|
|
368
375
|
</Button>
|
|
369
376
|
<SplitButton
|
|
370
377
|
size="xs"
|
|
371
378
|
variant="ai"
|
|
372
379
|
onClick={() => submit({ approved: true })}
|
|
373
|
-
menuLabel={
|
|
380
|
+
menuLabel={t().moreOptions({ title: title() })}
|
|
374
381
|
menuPosition="bottom-right"
|
|
375
382
|
items={[
|
|
376
383
|
{
|
|
377
|
-
label: detailsOpen() ?
|
|
384
|
+
label: detailsOpen() ? t().hideDetails : t().details,
|
|
378
385
|
icon: detailsOpen() ? "ti ti-eye-off" : "ti ti-eye",
|
|
379
386
|
action: () => setDetailsOpen((open) => !open),
|
|
380
387
|
},
|
|
381
388
|
...(props.block.approval?.allowAlways
|
|
382
389
|
? [
|
|
383
390
|
{
|
|
384
|
-
label:
|
|
391
|
+
label: t().alwaysApprove,
|
|
385
392
|
icon: "ti ti-shield-check",
|
|
386
393
|
action: () => submit({ approved: true, remember: "always" }),
|
|
387
394
|
},
|
|
@@ -400,7 +407,7 @@ function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
400
407
|
</footer>
|
|
401
408
|
</section>
|
|
402
409
|
<Show when={detailsOpen()}>
|
|
403
|
-
<div id={detailsId} class="mt-2 w-full" role="region" aria-label={
|
|
410
|
+
<div id={detailsId} class="mt-2 w-full" role="region" aria-label={t().detailsFor({ title: title() })}>
|
|
404
411
|
<Show
|
|
405
412
|
when={detailData()}
|
|
406
413
|
fallback={
|
|
@@ -418,6 +425,8 @@ function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
418
425
|
}
|
|
419
426
|
|
|
420
427
|
function CapabilityToolView(props: { block: ToolBlock }) {
|
|
428
|
+
const locale = useLocale();
|
|
429
|
+
const t = () => aiChatMessages(locale());
|
|
421
430
|
const presentation = () => props.block.presentation!;
|
|
422
431
|
const label = () => presentation().title;
|
|
423
432
|
const result = () => (isRecord(props.block.result) ? props.block.result : null);
|
|
@@ -453,8 +462,8 @@ function CapabilityToolView(props: { block: ToolBlock }) {
|
|
|
453
462
|
fallback={
|
|
454
463
|
<Chat.Activity
|
|
455
464
|
icon={aiToolIcon(props.block.name, presentation().appIcon)}
|
|
456
|
-
label={
|
|
457
|
-
description={capabilityErrorDescription(props.block.result)}
|
|
465
|
+
label={t().toolFailed({ title: label() })}
|
|
466
|
+
description={capabilityErrorDescription(props.block.result) || t().actionNotCompleted}
|
|
458
467
|
tone="danger"
|
|
459
468
|
accent={presentation().appAccent}
|
|
460
469
|
/>
|
|
@@ -496,10 +505,10 @@ function CapabilityToolView(props: { block: ToolBlock }) {
|
|
|
496
505
|
{typeof link.title === "string"
|
|
497
506
|
? link.title
|
|
498
507
|
: link.rel === "edit"
|
|
499
|
-
?
|
|
508
|
+
? t().edit
|
|
500
509
|
: link.rel === "download"
|
|
501
|
-
?
|
|
502
|
-
:
|
|
510
|
+
? t().download
|
|
511
|
+
: t().open}
|
|
503
512
|
</ButtonLink>
|
|
504
513
|
)}
|
|
505
514
|
</For>
|
|
@@ -515,19 +524,21 @@ function CapabilityToolView(props: { block: ToolBlock }) {
|
|
|
515
524
|
}
|
|
516
525
|
|
|
517
526
|
function RejectedToolView(props: { block: ToolBlock }) {
|
|
527
|
+
const locale = useLocale();
|
|
518
528
|
const presentation = () => props.block.presentation;
|
|
519
529
|
const title = () => presentation()?.title ?? displayToolName(props.block.name);
|
|
520
530
|
return (
|
|
521
531
|
<Chat.Activity
|
|
522
532
|
icon={aiToolIcon(props.block.name, presentation()?.appIcon)}
|
|
523
|
-
label={
|
|
533
|
+
label={aiChatMessages(locale()).toolRejected({ title: title() })}
|
|
524
534
|
accent={presentation()?.appAccent}
|
|
525
535
|
/>
|
|
526
536
|
);
|
|
527
537
|
}
|
|
528
538
|
|
|
529
|
-
function SurveyToolView(props: { turnId: string; block: ToolBlock; active?: boolean }) {
|
|
539
|
+
export function SurveyToolView(props: { turnId: string; block: ToolBlock; active?: boolean }) {
|
|
530
540
|
const actions = useAiChatActions();
|
|
541
|
+
const locale = useLocale();
|
|
531
542
|
const request = () => ({ turnId: props.turnId, callId: props.block.callId, name: props.block.name });
|
|
532
543
|
const submit = actions.onFrontendToolResult;
|
|
533
544
|
const submittedResult = () =>
|
|
@@ -538,7 +549,7 @@ function SurveyToolView(props: { turnId: string; block: ToolBlock; active?: bool
|
|
|
538
549
|
<CloudSurveyBlock
|
|
539
550
|
args={props.block.args}
|
|
540
551
|
disabled={!submit || actions.actionDisabled?.()}
|
|
541
|
-
disabledLabel={actions.actionDisabled?.() ?
|
|
552
|
+
disabledLabel={actions.actionDisabled?.() ? aiChatMessages(locale()).stoppingResponse : undefined}
|
|
542
553
|
onSubmit={submit ? (result) => submit(request(), result) : undefined}
|
|
543
554
|
/>
|
|
544
555
|
</Match>
|
|
@@ -554,8 +565,10 @@ function SurveyToolView(props: { turnId: string; block: ToolBlock; active?: bool
|
|
|
554
565
|
);
|
|
555
566
|
}
|
|
556
567
|
|
|
557
|
-
function TextEditorToolView(props: { turnId: string; block: ToolBlock; active?: boolean }) {
|
|
568
|
+
export function TextEditorToolView(props: { turnId: string; block: ToolBlock; active?: boolean }) {
|
|
558
569
|
const actions = useAiChatActions();
|
|
570
|
+
const locale = useLocale();
|
|
571
|
+
const t = () => aiChatMessages(locale());
|
|
559
572
|
const request = () => ({ turnId: props.turnId, callId: props.block.callId, name: props.block.name });
|
|
560
573
|
const submit = actions.onFrontendToolResult;
|
|
561
574
|
const completedResult = () =>
|
|
@@ -570,7 +583,7 @@ function TextEditorToolView(props: { turnId: string; block: ToolBlock; active?:
|
|
|
570
583
|
fallback={
|
|
571
584
|
<ToolResultDisclosure
|
|
572
585
|
blockId={props.block.id}
|
|
573
|
-
name=
|
|
586
|
+
name={t().textEditor}
|
|
574
587
|
toolName={props.block.name}
|
|
575
588
|
args={props.block.args}
|
|
576
589
|
result={props.block.result}
|
|
@@ -579,13 +592,13 @@ function TextEditorToolView(props: { turnId: string; block: ToolBlock; active?:
|
|
|
579
592
|
}
|
|
580
593
|
>
|
|
581
594
|
<Match when={props.block.status === "running"}>
|
|
582
|
-
<Chat.Activity label=
|
|
595
|
+
<Chat.Activity label={t().preparingTextEditor} icon="ti ti-edit" tone="ai" busy />
|
|
583
596
|
</Match>
|
|
584
597
|
<Match when={props.block.status === "awaiting_client"}>
|
|
585
598
|
<CloudTextEditorBlock
|
|
586
599
|
args={props.block.args}
|
|
587
600
|
disabled={!submit || actions.actionDisabled?.()}
|
|
588
|
-
disabledLabel={actions.actionDisabled?.() ?
|
|
601
|
+
disabledLabel={actions.actionDisabled?.() ? t().stoppingResponse : undefined}
|
|
589
602
|
onSubmit={submit ? (result) => submit(request(), result) : undefined}
|
|
590
603
|
/>
|
|
591
604
|
</Match>
|
|
@@ -599,15 +612,17 @@ function TextEditorToolView(props: { turnId: string; block: ToolBlock; active?:
|
|
|
599
612
|
}
|
|
600
613
|
|
|
601
614
|
function MemoryToolView(props: { block: ToolBlock }) {
|
|
602
|
-
const
|
|
615
|
+
const locale = useLocale();
|
|
616
|
+
const t = () => aiChatMessages(locale());
|
|
617
|
+
const presentation = () => memoryToolPresentation(props.block.args, props.block.result, locale());
|
|
603
618
|
return (
|
|
604
|
-
<Show when={props.block.status !== "running"} fallback={<Chat.Activity label=
|
|
619
|
+
<Show when={props.block.status !== "running"} fallback={<Chat.Activity label={t().usingMemory} icon="ti ti-brain" tone="ai" busy />}>
|
|
605
620
|
<Show
|
|
606
621
|
when={presentation()}
|
|
607
622
|
fallback={
|
|
608
623
|
<ToolResultDisclosure
|
|
609
624
|
blockId={props.block.id}
|
|
610
|
-
name=
|
|
625
|
+
name={t().memory}
|
|
611
626
|
toolName={props.block.name}
|
|
612
627
|
args={props.block.args}
|
|
613
628
|
result={props.block.result}
|
|
@@ -630,9 +645,10 @@ function MemoryToolView(props: { block: ToolBlock }) {
|
|
|
630
645
|
|
|
631
646
|
function ToolBlockView(props: { turnId: string; block: ToolBlock; active?: boolean }) {
|
|
632
647
|
const actions = useAiChatActions();
|
|
648
|
+
const locale = useLocale();
|
|
633
649
|
const status = () => props.block.status;
|
|
634
650
|
const fetchError = () =>
|
|
635
|
-
props.block.name === "fetch_file" && props.block.isError ? fetchFileErrorPresentation(props.block.result) : undefined;
|
|
651
|
+
props.block.name === "fetch_file" && props.block.isError ? fetchFileErrorPresentation(props.block.result, locale()) : undefined;
|
|
636
652
|
return (
|
|
637
653
|
<>
|
|
638
654
|
<Switch
|
|
@@ -661,7 +677,7 @@ function ToolBlockView(props: { turnId: string; block: ToolBlock; active?: boole
|
|
|
661
677
|
<Match
|
|
662
678
|
when={actions.renderCodePresentation && props.block.name === "code_present" && status() === "completed" && !props.block.isError}
|
|
663
679
|
>
|
|
664
|
-
{actions.renderCodePresentation?.(props.block.result)}
|
|
680
|
+
{untrack(() => actions.renderCodePresentation?.(() => props.block.result))}
|
|
665
681
|
</Match>
|
|
666
682
|
<Match when={props.block.name === "present" && !props.block.isError}>
|
|
667
683
|
<PresentToolBlock block={props.block} />
|
|
@@ -716,7 +732,7 @@ export function AiTurnBlockView(props: { block: AiTurnBlock; turnId: string; str
|
|
|
716
732
|
{(block) => <ThinkingBlockView text={block().text} streaming={props.streaming} />}
|
|
717
733
|
</Match>
|
|
718
734
|
<Match when={props.block.kind === "steer_applied"}>
|
|
719
|
-
<Chat.Activity label=
|
|
735
|
+
<Chat.Activity label={aiChatMessages(locale()).conversationSteered} icon="ti ti-route" tone="ai" />
|
|
720
736
|
</Match>
|
|
721
737
|
<Match when={props.block.kind === "tool" && props.block}>
|
|
722
738
|
{(block) => <ToolBlockView turnId={props.turnId} block={block()} active={props.active} />}
|
|
@@ -726,12 +742,15 @@ export function AiTurnBlockView(props: { block: AiTurnBlock; turnId: string; str
|
|
|
726
742
|
);
|
|
727
743
|
}
|
|
728
744
|
|
|
729
|
-
|
|
745
|
+
/** One step in the expanded work list: its title, target, and state, with input and output on demand. */
|
|
746
|
+
export function CompactToolRow(props: { block: ToolBlock; busy?: boolean }) {
|
|
730
747
|
const locale = useLocale();
|
|
731
|
-
const
|
|
748
|
+
const t = () => aiChatMessages(locale());
|
|
732
749
|
const args = () => (isRecord(props.block.args) ? props.block.args : {});
|
|
733
|
-
const detail = () => [args().path, args().name, args().query].find((value) => typeof value === "string");
|
|
734
|
-
const waiting = () => props.block.status === "awaiting_client";
|
|
750
|
+
const detail = () => [args().path, args().name, args().query, args().title].find((value) => typeof value === "string");
|
|
751
|
+
const waiting = () => props.block.status === "awaiting_client" || props.block.status === "awaiting_approval";
|
|
752
|
+
const state = () =>
|
|
753
|
+
waiting() ? t().stepWaiting : props.block.status === "rejected" ? t().stepRejected : isFailedTool(props.block) ? t().stepFailed : "";
|
|
735
754
|
return (
|
|
736
755
|
<AiToolActivity
|
|
737
756
|
blockId={props.block.id}
|
|
@@ -739,135 +758,23 @@ function CompactToolRow(props: { block: ToolBlock; busy?: boolean }) {
|
|
|
739
758
|
description={[
|
|
740
759
|
props.block.status === "running" ? (props.block.progress ?? "") : "",
|
|
741
760
|
typeof detail() === "string" ? String(detail()) : "",
|
|
742
|
-
|
|
761
|
+
state(),
|
|
743
762
|
]
|
|
744
763
|
.filter(Boolean)
|
|
745
764
|
.join(" · ")}
|
|
746
765
|
icon={waiting() ? "ti ti-clock" : aiToolIcon(props.block.name, props.block.presentation?.appIcon)}
|
|
747
|
-
|
|
748
|
-
busy={props.busy && !waiting()}
|
|
749
|
-
bodyInset={false}
|
|
766
|
+
busy={props.busy && props.block.status === "running"}
|
|
750
767
|
renderBody={() => (
|
|
751
768
|
<div
|
|
752
769
|
class="max-h-72 overflow-auto overscroll-contain rounded-md bg-zinc-100 p-3 dark:bg-zinc-950"
|
|
753
770
|
tabIndex={0}
|
|
754
771
|
role="region"
|
|
755
|
-
aria-label={
|
|
772
|
+
aria-label={t().toolInputOutput}
|
|
756
773
|
>
|
|
757
|
-
<ToolDetail title={
|
|
758
|
-
<ToolDetail title={
|
|
774
|
+
<ToolDetail title={t().input} toolName={props.block.name} value={props.block.args} />
|
|
775
|
+
<ToolDetail title={t().output} toolName={props.block.name} value={props.block.result} />
|
|
759
776
|
</div>
|
|
760
777
|
)}
|
|
761
778
|
/>
|
|
762
779
|
);
|
|
763
780
|
}
|
|
764
|
-
|
|
765
|
-
function ToolGroupView(props: { blocks: ToolBlock[]; active: boolean }) {
|
|
766
|
-
const locale = useLocale();
|
|
767
|
-
const last = () => props.blocks.at(-1)!;
|
|
768
|
-
const waiting = () => last().status === "awaiting_client";
|
|
769
|
-
const multiple = () => props.blocks.length > 1;
|
|
770
|
-
const detail = () => {
|
|
771
|
-
const args = last().args;
|
|
772
|
-
return isRecord(args) ? [args.path, args.name, args.query].find((value) => typeof value === "string") : undefined;
|
|
773
|
-
};
|
|
774
|
-
return (
|
|
775
|
-
<AiToolActivity
|
|
776
|
-
blockId={`group:${props.blocks[0]!.id}`}
|
|
777
|
-
bodyInset={false}
|
|
778
|
-
label={
|
|
779
|
-
props.active || !multiple()
|
|
780
|
-
? (last().presentation?.title ?? displayToolName(last().name))
|
|
781
|
-
: summarizeToolGroup(props.blocks, locale())
|
|
782
|
-
}
|
|
783
|
-
description={
|
|
784
|
-
waiting()
|
|
785
|
-
? locale().startsWith("de")
|
|
786
|
-
? "Wartet"
|
|
787
|
-
: "Waiting"
|
|
788
|
-
: last().status === "running"
|
|
789
|
-
? last().progress
|
|
790
|
-
: !multiple() && typeof detail() === "string"
|
|
791
|
-
? String(detail())
|
|
792
|
-
: undefined
|
|
793
|
-
}
|
|
794
|
-
icon={props.active || !multiple() ? (waiting() ? "ti ti-clock" : aiToolIcon(last().name)) : "ti ti-stack"}
|
|
795
|
-
tone={!multiple() && isFailedTool(last()) ? "danger" : "neutral"}
|
|
796
|
-
busy={props.active && !waiting()}
|
|
797
|
-
renderBody={() => (
|
|
798
|
-
<Show
|
|
799
|
-
when={multiple()}
|
|
800
|
-
fallback={
|
|
801
|
-
<div
|
|
802
|
-
class="max-h-72 overflow-auto overscroll-contain rounded-md bg-zinc-100 p-3 dark:bg-zinc-950"
|
|
803
|
-
tabIndex={0}
|
|
804
|
-
role="region"
|
|
805
|
-
aria-label={locale().startsWith("de") ? "Werkzeug-Eingabe und Ausgabe" : "Tool input and output"}
|
|
806
|
-
>
|
|
807
|
-
<ToolDetail title={locale().startsWith("de") ? "Eingabe" : "Input"} toolName={last().name} value={last().args} />
|
|
808
|
-
<ToolDetail title={locale().startsWith("de") ? "Ausgabe" : "Output"} toolName={last().name} value={last().result} />
|
|
809
|
-
</div>
|
|
810
|
-
}
|
|
811
|
-
>
|
|
812
|
-
<For each={props.blocks}>{(block) => <CompactToolRow block={block} />}</For>
|
|
813
|
-
</Show>
|
|
814
|
-
)}
|
|
815
|
-
/>
|
|
816
|
-
);
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
export function AiTurnBlockList(props: {
|
|
820
|
-
blocks: AiTurnBlock[];
|
|
821
|
-
turnId: string;
|
|
822
|
-
streaming?: boolean;
|
|
823
|
-
compact?: boolean;
|
|
824
|
-
active?: boolean;
|
|
825
|
-
disclosureState?: AiToolDisclosureState;
|
|
826
|
-
}) {
|
|
827
|
-
const groups = createMemo(() => groupToolBlocks(props.blocks.filter(isRenderableTurnBlock)));
|
|
828
|
-
const keyedGroups = createMemo(
|
|
829
|
-
() => new Map(groups().map((group) => [group.kind === "tools" ? `tools:${group.blocks[0]!.id}` : `block:${group.block.id}`, group])),
|
|
830
|
-
);
|
|
831
|
-
const keys = createMemo(() => [...keyedGroups().keys()]);
|
|
832
|
-
const disclosureState = props.disclosureState ?? createAiToolDisclosureState();
|
|
833
|
-
return (
|
|
834
|
-
<Show when={groups().length > 0}>
|
|
835
|
-
<AiToolDisclosureProvider state={disclosureState}>
|
|
836
|
-
<div class={`flex flex-col ${props.compact ? "gap-1" : "gap-2"}`}>
|
|
837
|
-
<For each={keys()}>
|
|
838
|
-
{(key, index) => {
|
|
839
|
-
const group = () => keyedGroups().get(key)!;
|
|
840
|
-
return (
|
|
841
|
-
<Switch>
|
|
842
|
-
<Match
|
|
843
|
-
when={(() => {
|
|
844
|
-
const value = group();
|
|
845
|
-
return value.kind === "tools" ? value : undefined;
|
|
846
|
-
})()}
|
|
847
|
-
>
|
|
848
|
-
{(tools) => <ToolGroupView blocks={tools().blocks} active={Boolean(props.active && index() === keys().length - 1)} />}
|
|
849
|
-
</Match>
|
|
850
|
-
<Match
|
|
851
|
-
when={(() => {
|
|
852
|
-
const value = group();
|
|
853
|
-
return value.kind === "block" ? value : undefined;
|
|
854
|
-
})()}
|
|
855
|
-
>
|
|
856
|
-
{(item) => (
|
|
857
|
-
<AiTurnBlockView
|
|
858
|
-
block={item().block}
|
|
859
|
-
turnId={props.turnId}
|
|
860
|
-
streaming={props.streaming && index() === keys().length - 1}
|
|
861
|
-
active={props.active}
|
|
862
|
-
/>
|
|
863
|
-
)}
|
|
864
|
-
</Match>
|
|
865
|
-
</Switch>
|
|
866
|
-
);
|
|
867
|
-
}}
|
|
868
|
-
</For>
|
|
869
|
-
</div>
|
|
870
|
-
</AiToolDisclosureProvider>
|
|
871
|
-
</Show>
|
|
872
|
-
);
|
|
873
|
-
}
|