@k2b/cloud 0.6.0 → 0.8.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/README.md +2 -2
- package/package.json +22 -11
- package/scripts/README.md +10 -0
- package/scripts/browser-performance.ts +14 -0
- package/scripts/build-pdf-renderer.ts +39 -0
- package/scripts/build.ts +20 -35
- package/scripts/preload.ts +16 -12
- package/scripts/sync-recovery-smoke.ts +22 -6
- package/src/_internal/app-presentation.ts +14 -2
- package/src/_internal/build-metadata.ts +8 -1
- package/src/_internal/capabilities.ts +131 -7
- package/src/_internal/capability-streams.ts +81 -0
- package/src/_internal/define-app.ts +80 -37
- package/src/_internal/heartbeat.ts +4 -0
- package/src/_internal/help-catalog.ts +27 -172
- package/src/_internal/help.ts +13 -76
- package/src/_internal/page-responses.ts +2 -0
- package/src/_internal/postgres-application-name.ts +30 -0
- package/src/_internal/process-identity.ts +15 -0
- package/src/_internal/process-sync.ts +1 -1
- package/src/_internal/readiness.ts +4 -1
- package/src/_internal/registry-validation.ts +29 -25
- package/src/_internal/registry.ts +2 -53
- package/src/_internal/runtime-context.ts +5 -16
- package/src/_internal/server-timing.ts +13 -0
- package/src/_internal/status-preserving-ssr.ts +18 -6
- package/src/_internal/web-vitals-asset.ts +10 -0
- package/src/access/PermissionEditor.tsx +18 -151
- package/src/access/PrincipalPicker.tsx +110 -0
- package/src/access/ResourceApiKeys.tsx +1 -1
- package/src/access/ui.ts +1 -0
- package/src/ai/admin.ts +14 -13
- package/src/ai/assistant-models.ts +12 -1
- package/src/ai/audio-format.ts +34 -0
- package/src/ai/audio-tool.ts +90 -0
- package/src/ai/browser-code-contracts.ts +201 -0
- package/src/ai/browser.ts +4 -0
- package/src/ai/capabilities.ts +55 -36
- package/src/ai/capability-execution.ts +47 -12
- package/src/ai/chat/blocks.tsx +229 -90
- package/src/ai/chat/builtin-tools.tsx +2 -4
- package/src/ai/chat/capability-result.ts +15 -0
- package/src/ai/chat/capability-table.tsx +2 -10
- package/src/ai/chat/composer-adapter.ts +83 -3
- package/src/ai/chat/message-actions.tsx +8 -7
- package/src/ai/chat/message-utils.ts +25 -1
- package/src/ai/chat/messages.ts +2 -0
- package/src/ai/chat/presentation.tsx +31 -47
- package/src/ai/chat/primitives.tsx +43 -1
- package/src/ai/chat/tool-disclosure.tsx +21 -2
- package/src/ai/chat/tool-groups.ts +58 -0
- package/src/ai/chat-quotas.ts +25 -0
- package/src/ai/chat-task-contracts.ts +14 -10
- package/src/ai/chat-tasks.ts +297 -43
- package/src/ai/client/controller.ts +165 -39
- package/src/ai/client/file-source.ts +12 -6
- package/src/ai/client/live-connection.ts +1 -0
- package/src/ai/client/projection.ts +29 -11
- package/src/ai/client/transport.ts +6 -0
- package/src/ai/code-capability-routes.ts +105 -0
- package/src/ai/code-capability-transport.ts +39 -0
- package/src/ai/code-execution.ts +53 -0
- package/src/ai/code-mode-skill.ts +118 -0
- package/src/ai/code-runtime-tools.ts +179 -0
- package/src/ai/code-source-contracts.ts +270 -0
- package/src/ai/code-source-tools.ts +188 -0
- package/src/ai/compaction.ts +27 -6
- package/src/ai/data-analysis-skill.ts +10 -0
- package/src/ai/default-tools.ts +99 -3
- package/src/ai/dictation-runtime.ts +171 -0
- package/src/ai/dictations.ts +188 -0
- package/src/ai/draft-content.ts +49 -0
- package/src/ai/enrich.ts +6 -0
- package/src/ai/executor.ts +189 -33
- package/src/ai/fetch-file-tool.ts +2 -2
- package/src/ai/file-content-version.ts +27 -0
- package/src/ai/file-context.ts +2 -1
- package/src/ai/file-media-type.ts +12 -0
- package/src/ai/file-reference-contracts.ts +15 -0
- package/src/ai/file-tools.ts +8 -1
- package/src/ai/files-store.ts +212 -71
- package/src/ai/fixtures/vision-encrypted.pdf +0 -0
- package/src/ai/grids-skill.ts +42 -10
- package/src/ai/http.ts +26 -8
- package/src/ai/index.ts +26 -3
- package/src/ai/inference-calls.ts +197 -0
- package/src/ai/live-events.ts +5 -1
- package/src/ai/live.ts +1 -1
- package/src/ai/markdown-pdf-tool.ts +2 -2
- package/src/ai/memories.ts +30 -27
- package/src/ai/memory-learning-runs.ts +1 -1
- package/src/ai/memory-learning.ts +64 -32
- package/src/ai/memory-tool.ts +1 -5
- package/src/ai/memory-workflow-evidence.ts +9 -4
- package/src/ai/message-queue.ts +178 -0
- package/src/ai/migrate.ts +189 -11
- package/src/ai/model-pricing.ts +43 -0
- package/src/ai/pdf-render-worker.ts +75 -0
- package/src/ai/pdf-render.fixture.ts +28 -0
- package/src/ai/pdf-render.ts +77 -0
- package/src/ai/projects-routes.ts +18 -43
- package/src/ai/projects.ts +33 -14
- package/src/ai/protocol.ts +3 -0
- package/src/ai/provider.ts +1 -8
- package/src/ai/quota-provider.ts +192 -0
- package/src/ai/quota-report.ts +152 -0
- package/src/ai/quotas-migrate.ts +45 -0
- package/src/ai/quotas.ts +168 -0
- package/src/ai/routes.ts +241 -11
- package/src/ai/run-timeout.ts +12 -0
- package/src/ai/runtime-tools.ts +7 -5
- package/src/ai/runtime.ts +74 -17
- package/src/ai/settings.ts +36 -8
- package/src/ai/short-id.ts +4 -5
- package/src/ai/skill-catalog.ts +0 -10
- package/src/ai/skill-search.ts +58 -0
- package/src/ai/skill-seeds.ts +114 -20
- package/src/ai/skill-tool.ts +21 -5
- package/src/ai/skills-routes.ts +49 -4
- package/src/ai/skills.ts +358 -163
- package/src/ai/store.ts +416 -201
- package/src/ai/stream.ts +5 -0
- package/src/ai/structured-runs.ts +4 -3
- package/src/ai/structured.ts +12 -1
- package/src/ai/system-prompt.ts +14 -2
- package/src/ai/task-contracts.ts +84 -0
- package/src/ai/task-execution.ts +92 -0
- package/src/ai/timeline.ts +2 -5
- package/src/ai/todo-contracts.ts +32 -0
- package/src/ai/todo-tool.ts +19 -0
- package/src/ai/tool-audit.ts +30 -40
- package/src/ai/tools.ts +6 -0
- package/src/ai/transcription.ts +202 -0
- package/src/ai/turn-timing.ts +114 -0
- package/src/ai/types.ts +58 -10
- package/src/ai/ui.tsx +3 -0
- package/src/ai/usage.ts +118 -118
- package/src/ai/vision-tool.ts +60 -10
- package/src/api/admin-ai-projects.ts +2 -2
- package/src/api/admin-ai-quotas.ts +80 -0
- package/src/api/admin-ai-skills.ts +48 -5
- package/src/api/admin-ai-usage.ts +2 -2
- package/src/api/admin-core-settings.ts +25 -0
- package/src/api/admin-identity.ts +91 -51
- package/src/api/admin-linux-identities.ts +10 -4
- package/src/api/admin-rail.ts +58 -0
- package/src/api/announcements.ts +14 -0
- package/src/api/app-approval.ts +2 -2
- package/src/api/auth.ts +1 -1
- package/src/api/capabilities.ts +324 -43
- package/src/api/capability-streams.ts +262 -0
- package/src/api/help.ts +8 -44
- package/src/api/index.ts +8 -2
- package/src/api/mcp.ts +45 -34
- package/src/api/me-web-vitals.ts +35 -0
- package/src/api/me.ts +2 -0
- package/src/api/search/schemas.ts +26 -15
- package/src/api/search.bench.ts +1 -1
- package/src/api/search.ts +54 -17
- package/src/browser/CloudResourceSearch.tsx +640 -702
- package/src/browser/app-approval-vault.ts +19 -7
- package/src/browser/command-bridge.ts +109 -0
- package/src/browser/command-shortcuts.ts +76 -0
- package/src/browser/commands.ts +67 -0
- package/src/browser/navigation-search.ts +48 -0
- package/src/browser/notifications.ts +1 -1
- package/src/browser/resource-picker.tsx +5 -18
- package/src/browser/resource-search-dialog.ts +9 -0
- package/src/browser/resource-search-input.ts +46 -0
- package/src/browser/resource-search-messages.ts +76 -40
- package/src/browser/resource-search.ts +7 -0
- package/src/browser/search-bridge.ts +102 -0
- package/src/browser/search-commands.ts +59 -0
- package/src/browser/search.ts +13 -0
- package/src/browser/spotlight-position.ts +208 -0
- package/src/browser/testing.ts +6 -0
- package/src/browser/web-vitals.ts +25 -0
- package/src/capabilities/claims.ts +179 -0
- package/src/capabilities/client.ts +34 -0
- package/src/capabilities/command-link.ts +16 -0
- package/src/capabilities/executions.ts +298 -0
- package/src/capabilities/migrate.ts +81 -0
- package/src/capabilities/server.ts +116 -35
- package/src/capabilities/stream-body.ts +16 -0
- package/src/capabilities/streams.ts +28 -0
- package/src/cli/admin/ai-quotas.ts +161 -0
- package/src/cli/admin/ai-skills.ts +64 -0
- package/src/cli/admin/ai-usage.ts +53 -30
- package/src/cli/admin/app-credentials.ts +77 -0
- package/src/cli/admin/data.ts +17 -0
- package/src/cli/admin/index.ts +10 -1
- package/src/cli/admin/instance.ts +2 -2
- package/src/cli/admin/linux.ts +4 -2
- package/src/cli/admin/sync.ts +1 -1
- package/src/cli/capabilities.ts +42 -0
- package/src/cli/capability-streams.ts +25 -0
- package/src/cli/index.ts +3 -2
- package/src/config/define-env.ts +97 -0
- package/src/config/env.ts +147 -51
- package/src/config/index.ts +3 -1
- package/src/contracts/app.ts +23 -3
- package/src/contracts/capabilities.ts +75 -5
- package/src/contracts/capability-streams.ts +46 -0
- package/src/contracts/commands.ts +49 -0
- package/src/contracts/index.ts +5 -2
- package/src/contracts/rail-admin.ts +38 -0
- package/src/contracts/rail-preferences.ts +16 -0
- package/src/contracts/registry.ts +5 -27
- package/src/contracts/settings-types.ts +1 -1
- package/src/contracts/shared.ts +18 -5
- package/src/contracts/web-vitals.ts +27 -0
- package/src/desktop/index.ts +1 -0
- package/src/desktop/solid.tsx +1 -1
- package/src/index.ts +8 -6
- package/src/server/index.ts +3 -0
- package/src/server/middleware/auth.ts +3 -2
- package/src/server/middleware/route-template.ts +9 -3
- package/src/server/middleware/runtime.ts +2 -1
- package/src/server/middleware/settings.ts +17 -11
- package/src/server/ratelimit.ts +1 -1
- package/src/server/services/access-revision.ts +10 -0
- package/src/server/services/access.ts +3 -1
- package/src/server/services/index.ts +1 -0
- package/src/services/account-category-policy.ts +9 -0
- package/src/services/account-lifecycle/audit.ts +1 -1
- package/src/services/accounts/app.ts +12 -2
- package/src/services/accounts/email-write.ts +23 -0
- package/src/services/accounts/groups.ts +15 -4
- package/src/services/accounts/identities.ts +249 -0
- package/src/services/accounts/identity-reconciliation.ts +87 -0
- package/src/services/accounts/ipa-data.ts +2 -2
- package/src/services/accounts/local-groups.ts +2 -2
- package/src/services/accounts/notification-sender.ts +12 -2
- package/src/services/accounts/posix.ts +45 -29
- package/src/services/accounts/users.ts +2 -2
- package/src/services/announcements/index.ts +69 -8
- package/src/services/app-approval.ts +11 -10
- package/src/services/audit/index.ts +1 -1
- package/src/services/cache-fill.ts +35 -0
- package/src/services/help/index.ts +109 -0
- package/src/services/help/maintenance.ts +25 -0
- package/src/services/help/store.ts +180 -0
- package/src/services/help/types.ts +46 -0
- package/src/services/identity/invocation-actor.ts +2 -2
- package/src/services/identity/key-config.ts +8 -5
- package/src/services/identity/key-ring.ts +1 -1
- package/src/services/identity/runtime-config.ts +6 -5
- package/src/services/index.ts +15 -0
- package/src/services/ipa/posix.ts +1 -1
- package/src/services/ipa/sync.ts +1 -1
- package/src/services/ipa/users.ts +268 -221
- package/src/services/legal-consent.ts +1 -1
- package/src/services/logging/index.ts +15 -13
- package/src/services/logging/trace.ts +2 -1
- package/src/services/logging/web-vitals.ts +81 -0
- package/src/services/mandates/index.ts +21 -4
- package/src/services/mandates/policy.ts +165 -2
- package/src/services/notifications/batches.ts +125 -57
- package/src/services/notifications/browser.ts +12 -0
- package/src/services/notifications/index.ts +0 -2
- package/src/services/notifications/platform.ts +0 -4
- package/src/services/oauth-tokens.ts +3 -3
- package/src/services/pdf/gotenberg.ts +131 -72
- package/src/services/pdf/index.ts +11 -8
- package/src/services/providers/local/users.ts +25 -16
- package/src/services/public-http.ts +101 -0
- package/src/services/rail-shortcuts.ts +95 -0
- package/src/services/rail-snapshot.ts +53 -0
- package/src/services/request-cache-redis.ts +29 -0
- package/src/services/session/index.ts +15 -2
- package/src/services/session/user.ts +62 -37
- package/src/services/settings/app.ts +5 -1
- package/src/services/settings/core-settings.ts +26 -35
- package/src/services/settings/defaults.ts +5 -0
- package/src/services/settings/index.ts +3 -3
- package/src/services/settings/store.ts +58 -84
- package/src/services/webauthn.ts +1 -1
- package/src/shared/ai-costs.ts +42 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/ai-quotas.ts +136 -0
- package/src/shared/ai-usage.ts +6 -16
- package/src/shared/app-presentation.ts +21 -15
- package/src/shared/capability-messages.ts +14 -0
- package/src/shared/help.ts +6 -42
- package/src/shared/index.ts +3 -0
- package/src/shared/markdown/extensions/info-blocks.ts +1 -1
- package/src/ssr/AdminLayout.tsx +7 -5
- package/src/ssr/AdminSidebar.tsx +30 -16
- package/src/ssr/AppLaunchpad.island.tsx +33 -136
- package/src/ssr/AppLaunchpadPanel.tsx +181 -0
- package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
- package/src/ssr/GlobalSearchDialog.tsx +167 -20
- package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
- package/src/ssr/Layout.tsx +17 -6
- package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
- package/src/ssr/LayoutFooter.tsx +1 -1
- package/src/ssr/LayoutHeader.tsx +46 -14
- package/src/ssr/LayoutHelp.tsx +22 -23
- package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
- package/src/ssr/LayoutRail.tsx +17 -9
- package/src/ssr/MinimalLayout.tsx +1 -1
- package/src/ssr/MobileNavigation.tsx +71 -0
- package/src/ssr/MobileProfileActions.tsx +49 -0
- package/src/ssr/ProfilePreferences.island.tsx +3 -36
- package/src/ssr/RailApps.island.tsx +2 -2
- package/src/ssr/RailEditor.tsx +17 -2
- package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
- package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
- package/src/ssr/admin-navigation.ts +11 -1
- package/src/ssr/help.ts +29 -0
- package/src/ssr/islands/SearchBar.tsx +67 -0
- package/src/ssr/islands/index.ts +3 -1
- package/src/ssr/layout-context.ts +2 -2
- package/src/ssr/layout-help-search.ts +15 -0
- package/src/ssr/mobile-menu-history.ts +70 -0
- package/src/ssr/platform-messages.ts +16 -8
- package/src/ssr/profile-actions.ts +42 -0
- package/src/ssr/rail-context.ts +8 -4
- package/src/ssr/rail-messages.ts +2 -0
- package/src/ssr/rail-navigation.ts +3 -3
- package/src/ssr/workspace-navigation.ts +75 -0
- package/src/styles/effects.css +6 -6
- package/src/styles/global.css +54 -0
- package/src/styles/input.css +43 -41
- package/src/styles/resource-search.css +505 -0
- package/src/styles/utilities-feedback.css +13 -0
- package/src/styles/utilities-layout.css +1 -5
- package/src/styles/utilities-navigation.css +3 -39
- package/src/types/ambient.d.ts +5 -0
- package/src/workflows/ai/index.ts +2 -2
- package/src/workflows/ai/runtime.ts +19 -90
- package/src/workflows/ai/store.ts +2 -0
- package/src/workflows/ai/types.ts +6 -82
- package/src/workflows/ai-actions.ts +16 -13
- package/src/workflows/definition.ts +10 -5
- package/src/workflows/store/actions.ts +44 -20
- package/src/workflows/store/index.ts +4 -2
- package/src/workflows/store/runs.ts +36 -0
- package/src/workflows/store/worker-pool.ts +55 -0
- package/src/workflows/store/worker-runtime.ts +90 -0
- package/src/ai/kit-skill.ts +0 -22
- package/src/ai/usage-migrate.ts +0 -81
- package/src/browser/notification-target.ts +0 -27
- package/src/contracts/notification-live.ts +0 -66
- package/src/server/services/freeipa/test-certificates.ts +0 -44
- package/src/services/notifications/live.ts +0 -50
- package/src/services/session/test-fixture.ts +0 -18
- package/src/ssr/BrowserNotifications.island.tsx +0 -135
- package/src/ssr/islands/SearchBar.island.tsx +0 -80
package/src/ai/chat/blocks.tsx
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { dates } from "@k2b/stdlib";
|
|
2
2
|
import { mutation } from "@k2b/stdlib/solid";
|
|
3
|
-
import { Button, ButtonLink, Chat, isStructuredDataValue, SplitButton, StructuredDataPreview, useLocale } from "@k2b/ui";
|
|
4
|
-
import { createSignal, For, type JSX, Match, Show, Switch } from "solid-js";
|
|
3
|
+
import { Button, ButtonLink, Chat, isStructuredDataValue, MarkdownView, SplitButton, StructuredDataPreview, useLocale } from "@k2b/ui";
|
|
4
|
+
import { createMemo, createSignal, For, type JSX, Match, Show, Switch } 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
8
|
import { isRenderableTurnBlock } from "../protocol";
|
|
9
9
|
import { hasSpecializedBuiltinToolView, SpecializedBuiltinToolBlock } from "./builtin-tools";
|
|
10
|
+
import { hasCapabilityTable } from "./capability-result";
|
|
10
11
|
import { CapabilityTablePreview } from "./capability-table";
|
|
11
12
|
import { PresentToolBlock } from "./file-tools";
|
|
12
13
|
import { useAiChatActions } from "./message-actions";
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
} from "./message-utils";
|
|
26
27
|
import { AssistantMarkdownBlock } from "./primitives";
|
|
27
28
|
import { AiToolActivity, AiToolDisclosureProvider, type AiToolDisclosureState, createAiToolDisclosureState } from "./tool-disclosure";
|
|
29
|
+
import { groupToolBlocks, isFailedTool, summarizeToolGroup } from "./tool-groups";
|
|
28
30
|
import { CloudCardBlock, CloudSurveyBlock, CloudSurveyResultBlock, CloudTextEditorBlock, CloudTextEditorResultBlock } from "./visual-tools";
|
|
29
31
|
import { FetchFileToolBlock, WebExtractToolBlock, WebSearchToolBlock } from "./web-tools";
|
|
30
32
|
|
|
@@ -288,14 +290,23 @@ function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
288
290
|
{(detail) => (
|
|
289
291
|
<section class="min-w-0" aria-label={detail.label}>
|
|
290
292
|
<h4 class="mb-1.5 font-semibold text-primary">{detail.label}</h4>
|
|
291
|
-
<
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
<Show
|
|
294
|
+
when={detail.format === "markdown"}
|
|
295
|
+
fallback={
|
|
296
|
+
<pre
|
|
297
|
+
class="max-h-72 overflow-auto whitespace-pre-wrap break-words pr-2 font-sans text-xs leading-5 text-secondary"
|
|
298
|
+
role="region"
|
|
299
|
+
tabIndex={0}
|
|
300
|
+
aria-label={`${detail.label} content`}
|
|
301
|
+
>
|
|
302
|
+
<ReviewDetailValue detail={detail} />
|
|
303
|
+
</pre>
|
|
304
|
+
}
|
|
296
305
|
>
|
|
297
|
-
<
|
|
298
|
-
|
|
306
|
+
<div class="max-h-72 overflow-auto text-xs text-secondary" role="region" tabIndex={0} aria-label={detail.label}>
|
|
307
|
+
<MarkdownView class="cloud-ai-approval-detail" markdown={detail.value} headingScale="compact" allowImages={false} />
|
|
308
|
+
</div>
|
|
309
|
+
</Show>
|
|
299
310
|
</section>
|
|
300
311
|
)}
|
|
301
312
|
</For>
|
|
@@ -496,9 +507,6 @@ function CapabilityToolView(props: { block: ToolBlock }) {
|
|
|
496
507
|
</Show>
|
|
497
508
|
</Show>
|
|
498
509
|
</Show>
|
|
499
|
-
<Show when={props.block.status !== "running" && !props.block.isError}>
|
|
500
|
-
<CapabilityTablePreview result={props.block.result} label={label()} />
|
|
501
|
-
</Show>
|
|
502
510
|
</>
|
|
503
511
|
);
|
|
504
512
|
}
|
|
@@ -618,85 +626,190 @@ function MemoryToolView(props: { block: ToolBlock }) {
|
|
|
618
626
|
}
|
|
619
627
|
|
|
620
628
|
function ToolBlockView(props: { turnId: string; block: ToolBlock; active?: boolean }) {
|
|
629
|
+
const actions = useAiChatActions();
|
|
621
630
|
const status = () => props.block.status;
|
|
622
631
|
const fetchError = () =>
|
|
623
632
|
props.block.name === "fetch_file" && props.block.isError ? fetchFileErrorPresentation(props.block.result) : undefined;
|
|
624
633
|
return (
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
<
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
<
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
<
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
<
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
634
|
+
<>
|
|
635
|
+
<Switch
|
|
636
|
+
fallback={
|
|
637
|
+
<ToolResultDisclosure
|
|
638
|
+
blockId={props.block.id}
|
|
639
|
+
name={displayToolName(props.block.name)}
|
|
640
|
+
toolName={props.block.name}
|
|
641
|
+
args={props.block.args}
|
|
642
|
+
result={props.block.result}
|
|
643
|
+
isError={Boolean(props.block.isError)}
|
|
644
|
+
errorLabel={fetchError()?.label}
|
|
645
|
+
errorDescription={fetchError()?.description}
|
|
646
|
+
/>
|
|
647
|
+
}
|
|
648
|
+
>
|
|
649
|
+
<Match when={status() === "awaiting_approval"}>
|
|
650
|
+
<ApprovalBlockView turnId={props.turnId} block={props.block} />
|
|
651
|
+
</Match>
|
|
652
|
+
<Match when={status() === "rejected"}>
|
|
653
|
+
<RejectedToolView block={props.block} />
|
|
654
|
+
</Match>
|
|
655
|
+
<Match when={props.block.presentation?.kind === "capability"}>
|
|
656
|
+
<CapabilityToolView block={props.block} />
|
|
657
|
+
</Match>
|
|
658
|
+
<Match
|
|
659
|
+
when={actions.renderCodePresentation && props.block.name === "code_present" && status() === "completed" && !props.block.isError}
|
|
660
|
+
>
|
|
661
|
+
{actions.renderCodePresentation?.(props.block.result)}
|
|
662
|
+
</Match>
|
|
663
|
+
<Match when={props.block.name === "present" && !props.block.isError}>
|
|
664
|
+
<PresentToolBlock block={props.block} />
|
|
665
|
+
</Match>
|
|
666
|
+
<Match when={props.block.name === "web_search" && !props.block.isError}>
|
|
667
|
+
<WebSearchToolBlock block={props.block} />
|
|
668
|
+
</Match>
|
|
669
|
+
<Match when={props.block.name === "web_extract" && !props.block.isError}>
|
|
670
|
+
<WebExtractToolBlock block={props.block} />
|
|
671
|
+
</Match>
|
|
672
|
+
<Match when={props.block.name === "fetch_file" && !props.block.isError}>
|
|
673
|
+
<FetchFileToolBlock block={props.block} />
|
|
674
|
+
</Match>
|
|
675
|
+
<Match when={props.block.name === "memory"}>
|
|
676
|
+
<MemoryToolView block={props.block} />
|
|
677
|
+
</Match>
|
|
678
|
+
<Match
|
|
679
|
+
when={hasSpecializedBuiltinToolView(props.block.name, props.block.result) && status() === "completed" && !props.block.isError}
|
|
680
|
+
>
|
|
681
|
+
<SpecializedBuiltinToolBlock block={props.block} />
|
|
682
|
+
</Match>
|
|
683
|
+
<Match when={isCardToolName(props.block.name) && !props.block.isError}>
|
|
684
|
+
<CloudCardBlock args={props.block.args} />
|
|
685
|
+
</Match>
|
|
686
|
+
<Match when={isSurveyToolName(props.block.name) && !props.block.isError}>
|
|
687
|
+
<SurveyToolView turnId={props.turnId} block={props.block} active={props.active} />
|
|
688
|
+
</Match>
|
|
689
|
+
<Match when={isTextEditorToolName(props.block.name)}>
|
|
690
|
+
<TextEditorToolView turnId={props.turnId} block={props.block} active={props.active} />
|
|
691
|
+
</Match>
|
|
692
|
+
<Match when={status() === "running" || status() === "awaiting_client"}>
|
|
693
|
+
<Chat.Activity label={displayToolName(props.block.name)} icon={aiToolIcon(props.block.name)} busy />
|
|
694
|
+
</Match>
|
|
695
|
+
</Switch>
|
|
696
|
+
<Show when={hasCapabilityTable(props.block)}>
|
|
697
|
+
<CapabilityTablePreview result={props.block.result} label={props.block.presentation?.title ?? displayToolName(props.block.name)} />
|
|
698
|
+
</Show>
|
|
699
|
+
</>
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** Render one unified turn block. Shared by persisted assistant groups and the live turn. */
|
|
704
|
+
export function AiTurnBlockView(props: { block: AiTurnBlock; turnId: string; streaming?: boolean; active?: boolean }) {
|
|
705
|
+
// The id/kind remain stable while the immutable block value changes during a turn.
|
|
706
|
+
return (
|
|
707
|
+
<Switch>
|
|
708
|
+
<Match when={props.block.kind === "text" && props.block}>
|
|
709
|
+
{(block) => <AssistantMarkdownBlock html={markdown.renderSync(block().text)} />}
|
|
668
710
|
</Match>
|
|
669
|
-
<Match when={
|
|
670
|
-
<
|
|
711
|
+
<Match when={props.block.kind === "thinking" && props.block}>
|
|
712
|
+
{(block) => <ThinkingBlockView text={block().text} streaming={props.streaming} />}
|
|
671
713
|
</Match>
|
|
672
|
-
<Match when={
|
|
673
|
-
<
|
|
714
|
+
<Match when={props.block.kind === "steer_applied"}>
|
|
715
|
+
<Chat.Activity label="Conversation steered" icon="ti ti-route" tone="ai" />
|
|
674
716
|
</Match>
|
|
675
|
-
<Match when={
|
|
676
|
-
<
|
|
717
|
+
<Match when={props.block.kind === "tool" && props.block}>
|
|
718
|
+
{(block) => <ToolBlockView turnId={props.turnId} block={block()} active={props.active} />}
|
|
677
719
|
</Match>
|
|
720
|
+
<Match when={props.block.kind === "compaction" && props.block}>{(block) => <CompactionBlockView block={block()} />}</Match>
|
|
678
721
|
</Switch>
|
|
679
722
|
);
|
|
680
723
|
}
|
|
681
724
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
725
|
+
function CompactToolRow(props: { block: ToolBlock; busy?: boolean }) {
|
|
726
|
+
const locale = useLocale();
|
|
727
|
+
const de = () => locale().startsWith("de");
|
|
728
|
+
const args = () => (isRecord(props.block.args) ? props.block.args : {});
|
|
729
|
+
const detail = () => [args().path, args().name, args().query].find((value) => typeof value === "string");
|
|
730
|
+
const waiting = () => props.block.status === "awaiting_client";
|
|
731
|
+
return (
|
|
732
|
+
<AiToolActivity
|
|
733
|
+
blockId={props.block.id}
|
|
734
|
+
label={props.block.presentation?.title ?? displayToolName(props.block.name)}
|
|
735
|
+
description={[
|
|
736
|
+
props.block.status === "running" ? (props.block.progress ?? "") : "",
|
|
737
|
+
typeof detail() === "string" ? String(detail()) : "",
|
|
738
|
+
waiting() ? (de() ? "Wartet" : "Waiting") : isFailedTool(props.block) ? (de() ? "Fehlgeschlagen" : "Failed") : "",
|
|
739
|
+
]
|
|
740
|
+
.filter(Boolean)
|
|
741
|
+
.join(" · ")}
|
|
742
|
+
icon={waiting() ? "ti ti-clock" : aiToolIcon(props.block.name, props.block.presentation?.appIcon)}
|
|
743
|
+
tone={isFailedTool(props.block) ? "danger" : "neutral"}
|
|
744
|
+
busy={props.busy && !waiting()}
|
|
745
|
+
bodyInset={false}
|
|
746
|
+
renderBody={() => (
|
|
747
|
+
<div
|
|
748
|
+
class="max-h-72 overflow-auto overscroll-contain rounded-md bg-zinc-100 p-3 dark:bg-zinc-950"
|
|
749
|
+
tabIndex={0}
|
|
750
|
+
role="region"
|
|
751
|
+
aria-label={de() ? "Werkzeug-Eingabe und Ausgabe" : "Tool input and output"}
|
|
752
|
+
>
|
|
753
|
+
<ToolDetail title={de() ? "Eingabe" : "Input"} toolName={props.block.name} value={props.block.args} />
|
|
754
|
+
<ToolDetail title={de() ? "Ausgabe" : "Output"} toolName={props.block.name} value={props.block.result} />
|
|
755
|
+
</div>
|
|
756
|
+
)}
|
|
757
|
+
/>
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function ToolGroupView(props: { blocks: ToolBlock[]; active: boolean }) {
|
|
762
|
+
const locale = useLocale();
|
|
763
|
+
const last = () => props.blocks.at(-1)!;
|
|
764
|
+
const waiting = () => last().status === "awaiting_client";
|
|
765
|
+
const multiple = () => props.blocks.length > 1;
|
|
766
|
+
const detail = () => {
|
|
767
|
+
const args = last().args;
|
|
768
|
+
return isRecord(args) ? [args.path, args.name, args.query].find((value) => typeof value === "string") : undefined;
|
|
769
|
+
};
|
|
770
|
+
return (
|
|
771
|
+
<AiToolActivity
|
|
772
|
+
blockId={`group:${props.blocks[0]!.id}`}
|
|
773
|
+
bodyInset={false}
|
|
774
|
+
label={
|
|
775
|
+
props.active || !multiple()
|
|
776
|
+
? (last().presentation?.title ?? displayToolName(last().name))
|
|
777
|
+
: summarizeToolGroup(props.blocks, locale())
|
|
778
|
+
}
|
|
779
|
+
description={
|
|
780
|
+
waiting()
|
|
781
|
+
? locale().startsWith("de")
|
|
782
|
+
? "Wartet"
|
|
783
|
+
: "Waiting"
|
|
784
|
+
: last().status === "running"
|
|
785
|
+
? last().progress
|
|
786
|
+
: !multiple() && typeof detail() === "string"
|
|
787
|
+
? String(detail())
|
|
788
|
+
: undefined
|
|
789
|
+
}
|
|
790
|
+
icon={props.active || !multiple() ? (waiting() ? "ti ti-clock" : aiToolIcon(last().name)) : "ti ti-stack"}
|
|
791
|
+
tone={!multiple() && isFailedTool(last()) ? "danger" : "neutral"}
|
|
792
|
+
busy={props.active && !waiting()}
|
|
793
|
+
renderBody={() => (
|
|
794
|
+
<Show
|
|
795
|
+
when={multiple()}
|
|
796
|
+
fallback={
|
|
797
|
+
<div
|
|
798
|
+
class="max-h-72 overflow-auto overscroll-contain rounded-md bg-zinc-100 p-3 dark:bg-zinc-950"
|
|
799
|
+
tabIndex={0}
|
|
800
|
+
role="region"
|
|
801
|
+
aria-label={locale().startsWith("de") ? "Werkzeug-Eingabe und Ausgabe" : "Tool input and output"}
|
|
802
|
+
>
|
|
803
|
+
<ToolDetail title={locale().startsWith("de") ? "Eingabe" : "Input"} toolName={last().name} value={last().args} />
|
|
804
|
+
<ToolDetail title={locale().startsWith("de") ? "Ausgabe" : "Output"} toolName={last().name} value={last().result} />
|
|
805
|
+
</div>
|
|
806
|
+
}
|
|
807
|
+
>
|
|
808
|
+
<For each={props.blocks}>{(block) => <CompactToolRow block={block} />}</For>
|
|
809
|
+
</Show>
|
|
810
|
+
)}
|
|
811
|
+
/>
|
|
812
|
+
);
|
|
700
813
|
}
|
|
701
814
|
|
|
702
815
|
export function AiTurnBlockList(props: {
|
|
@@ -707,21 +820,47 @@ export function AiTurnBlockList(props: {
|
|
|
707
820
|
active?: boolean;
|
|
708
821
|
disclosureState?: AiToolDisclosureState;
|
|
709
822
|
}) {
|
|
710
|
-
const
|
|
823
|
+
const groups = createMemo(() => groupToolBlocks(props.blocks.filter(isRenderableTurnBlock)));
|
|
824
|
+
const keyedGroups = createMemo(
|
|
825
|
+
() => new Map(groups().map((group) => [group.kind === "tools" ? `tools:${group.blocks[0]!.id}` : `block:${group.block.id}`, group])),
|
|
826
|
+
);
|
|
827
|
+
const keys = createMemo(() => [...keyedGroups().keys()]);
|
|
711
828
|
const disclosureState = props.disclosureState ?? createAiToolDisclosureState();
|
|
712
829
|
return (
|
|
713
|
-
<Show when={
|
|
830
|
+
<Show when={groups().length > 0}>
|
|
714
831
|
<AiToolDisclosureProvider state={disclosureState}>
|
|
715
832
|
<div class={`flex flex-col ${props.compact ? "gap-1" : "gap-2"}`}>
|
|
716
|
-
<For each={
|
|
717
|
-
{(
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
833
|
+
<For each={keys()}>
|
|
834
|
+
{(key, index) => {
|
|
835
|
+
const group = () => keyedGroups().get(key)!;
|
|
836
|
+
return (
|
|
837
|
+
<Switch>
|
|
838
|
+
<Match
|
|
839
|
+
when={(() => {
|
|
840
|
+
const value = group();
|
|
841
|
+
return value.kind === "tools" ? value : undefined;
|
|
842
|
+
})()}
|
|
843
|
+
>
|
|
844
|
+
{(tools) => <ToolGroupView blocks={tools().blocks} active={Boolean(props.active && index() === keys().length - 1)} />}
|
|
845
|
+
</Match>
|
|
846
|
+
<Match
|
|
847
|
+
when={(() => {
|
|
848
|
+
const value = group();
|
|
849
|
+
return value.kind === "block" ? value : undefined;
|
|
850
|
+
})()}
|
|
851
|
+
>
|
|
852
|
+
{(item) => (
|
|
853
|
+
<AiTurnBlockView
|
|
854
|
+
block={item().block}
|
|
855
|
+
turnId={props.turnId}
|
|
856
|
+
streaming={props.streaming && index() === keys().length - 1}
|
|
857
|
+
active={props.active}
|
|
858
|
+
/>
|
|
859
|
+
)}
|
|
860
|
+
</Match>
|
|
861
|
+
</Switch>
|
|
862
|
+
);
|
|
863
|
+
}}
|
|
725
864
|
</For>
|
|
726
865
|
</div>
|
|
727
866
|
</AiToolDisclosureProvider>
|
|
@@ -4,8 +4,8 @@ import { For, type JSX, Show } from "solid-js";
|
|
|
4
4
|
import { formatAiFileSize } from "../attachments";
|
|
5
5
|
import type { AiTurnBlock } from "../protocol";
|
|
6
6
|
import { aiToolIcon, displayToolName, formatToolDetailText, isRecord } from "./message-utils";
|
|
7
|
-
import { AiToolActivity } from "./tool-disclosure";
|
|
8
7
|
import { aiChatMessages } from "./messages";
|
|
8
|
+
import { AiToolActivity } from "./tool-disclosure";
|
|
9
9
|
|
|
10
10
|
type ToolBlock = Extract<AiTurnBlock, { kind: "tool" }>;
|
|
11
11
|
|
|
@@ -325,9 +325,7 @@ function ReadFileView(props: { block: ToolBlock }) {
|
|
|
325
325
|
const start = number(result().offset);
|
|
326
326
|
const end = number(result().nextOffset);
|
|
327
327
|
const t = aiChatMessages(locale());
|
|
328
|
-
return end > start
|
|
329
|
-
? t.byteRange({ start: start.toLocaleString(locale()), end: end.toLocaleString(locale()) })
|
|
330
|
-
: t.read;
|
|
328
|
+
return end > start ? t.byteRange({ start: start.toLocaleString(locale()), end: end.toLocaleString(locale()) }) : t.read;
|
|
331
329
|
};
|
|
332
330
|
return (
|
|
333
331
|
<CompletedActivity
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CapabilityTablePresentationSchema, capabilityDataAtPath } from "../../contracts/capabilities";
|
|
2
|
+
import type { AiTurnBlock } from "../protocol";
|
|
3
|
+
|
|
4
|
+
export const capabilityTable = (result: unknown) => {
|
|
5
|
+
if (!result || typeof result !== "object") return null;
|
|
6
|
+
const presentation = CapabilityTablePresentationSchema.safeParse(capabilityDataAtPath(result, ["presentation"]));
|
|
7
|
+
if (!presentation.success) return null;
|
|
8
|
+
const rows = capabilityDataAtPath(capabilityDataAtPath(result, ["data"]), presentation.data.rowsPath);
|
|
9
|
+
if (!Array.isArray(rows)) return null;
|
|
10
|
+
return { presentation: presentation.data, rows };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Result presentation is independent of optional app branding. */
|
|
14
|
+
export const hasCapabilityTable = (block: Extract<AiTurnBlock, { kind: "tool" }>): boolean =>
|
|
15
|
+
block.status === "completed" && !block.isError && capabilityTable(block.result) !== null;
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import { ButtonLink, DataTable, type DataTableColumn, useLocale } from "@k2b/ui";
|
|
2
2
|
import { createMemo, For, Show } from "solid-js";
|
|
3
|
-
import { CapabilitySemanticLinkSchema,
|
|
3
|
+
import { CapabilitySemanticLinkSchema, capabilityDataAtPath } from "../../contracts/capabilities";
|
|
4
|
+
import { capabilityTable } from "./capability-result";
|
|
4
5
|
import { aiChatMessages } from "./messages";
|
|
5
6
|
|
|
6
|
-
export const capabilityTable = (result: unknown) => {
|
|
7
|
-
if (!result || typeof result !== "object") return null;
|
|
8
|
-
const presentation = CapabilityTablePresentationSchema.safeParse(capabilityDataAtPath(result, ["presentation"]));
|
|
9
|
-
if (!presentation.success) return null;
|
|
10
|
-
const rows = capabilityDataAtPath(capabilityDataAtPath(result, ["data"]), presentation.data.rowsPath);
|
|
11
|
-
if (!Array.isArray(rows)) return null;
|
|
12
|
-
return { presentation: presentation.data, rows };
|
|
13
|
-
};
|
|
14
|
-
|
|
15
7
|
export const capabilityCellText = (value: unknown): string => {
|
|
16
8
|
if (value === undefined || value === null) return "—";
|
|
17
9
|
if (typeof value === "string") return value;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { ChatAttachment, ChatModelOption, ChatSubmitInput } from "@k2b/ui";
|
|
1
|
+
import type { ChatAttachment, ChatMention, ChatModelOption, ChatSubmitInput } from "@k2b/ui";
|
|
2
2
|
import { AI_TURN_IMAGE_MAX_TOTAL_BYTES } from "../limits";
|
|
3
3
|
import type { AiResourceMarker } from "../resource-markers";
|
|
4
|
-
import type { AiPublicModelProfile, AiUserContentPart } from "../types";
|
|
4
|
+
import type { AiDraftContentPart, AiPublicModelProfile, AiUserContentPart } from "../types";
|
|
5
5
|
import {
|
|
6
6
|
type AiComposerAttachment,
|
|
7
7
|
FILE_INPUT_ACCEPT,
|
|
@@ -21,6 +21,7 @@ const AI_COMPOSER_TEXT_MAX_BYTES = AI_COMPOSER_TEXT_MAX_CHARS * 4;
|
|
|
21
21
|
const AI_PASTED_TEXT_FILE_NAME = /^pasted-(?:text|[a-f0-9]{8})(?:-\d+)?\.txt$/i;
|
|
22
22
|
|
|
23
23
|
export type AiComposerSendInput = {
|
|
24
|
+
draftContent?: AiDraftContentPart[];
|
|
24
25
|
message?: string;
|
|
25
26
|
content?: AiUserContentPart[];
|
|
26
27
|
files?: File[];
|
|
@@ -96,7 +97,7 @@ const isAiComposerAttachment = (value: unknown): value is AiComposerAttachment =
|
|
|
96
97
|
if (!value || typeof value !== "object") return false;
|
|
97
98
|
const candidate = value as Partial<AiComposerAttachment>;
|
|
98
99
|
return (
|
|
99
|
-
["image", "file", "stored-file", "resource"].includes(candidate.kind ?? "") &&
|
|
100
|
+
["image", "file", "stored-file", "resource", "project-file"].includes(candidate.kind ?? "") &&
|
|
100
101
|
typeof candidate.id === "string" &&
|
|
101
102
|
typeof candidate.name === "string"
|
|
102
103
|
);
|
|
@@ -107,6 +108,24 @@ export const aiComposerAttachmentRecords = (attachments: readonly ChatAttachment
|
|
|
107
108
|
|
|
108
109
|
export const aiComposerSendInput = (input: ChatSubmitInput): AiComposerSendInput => {
|
|
109
110
|
const attachments = aiComposerAttachmentRecords(input.attachments);
|
|
111
|
+
if (input.mentions?.length || attachments.some((attachment) => attachment.kind === "project-file")) {
|
|
112
|
+
const draftContent: AiDraftContentPart[] = [];
|
|
113
|
+
let offset = 0;
|
|
114
|
+
for (const mention of [...(input.mentions ?? [])].sort((a, b) => a.start - b.start)) {
|
|
115
|
+
if (mention.start < offset || mention.end > input.text.length || mention.start >= mention.end) continue;
|
|
116
|
+
const attachment = aiComposerAttachmentRecords([mention.attachment])[0];
|
|
117
|
+
if (!attachment || attachment.kind === "file" || attachment.kind === "image") continue;
|
|
118
|
+
if (mention.start > offset) draftContent.push({ type: "text", text: input.text.slice(offset, mention.start) });
|
|
119
|
+
draftContent.push(composerAttachmentPart(attachment, true));
|
|
120
|
+
offset = mention.end;
|
|
121
|
+
}
|
|
122
|
+
if (offset < input.text.length) draftContent.push({ type: "text", text: input.text.slice(offset) });
|
|
123
|
+
for (const attachment of attachments) {
|
|
124
|
+
if (attachment.kind !== "file" && attachment.kind !== "image") draftContent.push(composerAttachmentPart(attachment));
|
|
125
|
+
}
|
|
126
|
+
const files = attachments.flatMap((attachment) => (attachment.kind === "file" || attachment.kind === "image" ? [attachment.file] : []));
|
|
127
|
+
return { draftContent, files: files.length ? files : undefined };
|
|
128
|
+
}
|
|
110
129
|
const files = attachments.filter(
|
|
111
130
|
(attachment): attachment is PendingAiImage | PendingAiVfsFile => attachment.kind === "image" || attachment.kind === "file",
|
|
112
131
|
);
|
|
@@ -167,3 +186,64 @@ export const readAiComposerFiles = async (
|
|
|
167
186
|
discarded: Math.max(0, files.length - candidates.length),
|
|
168
187
|
};
|
|
169
188
|
};
|
|
189
|
+
|
|
190
|
+
const composerAttachmentPart = (
|
|
191
|
+
attachment: Exclude<AiComposerAttachment, { kind: "image" | "file" }>,
|
|
192
|
+
inline = false,
|
|
193
|
+
): AiDraftContentPart => {
|
|
194
|
+
if (attachment.kind === "resource")
|
|
195
|
+
return { type: "resource", ref: attachment.ref, title: attachment.name, icon: attachment.icon, href: attachment.href, inline };
|
|
196
|
+
if (attachment.kind === "project-file") return { type: "project-file", path: attachment.path, inline };
|
|
197
|
+
return {
|
|
198
|
+
type: "file",
|
|
199
|
+
path: attachment.path,
|
|
200
|
+
version: attachment.version,
|
|
201
|
+
mediaType: attachment.mediaType,
|
|
202
|
+
size: attachment.size,
|
|
203
|
+
inline,
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/** Hydrate the same ordered content used by the server draft and queued messages. */
|
|
208
|
+
export function aiComposerDraft(content: readonly AiDraftContentPart[]): {
|
|
209
|
+
text: string;
|
|
210
|
+
mentions: ChatMention[];
|
|
211
|
+
attachments: AiComposerAttachment[];
|
|
212
|
+
} {
|
|
213
|
+
let text = "";
|
|
214
|
+
const mentions: ChatMention[] = [];
|
|
215
|
+
const attachments: AiComposerAttachment[] = [];
|
|
216
|
+
for (const part of content) {
|
|
217
|
+
if (part.type === "text") {
|
|
218
|
+
text += part.text;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const attachment: AiComposerAttachment =
|
|
222
|
+
part.type === "resource"
|
|
223
|
+
? {
|
|
224
|
+
kind: "resource",
|
|
225
|
+
id: `resource:${part.ref.type}:${part.ref.id}`,
|
|
226
|
+
ref: part.ref,
|
|
227
|
+
name: part.title ?? part.ref.id,
|
|
228
|
+
icon: part.icon ?? "ti ti-link",
|
|
229
|
+
href: part.href,
|
|
230
|
+
}
|
|
231
|
+
: part.type === "project-file"
|
|
232
|
+
? { kind: "project-file", id: part.path, path: part.path, name: part.path, icon: "ti ti-file" }
|
|
233
|
+
: {
|
|
234
|
+
kind: "stored-file",
|
|
235
|
+
id: `file:${part.path}:${part.version}`,
|
|
236
|
+
path: part.path,
|
|
237
|
+
name: part.path.split("/").at(-1) || part.path,
|
|
238
|
+
version: part.version,
|
|
239
|
+
mediaType: part.mediaType,
|
|
240
|
+
size: part.size,
|
|
241
|
+
icon: "ti ti-file",
|
|
242
|
+
};
|
|
243
|
+
if (part.inline) {
|
|
244
|
+
mentions.push({ start: text.length, end: text.length + attachment.name.length, attachment: aiChatAttachments([attachment])[0]! });
|
|
245
|
+
text += attachment.name;
|
|
246
|
+
} else attachments.push(attachment);
|
|
247
|
+
}
|
|
248
|
+
return { text, mentions, attachments };
|
|
249
|
+
}
|
|
@@ -28,6 +28,8 @@ type RetryMessageHandler = (entry: AiStoredMessage, input?: AiRetryMessageInput)
|
|
|
28
28
|
type RetrySteerHandler = (block: Extract<AiTurnBlock, { kind: "steer_message" }>) => void | Promise<void>;
|
|
29
29
|
|
|
30
30
|
export type AiChatActions = {
|
|
31
|
+
/** Host-owned visualizations, persisted independently of the execution session. */
|
|
32
|
+
renderCodePresentation?: (result: unknown) => JSX.Element;
|
|
31
33
|
/** Prevents turn-continuation actions while the current turn is stopping. */
|
|
32
34
|
actionDisabled?: () => boolean;
|
|
33
35
|
onApproval?: ApprovalHandler;
|
|
@@ -36,8 +38,12 @@ export type AiChatActions = {
|
|
|
36
38
|
onRetryMessage?: RetryMessageHandler;
|
|
37
39
|
onRetrySteer?: RetrySteerHandler;
|
|
38
40
|
onMessageFeedback?: (entry: AiStoredMessage, feedback: Omit<AiMessageFeedback, "updatedAt"> | null) => void | Promise<void>;
|
|
41
|
+
/** Open the originating background run for a delivered scheduled result. */
|
|
42
|
+
onOpenScheduledTaskRun?: (taskId: string, occurrenceId: string) => void;
|
|
39
43
|
/** Open a conversation VFS file in the host application's artifact surface. */
|
|
40
44
|
onOpenFile?: (path: string) => void;
|
|
45
|
+
/** Resolve a Markdown link only when it identifies a known conversation file. */
|
|
46
|
+
resolveFileLink?: (href: string) => { path: string; href: string } | null;
|
|
41
47
|
/** Download URL for a conversation VFS file (present blocks, attachment chips). */
|
|
42
48
|
fileUrl?: (path: string) => string | null;
|
|
43
49
|
};
|
|
@@ -53,8 +59,8 @@ export function AiChatActionsProvider(props: { actions?: AiChatActions; children
|
|
|
53
59
|
return <AiChatActionContext.Provider value={props.actions ?? {}}>{props.children}</AiChatActionContext.Provider>;
|
|
54
60
|
}
|
|
55
61
|
|
|
56
|
-
const usageValue = (usage: Usage | null | undefined, key: "input" | "output" | "total"
|
|
57
|
-
const value = (usage as Partial<Record<"input" | "output" | "total"
|
|
62
|
+
const usageValue = (usage: Usage | null | undefined, key: "input" | "output" | "total") => {
|
|
63
|
+
const value = (usage as Partial<Record<"input" | "output" | "total", unknown>> | null | undefined)?.[key];
|
|
58
64
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
59
65
|
};
|
|
60
66
|
|
|
@@ -70,7 +76,6 @@ const assistantResponseInfo = (entries: AiStoredMessage[], locale: string) => {
|
|
|
70
76
|
const toolCalls = blocks.filter((block) => block.type === "tool_call");
|
|
71
77
|
const aggregate = entry.loopAggregate;
|
|
72
78
|
const usage = aggregate?.usage ?? entry.usage;
|
|
73
|
-
const credits = usageValue(usage, "creditsUsed");
|
|
74
79
|
const timing = aggregate?.timing;
|
|
75
80
|
|
|
76
81
|
const meta = [
|
|
@@ -100,7 +105,6 @@ const assistantResponseInfo = (entries: AiStoredMessage[], locale: string) => {
|
|
|
100
105
|
input: usageValue(usage, "input"),
|
|
101
106
|
output: usageValue(usage, "output"),
|
|
102
107
|
total: usageValue(usage, "total"),
|
|
103
|
-
credits,
|
|
104
108
|
},
|
|
105
109
|
turns: aggregate?.assistantMessageCount ?? 1,
|
|
106
110
|
toolCallCount: aggregate?.toolCallCount ?? toolCalls.length,
|
|
@@ -184,9 +188,6 @@ const openAssistantResponseInfo = (entries: AiStoredMessage[], locale: string) =
|
|
|
184
188
|
accent={{ tone: "red", icon: "ti ti-alert-triangle" }}
|
|
185
189
|
/>
|
|
186
190
|
</Show>
|
|
187
|
-
<Show when={info.usage.credits !== null && info.usage.credits > 0}>
|
|
188
|
-
<StatCell label="Credits" value={info.usage.credits?.toLocaleString(locale, { maximumFractionDigits: 6 }) ?? "–"} />
|
|
189
|
-
</Show>
|
|
190
191
|
</StatGrid>
|
|
191
192
|
|
|
192
193
|
<Show when={info.tools.length > 0}>
|