@g9tm/aiassistant-svelte 1.0.0 → 1.1.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.
@@ -1,118 +1,130 @@
1
- <script lang="ts">
2
- // The root of one assistant: shares the core, texts, part styles, overrides and theme with every component inside.
3
- // All the library's styling is here, under .g9a-styled, from theme tokens; unstyled leaves structure and classes only.
4
- import type { Snippet } from "svelte";
5
- import type { Assistant, PartStyles } from "@g9tm/aiassistant";
6
- import { provideUi, type Overrides } from "./context.js";
7
- import { themeVars } from "./theme.js";
8
-
9
- let { assistant, styles = {}, unstyled = false, components = {}, children }: {
10
- assistant: Assistant; styles?: PartStyles; unstyled?: boolean; components?: Overrides; children: Snippet;
11
- } = $props();
12
-
13
- // Getters, not copies: a host that switches unstyled, styles or a replaced component sees it at once.
14
- provideUi({
15
- get assistant() { return assistant; },
16
- get texts() { return assistant.texts; },
17
- get styles() { return styles; },
18
- get unstyled() { return unstyled; },
19
- get components() { return components; },
20
- get layer() { return (assistant.host.sheetLayer ?? 1000) + 1; },
21
- });
22
- const vars = $derived(unstyled ? "" : themeVars(assistant.host.theme));
23
- </script>
24
-
25
- <div class={unstyled ? "g9a-root" : "g9a-root g9a-styled"} dir={assistant.host.dir} lang={assistant.host.locale} style={vars}>
26
- {@render children()}
27
- </div>
28
-
29
- <style>
30
- .g9a-root { display: contents; }
31
- .g9a-styled { font-family: var(--g9a-font); font-size: var(--g9a-font-size); color: var(--g9a-ink); line-height: 1.6; }
32
- .g9a-styled :global(button) { font: inherit; cursor: pointer; min-block-size: 38px; padding: 0 12px; border-radius: var(--g9a-radius-s); border: 1px solid var(--g9a-line); background: var(--g9a-surface); color: var(--g9a-ink); }
33
- .g9a-styled :global(input), .g9a-styled :global(textarea) { font: inherit; padding: 8px 10px; border: 1px solid var(--g9a-line); border-radius: var(--g9a-radius-s); background: var(--g9a-surface); color: var(--g9a-ink); }
34
- .g9a-styled :global(.g9a-panel) { box-sizing: border-box; display: flex; flex-direction: column; background: var(--g9a-surface); color: var(--g9a-ink); box-shadow: var(--g9a-shadow); overflow: hidden; }
35
- .g9a-styled :global(.g9a-panel[data-layout="side"]) { position: fixed; inset-block: 0; inset-inline-end: 0; inline-size: min(420px, 100%); border-inline-start: 1px solid var(--g9a-line); }
36
- .g9a-styled :global(.g9a-panel[data-layout="sheet"]) { position: fixed; inset-inline: 0; inset-block-end: 0; block-size: 85vh; border-start-start-radius: 16px; border-start-end-radius: 16px; }
37
- .g9a-styled :global(.g9a-panel[data-layout="fullscreen"]) { position: fixed; inset: 0; }
38
- .g9a-styled :global(.g9a-panel[data-layout="inline"]) { inline-size: 100%; block-size: 100%; box-shadow: none; border: 1px solid var(--g9a-line); border-radius: var(--g9a-radius); }
39
- .g9a-styled :global(.g9a-panel[data-layout="card"]) { border-radius: var(--g9a-radius); border: 1px solid var(--g9a-line); max-block-size: 520px; }
40
- .g9a-styled :global(.g9a-panel[data-layout="compact"]) { box-shadow: none; }
41
- .g9a-styled :global(.g9a-panel__header) { display: flex; align-items: center; gap: var(--g9a-gap); padding: var(--g9a-pad); border-bottom: 1px solid var(--g9a-line); }
42
- .g9a-styled :global(.g9a-panel__header > strong) { flex: 1; min-inline-size: 0; }
43
- .g9a-styled :global(.g9a-panel__body), .g9a-styled :global(.g9a-chat) { display: flex; flex-direction: column; flex: 1; min-block-size: 0; }
44
- .g9a-styled :global(.g9a-messages) { flex: 1; overflow-y: auto; padding: var(--g9a-pad); display: flex; flex-direction: column; gap: var(--g9a-gap); }
45
- .g9a-styled :global(.g9a-message) { max-inline-size: 88%; padding: 8px 12px; border-radius: var(--g9a-radius); background: var(--g9a-surface-2); }
46
- .g9a-styled :global(.g9a-message__user) { align-self: flex-start; background: color-mix(in srgb, var(--g9a-accent) 12%, var(--g9a-surface)); }
47
- .g9a-styled :global(.g9a-message__assistant) { align-self: flex-end; }
48
- .g9a-styled :global(.g9a-message[data-pending="true"]) { opacity: .7; }
49
- .g9a-styled :global(.g9a-message__feedback) { display: flex; gap: 6px; align-items: center; margin-block-start: 4px; }
50
- .g9a-styled :global(.g9a-message__feedback button) { min-block-size: 26px; font-size: 12px; padding: 0 8px; }
51
- .g9a-styled :global(.g9a-message__provenance) { font-size: 11px; color: var(--g9a-muted); }
52
- .g9a-styled :global(.g9a-p) { margin: 0 0 6px; white-space: pre-wrap; overflow-wrap: anywhere; }
53
- .g9a-styled :global(code) { font-family: ui-monospace, monospace; background: var(--g9a-surface); border-radius: 4px; padding: 0 4px; }
54
- .g9a-styled :global(a) { color: var(--g9a-accent); }
55
- .g9a-styled :global(.g9a-work) { display: flex; gap: var(--g9a-gap); align-items: flex-start; padding: var(--g9a-pad); border-top: 1px solid var(--g9a-line); }
56
- .g9a-styled :global(.g9a-work-main) { flex: 1; min-inline-size: 0; }
57
- .g9a-styled :global(.g9a-work-head) { display: flex; justify-content: space-between; gap: var(--g9a-gap); }
58
- .g9a-styled :global(.g9a-work__elapsed), .g9a-styled :global(.g9a-work__tool), .g9a-styled :global(.g9a-note) { color: var(--g9a-muted); font-size: 12px; }
59
- .g9a-styled :global(.g9a-stages), .g9a-styled :global(.g9a-stages-thinking) { display: flex; flex-wrap: wrap; gap: 6px; margin-block: 4px; }
60
- .g9a-styled :global(.g9a-work__stage), .g9a-styled :global(.g9a-stages-thinking > span) { padding: 1px 10px; border-radius: 999px; border: 1px solid var(--g9a-line); font-size: 12px; color: var(--g9a-muted); }
61
- .g9a-styled :global([data-state="on"].g9a-work__stage), .g9a-styled :global(.g9a-stages-thinking > [data-state="on"]) { background: color-mix(in srgb, var(--g9a-accent) 20%, transparent); color: var(--g9a-ink); border-color: var(--g9a-accent); }
62
- .g9a-styled :global(.g9a-thinking-orb) { inline-size: 18px; block-size: 18px; border-radius: 50%; background: var(--g9a-accent); animation: g9a-pulse 1.2s ease-in-out infinite; flex: none; margin-block-start: 4px; }
63
- .g9a-styled :global(.g9a-thinking-orb[data-still="true"]) { animation: none; }
64
- @keyframes -global-g9a-pulse { 0%, 100% { transform: scale(.8); opacity: .6; } 50% { transform: scale(1); opacity: 1; } }
65
- @media (prefers-reduced-motion: reduce) { .g9a-styled :global(.g9a-thinking-orb) { animation: none; } }
66
- .g9a-styled :global(.g9a-composer) { padding: var(--g9a-pad); border-top: 1px solid var(--g9a-line); }
67
- .g9a-styled :global(.g9a-composer-row) { display: flex; gap: var(--g9a-gap); align-items: flex-end; }
68
- .g9a-styled :global(.g9a-composer__input) { flex: 1; min-inline-size: 0; resize: none; max-block-size: 30vh; }
69
- .g9a-styled :global(.g9a-composer__send) { background: var(--g9a-accent); color: var(--g9a-accent-ink); border-color: var(--g9a-accent); }
70
- .g9a-styled :global(.g9a-file-chips) { display: flex; flex-wrap: wrap; gap: 4px; margin-block-end: 6px; }
71
- .g9a-styled :global(.g9a-file-chip) { background: var(--g9a-surface-2); border-radius: 999px; padding: 2px 4px 2px 10px; font-size: 12px; }
72
- .g9a-styled :global(.g9a-file-chip button) { min-block-size: 20px; border: 0; background: none; padding: 0 6px; }
73
- .g9a-styled :global(.g9a-card__approval) { border: 1px solid var(--g9a-approval-card-border); border-radius: var(--g9a-radius); padding: var(--g9a-pad); background: var(--g9a-surface); }
74
- .g9a-styled :global(.g9a-card__approval h3) { margin: 0 0 6px; font-size: 15px; }
75
- .g9a-styled :global(.g9a-card__approval__fact) { font-size: 12px; color: var(--g9a-muted); padding-inline-start: 24px; }
76
- .g9a-styled :global(.g9a-card__approval__confirm) { background: var(--g9a-accent); color: var(--g9a-accent-ink); border-color: var(--g9a-accent); }
77
- .g9a-styled :global(.g9a-warning) { color: var(--g9a-warn); font-size: 13px; }
78
- .g9a-styled :global(.g9a-irreversible) { color: var(--g9a-danger); font-size: 12px; }
79
- .g9a-styled :global(.g9a-row) { display: flex; gap: var(--g9a-gap); margin-block-start: 8px; }
80
- .g9a-styled :global(.g9a-mode-banner) { background: var(--g9a-surface-2); border-bottom: 1px solid var(--g9a-line); color: var(--g9a-warn); padding: 6px var(--g9a-pad); font-size: 13px; }
81
- .g9a-styled :global(.g9a-usage-chip) { display: inline-block; block-size: 22px; line-height: 22px; padding: 0 8px; border-radius: 999px; font-size: 11px;
82
- background: linear-gradient(to left, var(--g9a-surface-2) calc(100% - var(--g9a-usage)), color-mix(in srgb, var(--g9a-accent) 25%, transparent) 0); }
83
- .g9a-styled :global(.g9a-tabs) { display: flex; gap: 4px; border-bottom: 1px solid var(--g9a-line); margin-block-end: 8px; }
84
- .g9a-styled :global(.g9a-tabs [aria-selected="true"]) { border-bottom: 2px solid var(--g9a-accent); }
85
- .g9a-styled :global(.g9a-admin-capability) { border: 1px solid var(--g9a-line); border-radius: 8px; padding: 8px; margin-block-end: 8px; display: grid; gap: 4px; }
86
- .g9a-styled :global(.g9a-admin-capability label) { display: flex; justify-content: space-between; gap: 8px; font-size: 13px; }
87
- .g9a-styled :global([data-bad="true"]), .g9a-styled :global([data-ok="false"]) { color: var(--g9a-danger); }
88
- .g9a-styled :global(.g9a-block-title) { font-weight: 600; margin-block: 4px; }
89
- .g9a-styled :global(.g9a-block__table table) { border-collapse: collapse; inline-size: 100%; font-size: 13px; }
90
- .g9a-styled :global(.g9a-block__table th), .g9a-styled :global(.g9a-block__table td) { border-bottom: 1px solid var(--g9a-line); padding: 4px 6px; text-align: start; }
91
- .g9a-styled :global([data-kind="number"]), .g9a-styled :global([data-kind="money"]), .g9a-styled :global([data-kind="percent"]) { text-align: end; font-variant-numeric: tabular-nums; }
92
- .g9a-styled :global(.g9a-block__table__card) { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 0 0 6px; padding: 6px 8px; background: var(--g9a-surface); border-radius: var(--g9a-radius-s); }
93
- .g9a-styled :global(.g9a-block__table__card dt) { color: var(--g9a-muted); }
94
- .g9a-styled :global(.g9a-block__table__card dd) { margin: 0; }
95
- .g9a-styled :global(.g9a-block__chart__bar) { display: grid; grid-template-columns: 1fr; gap: 2px; margin-block-end: 6px; }
96
- .g9a-styled :global(.g9a-bar-track) { display: block; block-size: 10px; background: var(--g9a-surface); border-radius: 5px; overflow: hidden; }
97
- .g9a-styled :global(.g9a-bar-fill) { display: block; block-size: 100%; background: var(--g9a-chart-bar); border-radius: 5px; }
98
- .g9a-styled :global(.g9a-bar-value) { font-size: 12px; color: var(--g9a-muted); }
99
- .g9a-styled :global(.g9a-block__chart__line) { inline-size: 100%; block-size: 140px; }
100
- .g9a-styled :global(.g9a-block__chart__line path) { stroke: var(--g9a-chart-bar); stroke-width: 2; }
101
- .g9a-styled :global(.g9a-donut) { inline-size: 120px; }
102
- .g9a-styled :global(.g9a-donut path) { fill: none; stroke: var(--g9a-chart-bar); stroke-width: 14; }
103
- .g9a-styled :global(.g9a-block__kpi) { display: flex; flex-wrap: wrap; gap: var(--g9a-gap); }
104
- .g9a-styled :global(.g9a-block__kpi > div) { display: flex; flex-direction: column; padding: 6px 10px; background: var(--g9a-surface); border-radius: var(--g9a-radius-s); }
105
- .g9a-styled :global(.g9a-block__actions) { display: flex; flex-wrap: wrap; gap: 6px; margin-block-start: 6px; }
106
- .g9a-styled :global(.g9a-block__action) { background: color-mix(in srgb, var(--g9a-accent) 12%, var(--g9a-surface)); border-color: var(--g9a-accent); }
107
- .g9a-styled :global(.g9a-block__action::after) { content: " ‹"; }
108
- .g9a-styled :global([dir="ltr"] .g9a-block__action::after) { content: " ›"; }
109
- .g9a-styled :global(.g9a-suggested) { font-size: 10px; color: var(--g9a-muted); margin-inline-start: 6px; }
110
- .g9a-styled :global(.g9a-block__callout) { border-inline-start: 3px solid var(--g9a-accent); padding: 6px 10px; background: var(--g9a-surface); }
111
- .g9a-styled :global(.g9a-block__callout[data-tone="danger"]) { border-color: var(--g9a-danger); }
112
- .g9a-styled :global(.g9a-block__callout[data-tone="warn"]) { border-color: var(--g9a-warn); }
113
- .g9a-styled :global(.g9a-block__image img) { max-inline-size: 100%; border-radius: var(--g9a-radius-s); }
114
- .g9a-styled :global(.g9a-dialog) { position: fixed; inset: 10% 8%; background: var(--g9a-surface); border-radius: var(--g9a-radius); box-shadow: var(--g9a-shadow); padding: var(--g9a-pad); overflow: auto; }
115
- .g9a-styled :global(.g9a-hero) { padding: var(--g9a-pad); border: 1px solid var(--g9a-line); border-radius: var(--g9a-radius); background: var(--g9a-surface); }
116
- .g9a-styled :global(.g9a-badge) { margin-inline-start: 6px; background: var(--g9a-danger); color: #fff; border-radius: 999px; padding: 0 6px; font-size: 11px; }
117
- .g9a-styled :global(.g9a-measure) { inline-size: 100%; block-size: 0; }
118
- </style>
1
+ <script lang="ts">
2
+ // The root of one assistant: shares the core, texts, part styles, overrides and theme with every component inside.
3
+ // All the library's styling is here, under .g9a-styled, from theme tokens; unstyled leaves structure and classes only.
4
+ import type { Snippet } from "svelte";
5
+ import type { Assistant, PartStyles } from "@g9tm/aiassistant";
6
+ import { provideUi, type Overrides } from "./context.js";
7
+ import { themeVars } from "./theme.js";
8
+
9
+ let { assistant, styles = {}, unstyled = false, components = {}, children }: {
10
+ assistant: Assistant; styles?: PartStyles; unstyled?: boolean; components?: Overrides; children: Snippet;
11
+ } = $props();
12
+
13
+ // Getters, not copies: a host that switches unstyled, styles or a replaced component sees it at once.
14
+ provideUi({
15
+ get assistant() { return assistant; },
16
+ get texts() { return assistant.texts; },
17
+ get styles() { return styles; },
18
+ get unstyled() { return unstyled; },
19
+ get components() { return components; },
20
+ get layer() { return (assistant.host.sheetLayer ?? 1000) + 1; },
21
+ });
22
+ const vars = $derived(unstyled ? "" : themeVars(assistant.host.theme));
23
+ </script>
24
+
25
+ <div class={unstyled ? "g9a-root" : "g9a-root g9a-styled"} dir={assistant.host.dir} lang={assistant.host.locale} style={vars}>
26
+ {@render children()}
27
+ </div>
28
+
29
+ <style>
30
+ .g9a-root { display: contents; }
31
+ .g9a-styled { font-family: var(--g9a-font); font-size: var(--g9a-font-size); color: var(--g9a-ink); line-height: 1.6; }
32
+ .g9a-styled :global(button) { font: inherit; cursor: pointer; min-block-size: 38px; padding: 0 12px; border-radius: var(--g9a-radius-s); border: 1px solid var(--g9a-line); background: var(--g9a-surface); color: var(--g9a-ink); }
33
+ .g9a-styled :global(input), .g9a-styled :global(textarea) { font: inherit; padding: 8px 10px; border: 1px solid var(--g9a-line); border-radius: var(--g9a-radius-s); background: var(--g9a-surface); color: var(--g9a-ink); }
34
+ .g9a-styled :global(.g9a-panel) { box-sizing: border-box; display: flex; flex-direction: column; background: var(--g9a-surface); color: var(--g9a-ink); box-shadow: var(--g9a-shadow); overflow: hidden; }
35
+ .g9a-styled :global(.g9a-panel[data-layout="side"]) { position: fixed; inset-block: 0; inset-inline-end: 0; inline-size: min(420px, 100%); border-inline-start: 1px solid var(--g9a-line); }
36
+ .g9a-styled :global(.g9a-panel[data-layout="sheet"]) { position: fixed; inset-inline: 0; inset-block-end: 0; block-size: 85vh; border-start-start-radius: 16px; border-start-end-radius: 16px; }
37
+ .g9a-styled :global(.g9a-panel[data-layout="fullscreen"]) { position: fixed; inset: 0; }
38
+ .g9a-styled :global(.g9a-panel[data-layout="inline"]) { inline-size: 100%; block-size: 100%; box-shadow: none; border: 1px solid var(--g9a-line); border-radius: var(--g9a-radius); }
39
+ .g9a-styled :global(.g9a-panel[data-layout="card"]) { border-radius: var(--g9a-radius); border: 1px solid var(--g9a-line); max-block-size: 520px; }
40
+ .g9a-styled :global(.g9a-panel[data-layout="compact"]) { box-shadow: none; }
41
+ .g9a-styled :global(.g9a-panel__header) { display: flex; align-items: center; gap: var(--g9a-gap); padding: var(--g9a-pad); border-bottom: 1px solid var(--g9a-line); }
42
+ .g9a-styled :global(.g9a-panel__header > strong) { flex: 1; min-inline-size: 0; }
43
+ .g9a-styled :global(.g9a-panel__body), .g9a-styled :global(.g9a-chat) { display: flex; flex-direction: column; flex: 1; min-block-size: 0; }
44
+ .g9a-styled :global(.g9a-messages) { flex: 1; overflow-y: auto; padding: var(--g9a-pad); display: flex; flex-direction: column; gap: var(--g9a-gap); }
45
+ .g9a-styled :global(.g9a-message) { max-inline-size: 88%; padding: 8px 12px; border-radius: var(--g9a-radius); background: var(--g9a-surface-2); }
46
+ .g9a-styled :global(.g9a-message__user) { align-self: flex-start; background: color-mix(in srgb, var(--g9a-accent) 12%, var(--g9a-surface)); }
47
+ .g9a-styled :global(.g9a-message__assistant) { align-self: flex-end; }
48
+ .g9a-styled :global(.g9a-message[data-pending="true"]) { opacity: .7; }
49
+ .g9a-styled :global(.g9a-message__feedback) { display: flex; gap: 6px; align-items: center; margin-block-start: 4px; }
50
+ .g9a-styled :global(.g9a-message__feedback button) { min-block-size: 26px; font-size: 12px; padding: 0 8px; }
51
+ .g9a-styled :global(.g9a-message__provenance) { font-size: 11px; color: var(--g9a-muted); }
52
+ .g9a-styled :global(.g9a-p) { margin: 0 0 6px; white-space: pre-wrap; overflow-wrap: anywhere; }
53
+ .g9a-styled :global(code) { font-family: ui-monospace, monospace; background: var(--g9a-surface); border-radius: 4px; padding: 0 4px; }
54
+ .g9a-styled :global(a) { color: var(--g9a-accent); }
55
+ .g9a-styled :global(.g9a-work) { display: flex; gap: var(--g9a-gap); align-items: flex-start; padding: var(--g9a-pad); border-top: 1px solid var(--g9a-line); }
56
+ .g9a-styled :global(.g9a-work-main) { flex: 1; min-inline-size: 0; }
57
+ .g9a-styled :global(.g9a-work-head) { display: flex; justify-content: space-between; gap: var(--g9a-gap); }
58
+ .g9a-styled :global(.g9a-work__elapsed), .g9a-styled :global(.g9a-work__tool), .g9a-styled :global(.g9a-note) { color: var(--g9a-muted); font-size: 12px; }
59
+ .g9a-styled :global(.g9a-stages), .g9a-styled :global(.g9a-stages-thinking) { display: flex; flex-wrap: wrap; gap: 6px; margin-block: 4px; }
60
+ .g9a-styled :global(.g9a-work__stage), .g9a-styled :global(.g9a-stages-thinking > span) { padding: 1px 10px; border-radius: 999px; border: 1px solid var(--g9a-line); font-size: 12px; color: var(--g9a-muted); }
61
+ .g9a-styled :global([data-state="on"].g9a-work__stage), .g9a-styled :global(.g9a-stages-thinking > [data-state="on"]) { background: color-mix(in srgb, var(--g9a-accent) 20%, transparent); color: var(--g9a-ink); border-color: var(--g9a-accent); }
62
+ .g9a-styled :global(.g9a-thinking-orb) { inline-size: 18px; block-size: 18px; border-radius: 50%; background: var(--g9a-accent); animation: g9a-pulse 1.2s ease-in-out infinite; flex: none; margin-block-start: 4px; }
63
+ .g9a-styled :global(.g9a-thinking-orb[data-still="true"]) { animation: none; }
64
+ @keyframes -global-g9a-pulse { 0%, 100% { transform: scale(.8); opacity: .6; } 50% { transform: scale(1); opacity: 1; } }
65
+ @media (prefers-reduced-motion: reduce) { .g9a-styled :global(.g9a-thinking-orb) { animation: none; } }
66
+ .g9a-styled :global(.g9a-composer) { padding: var(--g9a-pad); border-top: 1px solid var(--g9a-line); }
67
+ .g9a-styled :global(.g9a-composer-row) { display: flex; gap: var(--g9a-gap); align-items: flex-end; }
68
+ .g9a-styled :global(.g9a-composer__input) { flex: 1; min-inline-size: 0; resize: none; max-block-size: 30vh; }
69
+ .g9a-styled :global(.g9a-composer__send) { background: var(--g9a-accent); color: var(--g9a-accent-ink); border-color: var(--g9a-accent); }
70
+ .g9a-styled :global(.g9a-file-chips) { display: flex; flex-wrap: wrap; gap: 4px; margin-block-end: 6px; }
71
+ .g9a-styled :global(.g9a-file-chip) { background: var(--g9a-surface-2); border-radius: 999px; padding: 2px 4px 2px 10px; font-size: 12px; }
72
+ .g9a-styled :global(.g9a-file-chip button) { min-block-size: 20px; border: 0; background: none; padding: 0 6px; }
73
+ .g9a-styled :global(.g9a-card__approval) { border: 1px solid var(--g9a-approval-card-border); border-radius: var(--g9a-radius); padding: var(--g9a-pad); background: var(--g9a-surface); }
74
+ .g9a-styled :global(.g9a-card__approval h3) { margin: 0 0 6px; font-size: 15px; }
75
+ .g9a-styled :global(.g9a-card__approval__fact) { font-size: 12px; color: var(--g9a-muted); padding-inline-start: 24px; }
76
+ .g9a-styled :global(.g9a-card__approval__confirm) { background: var(--g9a-accent); color: var(--g9a-accent-ink); border-color: var(--g9a-accent); }
77
+ .g9a-styled :global(.g9a-warning) { color: var(--g9a-warn); font-size: 13px; }
78
+ .g9a-styled :global(.g9a-irreversible) { color: var(--g9a-danger); font-size: 12px; }
79
+ .g9a-styled :global(.g9a-row) { display: flex; gap: var(--g9a-gap); margin-block-start: 8px; }
80
+ .g9a-styled :global(.g9a-mode-banner) { background: var(--g9a-surface-2); border-bottom: 1px solid var(--g9a-line); color: var(--g9a-warn); padding: 6px var(--g9a-pad); font-size: 13px; }
81
+ .g9a-styled :global(.g9a-usage-chip) { display: inline-block; block-size: 22px; line-height: 22px; padding: 0 8px; border-radius: 999px; font-size: 11px;
82
+ background: linear-gradient(to left, var(--g9a-surface-2) calc(100% - var(--g9a-usage)), color-mix(in srgb, var(--g9a-accent) 25%, transparent) 0); }
83
+ .g9a-styled :global(.g9a-tabs) { display: flex; gap: 4px; border-bottom: 1px solid var(--g9a-line); margin-block-end: 8px; }
84
+ .g9a-styled :global(.g9a-tabs [aria-selected="true"]) { border-bottom: 2px solid var(--g9a-accent); }
85
+ .g9a-styled :global(.g9a-admin-capability) { border: 1px solid var(--g9a-line); border-radius: 8px; padding: 8px; margin-block-end: 8px; display: grid; gap: 4px; }
86
+ .g9a-styled :global(.g9a-admin-capability label) { display: flex; justify-content: space-between; gap: 8px; font-size: 13px; }
87
+ .g9a-styled :global([data-bad="true"]), .g9a-styled :global([data-ok="false"]) { color: var(--g9a-danger); }
88
+ .g9a-styled :global(.g9a-block-title) { font-weight: 600; margin-block: 4px; }
89
+ .g9a-styled :global(.g9a-block__table table) { border-collapse: collapse; inline-size: 100%; font-size: 13px; }
90
+ .g9a-styled :global(.g9a-block__table th), .g9a-styled :global(.g9a-block__table td) { border-bottom: 1px solid var(--g9a-line); padding: 4px 6px; text-align: start; }
91
+ .g9a-styled :global([data-kind="number"]), .g9a-styled :global([data-kind="money"]), .g9a-styled :global([data-kind="percent"]) { text-align: end; font-variant-numeric: tabular-nums; }
92
+ .g9a-styled :global(.g9a-block__table__card) { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 0 0 6px; padding: 6px 8px; background: var(--g9a-surface); border-radius: var(--g9a-radius-s); }
93
+ .g9a-styled :global(.g9a-block__table__card dt) { color: var(--g9a-muted); }
94
+ .g9a-styled :global(.g9a-block__table__card dd) { margin: 0; }
95
+ .g9a-styled :global(.g9a-block__chart__bar) { display: grid; grid-template-columns: 1fr; gap: 2px; margin-block-end: 6px; }
96
+ .g9a-styled :global(.g9a-bar-track) { display: block; block-size: 10px; background: var(--g9a-surface); border-radius: 5px; overflow: hidden; }
97
+ .g9a-styled :global(.g9a-bar-fill) { display: block; block-size: 100%; background: var(--g9a-chart-bar); border-radius: 5px; }
98
+ .g9a-styled :global(.g9a-bar-value) { font-size: 12px; color: var(--g9a-muted); }
99
+ .g9a-styled :global(.g9a-block__chart__line) { inline-size: 100%; block-size: 140px; }
100
+ .g9a-styled :global(.g9a-block__chart__line path) { stroke: var(--g9a-chart-bar); stroke-width: 2; }
101
+ .g9a-styled :global(.g9a-donut) { inline-size: 120px; }
102
+ .g9a-styled :global(.g9a-donut path) { fill: none; stroke: var(--g9a-chart-bar); stroke-width: 14; }
103
+ .g9a-styled :global(.g9a-block__kpi) { display: flex; flex-wrap: wrap; gap: var(--g9a-gap); }
104
+ .g9a-styled :global(.g9a-block__kpi > div) { display: flex; flex-direction: column; padding: 6px 10px; background: var(--g9a-surface); border-radius: var(--g9a-radius-s); }
105
+ .g9a-styled :global(.g9a-block__actions) { display: flex; flex-wrap: wrap; gap: 6px; margin-block-start: 6px; }
106
+ .g9a-styled :global(.g9a-block__action) { background: color-mix(in srgb, var(--g9a-accent) 12%, var(--g9a-surface)); border-color: var(--g9a-accent); }
107
+ .g9a-styled :global(.g9a-block__action::after) { content: " ‹"; }
108
+ .g9a-styled :global([dir="ltr"] .g9a-block__action::after) { content: " ›"; }
109
+ .g9a-styled :global(.g9a-suggested) { font-size: 10px; color: var(--g9a-muted); margin-inline-start: 6px; }
110
+ .g9a-styled :global(.g9a-block__callout) { border-inline-start: 3px solid var(--g9a-accent); padding: 6px 10px; background: var(--g9a-surface); }
111
+ .g9a-styled :global(.g9a-block__callout[data-tone="danger"]) { border-color: var(--g9a-danger); }
112
+ .g9a-styled :global(.g9a-block__callout[data-tone="warn"]) { border-color: var(--g9a-warn); }
113
+ .g9a-styled :global(.g9a-block__image img) { max-inline-size: 100%; border-radius: var(--g9a-radius-s); }
114
+ .g9a-styled :global(.g9a-dialog) { position: fixed; inset: 10% 8%; background: var(--g9a-surface); border-radius: var(--g9a-radius); box-shadow: var(--g9a-shadow); padding: var(--g9a-pad); overflow: auto; }
115
+ .g9a-styled :global(.g9a-hero) { padding: var(--g9a-pad); border: 1px solid var(--g9a-line); border-radius: var(--g9a-radius); background: var(--g9a-surface); }
116
+ .g9a-styled :global(.g9a-badge) { margin-inline-start: 6px; background: var(--g9a-danger); color: #fff; border-radius: 999px; padding: 0 6px; font-size: 11px; }
117
+ .g9a-styled :global(.g9a-measure) { inline-size: 100%; block-size: 0; }
118
+ .g9a-styled :global(.g9a-count) { font-size: 11px; color: var(--g9a-muted); margin-inline-start: 4px; }
119
+ .g9a-styled :global(.g9a-history__list), .g9a-styled :global(.g9a-routines__list), .g9a-styled :global(.g9a-picker__list) { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
120
+ .g9a-styled :global(.g9a-history__row) { display: flex; gap: 4px; align-items: center; }
121
+ .g9a-styled :global(.g9a-history__open) { flex: 1; min-inline-size: 0; text-align: start; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
122
+ .g9a-styled :global(.g9a-history__row[data-waiting="true"] .g9a-history__open) { border-color: var(--g9a-warn); }
123
+ .g9a-styled :global(.g9a-history__delete), .g9a-styled :global(.g9a-history__delete-yes) { color: var(--g9a-danger); }
124
+ .g9a-styled :global(.g9a-routines__form) { display: grid; gap: var(--g9a-gap); }
125
+ .g9a-styled :global(.g9a-routines__slot) { display: grid; gap: 2px; }
126
+ .g9a-styled :global(.g9a-routines__slot[data-state="missing"] input), .g9a-styled :global(.g9a-routines__slot[data-state="invalid"] input) { border-color: var(--g9a-danger); }
127
+ .g9a-styled :global(.g9a-routines__choose) { inline-size: 100%; text-align: start; }
128
+ .g9a-styled :global(.g9a-picker__chosen) { font-weight: 600; margin-inline-end: 6px; }
129
+ .g9a-styled :global(.g9a-csv) { display: flex; gap: 6px; align-items: center; margin-block: 4px; }
130
+ </style>
@@ -1,74 +1,84 @@
1
- <script lang="ts">
2
- // Every block type from the core's view model; the host may replace any type (including its x:* blocks). A block no
3
- // renderer knows shows its fallback text, never an error or a hole.
4
- import { blockView } from "@g9tm/aiassistant";
5
- import { blockOverride, cls, useUi } from "../context.js";
6
- import Chart from "./Chart.svelte";
7
- import TextView from "../parts/TextView.svelte";
8
-
9
- let { block, compact = false }: { block: unknown; compact?: boolean } = $props();
10
- const ui = useUi();
11
- const t = ui.texts.t;
12
- const view = $derived(blockView(block, ui.texts.locale, ui.assistant.customBlocks));
13
- const custom = $derived(blockOverride(ui, view.type === "custom" ? view.name : view.type));
14
- let open = $state(false);
15
- const fileUrl = (id: string): string => "/api/assistant/files/" + encodeURIComponent(id); // only the server's own files
16
- </script>
17
-
18
- {#if custom}
19
- {@const Custom = custom}
20
- <Custom {view} {block} {compact} />
21
- {:else if view.type === "text"}
22
- <div class={cls(ui, "block.text")}><TextView text={view.text} /></div>
23
- {:else if view.type === "callout"}
24
- <div class={cls(ui, "block.callout")} data-tone={view.tone}><TextView text={view.text} /></div>
25
- {:else if view.type === "table"}
26
- <div class={cls(ui, "block.table")}>
27
- {#if view.title}<div class="g9a-block-title">{view.title}</div>{/if}
28
- {#if view.asCards || compact}
29
- {#each view.rows.slice(0, open ? view.rows.length : view.shown) as row, r (r)}
30
- <dl class={cls(ui, "block.table.card")}>
31
- {#each view.columns as c, i (c.name)}<dt>{c.label}</dt><dd data-kind={c.kind}>{row[i]}</dd>{/each}
32
- </dl>
33
- {/each}
34
- {:else}
35
- <table>
36
- <thead><tr>{#each view.columns as c (c.name)}<th data-kind={c.kind}>{c.label}</th>{/each}</tr></thead>
37
- <tbody>
38
- {#each view.rows.slice(0, open ? view.rows.length : view.shown) as row, r (r)}
39
- <tr>{#each row as v, i (i)}<td data-kind={view.columns[i]?.kind}>{v}</td>{/each}</tr>
40
- {/each}
41
- </tbody>
42
- </table>
43
- {/if}
44
- {#if view.more > 0 && !open}<button type="button" class="g9a-more" onclick={() => (open = true)}>{t("block.more_rows", { count: view.more })}</button>{/if}
45
- </div>
46
- {:else if view.type === "chart"}
47
- <div class={cls(ui, "block.chart")}>
48
- {#if view.title}<div class="g9a-block-title">{view.title}</div>{/if}
49
- <Chart {view} />
50
- {#if view.fewData}<div class="g9a-note">{t("block.few_data")}</div>{/if}
51
- </div>
52
- {:else if view.type === "kpi"}
53
- <div class={cls(ui, "block.kpi")}>{#each view.items as k, i (i)}<div><span>{k.label}</span><strong>{k.value}</strong></div>{/each}</div>
54
- {:else if view.type === "steps"}
55
- <ol class={cls(ui, "block.steps")}>{#each view.items as s, i (i)}<li data-state={s.state}>{s.text}</li>{/each}</ol>
56
- {:else if view.type === "actions"}
57
- <div class={cls(ui, "block.actions")}>
58
- {#each view.items as a, i (i)}
59
- <button type="button" class={cls(ui, "block.action")} onclick={() => ui.assistant.send(a.send)}>
60
- {a.label}{#if a.suggested}<span class="g9a-suggested">{t("block.suggested")}</span>{/if}
61
- </button>
62
- {/each}
63
- </div>
64
- {:else if view.type === "flow"}
65
- <div class={cls(ui, "block.flow")}>{view.line}</div>
66
- {:else if view.type === "image"}
67
- <div class={cls(ui, "block.image")}>{#each view.fileIds as id (id)}<img src={fileUrl(id)} alt={view.fallbackText} loading="lazy" />{/each}</div>
68
- {:else if view.type === "open" && ui.assistant.host.navigate}
69
- <button type="button" class={cls(ui, "block.open")} onclick={() => ui.assistant.host.navigate?.(view.type === "open" ? view.pageId : "")}>
70
- {ui.assistant.host.pageName?.(view.pageId) ?? view.fallbackText}
71
- </button>
72
- {:else}
73
- <div class={cls(ui, "block.fallback")}>{view.fallbackText}</div>
74
- {/if}
1
+ <script lang="ts">
2
+ // Every block type from the core's view model; the host may replace any type (including its x:* blocks). A block no
3
+ // renderer knows shows its fallback text, never an error or a hole.
4
+ import { blockView, can, obj } from "@g9tm/aiassistant";
5
+ import { blockOverride, cls, override, useUi } from "../context.js";
6
+ import Chart from "./Chart.svelte";
7
+ import CsvButton from "./CsvButton.svelte";
8
+ import TextView from "../parts/TextView.svelte";
9
+
10
+ let { block, compact = false }: { block: unknown; compact?: boolean } = $props();
11
+ const ui = useUi();
12
+ const t = ui.texts.t;
13
+ const view = $derived(blockView(block, ui.texts.locale, ui.assistant.customBlocks));
14
+ const custom = $derived(blockOverride(ui, view.type === "custom" ? view.name : view.type));
15
+ const Csv = $derived(override(ui, "CsvButton") ?? CsvButton);
16
+ // A table's file has the raw values (the export view of the same rows), not the text drawn for reading.
17
+ const raw = $derived(view.type === "table" ? blockView({ ...obj(block), type: "export" }, ui.texts.locale) : null);
18
+ let open = $state(false);
19
+ const fileUrl = (id: string): string => "/api/assistant/files/" + encodeURIComponent(id); // only the server's own files
20
+ </script>
21
+
22
+ {#if custom}
23
+ {@const Custom = custom}
24
+ <Custom {view} {block} {compact} />
25
+ {:else if view.type === "text"}
26
+ <div class={cls(ui, "block.text")}><TextView text={view.text} /></div>
27
+ {:else if view.type === "callout"}
28
+ <div class={cls(ui, "block.callout")} data-tone={view.tone}><TextView text={view.text} /></div>
29
+ {:else if view.type === "table"}
30
+ <div class={cls(ui, "block.table")}>
31
+ {#if view.title}<div class="g9a-block-title">{view.title}</div>{/if}
32
+ {#if view.asCards || compact}
33
+ {#each view.rows.slice(0, open ? view.rows.length : view.shown) as row, r (r)}
34
+ <dl class={cls(ui, "block.table.card")}>
35
+ {#each view.columns as c, i (c.name)}<dt>{c.label}</dt><dd data-kind={c.kind}>{row[i]}</dd>{/each}
36
+ </dl>
37
+ {/each}
38
+ {:else}
39
+ <table>
40
+ <thead><tr>{#each view.columns as c (c.name)}<th data-kind={c.kind}>{c.label}</th>{/each}</tr></thead>
41
+ <tbody>
42
+ {#each view.rows.slice(0, open ? view.rows.length : view.shown) as row, r (r)}
43
+ <tr>{#each row as v, i (i)}<td data-kind={view.columns[i]?.kind}>{v}</td>{/each}</tr>
44
+ {/each}
45
+ </tbody>
46
+ </table>
47
+ {/if}
48
+ {#if view.more > 0 && !open}<button type="button" class="g9a-more" onclick={() => (open = true)}>{t("block.more_rows", { count: view.more })}</button>{/if}
49
+ </div>
50
+ {#if raw?.type === "export"}<Csv title={view.title} labels={raw.columns.map((c) => c.label)} rows={raw.rows} />{/if}
51
+ {:else if view.type === "export"}
52
+ <div class={cls(ui, "block") + " g9a-block__export"}>
53
+ {#if view.title}<div class="g9a-block-title">{view.title}</div>{/if}
54
+ {#if can(ui.assistant.host, "save")}<Csv title={view.title} labels={view.columns.map((c) => c.label)} rows={view.rows} />{:else}<div>{view.fallbackText}</div>{/if}
55
+ </div>
56
+ {:else if view.type === "chart"}
57
+ <div class={cls(ui, "block.chart")}>
58
+ {#if view.title}<div class="g9a-block-title">{view.title}</div>{/if}
59
+ <Chart {view} />
60
+ {#if view.fewData}<div class="g9a-note">{t("block.few_data")}</div>{/if}
61
+ </div>
62
+ {:else if view.type === "kpi"}
63
+ <div class={cls(ui, "block.kpi")}>{#each view.items as k, i (i)}<div><span>{k.label}</span><strong>{k.value}</strong></div>{/each}</div>
64
+ {:else if view.type === "steps"}
65
+ <ol class={cls(ui, "block.steps")}>{#each view.items as s, i (i)}<li data-state={s.state}>{s.text}</li>{/each}</ol>
66
+ {:else if view.type === "actions"}
67
+ <div class={cls(ui, "block.actions")}>
68
+ {#each view.items as a, i (i)}
69
+ <button type="button" class={cls(ui, "block.action")} onclick={() => ui.assistant.send(a.send)}>
70
+ {a.label}{#if a.suggested}<span class="g9a-suggested">{t("block.suggested")}</span>{/if}
71
+ </button>
72
+ {/each}
73
+ </div>
74
+ {:else if view.type === "flow"}
75
+ <div class={cls(ui, "block.flow")}>{view.line}</div>
76
+ {:else if view.type === "image"}
77
+ <div class={cls(ui, "block.image")}>{#each view.fileIds as id (id)}<img src={fileUrl(id)} alt={view.fallbackText} loading="lazy" />{/each}</div>
78
+ {:else if view.type === "open" && ui.assistant.host.navigate}
79
+ <button type="button" class={cls(ui, "block.open")} onclick={() => ui.assistant.host.navigate?.(view.type === "open" ? view.pageId : "")}>
80
+ {ui.assistant.host.pageName?.(view.pageId) ?? view.fallbackText}
81
+ </button>
82
+ {:else}
83
+ <div class={cls(ui, "block.fallback")}>{view.fallbackText}</div>
84
+ {/if}
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ // "Save as CSV" for a table or an export block, through the host's file save (hidden when the host cannot save). The
3
+ // file is the core's: a BOM, every cell quoted, no formula injection. A short notice says whether it was saved.
4
+ import { can } from "@g9tm/aiassistant";
5
+ import { saveCsv } from "@g9tm/aiassistant/pages";
6
+ import { useUi } from "../context.js";
7
+ import { pageTexts } from "../pages/texts.js";
8
+
9
+ let { title = undefined, labels, rows }: { title?: string | undefined; labels: readonly string[]; rows: readonly (readonly string[])[] } = $props();
10
+ const ui = useUi();
11
+ const t = pageTexts(ui.texts).t;
12
+ let saved = $state<boolean | null>(null);
13
+ let busy = $state(false);
14
+
15
+ async function save(): Promise<void> {
16
+ busy = true;
17
+ saved = await saveCsv(ui.assistant.host, title, labels, rows);
18
+ busy = false;
19
+ }
20
+ </script>
21
+
22
+ {#if can(ui.assistant.host, "save")}
23
+ <div class="g9a-csv">
24
+ <button type="button" class="g9a-csv__save" disabled={busy} onclick={save}>{t("csv.save")}</button>
25
+ {#if saved !== null}<span class="g9a-note" role="status" data-saved={saved}>{t(saved ? "csv.saved" : "csv.not_saved")}</span>{/if}
26
+ </div>
27
+ {/if}
@@ -0,0 +1,8 @@
1
+ type $$ComponentProps = {
2
+ title?: string | undefined;
3
+ labels: readonly string[];
4
+ rows: readonly (readonly string[])[];
5
+ };
6
+ declare const CsvButton: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type CsvButton = ReturnType<typeof CsvButton>;
8
+ export default CsvButton;
package/dist/index.d.ts CHANGED
@@ -26,6 +26,10 @@ export { default as LearnedPage } from "./pages/LearnedPage.svelte";
26
26
  export { default as FindingsPage } from "./pages/FindingsPage.svelte";
27
27
  export { default as RoutineWizard } from "./pages/RoutineWizard.svelte";
28
28
  export { default as ActionsPage } from "./pages/ActionsPage.svelte";
29
+ export { default as SlotPicker } from "./pages/SlotPicker.svelte";
30
+ export { default as CsvButton } from "./blocks/CsvButton.svelte";
31
+ export { default as PdfButton } from "./parts/PdfButton.svelte";
32
+ export { pageTexts, pageKeys } from "./pages/texts.js";
29
33
  export { browserHost } from "./browser.js";
30
34
  export type { BrowserHostOptions } from "./browser.js";
31
35
  export { provideUi, useUi, cls, sty, override, blockOverride } from "./context.js";
package/dist/index.js CHANGED
@@ -26,6 +26,10 @@ export { default as LearnedPage } from "./pages/LearnedPage.svelte";
26
26
  export { default as FindingsPage } from "./pages/FindingsPage.svelte";
27
27
  export { default as RoutineWizard } from "./pages/RoutineWizard.svelte";
28
28
  export { default as ActionsPage } from "./pages/ActionsPage.svelte";
29
+ export { default as SlotPicker } from "./pages/SlotPicker.svelte";
30
+ export { default as CsvButton } from "./blocks/CsvButton.svelte";
31
+ export { default as PdfButton } from "./parts/PdfButton.svelte";
32
+ export { pageTexts, pageKeys } from "./pages/texts.js";
29
33
  export { browserHost } from "./browser.js";
30
34
  export { provideUi, useUi, cls, sty, override, blockOverride } from "./context.js";
31
35
  export { AssistantState, Ticker } from "./store.svelte.js";
@@ -1,6 +1,96 @@
1
- <script lang="ts">
2
- import Page from "./Page.svelte";
3
- let { onopen = undefined }: { onopen?: ((id: string) => void) | undefined } = $props();
4
- </script>
5
-
6
- <Page kind="history" {onopen} />
1
+ <script lang="ts">
2
+ // The person's conversations in four tabs with their counts (today, earlier, pinned, waiting for a code), one search
3
+ // over all of them, pin or unpin, delete after a confirmation, and open. The rows and tabs come from the core's
4
+ // page helpers; this only draws them.
5
+ import { HISTORY_TABS, historyTabs, pageCalls, readConversations, tabCounts, type ConversationRow, type HistoryTab } from "@g9tm/aiassistant/pages";
6
+ import { faDigits } from "@g9tm/aiassistant";
7
+ import { cls, override, useUi } from "../context.js";
8
+ import Dialog from "../slots/Dialog.svelte";
9
+ import { pageTexts } from "./texts.js";
10
+
11
+ let { onopen = undefined }: { onopen?: ((conversationId: string) => void) | undefined } = $props();
12
+ const ui = useUi();
13
+ const t = pageTexts(ui.texts).t;
14
+ const api = ui.assistant.api;
15
+ const Confirm = $derived(override(ui, "Dialog") ?? Dialog);
16
+ let rows = $state<ConversationRow[]>([]);
17
+ let failed = $state(false);
18
+ let search = $state("");
19
+ let tab = $state<HistoryTab>("today");
20
+ let removing = $state<ConversationRow | null>(null);
21
+ let asking = $state(false);
22
+ const digits = (n: number): string => (ui.texts.locale === "fa" ? faDigits(String(n)) : String(n));
23
+
24
+ async function load(): Promise<void> {
25
+ try {
26
+ rows = readConversations(await api.conversations());
27
+ failed = false;
28
+ } catch {
29
+ failed = true;
30
+ }
31
+ }
32
+ $effect(() => { void load(); });
33
+
34
+ const tabs = $derived(historyTabs(rows, new Date(), search));
35
+ const counts = $derived(tabCounts(tabs));
36
+ const shown = $derived(new Map(Object.entries(tabs)).get(tab) ?? []);
37
+
38
+ async function call(work: Promise<unknown>): Promise<void> {
39
+ try {
40
+ await work;
41
+ } catch {
42
+ failed = true;
43
+ }
44
+ await load();
45
+ }
46
+
47
+ function askRemove(row: ConversationRow): void {
48
+ removing = row;
49
+ asking = true;
50
+ }
51
+
52
+ async function remove(): Promise<void> {
53
+ const row = removing;
54
+ asking = false;
55
+ removing = null;
56
+ if (row) await call(pageCalls.remove(api, row.id));
57
+ }
58
+
59
+ async function open(id: string): Promise<void> {
60
+ await ui.assistant.open(id);
61
+ onopen?.(id);
62
+ }
63
+ </script>
64
+
65
+ <div class={cls(ui, "panel.body") + " g9a-history"} data-page="history">
66
+ {#if failed}<p class="g9a-note" role="alert">{t("error.network")}</p>{/if}
67
+ <input type="search" bind:value={search} placeholder={t("history.search")} aria-label={t("history.search")} />
68
+ <div role="tablist" class="g9a-tabs" aria-label={t("history.tabs")}>
69
+ {#each HISTORY_TABS as x (x)}
70
+ <button type="button" role="tab" data-tab={x} aria-selected={tab === x} onclick={() => { tab = x; }}>
71
+ {t("history." + x)} <span class="g9a-count">{digits(new Map(Object.entries(counts)).get(x) ?? 0)}</span>
72
+ </button>
73
+ {/each}
74
+ </div>
75
+ <div role="tabpanel" data-tab={tab}><ul class="g9a-history__list">
76
+ {#each shown as row (row.id)}
77
+ <li class="g9a-history__row" data-id={row.id} data-pinned={row.pinned} data-waiting={row.waiting}>
78
+ <button type="button" class="g9a-history__open" onclick={() => open(row.id)}>{row.title || t("history.untitled")}</button>
79
+ <button type="button" class="g9a-history__pin" aria-pressed={row.pinned} onclick={() => call(pageCalls.pin(api, row.id, !row.pinned))}>
80
+ {row.pinned ? t("history.unpin") : t("history.pin")}
81
+ </button>
82
+ <button type="button" class="g9a-history__delete" onclick={() => askRemove(row)}>{t("history.delete")}</button>
83
+ </li>
84
+ {:else}
85
+ <li class="g9a-note">{t("history.empty")}</li>
86
+ {/each}
87
+ </ul></div>
88
+ <Confirm bind:open={asking} label={t("history.delete_confirm")}>
89
+ <p>{t("history.delete_confirm")}</p>
90
+ <p><strong>{removing?.title || t("history.untitled")}</strong></p>
91
+ <div class="g9a-row">
92
+ <button type="button" class="g9a-history__delete-yes" onclick={remove}>{t("history.delete")}</button>
93
+ <button type="button" onclick={() => { asking = false; removing = null; }}>{t("no")}</button>
94
+ </div>
95
+ </Confirm>
96
+ </div>
@@ -1,5 +1,5 @@
1
1
  type $$ComponentProps = {
2
- onopen?: ((id: string) => void) | undefined;
2
+ onopen?: ((conversationId: string) => void) | undefined;
3
3
  };
4
4
  declare const History: import("svelte").Component<$$ComponentProps, {}, "">;
5
5
  type History = ReturnType<typeof History>;