@lotics/ui 20.0.2 → 20.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.
@@ -157,9 +157,10 @@ PUT and rolls the calls out BELOW it on press. The run ALWAYS ends on the agent'
157
157
  no global terminal node.
158
158
 
159
159
  **Tool labels.** A tool part carries the RAW tool name (`update_records` from `tool-update_records`,
160
- or a `dynamic-tool`'s `toolName`), resolved via a built-in map of the platform record/document
161
- tools + an optional **`labelForCall`** override (localize THERE the kit stays English). It gets
162
- the whole call `{ toolName, input, state }` (`AgentToolCall`) so a caller can phrase by what the
160
+ or a `dynamic-tool`'s `toolName`), resolved through the active locale's **`agentRun.tools`** map
161
+ so a `vi` provider renders Vietnamese tool rows with no per-app wiring, the same way the rest of the
162
+ feed's chrome already resolved. An optional **`labelForCall`** override still wins, for phrasing a
163
+ call by what it TARGETS rather than by which tool ran. It gets the whole call — `{ toolName, input, state }` (`AgentToolCall`) — so a caller can phrase by what the
163
164
  call targets or by whether it's awaiting; return `undefined` to fall back. Unknown names fall back
164
165
  to a prettified form. `stepsLabel` localizes the "{n} steps" suffix. The tool `state` folds ai's
165
166
  7-state lifecycle to **running / awaiting / done / error**: `approval-requested` → `awaiting` (the
@@ -206,8 +207,12 @@ feed**; the starting row IS the placeholder, consistent and localized.
206
207
  **Localization.** The feed's fixed chrome — the "Thinking" label, the "Input"/"Output"/"Error"
207
208
  panel titles, the "awaiting" annotation, and the "Retry" action — resolves through the
208
209
  `LoticsLocale` `agentRun` slice (translate once at the provider; a `vi` app gets them for free).
209
- It is provider-localized, not per-instance prop-overridable; `labelForCall`/`stepsLabel` localize
210
- the tool labels and the "{n} steps" suffix per call site.
210
+ **and so are the tool labels** (`agentRun.tools`, keyed by raw tool name). Translating a tool
211
+ row is no longer each app's job: an English row under a Vietnamese "Đang suy nghĩ…" was the kit
212
+ contradicting itself. The "{n} steps" suffix is locale-backed too (`agentRun.steps`). `labelForCall`
213
+ and `stepsLabel` remain as per-call-site OVERRIDES — for phrasing a row by what
214
+ it targets — not as the only route to a translation. A tool name in neither the locale map nor
215
+ `labelForCall` falls back to its prettified form.
211
216
 
212
217
  **Answering an awaiting call — `ApprovalPrompt`.** The `awaiting` row inside `AgentRun` is
213
218
  READ-ONLY — it only NAMES that a call is parked on a human decision. The surface that ANSWERS it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "20.0.2",
3
+ "version": "20.1.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./vite": {
@@ -6,6 +6,7 @@ import { WaveAvatar } from "./wave_avatar";
6
6
  import { FollowScroll } from "./follow_scroll";
7
7
  import { PressableHighlight } from "./pressable_highlight";
8
8
  import { AgentRun, resolveToolMeta, type AgentRunProps, type AgentToolCall } from "./agent_run";
9
+ import { useLoticsLocale } from "./locale";
9
10
  import { toSegments, lastRunningStep, type AgentUIPart } from "./agent_transform";
10
11
 
11
12
  export interface AgentProgressProps {
@@ -43,7 +44,14 @@ export function AgentProgress(props: AgentProgressProps) {
43
44
  const [expanded, setExpanded] = useState(defaultExpanded ?? false);
44
45
 
45
46
  const running = lastRunningStep(toSegments(parts));
46
- const runningLabel = running ? resolveToolMeta({ toolName: running.toolName, input: running.input, state: running.status }, labelForCall).label : undefined;
47
+ const locale = useLoticsLocale();
48
+ const runningLabel = running
49
+ ? resolveToolMeta(
50
+ { toolName: running.toolName, input: running.input, state: running.status },
51
+ labelForCall,
52
+ locale.agentRun.tools,
53
+ ).label
54
+ : undefined;
47
55
  const compact = label ?? runningLabel ?? (state === "done" ? "Done" : state === "error" ? "Stopped" : "Working…");
48
56
  const streaming = state === "streaming";
49
57
 
package/src/agent_run.tsx CHANGED
@@ -69,19 +69,28 @@ export interface AgentRunProps {
69
69
  }
70
70
 
71
71
  // ── Tool → display meta ───────────────────────────────────────────────────────
72
- // The bounded set of platform tools an app agent can call. English by default
73
- // (the kit is locale-neutral; apps localize via `labelForCall`). A tool the map
74
- // doesn't know falls back to a prettified name + a neutral icon.
75
- const TOOL_META: Record<string, { label: string; icon: IconName }> = {
76
- query_records: { label: "Searching records", icon: "search" },
77
- get_record: { label: "Reading a record", icon: "file-text" },
78
- get_records: { label: "Reading records", icon: "file-text" },
79
- create_records: { label: "Creating records", icon: "plus" },
80
- update_records: { label: "Updating records", icon: "square-pen" },
81
- delete_records: { label: "Removing records", icon: "trash" },
82
- generate_pdf_from_template: { label: "Generating PDF", icon: "file-down" },
83
- generate_excel_from_template: { label: "Generating spreadsheet", icon: "file-spreadsheet" },
84
- generate_docx_from_template: { label: "Generating document", icon: "file-text" },
72
+ // The ICON per platform tool. The LABEL lives in the locale (`agentRun.tools`)
73
+ // because it is language, and this component already renders localized chrome
74
+ // around these rows an English tool row under a Vietnamese "Đang suy nghĩ…"
75
+ // was the kit contradicting itself. An icon is not language, so it stays here.
76
+ // A tool neither map knows falls back to a prettified name + a neutral icon.
77
+ const TOOL_ICONS: Record<string, IconName> = {
78
+ query_records: "search",
79
+ get_record: "file-text",
80
+ get_records: "file-text",
81
+ create_records: "plus",
82
+ update_records: "square-pen",
83
+ delete_records: "trash",
84
+ run_app_query: "search",
85
+ run_app_workflow: "play",
86
+ grep_knowledge: "search",
87
+ read_knowledge: "book-open",
88
+ list_knowledge: "list",
89
+ view_files: "file-text",
90
+ ask_user_choice: "message-circle-question-mark",
91
+ generate_pdf_from_template: "file-down",
92
+ generate_excel_from_template: "file-spreadsheet",
93
+ generate_docx_from_template: "file-text",
85
94
  };
86
95
 
87
96
  function prettifyToolName(name: string): string {
@@ -90,26 +99,37 @@ function prettifyToolName(name: string): string {
90
99
  }
91
100
 
92
101
  /** Resolve a tool's display label + icon: a caller override wins on the label,
93
- * then the built-in map, then a prettified fallback. Exported so other agent
94
- * surfaces (e.g. `AgentProgress`) render the same labels. */
102
+ * then the active locale's `agentRun.tools`, then a prettified fallback.
103
+ * Exported so other agent surfaces (e.g. `AgentProgress`) render the same
104
+ * labels; `toolLabels` is optional so an existing external caller keeps
105
+ * working (it just falls back to the prettified name). */
95
106
  export function resolveToolMeta(
96
107
  call: AgentToolCall,
97
108
  labelForCall?: (call: AgentToolCall) => string | undefined,
109
+ toolLabels?: Record<string, string>,
98
110
  ): { label: string; icon: IconName } {
99
- const def =
100
- TOOL_META[call.toolName] ??
101
- (call.toolName.startsWith("generate_") && call.toolName.endsWith("_from_template")
102
- ? { label: "Generating document", icon: "file-text" as IconName }
103
- : undefined);
111
+ const generated =
112
+ call.toolName.startsWith("generate_") && call.toolName.endsWith("_from_template");
113
+ const label =
114
+ toolLabels?.[call.toolName] ??
115
+ (generated ? toolLabels?.generate_docx_from_template : undefined);
104
116
  return {
105
- label: labelForCall?.(call) ?? def?.label ?? prettifyToolName(call.toolName),
106
- icon: def?.icon ?? "list",
117
+ label: labelForCall?.(call) ?? label ?? prettifyToolName(call.toolName),
118
+ icon: TOOL_ICONS[call.toolName] ?? (generated ? "file-text" : "list"),
107
119
  };
108
120
  }
109
121
 
110
122
  /** Resolve a tool step's display label from its call. */
111
- function stepLabel(s: AgentStep, labelForCall?: (call: AgentToolCall) => string | undefined): string {
112
- return resolveToolMeta({ toolName: s.toolName, input: s.input, state: s.status }, labelForCall).label;
123
+ function stepLabel(
124
+ s: AgentStep,
125
+ labelForCall?: (call: AgentToolCall) => string | undefined,
126
+ toolLabels?: Record<string, string>,
127
+ ): string {
128
+ return resolveToolMeta(
129
+ { toolName: s.toolName, input: s.input, state: s.status },
130
+ labelForCall,
131
+ toolLabels,
132
+ ).label;
113
133
  }
114
134
 
115
135
  const INK = colors.zinc[700];
@@ -124,7 +144,7 @@ const INK = colors.zinc[700];
124
144
  * Pair with `Composer` + `ChangeReview`.
125
145
  */
126
146
  export function AgentRun(props: AgentRunProps) {
127
- const { parts, labelForCall, renderToolOutput, onRetry, stepsLabel = (n) => `${n} steps`, accessibilityLabel } = props;
147
+ const { parts, labelForCall, renderToolOutput, onRetry, stepsLabel, accessibilityLabel } = props;
128
148
  const locale = useLoticsLocale();
129
149
  const segments = toSegments(parts);
130
150
  const state = props.state ?? (anyRunning(segments) ? "streaming" : "done");
@@ -163,7 +183,7 @@ export function AgentRun(props: AgentRunProps) {
163
183
  onToggle={() => toggle(seg.id)}
164
184
  labelForCall={labelForCall}
165
185
  renderToolOutput={renderToolOutput}
166
- stepsLabel={stepsLabel}
186
+ stepsLabel={stepsLabel ?? locale.agentRun.steps}
167
187
  />
168
188
  );
169
189
  })}
@@ -369,7 +389,7 @@ function ToolGroup(props: {
369
389
  }) {
370
390
  const { steps, active, expanded, onToggle, labelForCall, renderToolOutput, stepsLabel } = props;
371
391
  const locale = useLoticsLocale();
372
- const resolve = (s: AgentStep) => stepLabel(s, labelForCall);
392
+ const resolve = (s: AgentStep) => stepLabel(s, labelForCall, locale.agentRun.tools);
373
393
 
374
394
  // ACTIVE — one pulsing row whose label swaps in place as each call fires (the
375
395
  // label is keyed by the current step's id, so a new call rises + fades into the
package/src/locale.tsx CHANGED
@@ -132,9 +132,27 @@ export interface LoticsLocale {
132
132
  /** `AgentRun`: the reasoning disclosure's label (settled / streaming), the
133
133
  * auto-built tool peek's Input / Error / Output panel titles, the `awaiting`
134
134
  * annotation on a call parked on a human decision, and the terminal error's
135
- * `retry` action. (Tool-step labels and the "{n} steps" suffix stay
136
- * prop-localized `labelForCall` / `stepsLabel`.) */
137
- agentRun: { starting: string; thinking: string; thinkingStreaming: string; input: string; error: string; output: string; awaiting: string; retry: string };
135
+ * `retry` action, the per-tool labels (`tools`) and the "{n} steps" suffix
136
+ * (`steps`). `labelForCall` / `stepsLabel` remain as per-call-site
137
+ * OVERRIDES, not as the only way to get a translation. */
138
+ /** `AgentRun` / `AgentProgress` chrome, plus `tools` — the display label per
139
+ * PLATFORM tool name. The tool set is bounded and kit-known, so localizing it
140
+ * here means every app inherits it; leaving it to each call site's
141
+ * `labelForCall` made a Vietnamese surface render Vietnamese chrome around
142
+ * English tool rows. A name absent from the map still falls back to its
143
+ * prettified form, and `labelForCall` still overrides per call. */
144
+ agentRun: {
145
+ starting: string;
146
+ thinking: string;
147
+ thinkingStreaming: string;
148
+ input: string;
149
+ error: string;
150
+ output: string;
151
+ awaiting: string;
152
+ retry: string;
153
+ tools: Record<string, string>;
154
+ steps: (n: number) => string;
155
+ };
138
156
  /** `ApprovalPrompt`: the default prompt line (overridable per instance) and
139
157
  * the Approve / Deny button labels — the surface that ANSWERS `AgentRun`'s
140
158
  * read-only `awaiting` row (approve/deny = the ai-sdk approval vocabulary). */
@@ -237,7 +255,35 @@ export const en: LoticsLocale = {
237
255
  matrix: { total: "Total", less: "Less", more: "More" },
238
256
  scrollToBottom: { tooltip: "Scroll to bottom" },
239
257
  textInputField: { clear: "Clear" },
240
- agentRun: { starting: "Starting…", thinking: "Thinking", thinkingStreaming: "Thinking…", input: "Input", error: "Error", output: "Output", awaiting: "Awaiting", retry: "Retry" },
258
+ agentRun: {
259
+ starting: "Starting…",
260
+ thinking: "Thinking",
261
+ thinkingStreaming: "Thinking…",
262
+ input: "Input",
263
+ error: "Error",
264
+ output: "Output",
265
+ awaiting: "Awaiting",
266
+ retry: "Retry",
267
+ tools: {
268
+ query_records: "Searching records",
269
+ get_record: "Reading a record",
270
+ get_records: "Reading records",
271
+ create_records: "Creating records",
272
+ update_records: "Updating records",
273
+ delete_records: "Removing records",
274
+ run_app_query: "Reading data",
275
+ run_app_workflow: "Running an action",
276
+ grep_knowledge: "Searching reference material",
277
+ read_knowledge: "Reading reference material",
278
+ list_knowledge: "Listing reference material",
279
+ view_files: "Reading files",
280
+ ask_user_choice: "Asking a question",
281
+ generate_pdf_from_template: "Generating PDF",
282
+ generate_excel_from_template: "Generating spreadsheet",
283
+ generate_docx_from_template: "Generating document",
284
+ },
285
+ steps: (n) => `${n} steps`,
286
+ },
241
287
  approvalPrompt: { message: "The assistant wants to perform an action that needs your approval.", approve: "Approve", deny: "Deny" },
242
288
  messageActions: { copy: "Copy", copied: "Copied", regenerate: "Regenerate", edit: "Edit", previousVersion: "Previous version", nextVersion: "Next version" },
243
289
  };
@@ -335,7 +381,35 @@ export const vi: LoticsLocale = {
335
381
  matrix: { total: "Tổng", less: "Ít", more: "Nhiều" },
336
382
  scrollToBottom: { tooltip: "Cuộn xuống cuối" },
337
383
  textInputField: { clear: "Xóa" },
338
- agentRun: { starting: "Đang bắt đầu…", thinking: "Suy nghĩ", thinkingStreaming: "Đang suy nghĩ…", input: "Đầu vào", error: "Lỗi", output: "Kết quả", awaiting: "Chờ duyệt", retry: "Thử lại" },
384
+ agentRun: {
385
+ starting: "Đang bắt đầu…",
386
+ thinking: "Suy nghĩ",
387
+ thinkingStreaming: "Đang suy nghĩ…",
388
+ input: "Đầu vào",
389
+ error: "Lỗi",
390
+ output: "Kết quả",
391
+ awaiting: "Chờ duyệt",
392
+ retry: "Thử lại",
393
+ tools: {
394
+ query_records: "Đang tìm dữ liệu",
395
+ get_record: "Đang đọc một bản ghi",
396
+ get_records: "Đang đọc dữ liệu",
397
+ create_records: "Đang tạo dữ liệu",
398
+ update_records: "Đang cập nhật dữ liệu",
399
+ delete_records: "Đang xoá dữ liệu",
400
+ run_app_query: "Đang đọc dữ liệu",
401
+ run_app_workflow: "Đang chạy tác vụ",
402
+ grep_knowledge: "Đang tra tài liệu",
403
+ read_knowledge: "Đang đọc tài liệu",
404
+ list_knowledge: "Đang liệt kê tài liệu",
405
+ view_files: "Đang đọc tệp",
406
+ ask_user_choice: "Đang hỏi thêm",
407
+ generate_pdf_from_template: "Đang tạo PDF",
408
+ generate_excel_from_template: "Đang tạo bảng tính",
409
+ generate_docx_from_template: "Đang tạo văn bản",
410
+ },
411
+ steps: (n) => `${n} bước`,
412
+ },
339
413
  approvalPrompt: { message: "Trợ lý muốn thực hiện thao tác cần bạn duyệt.", approve: "Cho phép", deny: "Từ chối" },
340
414
  messageActions: { copy: "Sao chép", copied: "Đã sao chép", regenerate: "Tạo lại", edit: "Chỉnh sửa", previousVersion: "Phiên bản trước", nextVersion: "Phiên bản sau" },
341
415
  };