@agent-native/core-corpus 0.216.0 → 0.218.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/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +61 -1
- package/corpus/templates/analytics/AGENTS.md +1 -1
- package/corpus/templates/analytics/CHANGELOG.md +3 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +9 -65
- package/corpus/templates/analytics/actions/list-event-catalog.ts +1 -1
- package/corpus/templates/analytics/actions/list-session-outputs.ts +40 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +17 -4
- package/corpus/templates/analytics/actions/view-screen.ts +29 -3
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +38 -167
- package/corpus/templates/analytics/app/i18n/en-US.ts +27 -0
- package/corpus/templates/analytics/app/pages/sessions/EventCatalogPage.tsx +18 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +3 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionOutputs.tsx +197 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsTriagePage.tsx +114 -4
- package/corpus/templates/analytics/changelog/2026-10-15-sessions-can-find-people-who-made-a-slides-deck-but-didn-t-p.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-the-activation-funnel-counts-visitors-who-decline-analytics-.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +67 -0
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +28 -22
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +36 -1
- package/corpus/templates/analytics/server/lib/output-outcomes.ts +712 -0
- package/corpus/templates/analytics/server/lib/replay-origins.ts +33 -0
- package/corpus/templates/analytics/server/lib/session-event-index.ts +20 -0
- package/corpus/templates/analytics/server/lib/session-performance.ts +6 -40
- package/corpus/templates/analytics/server/lib/session-recording-associations.ts +30 -1
- package/corpus/templates/analytics/server/lib/session-replay-triage.ts +99 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +45 -31
- package/corpus/templates/analytics/server/lib/sessions-triage-lab.ts +8 -2
- package/corpus/templates/analytics/server/plugins/db.ts +46 -0
- package/corpus/templates/analytics/shared/output-use.ts +205 -0
- package/corpus/templates/analytics/shared/session-events.ts +6 -0
- package/corpus/templates/assets/CHANGELOG.md +6 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
- package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
- package/corpus/templates/brain/app/routes/sources.tsx +1 -1
- package/corpus/templates/brain/vitest.config.ts +7 -0
- package/corpus/templates/calendar/CHANGELOG.md +12 -0
- package/corpus/templates/calendar/actions/create-event.ts +2 -4
- package/corpus/templates/calendar/actions/update-event.ts +35 -6
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
- package/corpus/templates/calendar/changelog/2026-10-21-solo-events-avoid-notification-approval.md +6 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
- package/corpus/templates/clips/CHANGELOG.md +7 -20
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
- package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
- package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
- package/corpus/templates/community/qr/package.json +1 -1
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -3
- package/corpus/templates/content/CHANGELOG.md +10 -0
- package/corpus/templates/content/actions/_browser-document-save-attempt.ts +23 -1
- package/corpus/templates/content/actions/_builder-cms-blob-custody.ts +467 -0
- package/corpus/templates/content/actions/_builder-cms-execution-lease.ts +17 -0
- package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +5 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +881 -21
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +28 -2
- package/corpus/templates/content/actions/_builder-cms-write-client.ts +109 -11
- package/corpus/templates/content/actions/_builder-docs-client.ts +1 -0
- package/corpus/templates/content/actions/_builder-private-blob.test-fixture.ts +90 -0
- package/corpus/templates/content/actions/_builder-source-execution-guard.ts +48 -0
- package/corpus/templates/content/actions/_content-save-outcomes.ts +5 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +290 -8
- package/corpus/templates/content/actions/_database-utils.ts +93 -7
- package/corpus/templates/content/actions/_database-view-setup.ts +21 -2
- package/corpus/templates/content/actions/_delete-content-space.ts +8 -1
- package/corpus/templates/content/actions/_github-source-freshness.ts +30 -4
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -6
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +110 -26
- package/corpus/templates/content/actions/change-content-database-source-role.ts +51 -7
- package/corpus/templates/content/actions/delete-document.ts +41 -3
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +68 -2
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +33 -2
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +900 -140
- package/corpus/templates/content/actions/get-document.ts +2 -0
- package/corpus/templates/content/actions/permanently-delete-document.ts +6 -0
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +155 -13
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +299 -73
- package/corpus/templates/content/actions/set-live-body-switch.ts +45 -0
- package/corpus/templates/content/actions/update-document.ts +139 -20
- package/corpus/templates/content/actions/validate-builder-source-execution.ts +135 -26
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +119 -30
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +56 -13
- package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +23 -0
- package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +27 -2
- package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +32 -8
- package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +27 -2
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +1 -0
- package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
- package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
- package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
- package/corpus/templates/content/app/global.css +31 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +40 -2
- package/corpus/templates/content/app/hooks/use-documents.ts +14 -0
- package/corpus/templates/content/app/i18n-data.ts +1 -0
- package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
- package/corpus/templates/content/changelog/2026-10-17-builder-content-avoids-retrying-a-write-when-builder-accepte.md +6 -0
- package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +1 -1
- package/corpus/templates/content/changelog/2026-10-20-builder-content-sync-keeps-read-and-write-status-accurate-wh.md +6 -0
- package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
- package/corpus/templates/content/changelog/2026-10-21-list-rows-can-show-each-pages-first-lines.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-a-collection-linked-to-a-big-github-folder-fills-faster-and-p.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.source.adapters.md +29 -0
- package/corpus/templates/content/parity/matrix.md +42 -42
- package/corpus/templates/content/parity/matrix.ts +3 -1
- package/corpus/templates/content/server/db/schema.ts +20 -0
- package/corpus/templates/content/server/lib/content-trash-purge.ts +15 -1
- package/corpus/templates/content/server/lib/live-body-writer.ts +138 -26
- package/corpus/templates/content/server/live-copy/binding.ts +31 -0
- package/corpus/templates/content/server/live-copy/bounded-task.d.ts +6 -0
- package/corpus/templates/content/server/live-copy/exec-db.ts +1 -0
- package/corpus/templates/content/server/live-copy/holds.ts +59 -0
- package/corpus/templates/content/server/live-copy/persist.ts +656 -0
- package/corpus/templates/content/server/live-copy/switch.ts +136 -0
- package/corpus/templates/content/server/live-copy/task-names.ts +4 -0
- package/corpus/templates/content/server/live-copy/tasks.ts +183 -0
- package/corpus/templates/content/server/live-copy/write-cause.ts +85 -0
- package/corpus/templates/content/server/live-copy/write.ts +85 -0
- package/corpus/templates/content/server/plugins/builder-private-payload-cleanup.ts +22 -0
- package/corpus/templates/content/server/plugins/collab.ts +3 -0
- package/corpus/templates/content/server/plugins/db.ts +28 -0
- package/corpus/templates/content/shared/api.ts +30 -1
- package/corpus/templates/content/shared/database-item-layout.ts +54 -22
- package/corpus/templates/content/shared/document-intent-merge.ts +16 -9
- package/corpus/templates/content/shared/og-description.ts +258 -36
- package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
- package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
- package/corpus/templates/content/shared/suggestion-text.ts +20 -5
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +5 -1
- package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +25 -4
- package/corpus/templates/design/CHANGELOG.md +5 -0
- package/corpus/templates/design/actions/get-getting-started.ts +53 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +10 -0
- package/corpus/templates/design/app/components/design/bridge/native-shader-runtime.bridge.ts +132 -79
- package/corpus/templates/design/app/components/design/bridge/native-source-provider.ts +268 -130
- package/corpus/templates/design/app/components/design/bridge/native-worker-scene-host.ts +122 -42
- package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +41 -4
- package/corpus/templates/design/app/components/design/design-canvas/text-content-change-result.ts +46 -12
- package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +15 -8
- package/corpus/templates/design/app/components/layout/GettingStarted.tsx +50 -0
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +3 -0
- package/corpus/templates/design/app/components/templates/DesignTemplateGrid.tsx +163 -0
- package/corpus/templates/design/app/components/templates/DesignTemplateLibrary.tsx +65 -78
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +6 -2
- package/corpus/templates/design/app/global.css +6 -0
- package/corpus/templates/design/app/i18n/en-US.ts +17 -0
- package/corpus/templates/design/app/lib/canvas-load-outcome.ts +147 -0
- package/corpus/templates/design/app/lib/canvas-outcome-tracking.ts +531 -0
- package/corpus/templates/design/app/lib/canvas-style-kind.ts +50 -0
- package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
- package/corpus/templates/design/app/pages/Index.tsx +28 -67
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +86 -29
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +38 -3
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-core.ts +9 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +2 -0
- package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
- package/corpus/templates/design/changelog/2026-10-21-a-getting-started-checklist-at-the-bottom-of-the-sidebar-tra.md +6 -0
- package/corpus/templates/design/changelog/2026-10-21-templates-now-open-a-read-only-preview-from-the-card-and-the.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-collapsed-shader-layers-no-longer-interrupt-other-shaders-on.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-shader-fills-keep-rendering-when-other-screen-content-cannot.md +6 -0
- package/corpus/templates/design/scripts/design-export-validation/export-baseline.json +24 -0
- package/corpus/templates/design/server/lib/getting-started.ts +267 -0
- package/corpus/templates/design/server/plugins/getting-started.ts +5 -0
- package/corpus/templates/design/shared/design-template-presets/analytics-dashboard.ts +127 -0
- package/corpus/templates/design/shared/design-template-presets/design-studio.ts +98 -0
- package/corpus/templates/design/shared/design-template-presets/messaging-app.ts +101 -0
- package/corpus/templates/design/shared/design-template-presets/project-tasks.ts +120 -0
- package/corpus/templates/design/shared/design-template-presets.ts +8 -0
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
- package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
- package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/plan/CHANGELOG.md +6 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
- package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/slides/CHANGELOG.md +7 -0
- package/corpus/templates/slides/actions/delete-deck.ts +7 -0
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
- package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
- package/corpus/templates/slides/app/pages/Index.tsx +7 -2
- package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-21-connected-deck-deletion-requires-approval.md +6 -0
- package/corpus/templates/slides/scripts/edit-fidelity/client-action.ts +61 -0
- package/corpus/templates/slides/scripts/edit-fidelity/run.ts +10 -0
- package/package.json +1 -1
- package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
- package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +0 -83
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Made it, didn't use it": per app, the output a person makes, the events
|
|
3
|
+
* that say they made one, and the events that say they used it. Opening an
|
|
4
|
+
* output never counts as using it; a use shows the output to people or takes
|
|
5
|
+
* it out of the app.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** A use counts only within this many days of the output being made. */
|
|
9
|
+
export const OUTPUT_USE_WINDOW_DAYS = 7;
|
|
10
|
+
export const OUTPUT_USE_WINDOW_MS = OUTPUT_USE_WINDOW_DAYS * 24 * 60 * 60_000;
|
|
11
|
+
/** A presentation shorter than this was a look, not a talk. */
|
|
12
|
+
export const MIN_PRESENTATION_MS = 30_000;
|
|
13
|
+
/** The most outputs one recording lists; the rest are counted, not listed. */
|
|
14
|
+
export const MAX_OUTPUTS_PER_RECORDING = 10;
|
|
15
|
+
/** Output ids are skipped past this length, never cut, like session ids. */
|
|
16
|
+
export const MAX_OUTPUT_ID_LENGTH = 256;
|
|
17
|
+
/** The Sessions URL param naming the app whose unused outputs to find. */
|
|
18
|
+
export const UNUSED_OUTPUT_PARAM = "unused";
|
|
19
|
+
/** Every generator sends this when it makes an output, so it marks an app that makes them. */
|
|
20
|
+
export const OUTPUT_MADE_EVENT = "generation_completed";
|
|
21
|
+
|
|
22
|
+
type EventProperties = Readonly<Record<string, unknown>>;
|
|
23
|
+
|
|
24
|
+
export interface OutputUseEvent {
|
|
25
|
+
eventName: string;
|
|
26
|
+
qualifies: (properties: EventProperties) => boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface OutputUseRule {
|
|
30
|
+
outputType: string;
|
|
31
|
+
madeEvents: readonly string[];
|
|
32
|
+
usedEvents: readonly OutputUseEvent[];
|
|
33
|
+
/** The output's page, relative to its app's origin. */
|
|
34
|
+
outputPath: (outputId: string) => string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A missing or non-numeric property never proves a use. */
|
|
38
|
+
function atLeast(value: unknown, minimum: number): boolean {
|
|
39
|
+
return (
|
|
40
|
+
typeof value === "number" && Number.isFinite(value) && value >= minimum
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const OUTPUT_USE_RULES = {
|
|
45
|
+
slides: {
|
|
46
|
+
outputType: "deck",
|
|
47
|
+
madeEvents: [OUTPUT_MADE_EVENT],
|
|
48
|
+
usedEvents: [
|
|
49
|
+
{
|
|
50
|
+
// Moving forward at least once means two or more slides were shown,
|
|
51
|
+
// even when the presentation started mid-deck.
|
|
52
|
+
eventName: "presentation_ended",
|
|
53
|
+
qualifies: (properties) =>
|
|
54
|
+
atLeast(properties.slides_advanced, 1) &&
|
|
55
|
+
atLeast(properties.duration_ms, MIN_PRESENTATION_MS),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
// `slide_export_started` fires before the export runs, so only a
|
|
59
|
+
// finished export of a deck with slides counts.
|
|
60
|
+
eventName: "deck_exported",
|
|
61
|
+
qualifies: (properties) =>
|
|
62
|
+
properties.status === "completed" &&
|
|
63
|
+
atLeast(properties.slide_count, 1),
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
outputPath: (outputId) => `/deck/${encodeURIComponent(outputId)}`,
|
|
67
|
+
},
|
|
68
|
+
} as const satisfies Record<string, OutputUseRule>;
|
|
69
|
+
|
|
70
|
+
export type OutputUseApp = keyof typeof OUTPUT_USE_RULES;
|
|
71
|
+
|
|
72
|
+
export const OUTPUT_USE_APPS = Object.keys(OUTPUT_USE_RULES) as [
|
|
73
|
+
OutputUseApp,
|
|
74
|
+
...OutputUseApp[],
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
export function isOutputUseApp(value: unknown): value is OutputUseApp {
|
|
78
|
+
return (
|
|
79
|
+
typeof value === "string" &&
|
|
80
|
+
Object.prototype.hasOwnProperty.call(OUTPUT_USE_RULES, value)
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function outputUseRule(
|
|
85
|
+
app: string | null | undefined,
|
|
86
|
+
): OutputUseRule | null {
|
|
87
|
+
return isOutputUseApp(app) ? OUTPUT_USE_RULES[app] : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Every event name any rule reads, for the catalog and for ingest. */
|
|
91
|
+
export const OUTPUT_USE_EVENT_NAMES: ReadonlySet<string> = new Set(
|
|
92
|
+
Object.values(OUTPUT_USE_RULES).flatMap((rule: OutputUseRule) => [
|
|
93
|
+
...rule.madeEvents,
|
|
94
|
+
...rule.usedEvents.map((used) => used.eventName),
|
|
95
|
+
]),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
export function outputIdOf(properties: EventProperties): string | null {
|
|
99
|
+
const value = properties.output_id;
|
|
100
|
+
if (typeof value !== "string") return null;
|
|
101
|
+
const outputId = value.trim();
|
|
102
|
+
return outputId && outputId.length <= MAX_OUTPUT_ID_LENGTH ? outputId : null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Where a recording's session ran, as far as the recording shows. */
|
|
106
|
+
export interface OutputRecordingPages {
|
|
107
|
+
firstUrl: string | null;
|
|
108
|
+
lastUrl: string | null;
|
|
109
|
+
/**
|
|
110
|
+
* Origins the recording's replay key lists. An empty list accepts uploads
|
|
111
|
+
* from anywhere, so it vouches for no origin and yields no link.
|
|
112
|
+
*/
|
|
113
|
+
allowedOrigins: readonly string[];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function recordedPage(value: string | null): URL | null {
|
|
117
|
+
if (!value) return null;
|
|
118
|
+
try {
|
|
119
|
+
const url = new URL(value);
|
|
120
|
+
return url.origin === "null" ? null : url;
|
|
121
|
+
// coercion-ok: a recording without a parseable URL has no app to link to; the output id still shows.
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Where a session's output opens: the app origin the session was recorded on,
|
|
129
|
+
* its mount path, and the output's page. The app checks access when it opens.
|
|
130
|
+
* A recording's URLs are whatever its uploader sent, so only an origin the
|
|
131
|
+
* replay key's owner allowed becomes a link; otherwise this is null.
|
|
132
|
+
*/
|
|
133
|
+
export function outputUrl(
|
|
134
|
+
app: string,
|
|
135
|
+
outputId: string,
|
|
136
|
+
recording: OutputRecordingPages,
|
|
137
|
+
): string | null {
|
|
138
|
+
const rule = outputUseRule(app);
|
|
139
|
+
if (!rule) return null;
|
|
140
|
+
const pages = [recording.firstUrl, recording.lastUrl].flatMap((value) => {
|
|
141
|
+
const page = recordedPage(value);
|
|
142
|
+
return page ? [page] : [];
|
|
143
|
+
});
|
|
144
|
+
// A recording's id lives in its tab's sessionStorage, which is per origin,
|
|
145
|
+
// so a real recording never spans origins; pages on two came from a forged
|
|
146
|
+
// upload, and neither can be trusted.
|
|
147
|
+
const origin = pages[0]?.origin;
|
|
148
|
+
if (
|
|
149
|
+
!origin ||
|
|
150
|
+
pages.some((page) => page.origin !== origin) ||
|
|
151
|
+
!recording.allowedOrigins.includes(origin)
|
|
152
|
+
) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
// A workspace deploy mounts each app at `/<app id>` on one origin, and a
|
|
156
|
+
// tab's recording follows it from app to app, so either page may show the
|
|
157
|
+
// mount. Reading that prefix as the mount assumes the app has no route of
|
|
158
|
+
// its own there (Slides has none). The recording's app label can't decide
|
|
159
|
+
// it: without a declared app it is the hostname's first label.
|
|
160
|
+
const mount = `/${app}`;
|
|
161
|
+
const mounted = pages.some(
|
|
162
|
+
(page) => page.pathname === mount || page.pathname.startsWith(`${mount}/`),
|
|
163
|
+
);
|
|
164
|
+
return `${origin}${mounted ? mount : ""}${rule.outputPath(outputId)}`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* What became of one output made in a session. `open` means the use window
|
|
169
|
+
* has not closed yet; `unmeasured` means a failed write may have dropped a
|
|
170
|
+
* use, so it cannot be called unused.
|
|
171
|
+
*/
|
|
172
|
+
export type SessionOutputStatus = "used" | "unused" | "open" | "unmeasured";
|
|
173
|
+
|
|
174
|
+
export interface SessionOutput {
|
|
175
|
+
app: OutputUseApp;
|
|
176
|
+
outputType: string;
|
|
177
|
+
outputId: string;
|
|
178
|
+
madeAt: string;
|
|
179
|
+
useBy: string;
|
|
180
|
+
status: SessionOutputStatus;
|
|
181
|
+
/** The first qualifying use's event name, when it was used in time. */
|
|
182
|
+
usedEvent: string | null;
|
|
183
|
+
usedAt: string | null;
|
|
184
|
+
url: string | null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface SessionRecordingOutputs {
|
|
188
|
+
/**
|
|
189
|
+
* By recording id, for each requested recording the viewer can read: its
|
|
190
|
+
* outputs, [] when it made none since coverage began, or null when they
|
|
191
|
+
* are unknown: it was shared from another tenant, whose events a share does
|
|
192
|
+
* not grant, its tenant has not started output tracking, or a batch lost in
|
|
193
|
+
* ingest overlapped it and may have held an output it made. Ids the viewer
|
|
194
|
+
* cannot read are absent.
|
|
195
|
+
*/
|
|
196
|
+
outputs: Record<string, SessionOutput[] | null>;
|
|
197
|
+
/**
|
|
198
|
+
* How many more outputs a recording made than `outputs` lists, by
|
|
199
|
+
* recording id; absent when none were left out. Unused outputs are listed
|
|
200
|
+
* first.
|
|
201
|
+
*/
|
|
202
|
+
omittedOutputs: Record<string, number>;
|
|
203
|
+
/** When the viewer's output tracking began, or null before it has. */
|
|
204
|
+
coverageStartedAt: string | null;
|
|
205
|
+
}
|
|
@@ -54,6 +54,8 @@ export interface EventCatalogEntry {
|
|
|
54
54
|
description: string | null;
|
|
55
55
|
automatic: boolean;
|
|
56
56
|
stoppedFiring: boolean;
|
|
57
|
+
/** Whether "made it, didn't use it" reads this event as making or using its app's output. */
|
|
58
|
+
outputRole: "made" | "used" | null;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
export interface EventCatalogApp {
|
|
@@ -61,6 +63,10 @@ export interface EventCatalogApp {
|
|
|
61
63
|
eventCount: number;
|
|
62
64
|
volume: number;
|
|
63
65
|
onlyAutomaticEvents: boolean;
|
|
66
|
+
/** The app sent `generation_completed` in range, so it makes outputs. */
|
|
67
|
+
makesOutputs: boolean;
|
|
68
|
+
/** Events that count as using this app's output; empty for an app that makes outputs is a catalog gap. */
|
|
69
|
+
usedItEvents: string[];
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
export interface EventCatalogResult {
|
|
@@ -9,6 +9,12 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
9
9
|
|
|
10
10
|
- Apps no longer keep server functions and databases warm by default; set `AGENT_NATIVE_ENABLE_KEEP_WARM=1` to opt in.
|
|
11
11
|
|
|
12
|
+
## 2026-10-16
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Owned and shared chats appear in separate history sections, with each shared chat's owner shown as an avatar and a Copy link action on every chat.
|
|
17
|
+
|
|
12
18
|
## 2026-10-09
|
|
13
19
|
|
|
14
20
|
### Improved
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
navigateWithAgentChatViewTransition,
|
|
3
3
|
useChatThreads,
|
|
4
|
-
type ChatThreadSummary,
|
|
5
4
|
} from "@agent-native/core/client/agent-chat";
|
|
5
|
+
import { appPath } from "@agent-native/core/client/api-path";
|
|
6
6
|
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
7
7
|
import { useT } from "@agent-native/core/client/i18n";
|
|
8
8
|
import { focusAgentChat } from "@agent-native/toolkit/app/chat";
|
|
9
|
+
import {
|
|
10
|
+
OwnershipChatHistoryRail,
|
|
11
|
+
useOwnershipChatHistory,
|
|
12
|
+
ownDraftRows,
|
|
13
|
+
} from "@agent-native/toolkit/app/chat/ownership-history";
|
|
9
14
|
import { DevDatabaseLink } from "@agent-native/toolkit/app/db-admin";
|
|
10
15
|
import { FeedbackButton } from "@agent-native/toolkit/app/feedback";
|
|
11
16
|
import { OrgSwitcher } from "@agent-native/toolkit/app/org";
|
|
@@ -14,10 +19,6 @@ import {
|
|
|
14
19
|
AppSidebar,
|
|
15
20
|
AppSidebarNavItem,
|
|
16
21
|
} from "@agent-native/toolkit/app/shared";
|
|
17
|
-
import {
|
|
18
|
-
ChatHistoryRail,
|
|
19
|
-
type ChatHistoryItem,
|
|
20
|
-
} from "@agent-native/toolkit/chat-history";
|
|
21
22
|
import {
|
|
22
23
|
IconClipboardList,
|
|
23
24
|
IconLayoutGrid,
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
IconShare3,
|
|
27
28
|
IconTemplate,
|
|
28
29
|
} from "@tabler/icons-react";
|
|
29
|
-
import { useEffect,
|
|
30
|
+
import { useEffect, useState } from "react";
|
|
30
31
|
import { useLocation, useNavigate } from "react-router";
|
|
31
32
|
import { toast } from "sonner";
|
|
32
33
|
|
|
@@ -53,40 +54,6 @@ const auditNavItem = {
|
|
|
53
54
|
const COLLAPSE_KEY = "assets.sidebar.collapsed";
|
|
54
55
|
const ASSETS_ACTIVE_THREAD_KEY = `agent-chat-active-thread:${ASSETS_CHAT_STORAGE_KEY}`;
|
|
55
56
|
|
|
56
|
-
function formatThreadAge(updatedAt: number) {
|
|
57
|
-
const diffMs = Math.max(0, Date.now() - updatedAt);
|
|
58
|
-
const minutes = Math.floor(diffMs / 60_000);
|
|
59
|
-
if (minutes < 1) return "now";
|
|
60
|
-
if (minutes < 60) return `${minutes}m`;
|
|
61
|
-
const hours = Math.floor(minutes / 60);
|
|
62
|
-
if (hours < 24) return `${hours}h`;
|
|
63
|
-
const days = Math.floor(hours / 24);
|
|
64
|
-
if (days < 7) return `${days}d`;
|
|
65
|
-
return new Date(updatedAt).toLocaleDateString([], {
|
|
66
|
-
month: "short",
|
|
67
|
-
day: "numeric",
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function threadTitle(thread: ChatThreadSummary) {
|
|
72
|
-
return thread.title || thread.preview || "Untitled chat";
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function threadUpdatedAt(thread: ChatThreadSummary) {
|
|
76
|
-
return Number.isFinite(thread.updatedAt)
|
|
77
|
-
? thread.updatedAt
|
|
78
|
-
: Number.isFinite(thread.createdAt)
|
|
79
|
-
? thread.createdAt
|
|
80
|
-
: 0;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function compareThreads(a: ChatThreadSummary, b: ChatThreadSummary) {
|
|
84
|
-
const aPinned = a.pinnedAt ?? 0;
|
|
85
|
-
const bPinned = b.pinnedAt ?? 0;
|
|
86
|
-
if (aPinned || bPinned) return bPinned - aPinned;
|
|
87
|
-
return threadUpdatedAt(b) - threadUpdatedAt(a);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
57
|
function persistedActiveThreadId() {
|
|
91
58
|
try {
|
|
92
59
|
return localStorage.getItem(ASSETS_ACTIVE_THREAD_KEY);
|
|
@@ -129,55 +96,37 @@ function AssetsChatsSection({ open }: { open: boolean }) {
|
|
|
129
96
|
archiveThread,
|
|
130
97
|
renameThread,
|
|
131
98
|
createThreadShareLink,
|
|
132
|
-
|
|
99
|
+
isNewThread,
|
|
133
100
|
} = useChatThreads(undefined, ASSETS_CHAT_STORAGE_KEY, undefined, {
|
|
101
|
+
ownership: "mine",
|
|
134
102
|
autoCreate: false,
|
|
135
103
|
restoreActiveThread: false,
|
|
136
104
|
});
|
|
137
105
|
|
|
138
|
-
const visibleThreads = useMemo(
|
|
139
|
-
() =>
|
|
140
|
-
threads
|
|
141
|
-
.filter((thread) => thread.messageCount > 0 && !thread.archivedAt)
|
|
142
|
-
.sort(compareThreads),
|
|
143
|
-
[threads],
|
|
144
|
-
);
|
|
145
106
|
const displayedActiveThreadId =
|
|
146
107
|
threadIdFromPath(location.pathname) ??
|
|
147
108
|
(location.pathname === "/home" ? null : activeThreadId);
|
|
148
|
-
const
|
|
149
|
-
() =>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (typeof detail?.isRunning === "boolean") refreshThreads();
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
window.addEventListener("agent-chat:threads-updated", refresh);
|
|
173
|
-
window.addEventListener("agentNative.chatRunning", handleRunning);
|
|
174
|
-
window.addEventListener("focus", refresh);
|
|
175
|
-
return () => {
|
|
176
|
-
window.removeEventListener("agent-chat:threads-updated", refresh);
|
|
177
|
-
window.removeEventListener("agentNative.chatRunning", handleRunning);
|
|
178
|
-
window.removeEventListener("focus", refresh);
|
|
179
|
-
};
|
|
180
|
-
}, [refreshThreads]);
|
|
109
|
+
const history = useOwnershipChatHistory({
|
|
110
|
+
getThreadUrl: (threadId) => appPath(chatThreadPath(threadId)),
|
|
111
|
+
activeId: displayedActiveThreadId,
|
|
112
|
+
localMineRows: ownDraftRows({ threads, isNewThread }),
|
|
113
|
+
mutations: {
|
|
114
|
+
pin: pinThread,
|
|
115
|
+
rename: renameThread,
|
|
116
|
+
archive: archiveThread,
|
|
117
|
+
},
|
|
118
|
+
onMutationFailed: (kind) => {
|
|
119
|
+
toast.error(
|
|
120
|
+
t(
|
|
121
|
+
kind === "archive"
|
|
122
|
+
? "chat.archiveFailed"
|
|
123
|
+
: kind === "rename"
|
|
124
|
+
? "chat.renameFailed"
|
|
125
|
+
: "agentChat.common.saveFailed",
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
181
130
|
|
|
182
131
|
function openThread(threadId: string, options?: { isNew?: boolean }) {
|
|
183
132
|
switchThread(threadId);
|
|
@@ -203,14 +152,12 @@ function AssetsChatsSection({ open }: { open: boolean }) {
|
|
|
203
152
|
async function handleArchiveThread(threadId: string) {
|
|
204
153
|
const wasActive =
|
|
205
154
|
threadId === activeThreadId || threadId === persistedActiveThreadId();
|
|
206
|
-
const archived = await
|
|
207
|
-
if (!archived)
|
|
208
|
-
toast.error(t("chat.archiveFailed"));
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
155
|
+
const archived = await history.listProps.onDelete!(threadId);
|
|
156
|
+
if (!archived) return false;
|
|
211
157
|
if (wasActive) {
|
|
212
158
|
await handleNewChat();
|
|
213
159
|
}
|
|
160
|
+
return true;
|
|
214
161
|
}
|
|
215
162
|
|
|
216
163
|
async function handleCopyShareLink(threadId: string) {
|
|
@@ -233,12 +180,6 @@ function AssetsChatsSection({ open }: { open: boolean }) {
|
|
|
233
180
|
}
|
|
234
181
|
}
|
|
235
182
|
|
|
236
|
-
function handleRenameThread(threadId: string, title: string) {
|
|
237
|
-
void renameThread(threadId, title).then((renamed) => {
|
|
238
|
-
if (!renamed) toast.error(t("chat.renameFailed"));
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
183
|
return (
|
|
243
184
|
<div
|
|
244
185
|
className="an-chat-history-rail__collapse"
|
|
@@ -246,39 +187,32 @@ function AssetsChatsSection({ open }: { open: boolean }) {
|
|
|
246
187
|
aria-hidden={!open}
|
|
247
188
|
>
|
|
248
189
|
<div className="ms-4">
|
|
249
|
-
<
|
|
250
|
-
|
|
190
|
+
<OwnershipChatHistoryRail
|
|
191
|
+
{...history}
|
|
251
192
|
activeId={displayedActiveThreadId}
|
|
252
193
|
onSelect={openThread}
|
|
253
194
|
onNewChat={() => void handleNewChat()}
|
|
254
195
|
railLabels={{
|
|
255
196
|
newChat: t("chat.newChat"),
|
|
256
|
-
showMore: t("chat.chats"),
|
|
257
|
-
showLess: t("chat.chats"),
|
|
258
|
-
}}
|
|
259
|
-
previewCount={5}
|
|
260
|
-
expandedCount={15}
|
|
261
|
-
onTogglePin={(threadId) => {
|
|
262
|
-
const thread = visibleThreads.find((item) => item.id === threadId);
|
|
263
|
-
if (thread) void pinThread(threadId, !thread.pinnedAt);
|
|
264
197
|
}}
|
|
265
|
-
onRename={handleRenameThread}
|
|
266
198
|
renameMaxLength={160}
|
|
267
199
|
onDelete={(threadId) => void handleArchiveThread(threadId)}
|
|
268
|
-
renderAdditionalRowActions={(item, closeMenu) =>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
200
|
+
renderAdditionalRowActions={(item, closeMenu) =>
|
|
201
|
+
item.readOnly ? null : (
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
role="menuitem"
|
|
205
|
+
className="an-chat-history-row__menu-item"
|
|
206
|
+
onClick={() => {
|
|
207
|
+
closeMenu();
|
|
208
|
+
void handleCopyShareLink(item.id);
|
|
209
|
+
}}
|
|
210
|
+
>
|
|
211
|
+
<IconShare3 size={13} strokeWidth={1.8} />
|
|
212
|
+
<span>{t("chat.copyShareLink")}</span>
|
|
213
|
+
</button>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
282
216
|
labels={{
|
|
283
217
|
options: (item) =>
|
|
284
218
|
t("chat.optionsFor", { title: item.titleText ?? "" }),
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
navigateWithAgentChatViewTransition,
|
|
3
3
|
useChatThreads,
|
|
4
|
-
type ChatThreadSummary,
|
|
5
4
|
} from "@agent-native/core/client/agent-chat";
|
|
5
|
+
import { appPath } from "@agent-native/core/client/api-path";
|
|
6
6
|
import { useT } from "@agent-native/core/client/i18n";
|
|
7
|
+
import {
|
|
8
|
+
OwnershipChatHistoryRail,
|
|
9
|
+
useOwnershipChatHistory,
|
|
10
|
+
ownDraftRows,
|
|
11
|
+
} from "@agent-native/toolkit/app/chat/ownership-history";
|
|
7
12
|
import { DevDatabaseLink } from "@agent-native/toolkit/app/db-admin";
|
|
8
13
|
import { FeedbackButton } from "@agent-native/toolkit/app/feedback";
|
|
9
14
|
import { OrgSwitcher } from "@agent-native/toolkit/app/org";
|
|
@@ -12,12 +17,7 @@ import {
|
|
|
12
17
|
AppSidebar,
|
|
13
18
|
AppSidebarNavItem,
|
|
14
19
|
} from "@agent-native/toolkit/app/shared";
|
|
15
|
-
import {
|
|
16
|
-
ChatHistoryRail,
|
|
17
|
-
type ChatHistoryItem,
|
|
18
|
-
} from "@agent-native/toolkit/chat-history";
|
|
19
20
|
import { IconSearch } from "@tabler/icons-react";
|
|
20
|
-
import { useEffect, useMemo } from "react";
|
|
21
21
|
import { useLocation, useNavigate } from "react-router";
|
|
22
22
|
import { toast } from "sonner";
|
|
23
23
|
|
|
@@ -43,40 +43,6 @@ interface SidebarProps {
|
|
|
43
43
|
onCollapsedChange?: (collapsed: boolean) => void;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function formatThreadAge(updatedAt: number) {
|
|
47
|
-
const diffMs = Math.max(0, Date.now() - updatedAt);
|
|
48
|
-
const minutes = Math.floor(diffMs / 60_000);
|
|
49
|
-
if (minutes < 1) return "now";
|
|
50
|
-
if (minutes < 60) return `${minutes}m`;
|
|
51
|
-
const hours = Math.floor(minutes / 60);
|
|
52
|
-
if (hours < 24) return `${hours}h`;
|
|
53
|
-
const days = Math.floor(hours / 24);
|
|
54
|
-
if (days < 7) return `${days}d`;
|
|
55
|
-
return new Date(updatedAt).toLocaleDateString([], {
|
|
56
|
-
month: "short",
|
|
57
|
-
day: "numeric",
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function threadTitle(thread: ChatThreadSummary) {
|
|
62
|
-
return thread.title || thread.preview || "Untitled chat";
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function threadUpdatedAt(thread: ChatThreadSummary) {
|
|
66
|
-
return Number.isFinite(thread.updatedAt)
|
|
67
|
-
? thread.updatedAt
|
|
68
|
-
: Number.isFinite(thread.createdAt)
|
|
69
|
-
? thread.createdAt
|
|
70
|
-
: 0;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function compareThreads(a: ChatThreadSummary, b: ChatThreadSummary) {
|
|
74
|
-
const aPinned = a.pinnedAt ?? 0;
|
|
75
|
-
const bPinned = b.pinnedAt ?? 0;
|
|
76
|
-
if (aPinned || bPinned) return bPinned - aPinned;
|
|
77
|
-
return threadUpdatedAt(b) - threadUpdatedAt(a);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
46
|
function persistedActiveThreadId() {
|
|
81
47
|
try {
|
|
82
48
|
return localStorage.getItem(BRAIN_ACTIVE_THREAD_KEY);
|
|
@@ -94,8 +60,9 @@ function BrainChatsSection({ open }: { open: boolean }) {
|
|
|
94
60
|
pinThread,
|
|
95
61
|
archiveThread,
|
|
96
62
|
renameThread,
|
|
97
|
-
|
|
63
|
+
isNewThread,
|
|
98
64
|
} = useChatThreads(undefined, BRAIN_CHAT_STORAGE_KEY, undefined, {
|
|
65
|
+
ownership: "mine",
|
|
99
66
|
autoCreate: false,
|
|
100
67
|
restoreActiveThread: false,
|
|
101
68
|
});
|
|
@@ -104,46 +71,27 @@ function BrainChatsSection({ open }: { open: boolean }) {
|
|
|
104
71
|
const location = useLocation();
|
|
105
72
|
const activeThreadId = brainAskThreadIdFromPath(location.pathname);
|
|
106
73
|
|
|
107
|
-
const
|
|
108
|
-
() =>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
useEffect(() => {
|
|
130
|
-
const refresh = () => refreshThreads();
|
|
131
|
-
const handleRunning = (event: Event) => {
|
|
132
|
-
const detail = (event as CustomEvent).detail as
|
|
133
|
-
| { isRunning?: unknown }
|
|
134
|
-
| undefined;
|
|
135
|
-
if (typeof detail?.isRunning === "boolean") refreshThreads();
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
window.addEventListener("agent-chat:threads-updated", refresh);
|
|
139
|
-
window.addEventListener("agentNative.chatRunning", handleRunning);
|
|
140
|
-
window.addEventListener("focus", refresh);
|
|
141
|
-
return () => {
|
|
142
|
-
window.removeEventListener("agent-chat:threads-updated", refresh);
|
|
143
|
-
window.removeEventListener("agentNative.chatRunning", handleRunning);
|
|
144
|
-
window.removeEventListener("focus", refresh);
|
|
145
|
-
};
|
|
146
|
-
}, [refreshThreads]);
|
|
74
|
+
const history = useOwnershipChatHistory({
|
|
75
|
+
getThreadUrl: (threadId) => appPath(brainAskThreadPath(threadId)),
|
|
76
|
+
activeId: activeThreadId,
|
|
77
|
+
localMineRows: ownDraftRows({ threads, isNewThread }),
|
|
78
|
+
mutations: {
|
|
79
|
+
pin: pinThread,
|
|
80
|
+
rename: renameThread,
|
|
81
|
+
archive: archiveThread,
|
|
82
|
+
},
|
|
83
|
+
onMutationFailed: (kind) => {
|
|
84
|
+
toast.error(
|
|
85
|
+
t(
|
|
86
|
+
kind === "archive"
|
|
87
|
+
? "chat.archiveFailed"
|
|
88
|
+
: kind === "rename"
|
|
89
|
+
? "chat.renameFailed"
|
|
90
|
+
: "agentChat.common.saveFailed",
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
147
95
|
|
|
148
96
|
function openThread(threadId: string) {
|
|
149
97
|
switchThread(threadId);
|
|
@@ -165,20 +113,12 @@ function BrainChatsSection({ open }: { open: boolean }) {
|
|
|
165
113
|
async function handleArchiveThread(threadId: string) {
|
|
166
114
|
const wasActive =
|
|
167
115
|
threadId === activeThreadId || threadId === persistedActiveThreadId();
|
|
168
|
-
const archived = await
|
|
169
|
-
if (!archived)
|
|
170
|
-
toast.error(t("chat.archiveFailed"));
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
116
|
+
const archived = await history.listProps.onDelete!(threadId);
|
|
117
|
+
if (!archived) return false;
|
|
173
118
|
if (wasActive) {
|
|
174
119
|
handleNewChat();
|
|
175
120
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
function handleRenameThread(threadId: string, title: string) {
|
|
179
|
-
void renameThread(threadId, title).then((renamed) => {
|
|
180
|
-
if (!renamed) toast.error(t("chat.renameFailed"));
|
|
181
|
-
});
|
|
121
|
+
return true;
|
|
182
122
|
}
|
|
183
123
|
|
|
184
124
|
return (
|
|
@@ -188,8 +128,8 @@ function BrainChatsSection({ open }: { open: boolean }) {
|
|
|
188
128
|
aria-hidden={!open}
|
|
189
129
|
>
|
|
190
130
|
<div className="ms-4">
|
|
191
|
-
<
|
|
192
|
-
|
|
131
|
+
<OwnershipChatHistoryRail
|
|
132
|
+
{...history}
|
|
193
133
|
activeId={activeThreadId}
|
|
194
134
|
onSelect={openThread}
|
|
195
135
|
onNewChat={handleNewChat}
|
|
@@ -198,13 +138,6 @@ function BrainChatsSection({ open }: { open: boolean }) {
|
|
|
198
138
|
showMore: t("chat.chats"),
|
|
199
139
|
showLess: t("chat.chats"),
|
|
200
140
|
}}
|
|
201
|
-
previewCount={5}
|
|
202
|
-
expandedCount={15}
|
|
203
|
-
onTogglePin={(threadId) => {
|
|
204
|
-
const thread = visibleThreads.find((item) => item.id === threadId);
|
|
205
|
-
if (thread) void pinThread(threadId, !thread.pinnedAt);
|
|
206
|
-
}}
|
|
207
|
-
onRename={handleRenameThread}
|
|
208
141
|
renameMaxLength={160}
|
|
209
142
|
onDelete={(threadId) => void handleArchiveThread(threadId)}
|
|
210
143
|
labels={{
|