@k2b/cloud 0.40.0 → 0.42.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 +9 -5
- package/src/_internal/bounded-json.ts +14 -2
- package/src/_internal/capabilities.ts +87 -8
- package/src/_internal/capability-reviews.ts +6 -0
- package/src/_internal/capability-sentences.ts +2 -1
- package/src/_internal/capability-timeout.ts +71 -0
- package/src/_internal/define-app.ts +158 -92
- package/src/_internal/heartbeat.ts +203 -14
- package/src/_internal/http-drain.ts +56 -0
- package/src/_internal/registry-validation.ts +3 -0
- package/src/_internal/registry.ts +28 -1
- package/src/_internal/runtime-context.ts +1 -0
- package/src/_internal/setup-lock.ts +103 -0
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.tsx +16 -17
- package/src/access/PrincipalPicker.tsx +45 -10
- package/src/access/messages.ts +8 -0
- package/src/access/principal-label.ts +18 -0
- package/src/access/ui.ts +1 -0
- package/src/account/EntitySearch.tsx +5 -4
- package/src/ai/action-review.ts +2 -1
- package/src/ai/app-skill-store.ts +1 -1
- package/src/ai/capabilities.ts +21 -3
- package/src/ai/capability-execution.ts +14 -12
- package/src/ai/chat/action-card.ts +76 -4
- package/src/ai/chat/action-messages.ts +14 -0
- package/src/ai/chat/action-question.browser-harness.tsx +12 -0
- package/src/ai/chat/action-question.tsx +214 -23
- package/src/ai/chat/action-text.ts +288 -0
- package/src/ai/chat/tool-action-words.ts +31 -0
- package/src/ai/chat/tool-action.tsx +1 -26
- package/src/ai/chat-tasks.ts +2 -1
- package/src/ai/client/controller.ts +150 -32
- package/src/ai/client/transport.ts +14 -2
- package/src/ai/code-capability-routes.ts +27 -23
- package/src/ai/code-runtime-tools.ts +14 -16
- package/src/ai/code-source-tools.ts +16 -18
- package/src/ai/executor.ts +64 -21
- package/src/ai/fetch-file-tool.ts +67 -76
- package/src/ai/firecrawl-tools.ts +15 -2
- package/src/ai/http.ts +1 -0
- package/src/ai/index.ts +1 -0
- package/src/ai/migrate.ts +28 -0
- package/src/ai/open-questions.ts +168 -0
- package/src/ai/projects.ts +1 -2
- package/src/ai/protocol.ts +2 -2
- package/src/ai/question-text.ts +8 -0
- package/src/ai/recent-resources.ts +12 -5
- package/src/ai/review-content.ts +77 -0
- package/src/ai/routes.ts +119 -35
- package/src/ai/runtime.ts +1 -0
- package/src/ai/skill-tool.ts +17 -6
- package/src/ai/skills-routes.ts +72 -27
- package/src/ai/skills.ts +139 -60
- package/src/ai/store.ts +184 -31
- package/src/ai/testing.ts +36 -0
- package/src/ai/tool-sentences.ts +152 -34
- package/src/ai/tool-wording.ts +12 -6
- package/src/ai/tools.ts +8 -3
- package/src/ai/types.ts +25 -4
- package/src/ai/usage.ts +2 -2
- package/src/api/admin-core-settings.ts +15 -2
- package/src/api/admin-geo.ts +54 -0
- package/src/api/admin-linux-identities.ts +6 -3
- package/src/api/admin-outgoing-mail.ts +29 -15
- package/src/api/app-approval-actions.ts +31 -7
- package/src/api/approvals-loader.ts +3 -3
- package/src/api/approvals.ts +51 -0
- package/src/api/apps.ts +4 -2
- package/src/api/auth/schemas.ts +3 -0
- package/src/api/auth.ts +54 -18
- package/src/api/capabilities.ts +258 -181
- package/src/api/capability-streams.ts +21 -13
- package/src/api/cli-plugins.ts +4 -1
- package/src/api/geo-glyphs.ts +11 -0
- package/src/api/geo.ts +128 -0
- package/src/api/help.ts +12 -4
- package/src/api/index.ts +5 -0
- package/src/api/mcp.ts +73 -41
- package/src/api/me-panel-consent.ts +8 -5
- package/src/api/me.ts +79 -3
- package/src/api/search.ts +21 -14
- package/src/api/sync-ops.ts +19 -13
- package/src/api/widgets.ts +42 -18
- package/src/approvals/Approvals.tsx +30 -66
- package/src/approvals/approval-card.ts +68 -0
- package/src/browser/CloudResourceSearch.browser-harness.tsx +53 -1
- package/src/browser/CloudResourceSearch.tsx +3 -13
- package/src/browser/PanelLeaveNotice.tsx +18 -0
- package/src/browser/navigation-search.ts +78 -25
- package/src/browser/panel-leave.ts +69 -0
- package/src/browser/panel-messages.ts +12 -0
- package/src/browser/panel-slot.ts +173 -24
- package/src/browser/panel.ts +3 -0
- package/src/browser/resource-search-messages.ts +2 -0
- package/src/capabilities/client.ts +28 -1
- package/src/capabilities/executions.ts +22 -3
- package/src/capabilities/geo-client.ts +57 -0
- package/src/capabilities/response.ts +2 -1
- package/src/capabilities/review-storage.ts +24 -0
- package/src/capabilities/reviews.ts +17 -3
- package/src/capabilities/server.ts +36 -16
- package/src/cli/access.ts +15 -23
- package/src/cli/admin/linux.ts +13 -2
- package/src/cli/admin/outgoing-mail.ts +26 -4
- package/src/cli/capabilities.ts +19 -0
- package/src/config/env.ts +0 -6
- package/src/contracts/app-access.ts +48 -0
- package/src/contracts/app-approval.ts +3 -3
- package/src/contracts/app.ts +4 -1
- package/src/contracts/approvals.ts +2 -2
- package/src/contracts/avatar.ts +186 -0
- package/src/contracts/capabilities.ts +149 -55
- package/src/contracts/geo-admin.ts +127 -0
- package/src/contracts/geo-map.ts +19 -0
- package/src/contracts/geo-route.ts +168 -0
- package/src/contracts/geo.ts +248 -0
- package/src/contracts/index.ts +4 -0
- package/src/contracts/outgoing-mail.ts +17 -4
- package/src/contracts/registry.ts +2 -0
- package/src/contracts/shared.ts +3 -0
- package/src/events/app-access.ts +68 -0
- package/src/events/live-delivery.ts +128 -0
- package/src/events/live.ts +14 -130
- package/src/geo/CloudMap.tsx +95 -0
- package/src/geo/map-messages.ts +17 -0
- package/src/geo/ui.ts +1 -0
- package/src/index.ts +2 -0
- package/src/server/client-ip.ts +36 -0
- package/src/server/index.ts +3 -4
- package/src/server/middleware/app-access.ts +312 -0
- package/src/server/middleware/auth.ts +5 -3
- package/src/server/middleware/invocation.ts +11 -0
- package/src/server/middleware/rate-limit.ts +4 -4
- package/src/server/services/access.ts +13 -9
- package/src/server/services/index.ts +0 -2
- package/src/server/services/services.ts +0 -2
- package/src/services/accounts/app.ts +23 -3
- package/src/services/accounts/posix.ts +21 -7
- package/src/services/accounts/users.ts +12 -1
- package/src/services/allowed-network-fetch.ts +16 -10
- package/src/services/app-access-metrics.ts +24 -0
- package/src/services/app-access.ts +123 -0
- package/src/services/app-discovery.ts +27 -0
- package/src/services/approvals/content.ts +40 -0
- package/src/services/approvals/device.ts +49 -33
- package/src/services/approvals/index.ts +15 -6
- package/src/services/approvals/list.ts +6 -11
- package/src/services/approvals/question.ts +72 -0
- package/src/services/audit/index.ts +1 -0
- package/src/services/auth-flows/ipa.ts +39 -2
- package/src/services/auth-flows/password-reset.ts +32 -6
- package/src/services/downstream-budget.ts +87 -0
- package/src/services/geo/admin.ts +262 -0
- package/src/services/geo/bucket.ts +67 -0
- package/src/services/geo/cache.ts +57 -0
- package/src/services/geo/costs.ts +5 -0
- package/src/services/geo/errors.ts +41 -0
- package/src/services/geo/fixtures/README.md +17 -0
- package/src/services/geo/fixtures/along.json +355 -0
- package/src/services/geo/fixtures/autocomplete.json +218 -0
- package/src/services/geo/fixtures/geocode.json +129 -0
- package/src/services/geo/fixtures/places.json +355 -0
- package/src/services/geo/fixtures/reverse.json +61 -0
- package/src/services/geo/fixtures/routing.json +57 -0
- package/src/services/geo/geoapify-route.ts +93 -0
- package/src/services/geo/geoapify.ts +281 -0
- package/src/services/geo/ledger.ts +122 -0
- package/src/services/geo/map.ts +160 -0
- package/src/services/geo/migrate.ts +25 -0
- package/src/services/geo/polyline.ts +184 -0
- package/src/services/geo/route-chain.ts +145 -0
- package/src/services/geo/route-costs.ts +9 -0
- package/src/services/geo/route-geometry.ts +39 -0
- package/src/services/geo/route-plan.ts +172 -0
- package/src/services/geo/route-service.ts +433 -0
- package/src/services/geo/service.ts +300 -0
- package/src/services/geo/settings.ts +49 -0
- package/src/services/help/index.ts +16 -7
- package/src/services/help/types.ts +6 -1
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/issue-invocation.ts +67 -0
- package/src/services/identity/key-ring.ts +5 -3
- package/src/services/identity/runtime-config.ts +23 -0
- package/src/services/index.ts +11 -0
- package/src/services/mandates/index.ts +40 -3
- package/src/services/network-security.ts +6 -3
- package/src/services/network-transport.ts +99 -0
- package/src/services/notifications/catalog.ts +3 -2
- package/src/services/notifications/runtime.ts +1 -0
- package/src/services/notifications/web-push-transport.ts +45 -38
- package/src/services/outgoing-mail/dispatcher.ts +6 -1
- package/src/services/outgoing-mail/index.ts +55 -1
- package/src/services/outgoing-mail/messages.ts +82 -3
- package/src/services/outgoing-mail/resolve-host.ts +2 -2
- package/src/services/outgoing-mail/retention.ts +13 -1
- package/src/services/outgoing-mail/store.ts +36 -6
- package/src/services/outgoing-mail/test-send.ts +34 -4
- package/src/services/outgoing-mail/transport.ts +1 -1
- package/src/services/pdf/gotenberg.ts +11 -38
- package/src/services/pdf/markdown.ts +160 -2
- package/src/services/pdf/template-preview.ts +1 -1
- package/src/services/public-http.ts +28 -40
- package/src/services/session/index.ts +203 -29
- package/src/services/settings/app.ts +4 -1
- package/src/services/settings/core-settings.ts +88 -0
- package/src/services/settings/defaults.ts +8 -2
- package/src/services/settings/errors.ts +11 -0
- package/src/services/settings/geo-budget.ts +40 -0
- package/src/services/settings/index.ts +4 -3
- package/src/services/settings/store.ts +25 -17
- package/src/services/weather/geo.ts +29 -111
- package/src/services/weather/index.ts +4 -3
- package/src/services/weather/locations.ts +8 -5
- package/src/services/weather/migrate.ts +1 -0
- package/src/services/weather/settings.ts +0 -16
- package/src/shared/__snapshots__/avatar-art.test.ts.snap +5 -0
- package/src/shared/avatar-art.ts +400 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/network-address.ts +22 -3
- package/src/shared/template-rendering.ts +36 -4
- package/src/shared-text/documents.ts +1 -1
- package/src/shared-text/index.ts +2 -0
- package/src/shared-text/log.ts +51 -3
- package/src/shared-text/snapshots.ts +340 -0
- package/src/ssr/AdminLayout.tsx +4 -2
- package/src/ssr/CloudShell.island.tsx +6 -0
- package/src/ssr/HelpPanelArticle.island.tsx +308 -0
- package/src/ssr/HelpPanelBack.island.tsx +32 -0
- package/src/ssr/HelpPanelMenu.island.tsx +133 -0
- package/src/ssr/HelpPanelSearch.island.tsx +123 -0
- package/src/ssr/HelpPanelShortcuts.island.tsx +22 -0
- package/src/ssr/HelpPanelView.island.tsx +27 -0
- package/src/ssr/Layout.tsx +65 -15
- package/src/ssr/LayoutHelp.browser-harness.tsx +1 -1
- package/src/ssr/LayoutHelp.tsx +185 -542
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +6 -7
- package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
- package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
- package/src/ssr/PanelRuntime.island.tsx +11 -7
- package/src/ssr/PwaLayout.tsx +2 -2
- package/src/ssr/RailApps.tsx +6 -0
- package/src/ssr/ShellRuntime.island.tsx +23 -7
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +151 -0
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
- package/src/ssr/admin-navigation.ts +1 -0
- package/src/ssr/app-badges.ts +6 -1
- package/src/ssr/compact-shell.browser-harness.ts +44 -5
- package/src/ssr/help.ts +7 -5
- package/src/ssr/index.ts +2 -1
- package/src/ssr/islands/index.ts +3 -4
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/layout-help-panel-controls.tsx +43 -0
- package/src/ssr/layout-help-panel-document.ts +67 -0
- package/src/ssr/layout-help-panel-lifecycle.ts +32 -0
- package/src/ssr/layout-help-panel-menu.tsx +113 -0
- package/src/ssr/layout-help-panel-modules.ts +7 -0
- package/src/ssr/layout-help-panel-reader.tsx +159 -0
- package/src/ssr/layout-help-panel-search.tsx +69 -0
- package/src/ssr/layout-help-panel-shortcuts.ts +7 -0
- package/src/ssr/layout-help-panel-state.ts +141 -0
- package/src/ssr/layout-help-panel-types.ts +19 -0
- package/src/ssr/layout-help-views.tsx +484 -0
- package/src/ssr/mobile-menu-history.ts +4 -0
- package/src/ssr/page-swap-fixture/Column.island.tsx +15 -0
- package/src/ssr/page-swap-fixture/ProviderProbe.island.tsx +31 -0
- package/src/ssr/page-swap.browser-harness.tsx +177 -0
- package/src/ssr/page-swap.ts +28 -0
- package/src/ssr/page-trail-runtime.ts +32 -13
- package/src/ssr/panel-consent.ts +2 -2
- package/src/ssr/panel-idle.ts +9 -0
- package/src/ssr/panel-slot.browser-harness.tsx +3 -0
- package/src/ssr/platform-messages.ts +8 -0
- package/src/ssr/runtime.ts +17 -0
- package/src/ssr/shell-data.tsx +10 -2
- package/src/styles/approvals.css +6 -0
- package/src/styles/effects.css +15 -0
- package/src/styles/global.css +70 -6
- package/src/server/services/geo.ts +0 -215
|
@@ -4,15 +4,25 @@ import {
|
|
|
4
4
|
Chat,
|
|
5
5
|
type ChatDecisionState,
|
|
6
6
|
type ChatOutcomeLine,
|
|
7
|
+
dialogCore,
|
|
7
8
|
isStructuredDataValue,
|
|
8
9
|
LocaleProvider,
|
|
9
10
|
MarkdownView,
|
|
11
|
+
PanelDialog,
|
|
12
|
+
Placeholder,
|
|
13
|
+
panelDialogOptions,
|
|
10
14
|
ReferenceChip,
|
|
11
15
|
StructuredDataPreview,
|
|
12
16
|
} from "@k2b/ui";
|
|
13
17
|
import { Lexer, type Token, type Tokens } from "marked";
|
|
14
|
-
import { createMemo, createSignal, createUniqueId, For, type JSX, Match, Show, Switch } from "solid-js";
|
|
15
|
-
import
|
|
18
|
+
import { createMemo, createSignal, createUniqueId, For, type JSX, Match, onCleanup, onMount, Show, Switch } from "solid-js";
|
|
19
|
+
import { readCapabilityReviewContent } from "../../capabilities/client";
|
|
20
|
+
import type {
|
|
21
|
+
CapabilityPerson,
|
|
22
|
+
CapabilityPreview,
|
|
23
|
+
CapabilityReviewContent,
|
|
24
|
+
CapabilityReviewContentRef,
|
|
25
|
+
} from "../../contracts/capabilities";
|
|
16
26
|
import { formatBytes } from "../../shared/format";
|
|
17
27
|
import type { ActionCard, ActionChip, ActionPreviewModel, ActionText } from "./action-card";
|
|
18
28
|
import { actionMessages } from "./action-messages";
|
|
@@ -160,13 +170,13 @@ const mayOverflow = (text: string): boolean =>
|
|
|
160
170
|
* not fit offers to show the rest in place, and any other text stands whole, so nothing a person approves stays out of
|
|
161
171
|
* reach even where the estimate is short, such as for wide characters.
|
|
162
172
|
*/
|
|
163
|
-
function PreviewExcerpt(props: { text: string; readMore?: string; label: string }) {
|
|
173
|
+
function PreviewExcerpt(props: { text: string; readMore?: string; label: string; full?: boolean; compact?: boolean }) {
|
|
164
174
|
const id = createUniqueId();
|
|
165
175
|
const [expanded, setExpanded] = createSignal(false);
|
|
166
|
-
const offer = () => !props.readMore && mayOverflow(props.text);
|
|
176
|
+
const offer = () => !props.full && !props.compact && !props.readMore && mayOverflow(props.text);
|
|
167
177
|
return (
|
|
168
178
|
<>
|
|
169
|
-
<Chat.Preview.Excerpt id={id} expanded={expanded() || (!props.readMore && !offer())}>
|
|
179
|
+
<Chat.Preview.Excerpt id={id} expanded={props.full || expanded() || (!props.compact && !props.readMore && !offer())}>
|
|
170
180
|
{props.text}
|
|
171
181
|
</Chat.Preview.Excerpt>
|
|
172
182
|
<Show when={offer() && !expanded()}>
|
|
@@ -217,22 +227,33 @@ function MessagePreview(props: {
|
|
|
217
227
|
locale: string;
|
|
218
228
|
timeZone: string;
|
|
219
229
|
readMore?: string;
|
|
230
|
+
/** Only a question that sends says when the mail goes out; a draft that is created or deleted goes nowhere. */
|
|
231
|
+
sends: boolean;
|
|
232
|
+
full?: boolean;
|
|
233
|
+
compact?: boolean;
|
|
220
234
|
}) {
|
|
221
235
|
const t = () => actionMessages(props.locale);
|
|
236
|
+
const text = (value: string) => (props.full ? visibleReviewText(value) : value);
|
|
237
|
+
const person = (value: CapabilityPerson) => ({
|
|
238
|
+
...personRow(value),
|
|
239
|
+
...(value.name !== undefined ? { name: text(value.name) } : {}),
|
|
240
|
+
...(value.address !== undefined ? { address: text(value.address) } : {}),
|
|
241
|
+
});
|
|
222
242
|
const rows = () => {
|
|
223
243
|
const p = props.preview;
|
|
224
244
|
const more = (count?: number) => (count ? { value: <> {t().andMore({ count })}</> } : {});
|
|
225
245
|
return [
|
|
226
|
-
...(p.from ? [{ label: t().from, people: [
|
|
227
|
-
{ label: t().to, people: p.to.map(
|
|
228
|
-
...(p.cc?.length || p.more?.cc ? [{ label: t().cc, people: (p.cc ?? []).map(
|
|
246
|
+
...(p.from ? [{ label: t().from, people: [person(p.from)] }] : []),
|
|
247
|
+
{ label: t().to, people: p.to.map(person), ...more(p.more?.to) },
|
|
248
|
+
...(p.cc?.length || p.more?.cc ? [{ label: t().cc, people: (p.cc ?? []).map(person), ...more(p.more?.cc) }] : []),
|
|
229
249
|
...(p.bcc?.length || p.more?.bcc
|
|
230
|
-
? [{ label: t().bcc, people: (p.bcc ?? []).map(
|
|
250
|
+
? [{ label: t().bcc, people: (p.bcc ?? []).map(person), emphasis: true, ...more(p.more?.bcc) }]
|
|
231
251
|
: []),
|
|
232
252
|
];
|
|
233
253
|
};
|
|
234
254
|
const note = () => {
|
|
235
255
|
const p = props.preview;
|
|
256
|
+
if (!props.sends) return undefined;
|
|
236
257
|
if (p.sendAt) {
|
|
237
258
|
const [before, after] = t().sendsAt({ at: "\u0000" }).split("\u0000");
|
|
238
259
|
return (
|
|
@@ -248,9 +269,15 @@ function MessagePreview(props: {
|
|
|
248
269
|
return (
|
|
249
270
|
<>
|
|
250
271
|
<Chat.Preview.Header rows={rows()} note={note()} />
|
|
251
|
-
<Chat.Preview.Title>{props.preview.subject || t().noSubject}</Chat.Preview.Title>
|
|
252
|
-
<Show when={props.preview.body.trim()}>
|
|
253
|
-
<PreviewExcerpt
|
|
272
|
+
<Chat.Preview.Title>{text(props.preview.subject) || t().noSubject}</Chat.Preview.Title>
|
|
273
|
+
<Show when={props.full || props.preview.body.trim()}>
|
|
274
|
+
<PreviewExcerpt
|
|
275
|
+
text={props.full ? visibleReviewText(props.preview.body) : excerptText(props.preview.body, props.preview.format)}
|
|
276
|
+
readMore={props.readMore}
|
|
277
|
+
label={t().readMail}
|
|
278
|
+
full={props.full}
|
|
279
|
+
compact={props.compact}
|
|
280
|
+
/>
|
|
254
281
|
</Show>
|
|
255
282
|
<For each={props.preview.attachments ?? []}>
|
|
256
283
|
{(attachment) => (
|
|
@@ -259,7 +286,7 @@ function MessagePreview(props: {
|
|
|
259
286
|
accent={attachmentLook(attachment.name).accent}
|
|
260
287
|
meta={attachment.size !== undefined ? formatBytes(attachment.size, { locale: props.locale }) : undefined}
|
|
261
288
|
>
|
|
262
|
-
{attachment.name}
|
|
289
|
+
{text(attachment.name)}
|
|
263
290
|
</Chat.Preview.Line>
|
|
264
291
|
)}
|
|
265
292
|
</For>
|
|
@@ -273,7 +300,7 @@ function MessagePreview(props: {
|
|
|
273
300
|
<For each={props.preview.warnings ?? []}>
|
|
274
301
|
{(warning) => (
|
|
275
302
|
<Chat.Preview.Line icon="ti ti-alert-triangle" tone="warning">
|
|
276
|
-
<WarningText text={warning} />
|
|
303
|
+
<WarningText text={text(warning)} />
|
|
277
304
|
</Chat.Preview.Line>
|
|
278
305
|
)}
|
|
279
306
|
</For>
|
|
@@ -288,7 +315,14 @@ function MessagePreview(props: {
|
|
|
288
315
|
);
|
|
289
316
|
}
|
|
290
317
|
|
|
291
|
-
function ReviewPreviewBody(props: {
|
|
318
|
+
function ReviewPreviewBody(props: {
|
|
319
|
+
model: Extract<ActionPreviewModel, { kind: "review" }>;
|
|
320
|
+
card: ActionCard;
|
|
321
|
+
readMore?: string;
|
|
322
|
+
full?: boolean;
|
|
323
|
+
baseRevision?: number;
|
|
324
|
+
compact?: boolean;
|
|
325
|
+
}) {
|
|
292
326
|
const t = () => actionMessages(props.model.locale);
|
|
293
327
|
const locale = () => props.model.locale;
|
|
294
328
|
const tz = () => props.model.timeZone;
|
|
@@ -305,9 +339,22 @@ function ReviewPreviewBody(props: { model: Extract<ActionPreviewModel, { kind: "
|
|
|
305
339
|
const preview = () => props.model.preview;
|
|
306
340
|
return (
|
|
307
341
|
<Chat.Preview>
|
|
342
|
+
<Show when={props.baseRevision !== undefined}>
|
|
343
|
+
<Chat.Preview.Line icon="ti ti-history">{t().baseRevision({ revision: props.baseRevision! })}</Chat.Preview.Line>
|
|
344
|
+
</Show>
|
|
308
345
|
<Switch>
|
|
309
346
|
<Match when={preview()?.kind === "message" ? (preview() as Extract<CapabilityPreview, { kind: "message" }>) : undefined}>
|
|
310
|
-
{(message) =>
|
|
347
|
+
{(message) => (
|
|
348
|
+
<MessagePreview
|
|
349
|
+
preview={message()}
|
|
350
|
+
locale={locale()}
|
|
351
|
+
timeZone={tz()}
|
|
352
|
+
readMore={props.readMore}
|
|
353
|
+
sends={props.card.reason.code === "sends"}
|
|
354
|
+
full={props.full}
|
|
355
|
+
compact={props.compact}
|
|
356
|
+
/>
|
|
357
|
+
)}
|
|
311
358
|
</Match>
|
|
312
359
|
<Match when={preview()?.kind === "event" ? (preview() as Extract<CapabilityPreview, { kind: "event" }>) : undefined}>
|
|
313
360
|
{(event) => (
|
|
@@ -396,9 +443,17 @@ function ReviewPreviewBody(props: { model: Extract<ActionPreviewModel, { kind: "
|
|
|
396
443
|
<Match when={preview()?.kind === "page" ? (preview() as Extract<CapabilityPreview, { kind: "page" }>) : undefined}>
|
|
397
444
|
{(page) => (
|
|
398
445
|
<>
|
|
399
|
-
<Chat.Preview.Title meta={page().place
|
|
400
|
-
|
|
401
|
-
|
|
446
|
+
<Chat.Preview.Title meta={props.full && page().place ? visibleReviewText(page().place!) : page().place}>
|
|
447
|
+
{props.full ? visibleReviewText(page().title) : page().title}
|
|
448
|
+
</Chat.Preview.Title>
|
|
449
|
+
<Show when={props.full || page().body.trim()}>
|
|
450
|
+
<PreviewExcerpt
|
|
451
|
+
text={props.full ? visibleReviewText(page().body) : excerptText(page().body, page().format)}
|
|
452
|
+
readMore={props.readMore}
|
|
453
|
+
label={t().readPage}
|
|
454
|
+
full={props.full}
|
|
455
|
+
compact={props.compact}
|
|
456
|
+
/>
|
|
402
457
|
</Show>
|
|
403
458
|
<Show when={page().changes?.length ? page().changes : undefined}>
|
|
404
459
|
{(changes) => (
|
|
@@ -556,15 +611,113 @@ function LegacyPreview(props: { model: Extract<ActionPreviewModel, { kind: "lega
|
|
|
556
611
|
);
|
|
557
612
|
}
|
|
558
613
|
|
|
614
|
+
/** Controls remain inspectable instead of being removed or changing reading order. */
|
|
615
|
+
export const visibleReviewText = (text: string): string =>
|
|
616
|
+
text.replace(/[\p{Cc}\p{Cf}\p{Cs}\u2028\u2029]/gu, (character) =>
|
|
617
|
+
character === "\n" || character === "\t" ? character : `⟦U+${character.codePointAt(0)!.toString(16).toUpperCase().padStart(4, "0")}⟧`,
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
function ReviewContentPanel(props: {
|
|
621
|
+
card: ActionCard;
|
|
622
|
+
source: CapabilityReviewContentRef;
|
|
623
|
+
timeZone: string;
|
|
624
|
+
close: () => void;
|
|
625
|
+
onLoaded: () => void;
|
|
626
|
+
}) {
|
|
627
|
+
const t = () => actionMessages(props.card.locale);
|
|
628
|
+
const [content, setContent] = createSignal<CapabilityReviewContent>();
|
|
629
|
+
const [failed, setFailed] = createSignal(false);
|
|
630
|
+
const [loading, setLoading] = createSignal(false);
|
|
631
|
+
let request: AbortController | undefined;
|
|
632
|
+
let disposed = false;
|
|
633
|
+
onCleanup(() => {
|
|
634
|
+
disposed = true;
|
|
635
|
+
request?.abort();
|
|
636
|
+
});
|
|
637
|
+
const load = async () => {
|
|
638
|
+
if (loading() || disposed) return;
|
|
639
|
+
request = new AbortController();
|
|
640
|
+
setLoading(true);
|
|
641
|
+
setFailed(false);
|
|
642
|
+
try {
|
|
643
|
+
const result = await readCapabilityReviewContent(props.source, { signal: request.signal });
|
|
644
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
645
|
+
if (disposed) return;
|
|
646
|
+
setContent(result.data);
|
|
647
|
+
props.onLoaded();
|
|
648
|
+
} catch {
|
|
649
|
+
if (!disposed) setFailed(true);
|
|
650
|
+
} finally {
|
|
651
|
+
if (!disposed) setLoading(false);
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
onMount(() => void load());
|
|
655
|
+
return (
|
|
656
|
+
<LocaleProvider locale={props.card.locale}>
|
|
657
|
+
<PanelDialog>
|
|
658
|
+
<PanelDialog.Header
|
|
659
|
+
icon={props.card.app.icon}
|
|
660
|
+
title={t().fullReview}
|
|
661
|
+
subtitle={props.card.questionText}
|
|
662
|
+
close={props.close}
|
|
663
|
+
closeLabel={t().fullReviewClose}
|
|
664
|
+
/>
|
|
665
|
+
<PanelDialog.Body>
|
|
666
|
+
<Show
|
|
667
|
+
when={content()}
|
|
668
|
+
fallback={
|
|
669
|
+
<Show when={failed()} fallback={<Placeholder state="loading" title={t().fullReviewLoading} variant="panel" />}>
|
|
670
|
+
<Placeholder
|
|
671
|
+
state="error"
|
|
672
|
+
title={t().fullReviewFailed}
|
|
673
|
+
variant="panel"
|
|
674
|
+
action={
|
|
675
|
+
<Button variant="secondary" onClick={() => void load()}>
|
|
676
|
+
{t().fullReviewRetry}
|
|
677
|
+
</Button>
|
|
678
|
+
}
|
|
679
|
+
/>
|
|
680
|
+
</Show>
|
|
681
|
+
}
|
|
682
|
+
>
|
|
683
|
+
{(value) => (
|
|
684
|
+
<ReviewPreviewBody
|
|
685
|
+
card={props.card}
|
|
686
|
+
full
|
|
687
|
+
baseRevision={value().baseRevision}
|
|
688
|
+
model={{
|
|
689
|
+
kind: "review",
|
|
690
|
+
locale: props.card.locale,
|
|
691
|
+
timeZone: props.timeZone,
|
|
692
|
+
preview: value(),
|
|
693
|
+
slots: {},
|
|
694
|
+
consequences: [],
|
|
695
|
+
}}
|
|
696
|
+
/>
|
|
697
|
+
)}
|
|
698
|
+
</Show>
|
|
699
|
+
</PanelDialog.Body>
|
|
700
|
+
</PanelDialog>
|
|
701
|
+
</LocaleProvider>
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
|
|
559
705
|
/** The thing the question is about, from its review, or an old review's rows and Markdown. */
|
|
560
|
-
export function ActionPreview(props: { card: ActionCard; timeZone: string }) {
|
|
706
|
+
export function ActionPreview(props: { card: ActionCard; timeZone: string; onContentLoaded?: (key: string) => void }) {
|
|
561
707
|
return (
|
|
562
708
|
<>
|
|
563
709
|
<Show when={props.card.preview}>
|
|
564
710
|
{(model) => (
|
|
565
711
|
<Switch>
|
|
566
712
|
<Match when={model().kind === "review" ? (model() as Extract<ActionPreviewModel, { kind: "review" }>) : undefined}>
|
|
567
|
-
{(review) =>
|
|
713
|
+
{(review) => (
|
|
714
|
+
<ReviewPreviewBody
|
|
715
|
+
model={review()}
|
|
716
|
+
card={props.card}
|
|
717
|
+
compact={!!props.card.contentRef}
|
|
718
|
+
readMore={props.card.contentRef ? undefined : props.card.readMore}
|
|
719
|
+
/>
|
|
720
|
+
)}
|
|
568
721
|
</Match>
|
|
569
722
|
<Match when={model().kind === "legacy" ? (model() as Extract<ActionPreviewModel, { kind: "legacy" }>) : undefined}>
|
|
570
723
|
{(legacy) => <LegacyPreview model={legacy()} card={props.card} timeZone={props.timeZone} />}
|
|
@@ -572,6 +725,41 @@ export function ActionPreview(props: { card: ActionCard; timeZone: string }) {
|
|
|
572
725
|
</Switch>
|
|
573
726
|
)}
|
|
574
727
|
</Show>
|
|
728
|
+
<Show when={props.card.contentRef}>
|
|
729
|
+
{(reference) => (
|
|
730
|
+
<Chat.Preview>
|
|
731
|
+
<Button
|
|
732
|
+
variant="ghost"
|
|
733
|
+
size="sm"
|
|
734
|
+
onClick={() => {
|
|
735
|
+
const card = props.card;
|
|
736
|
+
const source = reference();
|
|
737
|
+
void dialogCore.open<void>(
|
|
738
|
+
(close) => (
|
|
739
|
+
<ReviewContentPanel
|
|
740
|
+
card={card}
|
|
741
|
+
source={source}
|
|
742
|
+
timeZone={props.timeZone}
|
|
743
|
+
close={close}
|
|
744
|
+
onLoaded={() => props.onContentLoaded?.(`${source.href}:${source.digest}`)}
|
|
745
|
+
/>
|
|
746
|
+
),
|
|
747
|
+
{ ...panelDialogOptions, history: true, ariaLabel: actionMessages(card.locale).fullReview },
|
|
748
|
+
);
|
|
749
|
+
}}
|
|
750
|
+
>
|
|
751
|
+
{actionMessages(props.card.locale).fullReview}
|
|
752
|
+
</Button>
|
|
753
|
+
<Show when={props.card.readMore}>
|
|
754
|
+
{(href) => (
|
|
755
|
+
<Chat.Preview.ReadMore href={href()}>
|
|
756
|
+
{actionMessages(props.card.locale).openIn({ app: props.card.app.name })}
|
|
757
|
+
</Chat.Preview.ReadMore>
|
|
758
|
+
)}
|
|
759
|
+
</Show>
|
|
760
|
+
</Chat.Preview>
|
|
761
|
+
)}
|
|
762
|
+
</Show>
|
|
575
763
|
{/* The review's other links, such as editing the draft, stay one quiet click away before the answer. */}
|
|
576
764
|
<Show when={props.card.links.length > 0 || (!props.card.preview && props.card.readMore)}>
|
|
577
765
|
<Chat.Preview class="cloud-action-preview__links">
|
|
@@ -644,6 +832,9 @@ const confirmIcon = (card: ActionCard): string | undefined =>
|
|
|
644
832
|
|
|
645
833
|
/** One Assistant question in card v4. An answer collapses it at once to its outcome line, which takes the focus. */
|
|
646
834
|
export function ActionQuestion(props: ActionQuestionProps) {
|
|
835
|
+
const [loadedContent, setLoadedContent] = createSignal<string>();
|
|
836
|
+
const needsContent = () => props.card.contentRef && loadedContent() !== `${props.card.contentRef.href}:${props.card.contentRef.digest}`;
|
|
837
|
+
const unavailable = () => props.unavailable ?? (needsContent() ? actionMessages(props.card.locale).fullReviewRequired : undefined);
|
|
647
838
|
return (
|
|
648
839
|
<LocaleProvider locale={props.card.locale}>
|
|
649
840
|
<Chat.Decision
|
|
@@ -652,8 +843,8 @@ export function ActionQuestion(props: ActionQuestionProps) {
|
|
|
652
843
|
anchorId={props.anchorId}
|
|
653
844
|
sentence={<ActionTextView text={props.card.question} accent={props.card.app.accent} />}
|
|
654
845
|
reason={{ text: props.card.reason.text, icon: props.card.reason.icon, tone: props.card.reason.tone }}
|
|
655
|
-
unavailable={
|
|
656
|
-
preview={<ActionPreview card={props.card} timeZone={props.timeZone} />}
|
|
846
|
+
unavailable={unavailable() ? { text: unavailable()!, icon: "ti ti-player-pause", tone: "neutral" } : undefined}
|
|
847
|
+
preview={<ActionPreview card={props.card} timeZone={props.timeZone} onContentLoaded={setLoadedContent} />}
|
|
657
848
|
confirm={{
|
|
658
849
|
label: props.card.confirm.label,
|
|
659
850
|
variant: props.card.confirm.variant,
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { dates, i18n } from "@k2b/stdlib";
|
|
2
|
+
import { capabilityPersonText, capabilitySentenceText } from "../../_internal/capability-sentences";
|
|
3
|
+
import type { CapabilityAskReason, CapabilityPerson, CapabilitySentence } from "../../contracts/capabilities";
|
|
4
|
+
import { formatBytes } from "../../shared/format";
|
|
5
|
+
import type { ActionCard } from "./action-card";
|
|
6
|
+
import { actionMessages } from "./action-messages";
|
|
7
|
+
|
|
8
|
+
export type ActionTextRow = { label: string; value: string; format?: "date" | "date-time"; block?: true };
|
|
9
|
+
export type ActionQuestionText = {
|
|
10
|
+
locale: string;
|
|
11
|
+
question: string;
|
|
12
|
+
reason: string;
|
|
13
|
+
reasonCode: CapabilityAskReason;
|
|
14
|
+
rows: ActionTextRow[];
|
|
15
|
+
readMore?: string;
|
|
16
|
+
confirm: string;
|
|
17
|
+
decline: string;
|
|
18
|
+
incomplete: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const messages = i18n.define({
|
|
22
|
+
baseLocale: "en",
|
|
23
|
+
messages: {
|
|
24
|
+
en: {
|
|
25
|
+
from: "From",
|
|
26
|
+
to: "To",
|
|
27
|
+
cc: "Cc",
|
|
28
|
+
bcc: "Bcc",
|
|
29
|
+
subject: "Subject",
|
|
30
|
+
sendAt: "Send at",
|
|
31
|
+
warning: "Warning",
|
|
32
|
+
attachments: "Attachments",
|
|
33
|
+
text: "Text",
|
|
34
|
+
when: "When",
|
|
35
|
+
until: "Until",
|
|
36
|
+
where: "Where",
|
|
37
|
+
with: "With",
|
|
38
|
+
repeats: "Repeats",
|
|
39
|
+
due: "Due",
|
|
40
|
+
assignees: "Assignees",
|
|
41
|
+
status: "Status",
|
|
42
|
+
checklist: "Checklist",
|
|
43
|
+
tags: "Tags",
|
|
44
|
+
folder: "Folder",
|
|
45
|
+
size: "Size",
|
|
46
|
+
target: "To",
|
|
47
|
+
place: "Place",
|
|
48
|
+
added: "Added",
|
|
49
|
+
removed: "Removed",
|
|
50
|
+
starts: "Starts",
|
|
51
|
+
may: "May",
|
|
52
|
+
mayAdded: "May (added)",
|
|
53
|
+
mayRemoved: "May (removed)",
|
|
54
|
+
also: "Also",
|
|
55
|
+
readAll: "Read all",
|
|
56
|
+
title: "Title",
|
|
57
|
+
name: "Name",
|
|
58
|
+
note: "Note",
|
|
59
|
+
before: ({ label }: { label: string }) => `${label} (before)`,
|
|
60
|
+
more: ({ count }: { count: number }) => `${count.toLocaleString("en")} more`,
|
|
61
|
+
},
|
|
62
|
+
de: {
|
|
63
|
+
from: "Von",
|
|
64
|
+
to: "An",
|
|
65
|
+
cc: "Cc",
|
|
66
|
+
bcc: "Bcc",
|
|
67
|
+
subject: "Betreff",
|
|
68
|
+
sendAt: "Senden am",
|
|
69
|
+
warning: "Warnung",
|
|
70
|
+
attachments: "Anhänge",
|
|
71
|
+
text: "Text",
|
|
72
|
+
when: "Wann",
|
|
73
|
+
until: "Bis",
|
|
74
|
+
where: "Wo",
|
|
75
|
+
with: "Mit",
|
|
76
|
+
repeats: "Wiederholung",
|
|
77
|
+
due: "Fällig",
|
|
78
|
+
assignees: "Zuständig",
|
|
79
|
+
status: "Status",
|
|
80
|
+
checklist: "Checkliste",
|
|
81
|
+
tags: "Tags",
|
|
82
|
+
folder: "Ordner",
|
|
83
|
+
size: "Größe",
|
|
84
|
+
target: "Ziel",
|
|
85
|
+
place: "Ort",
|
|
86
|
+
added: "Neu",
|
|
87
|
+
removed: "Entfernt",
|
|
88
|
+
starts: "Startet",
|
|
89
|
+
may: "Darf",
|
|
90
|
+
mayAdded: "Darf (neu)",
|
|
91
|
+
mayRemoved: "Darf (entfernt)",
|
|
92
|
+
also: "Folge",
|
|
93
|
+
readAll: "Ganz lesen",
|
|
94
|
+
title: "Titel",
|
|
95
|
+
name: "Name",
|
|
96
|
+
note: "Hinweis",
|
|
97
|
+
before: ({ label }) => `${label} (vorher)`,
|
|
98
|
+
more: ({ count }) => `${count.toLocaleString("de")} weitere`,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
export const checkActionTextMessages = () => messages.check();
|
|
103
|
+
/** The label for a row an old review left without one, where a transport requires labels. */
|
|
104
|
+
export const actionNoteLabel = (locale: string): string => messages.resolve([locale]).t.note;
|
|
105
|
+
|
|
106
|
+
/** Flatten the question card once. Transport limits and terminal escaping belong to its callers. */
|
|
107
|
+
export const actionCardText = (card: ActionCard): ActionQuestionText => {
|
|
108
|
+
const t = messages.resolve([card.locale]).t;
|
|
109
|
+
const rows: ActionTextRow[] = [];
|
|
110
|
+
let incomplete = Boolean(card.contentRef);
|
|
111
|
+
const row = (label: string, value: string | undefined, extra: Pick<ActionTextRow, "block" | "format"> = {}) => {
|
|
112
|
+
if (value !== undefined && value !== "") rows.push({ label, value, ...extra });
|
|
113
|
+
};
|
|
114
|
+
const list = (values: string[], more = 0) => {
|
|
115
|
+
if (more > 0) {
|
|
116
|
+
incomplete = true;
|
|
117
|
+
values.push(t.more({ count: more }));
|
|
118
|
+
}
|
|
119
|
+
return new Intl.ListFormat(card.locale, { type: "conjunction" }).format(values);
|
|
120
|
+
};
|
|
121
|
+
const people = (values: CapabilityPerson[] | undefined, more = 0) => list((values ?? []).map(capabilityPersonText), more);
|
|
122
|
+
// The thing's own name, unless the question already says it.
|
|
123
|
+
const named = (label: string, value: string) => {
|
|
124
|
+
if (!card.questionText.includes(value)) row(label, value);
|
|
125
|
+
};
|
|
126
|
+
const model = card.preview;
|
|
127
|
+
if (model?.kind === "legacy") {
|
|
128
|
+
rows.push(...model.rows);
|
|
129
|
+
row(t.text, model.markdown, { block: true });
|
|
130
|
+
} else if (model?.kind === "review") {
|
|
131
|
+
const context = { locale: card.locale, timeZone: model.timeZone };
|
|
132
|
+
const sentence = (value: CapabilitySentence) => capabilitySentenceText(value.text, value.slots ?? model.slots ?? {}, context);
|
|
133
|
+
const preview = model.preview;
|
|
134
|
+
if (preview && "truncated" in preview && preview.truncated) incomplete = true;
|
|
135
|
+
switch (preview?.kind) {
|
|
136
|
+
case "message":
|
|
137
|
+
row(t.from, preview.from && capabilityPersonText(preview.from));
|
|
138
|
+
row(t.to, people(preview.to, preview.more?.to));
|
|
139
|
+
row(t.cc, people(preview.cc, preview.more?.cc));
|
|
140
|
+
row(t.bcc, people(preview.bcc, preview.more?.bcc));
|
|
141
|
+
row(t.subject, preview.subject);
|
|
142
|
+
row(t.sendAt, preview.sendAt, { format: "date-time" });
|
|
143
|
+
row(t.warning, preview.warnings?.join(" · "));
|
|
144
|
+
row(
|
|
145
|
+
t.attachments,
|
|
146
|
+
list(
|
|
147
|
+
(preview.attachments ?? []).map((file) =>
|
|
148
|
+
file.size === undefined ? file.name : `${file.name} (${formatBytes(file.size, { locale: card.locale })})`,
|
|
149
|
+
),
|
|
150
|
+
preview.moreAttachments,
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
row(t.text, preview.body, { block: true });
|
|
154
|
+
break;
|
|
155
|
+
case "event": {
|
|
156
|
+
named(t.title, preview.title);
|
|
157
|
+
const dateOnly = (value: string) => /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
158
|
+
const allDay = preview.allDay === true || dateOnly(preview.start);
|
|
159
|
+
// Both ends stay typed, so a device shows them in its own zone. An all-day instant becomes its calendar day
|
|
160
|
+
// in the card's zone first, so no reader's zone moves it to another day.
|
|
161
|
+
const day = (value: string) =>
|
|
162
|
+
dateOnly(value)
|
|
163
|
+
? value
|
|
164
|
+
: new Intl.DateTimeFormat("en-CA", { timeZone: model.timeZone, year: "numeric", month: "2-digit", day: "2-digit" }).format(
|
|
165
|
+
new Date(value),
|
|
166
|
+
);
|
|
167
|
+
const format = allDay ? "date" : "date-time";
|
|
168
|
+
row(t.when, allDay ? day(preview.start) : preview.start, { format });
|
|
169
|
+
if (preview.end) row(t.until, allDay ? day(preview.end) : preview.end, { format });
|
|
170
|
+
row(t.where, [preview.place, preview.location].filter(Boolean).join(" · "));
|
|
171
|
+
row(t.with, people(preview.attendees));
|
|
172
|
+
row(t.repeats, preview.recurrence);
|
|
173
|
+
row(t.text, preview.description, { block: true });
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case "task":
|
|
177
|
+
named(t.title, preview.title);
|
|
178
|
+
row(t.place, preview.place);
|
|
179
|
+
row(t.due, preview.due, { format: /^\d{4}-\d{2}-\d{2}$/.test(preview.due ?? "") ? "date" : "date-time" });
|
|
180
|
+
row(t.assignees, people(preview.assignees));
|
|
181
|
+
row(t.status, preview.status);
|
|
182
|
+
row(t.checklist, preview.checklist?.map((item) => `${item.done ? "☑" : "☐"} ${item.label}`).join(" · "));
|
|
183
|
+
row(t.tags, preview.tags?.join(", "));
|
|
184
|
+
row(t.text, preview.description, { block: true });
|
|
185
|
+
break;
|
|
186
|
+
case "file":
|
|
187
|
+
named(t.name, preview.name);
|
|
188
|
+
row(t.folder, preview.place ?? preview.path);
|
|
189
|
+
row(t.size, preview.size === undefined ? undefined : formatBytes(preview.size, { locale: card.locale }));
|
|
190
|
+
row(t.target, preview.to);
|
|
191
|
+
break;
|
|
192
|
+
case "page":
|
|
193
|
+
named(t.title, preview.title);
|
|
194
|
+
row(t.place, preview.place);
|
|
195
|
+
row(t.text, preview.body, { block: true });
|
|
196
|
+
for (const change of preview.changes ?? []) row(change.change === "added" ? t.added : t.removed, change.text);
|
|
197
|
+
break;
|
|
198
|
+
case "rule":
|
|
199
|
+
row(t.starts, sentence(preview.when));
|
|
200
|
+
row(t.text, preview.does, { block: true });
|
|
201
|
+
for (const may of preview.may)
|
|
202
|
+
row(may.mark === "added" ? t.mayAdded : may.mark === "removed" ? t.mayRemoved : t.may, sentence(may));
|
|
203
|
+
// The card's fixed line: a routine knows only its instruction, never this chat.
|
|
204
|
+
if (!preview.seesChat) row(t.note, actionMessages(card.locale).ruleBlindToChat);
|
|
205
|
+
break;
|
|
206
|
+
case "fields":
|
|
207
|
+
for (const field of preview.rows) {
|
|
208
|
+
rows.push({ label: field.label, value: field.value, ...(field.format ? { format: field.format } : {}) });
|
|
209
|
+
if (field.before !== undefined)
|
|
210
|
+
rows.push({ label: t.before({ label: field.label }), value: field.before, ...(field.format ? { format: field.format } : {}) });
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
for (const consequence of model.consequences)
|
|
215
|
+
row(consequence.mark === "added" ? t.added : consequence.mark === "removed" ? t.removed : t.also, consequence.plain);
|
|
216
|
+
}
|
|
217
|
+
// A long question shortens slot values and names at most three people of a slot, then "N more". The text is
|
|
218
|
+
// complete only when the question or a row shows each value whole; people the app left out are never shown.
|
|
219
|
+
const shown = [card.questionText, ...rows.map((entry) => entry.value)];
|
|
220
|
+
const whole = (value: string) => shown.some((text) => text.includes(value));
|
|
221
|
+
const typed = (value: string, format: "date" | "date-time") =>
|
|
222
|
+
rows.some((entry) => entry.value === value && (entry.format === "date-time" || entry.format === format));
|
|
223
|
+
if (
|
|
224
|
+
card.slotValues?.unlisted ||
|
|
225
|
+
card.slotValues?.values.some((value) => !whole(value)) ||
|
|
226
|
+
card.slotValues?.times.some(
|
|
227
|
+
(time) => !whole(time.text) && !(typed(time.start, time.format) && (!time.end || typed(time.end, time.format))),
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
incomplete = true;
|
|
231
|
+
return {
|
|
232
|
+
locale: card.locale,
|
|
233
|
+
question: card.questionText,
|
|
234
|
+
reason: card.reason.text,
|
|
235
|
+
reasonCode: card.reason.code,
|
|
236
|
+
rows,
|
|
237
|
+
...(card.readMore ? { readMore: card.readMore } : {}),
|
|
238
|
+
confirm: card.confirm.label,
|
|
239
|
+
decline: card.decline,
|
|
240
|
+
incomplete,
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
/** The CLI's plain question block, before terminal-safe sanitizing at the CLI boundary. */
|
|
245
|
+
export const actionQuestionLines = (
|
|
246
|
+
text: ActionQuestionText,
|
|
247
|
+
extra: { origin?: string; commands?: { approve: string; reject: string }; timeZone: string },
|
|
248
|
+
): string[] => {
|
|
249
|
+
const t = messages.resolve([text.locale]).t;
|
|
250
|
+
const lines = [`? ${text.question}`, ` ${text.reason}`];
|
|
251
|
+
if (extra.origin) lines.push(` ${extra.origin}`);
|
|
252
|
+
const width = Math.max(0, ...text.rows.filter((row) => !row.block && row.label).map((row) => Array.from(row.label).length + 1));
|
|
253
|
+
let markedIncomplete = false;
|
|
254
|
+
for (const row of text.rows) {
|
|
255
|
+
if (row.block) {
|
|
256
|
+
const body = row.value.split(/\r?\n/);
|
|
257
|
+
// Cut a long text only when "Read all" can open the whole thing; otherwise the terminal shows all of it.
|
|
258
|
+
const shown = text.readMore ? body.slice(0, 8) : body;
|
|
259
|
+
lines.push(...shown.map((line) => ` ${line}`));
|
|
260
|
+
if (shown.length < body.length || (text.incomplete && !markedIncomplete)) {
|
|
261
|
+
lines.push(" …");
|
|
262
|
+
markedIncomplete = true;
|
|
263
|
+
}
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const options = { locale: text.locale, timeZone: extra.timeZone };
|
|
267
|
+
const value =
|
|
268
|
+
row.format === "date-time"
|
|
269
|
+
? dates.formatDateTime(row.value, options)
|
|
270
|
+
: row.format === "date"
|
|
271
|
+
? dates.formatDate(/^\d{4}-\d{2}-\d{2}$/.test(row.value) ? `${row.value}T12:00:00.000Z` : row.value, {
|
|
272
|
+
...options,
|
|
273
|
+
...(/^\d{4}-\d{2}-\d{2}$/.test(row.value) ? { timeZone: "UTC" } : {}),
|
|
274
|
+
})
|
|
275
|
+
: row.value;
|
|
276
|
+
lines.push(row.label ? ` ${(row.label + ":").padEnd(width)} ${value}` : ` ${value}`);
|
|
277
|
+
}
|
|
278
|
+
if (text.incomplete && !markedIncomplete) lines.push(" …");
|
|
279
|
+
if (text.readMore) lines.push(` ${t.readAll}: ${text.readMore}`);
|
|
280
|
+
if (extra.commands) {
|
|
281
|
+
const width = Math.max(Array.from(text.confirm).length, Array.from(text.decline).length) + 1;
|
|
282
|
+
lines.push(
|
|
283
|
+
` ${(text.confirm + ":").padEnd(width)} ${extra.commands.approve}`,
|
|
284
|
+
` ${(text.decline + ":").padEnd(width)} ${extra.commands.reject}`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
return lines;
|
|
288
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { capabilityUnknownText } from "../../_internal/capability-sentences";
|
|
2
|
+
import type { AiTurnBlock } from "../protocol";
|
|
3
|
+
import { toolOutcome } from "../tool-wording";
|
|
4
|
+
import { isRecord } from "./message-utils";
|
|
5
|
+
import { aiChatMessages } from "./messages";
|
|
6
|
+
|
|
7
|
+
type ToolBlock = Extract<AiTurnBlock, { kind: "tool" }>;
|
|
8
|
+
|
|
9
|
+
/** The application's own short summary of a result, if it fits on one line. */
|
|
10
|
+
const resultSummary = (block: ToolBlock): string | undefined => {
|
|
11
|
+
const value = isRecord(block.result) ? block.result.summary : undefined;
|
|
12
|
+
return typeof value === "string" && value.trim() && value.trim().length <= 500 ? value.trim() : undefined;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** The words of a call without an app sentence: Cloud's frames around its catalog sentence or title. */
|
|
16
|
+
export const legacyOutcomeWords = (block: ToolBlock, locale: string, timeZone: string) => (subject: string) => {
|
|
17
|
+
const t = aiChatMessages(locale);
|
|
18
|
+
const capability = block.presentation?.kind === "capability";
|
|
19
|
+
return {
|
|
20
|
+
running: t.receiptRunning({ title: subject }),
|
|
21
|
+
// The app's done sentence, else its own summary of the call, else Cloud's frame.
|
|
22
|
+
done:
|
|
23
|
+
toolOutcome(block, "done", locale, timeZone) ??
|
|
24
|
+
resultSummary(block) ??
|
|
25
|
+
(capability ? t.receiptDone({ title: subject }) : t.receiptApproved({ title: subject })),
|
|
26
|
+
rejected: toolOutcome(block, "rejected", locale, timeZone) ?? t.receiptRejected({ title: subject }),
|
|
27
|
+
notRun: toolOutcome(block, "notRun", locale, timeZone) ?? t.receiptNotRun({ title: subject, stopped: false }),
|
|
28
|
+
unknown: capabilityUnknownText(subject, locale),
|
|
29
|
+
unknownStill: capabilityUnknownText(subject, locale, true),
|
|
30
|
+
};
|
|
31
|
+
};
|