@artooi/ag-ui-web-component 0.5.0 → 0.7.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +73 -1
  2. package/README.md +208 -7
  3. package/dist/ag-ui-web-component.bundle.js +268 -58
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +16 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +33 -1
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/agui_client.d.ts +23 -1
  10. package/dist/core/agui_client.d.ts.map +1 -1
  11. package/dist/core/attachment.d.ts +35 -0
  12. package/dist/core/attachment.d.ts.map +1 -0
  13. package/dist/core/upload_attachment.d.ts +32 -0
  14. package/dist/core/upload_attachment.d.ts.map +1 -0
  15. package/dist/index.d.ts +5 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1247 -245
  18. package/dist/index.js.map +4 -4
  19. package/dist/tools/page_action_tools.d.ts +31 -0
  20. package/dist/tools/page_action_tools.d.ts.map +1 -0
  21. package/dist/ui/attachment_chips.d.ts +13 -0
  22. package/dist/ui/attachment_chips.d.ts.map +1 -0
  23. package/dist/ui/attachment_tray.d.ts +45 -0
  24. package/dist/ui/attachment_tray.d.ts.map +1 -0
  25. package/dist/ui/confirmation_card.d.ts +4 -1
  26. package/dist/ui/confirmation_card.d.ts.map +1 -1
  27. package/dist/ui/relative_time.d.ts +5 -3
  28. package/dist/ui/relative_time.d.ts.map +1 -1
  29. package/dist/ui/styles.d.ts +1 -1
  30. package/dist/ui/styles.d.ts.map +1 -1
  31. package/dist/ui/thread_drawer.d.ts +7 -1
  32. package/dist/ui/thread_drawer.d.ts.map +1 -1
  33. package/dist/ui/tool_call_card.d.ts +6 -2
  34. package/dist/ui/tool_call_card.d.ts.map +1 -1
  35. package/dist/ui/ui_strings.d.ts +126 -0
  36. package/dist/ui/ui_strings.d.ts.map +1 -0
  37. package/package.json +1 -1
  38. package/src/constants.ts +18 -0
  39. package/src/core/ag_ui_chat.ts +389 -51
  40. package/src/core/agui_client.ts +48 -4
  41. package/src/core/attachment.ts +39 -0
  42. package/src/core/upload_attachment.ts +113 -0
  43. package/src/index.ts +13 -0
  44. package/src/tools/page_action_tools.ts +130 -0
  45. package/src/ui/attachment_chips.ts +68 -0
  46. package/src/ui/attachment_tray.ts +243 -0
  47. package/src/ui/confirmation_card.ts +15 -5
  48. package/src/ui/relative_time.ts +15 -8
  49. package/src/ui/styles.ts +208 -0
  50. package/src/ui/thread_drawer.ts +53 -25
  51. package/src/ui/tool_call_card.ts +40 -17
  52. package/src/ui/ui_strings.ts +208 -0
  53. package/src/version.ts +1 -1
@@ -1,5 +1,6 @@
1
1
  import type { ThreadMeta } from "../core/conversation_store.js";
2
2
  import { relativeTime } from "./relative_time.js";
3
+ import { DEFAULT_UI_STRINGS, type UiStrings } from "./ui_strings.js";
3
4
 
4
5
  /** Actions the host ({@link AgUiChat}) wires to the drawer's rows. */
5
6
  export interface ThreadDrawerCallbacks {
@@ -20,52 +21,76 @@ export interface ThreadDrawerCallbacks {
20
21
  * {@link element}, toggles it, feeds rows via {@link setThreads}, and acts on
21
22
  * the callbacks. The drawer is a *view*: it does not mutate the store; after a
22
23
  * callback the host updates the store and calls {@link setThreads} to refresh.
24
+ *
25
+ * All visible text comes from {@link UiStrings}; {@link setStrings} re-localizes
26
+ * a drawer the host built before its strings resolved.
23
27
  */
24
28
  export class ThreadDrawer {
25
29
  /** The drawer root (backdrop + panel). Append to the chat shell; hidden until opened. */
26
30
  readonly element: HTMLDivElement;
27
31
 
28
32
  readonly #callbacks: ThreadDrawerCallbacks;
33
+ readonly #panel: HTMLDivElement;
34
+ readonly #heading: HTMLSpanElement;
35
+ readonly #newButton: HTMLButtonElement;
29
36
  readonly #list: HTMLDivElement;
37
+ #strings: UiStrings;
30
38
  #threads: readonly ThreadMeta[] = [];
31
39
  #activeId = "";
32
40
 
33
- constructor(callbacks: ThreadDrawerCallbacks) {
41
+ constructor(callbacks: ThreadDrawerCallbacks, strings: UiStrings = DEFAULT_UI_STRINGS) {
34
42
  this.#callbacks = callbacks;
43
+ this.#strings = strings;
35
44
 
36
45
  this.element = document.createElement("div");
37
46
  this.element.className = "drawer";
47
+ this.element.setAttribute("part", "drawer");
38
48
  this.element.hidden = true;
39
49
 
40
50
  const backdrop = document.createElement("div");
41
51
  backdrop.className = "drawer-backdrop";
52
+ backdrop.setAttribute("part", "drawer-backdrop");
42
53
  backdrop.addEventListener("click", () => this.close());
43
54
 
44
- const panel = document.createElement("div");
45
- panel.className = "drawer-panel";
46
- panel.setAttribute("role", "dialog");
47
- panel.setAttribute("aria-label", "Chat history");
55
+ this.#panel = document.createElement("div");
56
+ this.#panel.className = "drawer-panel";
57
+ this.#panel.setAttribute("part", "drawer-panel");
58
+ this.#panel.setAttribute("role", "dialog");
59
+ this.#panel.setAttribute("aria-label", strings.chatHistory);
48
60
 
49
61
  const header = document.createElement("div");
50
62
  header.className = "drawer-header";
51
- const heading = document.createElement("span");
52
- heading.className = "drawer-title";
53
- heading.textContent = "Chats";
54
- const newButton = document.createElement("button");
55
- newButton.type = "button";
56
- newButton.className = "drawer-new";
57
- newButton.textContent = "New chat";
58
- newButton.addEventListener("click", () => {
63
+ header.setAttribute("part", "drawer-header");
64
+ this.#heading = document.createElement("span");
65
+ this.#heading.className = "drawer-title";
66
+ this.#heading.setAttribute("part", "drawer-title");
67
+ this.#heading.textContent = strings.chats;
68
+ this.#newButton = document.createElement("button");
69
+ this.#newButton.type = "button";
70
+ this.#newButton.className = "drawer-new";
71
+ this.#newButton.setAttribute("part", "drawer-new");
72
+ this.#newButton.textContent = strings.newChat;
73
+ this.#newButton.addEventListener("click", () => {
59
74
  this.close();
60
75
  this.#callbacks.onNew();
61
76
  });
62
- header.append(heading, newButton);
77
+ header.append(this.#heading, this.#newButton);
63
78
 
64
79
  this.#list = document.createElement("div");
65
80
  this.#list.className = "drawer-list";
81
+ this.#list.setAttribute("part", "drawer-list");
66
82
 
67
- panel.append(header, this.#list);
68
- this.element.append(backdrop, panel);
83
+ this.#panel.append(header, this.#list);
84
+ this.element.append(backdrop, this.#panel);
85
+ }
86
+
87
+ /** Re-localize the drawer's chrome and rows (the host calls this on connect). */
88
+ setStrings(strings: UiStrings): void {
89
+ this.#strings = strings;
90
+ this.#panel.setAttribute("aria-label", strings.chatHistory);
91
+ this.#heading.textContent = strings.chats;
92
+ this.#newButton.textContent = strings.newChat;
93
+ this.#renderList();
69
94
  }
70
95
 
71
96
  isOpen(): boolean {
@@ -96,7 +121,8 @@ export class ThreadDrawer {
96
121
  if (this.#threads.length === 0) {
97
122
  const empty = document.createElement("div");
98
123
  empty.className = "drawer-empty";
99
- empty.textContent = "No conversations yet.";
124
+ empty.setAttribute("part", "drawer-empty");
125
+ empty.textContent = this.#strings.noConversations;
100
126
  this.#list.appendChild(empty);
101
127
  return;
102
128
  }
@@ -108,6 +134,7 @@ export class ThreadDrawer {
108
134
  #renderRow(meta: ThreadMeta): HTMLDivElement {
109
135
  const row = document.createElement("div");
110
136
  row.className = "drawer-row";
137
+ row.setAttribute("part", "drawer-row");
111
138
  if (meta.threadId === this.#activeId) {
112
139
  row.classList.add("drawer-row--active");
113
140
  }
@@ -115,12 +142,13 @@ export class ThreadDrawer {
115
142
  const select = document.createElement("button");
116
143
  select.type = "button";
117
144
  select.className = "drawer-row-select";
145
+ select.setAttribute("part", "drawer-row-select");
118
146
  const title = document.createElement("span");
119
147
  title.className = "drawer-row-title";
120
148
  title.textContent = meta.title;
121
149
  const time = document.createElement("span");
122
150
  time.className = "drawer-row-time";
123
- time.textContent = relativeTime(meta.updatedAt);
151
+ time.textContent = relativeTime(meta.updatedAt, undefined, this.#strings);
124
152
  const preview = document.createElement("span");
125
153
  preview.className = "drawer-row-preview";
126
154
  preview.textContent = meta.preview;
@@ -133,16 +161,16 @@ export class ThreadDrawer {
133
161
  const rename = document.createElement("button");
134
162
  rename.type = "button";
135
163
  rename.className = "drawer-row-rename";
136
- rename.title = "Rename";
137
- rename.setAttribute("aria-label", "Rename conversation");
164
+ rename.title = this.#strings.rename;
165
+ rename.setAttribute("aria-label", this.#strings.renameConversation);
138
166
  rename.textContent = "✎";
139
167
  rename.addEventListener("click", () => this.#startRename(row, meta));
140
168
 
141
169
  const remove = document.createElement("button");
142
170
  remove.type = "button";
143
171
  remove.className = "drawer-row-delete";
144
- remove.title = "Delete";
145
- remove.setAttribute("aria-label", "Delete conversation");
172
+ remove.title = this.#strings.delete;
173
+ remove.setAttribute("aria-label", this.#strings.deleteConversation);
146
174
  remove.textContent = "🗑";
147
175
  remove.addEventListener("click", () => this.#confirmDelete(row, meta));
148
176
 
@@ -183,16 +211,16 @@ export class ThreadDrawer {
183
211
  confirm.className = "drawer-confirm";
184
212
  const label = document.createElement("span");
185
213
  label.className = "drawer-confirm-label";
186
- label.textContent = "Delete?";
214
+ label.textContent = this.#strings.deletePrompt;
187
215
  const yes = document.createElement("button");
188
216
  yes.type = "button";
189
217
  yes.className = "drawer-confirm-yes";
190
- yes.textContent = "Delete";
218
+ yes.textContent = this.#strings.delete;
191
219
  yes.addEventListener("click", () => this.#callbacks.onDelete(meta.threadId));
192
220
  const no = document.createElement("button");
193
221
  no.type = "button";
194
222
  no.className = "drawer-confirm-no";
195
- no.textContent = "Cancel";
223
+ no.textContent = this.#strings.cancel;
196
224
  no.addEventListener("click", () => this.#renderList());
197
225
  confirm.append(label, yes, no);
198
226
  row.replaceChildren(confirm);
@@ -1,4 +1,5 @@
1
1
  import { TOOL_CALL_STATUS, TOOL_DISPLAY } from "../constants.js";
2
+ import { DEFAULT_UI_STRINGS, type UiStrings } from "./ui_strings.js";
2
3
 
3
4
  /** Any state a tool-call card can be in. */
4
5
  export type ToolCallStatus = (typeof TOOL_CALL_STATUS)[keyof typeof TOOL_CALL_STATUS];
@@ -9,20 +10,24 @@ export type ToolDisplayMode = (typeof TOOL_DISPLAY)[keyof typeof TOOL_DISPLAY];
9
10
  /** The terminal states a card settles into (everything but `pending`). */
10
11
  export type SettledStatus = Exclude<ToolCallStatus, typeof TOOL_CALL_STATUS.PENDING>;
11
12
 
12
- /** Short pill text shown for each status. */
13
- const STATUS_LABEL: Record<ToolCallStatus, string> = {
14
- [TOOL_CALL_STATUS.PENDING]: "running…",
15
- [TOOL_CALL_STATUS.DONE]: "✓ done",
16
- [TOOL_CALL_STATUS.ERROR]: "⚠ error",
17
- [TOOL_CALL_STATUS.DECLINED]: "⊘ declined",
18
- };
13
+ /** Short pill text shown for each status, drawn from the string table. */
14
+ function statusLabels(strings: UiStrings): Record<ToolCallStatus, string> {
15
+ return {
16
+ [TOOL_CALL_STATUS.PENDING]: strings.toolRunning,
17
+ [TOOL_CALL_STATUS.DONE]: strings.toolDone,
18
+ [TOOL_CALL_STATUS.ERROR]: strings.toolError,
19
+ [TOOL_CALL_STATUS.DECLINED]: strings.toolDeclined,
20
+ };
21
+ }
19
22
 
20
23
  /** Toggle-button label for each settled outcome's collapsible body (full mode). */
21
- const RESULT_LABEL: Record<SettledStatus, string> = {
22
- [TOOL_CALL_STATUS.DONE]: "Result",
23
- [TOOL_CALL_STATUS.ERROR]: "Error",
24
- [TOOL_CALL_STATUS.DECLINED]: "Declined",
25
- };
24
+ function resultLabels(strings: UiStrings): Record<SettledStatus, string> {
25
+ return {
26
+ [TOOL_CALL_STATUS.DONE]: strings.resultLabel,
27
+ [TOOL_CALL_STATUS.ERROR]: strings.errorLabel,
28
+ [TOOL_CALL_STATUS.DECLINED]: strings.declinedLabel,
29
+ };
30
+ }
26
31
 
27
32
  /**
28
33
  * A live tool-call card for the chat transcript.
@@ -37,7 +42,8 @@ const RESULT_LABEL: Record<SettledStatus, string> = {
37
42
  * - `full` — the result (or error / decline message) behind its own toggle.
38
43
  *
39
44
  * Pure DOM (no framework); the host appends {@link element} into its shadow
40
- * root and themes it via the `--ag-ui-*` custom properties.
45
+ * root and themes it via the `--ag-ui-*` custom properties or the exposed
46
+ * `tool-card*` `part`s. All visible text is sourced from {@link UiStrings}.
41
47
  */
42
48
  export class ToolCallCard {
43
49
  /** The card's root element; append this into the message list. */
@@ -46,33 +52,41 @@ export class ToolCallCard {
46
52
  readonly #status: HTMLSpanElement;
47
53
  readonly #mode: ToolDisplayMode;
48
54
  readonly #args: Record<string, unknown>;
55
+ readonly #strings: UiStrings;
56
+ #settled = false;
49
57
 
50
58
  constructor(
51
59
  name: string,
52
60
  args: Record<string, unknown>,
53
61
  mode: ToolDisplayMode = TOOL_DISPLAY.FULL,
54
62
  summary?: string,
63
+ strings: UiStrings = DEFAULT_UI_STRINGS,
55
64
  ) {
56
65
  this.#mode = mode;
57
66
  this.#args = args;
67
+ this.#strings = strings;
58
68
 
59
69
  this.element = document.createElement("div");
60
70
  this.element.className = "tool-call";
71
+ this.element.setAttribute("part", "tool-card");
61
72
  this.element.setAttribute("data-tool-name", name);
62
73
  this.element.setAttribute("data-status", TOOL_CALL_STATUS.PENDING);
63
74
  this.element.setAttribute("data-display", mode);
64
75
 
65
76
  const head = document.createElement("div");
66
77
  head.className = "tool-call-head";
78
+ head.setAttribute("part", "tool-card-head");
67
79
 
68
80
  const label = document.createElement("span");
69
81
  label.className = "tool-call-name";
82
+ label.setAttribute("part", "tool-card-name");
70
83
  // A server-provided `x-summary` label reads better than the raw tool name.
71
84
  label.textContent = `🔧 ${summary ?? name}`;
72
85
 
73
86
  this.#status = document.createElement("span");
74
87
  this.#status.className = "tool-call-status";
75
- this.#status.textContent = STATUS_LABEL[TOOL_CALL_STATUS.PENDING];
88
+ this.#status.setAttribute("part", "tool-card-status");
89
+ this.#status.textContent = statusLabels(strings)[TOOL_CALL_STATUS.PENDING];
76
90
 
77
91
  head.append(label, this.#status);
78
92
  this.element.append(head);
@@ -80,19 +94,26 @@ export class ToolCallCard {
80
94
  if (mode === TOOL_DISPLAY.FULL) {
81
95
  const argsEl = document.createElement("pre");
82
96
  argsEl.className = "tool-call-args";
97
+ argsEl.setAttribute("part", "tool-card-args");
83
98
  argsEl.textContent = JSON.stringify(args, null, 2);
84
99
  this.element.append(argsEl);
85
100
  }
86
101
  }
87
102
 
103
+ /** Whether {@link settle} has already run (so a terminal sweep can skip it). */
104
+ get settled(): boolean {
105
+ return this.#settled;
106
+ }
107
+
88
108
  /**
89
109
  * Flip the status pill to ``status`` and, unless in `minimal` mode, append a
90
110
  * collapsed body behind a click-to-expand toggle: the result alone (`full`),
91
111
  * or the args + result together (`compact`).
92
112
  */
93
113
  settle(status: SettledStatus, text: string): void {
114
+ this.#settled = true;
94
115
  this.element.setAttribute("data-status", status);
95
- this.#status.textContent = STATUS_LABEL[status];
116
+ this.#status.textContent = statusLabels(this.#strings)[status];
96
117
 
97
118
  if (this.#mode === TOOL_DISPLAY.MINIMAL) {
98
119
  return;
@@ -101,17 +122,19 @@ export class ToolCallCard {
101
122
  const toggle = document.createElement("button");
102
123
  toggle.type = "button";
103
124
  toggle.className = "tool-call-toggle";
125
+ toggle.setAttribute("part", "tool-card-toggle");
104
126
  toggle.setAttribute("aria-expanded", "false");
105
127
 
106
128
  const output = document.createElement("pre");
107
129
  output.className = "tool-call-result";
130
+ output.setAttribute("part", "tool-card-result");
108
131
  output.hidden = true;
109
132
 
110
133
  if (this.#mode === TOOL_DISPLAY.COMPACT) {
111
- toggle.textContent = "Details";
134
+ toggle.textContent = this.#strings.details;
112
135
  output.textContent = `args: ${JSON.stringify(this.#args)}\n\n${text}`;
113
136
  } else {
114
- toggle.textContent = RESULT_LABEL[status];
137
+ toggle.textContent = resultLabels(this.#strings)[status];
115
138
  output.textContent = text;
116
139
  }
117
140
 
@@ -0,0 +1,208 @@
1
+ /**
2
+ * The flat table of every user-facing string the chat shell renders — labels,
3
+ * placeholders, `aria-label`s, and `title` tooltips. A localizable seam, not a
4
+ * framework: a host overrides any subset (via the element's `strings` property
5
+ * or its `data-strings` JSON attribute) and the rest fall back to the English
6
+ * defaults.
7
+ *
8
+ * A handful of values are **templates** carrying `{token}` placeholders the call
9
+ * site fills in (e.g. `minutesAgo` → `"{n}m ago"`); the token names are noted on
10
+ * each key. Translators keep the token verbatim.
11
+ *
12
+ * This module is one cohesive unit — the `UiStrings` shape, its `DEFAULT_UI_STRINGS`
13
+ * constant-like backing, and the {@link mergeUiStrings} merge over those defaults —
14
+ * so it bends the one-symbol-per-file rule the way `tool_call_card.ts` (class +
15
+ * its types) and `route_map.ts` (factory + its types) already do.
16
+ */
17
+ export interface UiStrings {
18
+ // ── Header ────────────────────────────────────────────────────────────────
19
+ /** Default header title (the `title-text` attribute overrides per element). */
20
+ title: string;
21
+ /** History button + drawer dialog label. */
22
+ chatHistory: string;
23
+ /** New-chat button (header + drawer). */
24
+ newChat: string;
25
+ /** Collapse button. */
26
+ collapse: string;
27
+ /** Expand affordance (the sidebar rail toggle). */
28
+ expand: string;
29
+
30
+ // ── Messages region ─────────────────────────────────────────────────────────
31
+ /** `aria-label` of the scrolling message log. */
32
+ conversation: string;
33
+ /** `aria-label` of the "thinking" pending indicator. */
34
+ thinking: string;
35
+ /** The muted note after a cancelled run. */
36
+ stopped: string;
37
+ /** Error shown when the stream drops without a terminal AG-UI event. */
38
+ connectionLost: string;
39
+ /** Fallback when a tool call produced no result. */
40
+ noResult: string;
41
+ /** Tool-result content when the user declines a confirmed action. */
42
+ declinedAction: string;
43
+ /** A navigating tool's card text while the page reloads. */
44
+ navigating: string;
45
+ /** Missing-placeholder skill hint. Tokens: `{title}`, `{fields}`. */
46
+ skillNeeds: string;
47
+
48
+ // ── Composer ────────────────────────────────────────────────────────────────
49
+ /** `aria-label` of the message textarea. */
50
+ message: string;
51
+ /** Placeholder of the message textarea. */
52
+ inputPlaceholder: string;
53
+ /** Send button (idle composer). */
54
+ send: string;
55
+ /** Stop button (composer while a run is in flight). */
56
+ stop: string;
57
+ /** Attach-files button. */
58
+ attachFiles: string;
59
+
60
+ // ── Tool-call card ──────────────────────────────────────────────────────────
61
+ /** Status pill while the call runs. */
62
+ toolRunning: string;
63
+ /** Status pill on success. */
64
+ toolDone: string;
65
+ /** Status pill on error. */
66
+ toolError: string;
67
+ /** Status pill on a declined call. */
68
+ toolDeclined: string;
69
+ /** Toggle label revealing a successful result (full mode). */
70
+ resultLabel: string;
71
+ /** Toggle label revealing an error (full mode). */
72
+ errorLabel: string;
73
+ /** Toggle label revealing a declined call (full mode). */
74
+ declinedLabel: string;
75
+ /** Toggle label revealing args + result together (compact mode). */
76
+ details: string;
77
+
78
+ // ── Confirmation card ───────────────────────────────────────────────────────
79
+ /** `aria-label` of the inline confirmation card. */
80
+ confirmAction: string;
81
+ /** Generic confirmation prompt when a tool has no `x-confirm`. Token: `{tool}`. */
82
+ confirmRun: string;
83
+ /** Confirm button. */
84
+ confirm: string;
85
+ /** Cancel button (confirmation + delete confirm). */
86
+ cancel: string;
87
+
88
+ // ── Chat-history drawer ─────────────────────────────────────────────────────
89
+ /** Drawer heading. */
90
+ chats: string;
91
+ /** Empty-state line when there are no threads. */
92
+ noConversations: string;
93
+ /** Rename row button (`title`). */
94
+ rename: string;
95
+ /** Rename row button `aria-label`. */
96
+ renameConversation: string;
97
+ /** Delete row button (`title`) + the inline-confirm action. */
98
+ delete: string;
99
+ /** Delete row button `aria-label`. */
100
+ deleteConversation: string;
101
+ /** Inline delete-confirm prompt. */
102
+ deletePrompt: string;
103
+
104
+ // ── Attachments ─────────────────────────────────────────────────────────────
105
+ /** Oversize rejection. Token: `{size}`. */
106
+ tooLarge: string;
107
+ /** Disallowed-type rejection. */
108
+ fileTypeNotAllowed: string;
109
+ /** Generic upload failure (when the error carries no message). */
110
+ uploadFailed: string;
111
+ /** Retry-upload button (`title`). */
112
+ retry: string;
113
+ /** Retry-upload button `aria-label`. */
114
+ retryUpload: string;
115
+ /** Remove-attachment button (`title`). */
116
+ remove: string;
117
+ /** Remove-attachment button `aria-label`. */
118
+ removeAttachment: string;
119
+
120
+ // ── Relative time (drawer rows) ─────────────────────────────────────────────
121
+ /** Under a minute ago. */
122
+ justNow: string;
123
+ /** Minutes ago. Token: `{n}`. */
124
+ minutesAgo: string;
125
+ /** Hours ago. Token: `{n}`. */
126
+ hoursAgo: string;
127
+ /** Days ago. Token: `{n}`. */
128
+ daysAgo: string;
129
+ /** Weeks ago. Token: `{n}`. */
130
+ weeksAgo: string;
131
+ }
132
+
133
+ /** The built-in English strings — the fallback every override merges over. */
134
+ export const DEFAULT_UI_STRINGS: UiStrings = {
135
+ title: "Assistant",
136
+ chatHistory: "Chat history",
137
+ newChat: "New chat",
138
+ collapse: "Collapse",
139
+ expand: "Expand",
140
+
141
+ conversation: "Conversation",
142
+ thinking: "Assistant is thinking…",
143
+ stopped: "⏹ Stopped",
144
+ connectionLost: "Connection lost",
145
+ noResult: "No result returned.",
146
+ declinedAction: "User declined the action.",
147
+ navigating: "Navigating…",
148
+ skillNeeds: "“{title}” needs: {fields}",
149
+
150
+ message: "Message",
151
+ inputPlaceholder: "Ask anything…",
152
+ send: "Send",
153
+ stop: "Stop",
154
+ attachFiles: "Attach files",
155
+
156
+ toolRunning: "running…",
157
+ toolDone: "✓ done",
158
+ toolError: "⚠ error",
159
+ toolDeclined: "⊘ declined",
160
+ resultLabel: "Result",
161
+ errorLabel: "Error",
162
+ declinedLabel: "Declined",
163
+ details: "Details",
164
+
165
+ confirmAction: "Confirm action",
166
+ confirmRun: "Run “{tool}”?",
167
+ confirm: "Confirm",
168
+ cancel: "Cancel",
169
+
170
+ chats: "Chats",
171
+ noConversations: "No conversations yet.",
172
+ rename: "Rename",
173
+ renameConversation: "Rename conversation",
174
+ delete: "Delete",
175
+ deleteConversation: "Delete conversation",
176
+ deletePrompt: "Delete?",
177
+
178
+ tooLarge: "Too large (max {size})",
179
+ fileTypeNotAllowed: "File type not allowed",
180
+ uploadFailed: "upload failed",
181
+ retry: "Retry",
182
+ retryUpload: "Retry upload",
183
+ remove: "Remove",
184
+ removeAttachment: "Remove attachment",
185
+
186
+ justNow: "just now",
187
+ minutesAgo: "{n}m ago",
188
+ hoursAgo: "{n}h ago",
189
+ daysAgo: "{n}d ago",
190
+ weeksAgo: "{n}w ago",
191
+ };
192
+
193
+ /**
194
+ * Merge a partial set of overrides over {@link DEFAULT_UI_STRINGS}, yielding a
195
+ * complete {@link UiStrings}. Keys whose override is `undefined` keep the
196
+ * default (so a `data-strings` JSON with only a few keys — or a property carrying
197
+ * explicit `undefined` — leaves the rest English).
198
+ */
199
+ export function mergeUiStrings(overrides: Partial<UiStrings>): UiStrings {
200
+ const merged: UiStrings = { ...DEFAULT_UI_STRINGS };
201
+ for (const key of Object.keys(overrides) as (keyof UiStrings)[]) {
202
+ const value = overrides[key];
203
+ if (value !== undefined) {
204
+ merged[key] = value;
205
+ }
206
+ }
207
+ return merged;
208
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION: string = "0.5.0";
1
+ export const VERSION: string = "0.7.0";