@lotics/ui 46.1.0 → 46.2.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/docs/ai_patterns.md +23 -14
- package/docs/catalog.md +10 -7
- package/package.json +1 -1
- package/src/agent_run.tsx +74 -58
- package/src/agent_run_pane.tsx +7 -5
- package/src/locale.tsx +4 -4
package/docs/ai_patterns.md
CHANGED
|
@@ -177,8 +177,15 @@ PUT and rolls the calls out BELOW it on press.
|
|
|
177
177
|
|
|
178
178
|
**ONE row for the whole run, and NO prose until it ends.** While the agent works, the entire feed
|
|
179
179
|
is a single row. It names the call in flight — "Searching records" — and says
|
|
180
|
-
**"Thinking
|
|
181
|
-
calls.
|
|
180
|
+
**"Thinking"** whenever the model is writing rather than calling, which is every gap between
|
|
181
|
+
calls. Nothing rolls out unless the reader asks.
|
|
182
|
+
|
|
183
|
+
**An unsettled row BREATHES**, label and dot together — the label on the same opacity rhythm the
|
|
184
|
+
dot's halo runs (750ms each way), so one line carries one animation rather than two that drift
|
|
185
|
+
apart. That breath is what says "in progress", which is why no live label ends in an ellipsis:
|
|
186
|
+
punctuation was standing in for motion, and a screen reader drops it anyway. A row that is not
|
|
187
|
+
working does not breathe — settled, and `awaiting` a human decision, which is parked rather than
|
|
188
|
+
in flight.
|
|
182
189
|
|
|
183
190
|
**No step count.** The row says WHAT is happening and stops there. A tally was a number the reader
|
|
184
191
|
could act on in no way: live, the label already changes on every call, so it added no motion the
|
|
@@ -208,12 +215,14 @@ reasoning part. An interleaved-thinking run therefore rebuilt the stack one grou
|
|
|
208
215
|
reader watched finished rows pile up while they waited, and was then left with that pile sitting
|
|
209
216
|
above the answer at the answer's weight, every row of it stale.
|
|
210
217
|
|
|
211
|
-
**Expanding
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
218
|
+
**Expanding works the same live and settled** — the timeline opens IN FULL, and the HOST'S OWN
|
|
219
|
+
scroller keeps the newest row in view. `AgentRun` never frames itself. It shipped once with a
|
|
220
|
+
live-only 220px window, and that window was a scroll container nested inside whatever scroll
|
|
221
|
+
container the run already sat in: in chat, an inverted list inside an inverted list, which painted
|
|
222
|
+
stale content over the neighbouring messages and took the wheel from the surface the reader was
|
|
223
|
+
on. A component in document flow cannot know whether it is inside a scroller, so it must not act
|
|
224
|
+
as one. A host that genuinely needs a bound takes the framing job itself — `collapseProcess={false}`
|
|
225
|
+
plus its own `FollowScroll`. Settling never closes what a reader opened.
|
|
217
226
|
|
|
218
227
|
The split is POSITIONAL (`splitTimeline` in `agent_transform`): what comes after the last tool
|
|
219
228
|
call is the answer, everything before it is the work. Nothing has to decide whether a paragraph
|
|
@@ -261,10 +270,10 @@ tinted panel rather than another step row, and it carries `role="alert"` so a sc
|
|
|
261
270
|
action (a secondary `Button`) sits INSIDE that panel so the operator can re-fire the run; omit it
|
|
262
271
|
and the panel carries the message alone.
|
|
263
272
|
|
|
264
|
-
**`FollowScroll` — only when you took the job (`collapseProcess={false}`).**
|
|
265
|
-
|
|
266
|
-
scroller
|
|
267
|
-
the pointer happens to be over. Reach for the wrapper when the run is a raw timeline you are
|
|
273
|
+
**`FollowScroll` — only when you took the job (`collapseProcess={false}`).** On the default, the
|
|
274
|
+
run is plain content in your flow and your own scroller follows it; adding a wrapper there nests a
|
|
275
|
+
scroller in a scroller — the outer never overflows, the inner holds the content, and the wheel
|
|
276
|
+
lands on whichever the pointer happens to be over. Reach for the wrapper when the run is a raw timeline you are
|
|
268
277
|
framing yourself — a dialog, drawer, or fixed-height panel where the feed grows BELOW the fold and
|
|
269
278
|
a plain scroll container doesn't follow. Wrap it then:
|
|
270
279
|
`<FollowScroll style={{ maxHeight: … }}><AgentRun … collapseProcess={false} /></FollowScroll>` — the same
|
|
@@ -281,7 +290,7 @@ for free — no hand-assembly): `<AgentRun parts={run.parts} state={…} error={
|
|
|
281
290
|
[the SDK doc](../../app-sdk/docs/ai.md) for the hook. The `ai` package is a regular dependency of
|
|
282
291
|
`@lotics/ui` — its part TYPES resolve transitively in any consumer's typecheck with nothing to
|
|
283
292
|
install; the imports are type-only (purity-enforced), so no `ai` runtime ever enters a bundle.
|
|
284
|
-
**A run with no parts yet renders the breathing "Starting
|
|
293
|
+
**A run with no parts yet renders the breathing "Starting" row** (streaming state only) — the
|
|
285
294
|
live dot plus a label that pulses, covering the gap between the CTA press and the first streamed
|
|
286
295
|
part (upload + run creation + first token). **The law: pass
|
|
287
296
|
`state="streaming"` from the moment the paid CTA fires — uploads included — and render
|
|
@@ -518,7 +527,7 @@ whole pane with no backend ([`tpl_item_list`](../examples/tpl_item_list.tsx) doe
|
|
|
518
527
|
|
|
519
528
|
**Customize through the seams, or drop to the primitives.** `labelForCall` / `renderToolOutput`
|
|
520
529
|
pass through to `AgentRun`; the `run` object is the data seam. There is deliberately NO
|
|
521
|
-
empty-state slot — `AgentRun` renders its own localized "Starting
|
|
530
|
+
empty-state slot — `AgentRun` renders its own localized "Starting" row on zero parts, and the law
|
|
522
531
|
above forbids hand-rolling a placeholder. An app that wants a different ARRANGEMENT does not fight
|
|
523
532
|
the pane: `AgentRun`, `ClarifyWizard`, `ClarifyWizardScope`/`Actions`, `FollowScroll` and
|
|
524
533
|
`DialogScrollArea` all remain exported, and composing them is what this pane itself does.
|
package/docs/catalog.md
CHANGED
|
@@ -2094,13 +2094,15 @@ component rather than showing it at zero.
|
|
|
2094
2094
|
hatch — input/error untouched). A per-tool failure shows amber with the reason in its expanded
|
|
2095
2095
|
Error panel; a run-level breaking `error` (outside `parts`) renders as a terminal danger row, with
|
|
2096
2096
|
an optional `onRetry` Button under it. While it runs the whole feed is ONE row and NO prose: the
|
|
2097
|
-
row names the call in flight ("Searching records") and says "Thinking
|
|
2098
|
-
model is writing rather than calling
|
|
2097
|
+
row names the call in flight ("Searching records") and says "Thinking" whenever the
|
|
2098
|
+
model is writing rather than calling; an unsettled row BREATHES, label and dot on one rhythm,
|
|
2099
|
+
which is why no live label carries an ellipsis (a row that is parked `awaiting` does not breathe). The agent's between-call narration is never shown — the one
|
|
2099
2100
|
message needing a response is a bulk-mutation confirmation, and the agent waits there, so the run
|
|
2100
2101
|
stops and that text becomes the answer. A reply with no tool calls is all answer and still streams.
|
|
2101
|
-
At settle the work folds to "{last action}" with the answer below. Expanding
|
|
2102
|
-
|
|
2103
|
-
|
|
2102
|
+
At settle the work folds to "{last action}" with the answer below. Expanding opens the timeline
|
|
2103
|
+
IN FULL, live or settled — the run never frames itself, and the HOST'S scroller follows it (a
|
|
2104
|
+
self-imposed window nests a scroller inside the host's own, which in chat means an inverted list
|
|
2105
|
+
inside an inverted list). Stays fully open while a call is parked, when a SETTLED run ended on
|
|
2104
2106
|
a tool call, and when the work is already one row. `collapseProcess={false}` hands the job back to
|
|
2105
2107
|
the caller (a surface that already frames the run — it then owns the `FollowScroll` too);
|
|
2106
2108
|
`summarizeRun(steps)` renames the row.
|
|
@@ -2134,7 +2136,8 @@ component rather than showing it at zero.
|
|
|
2134
2136
|
the chat message list's inverted mechanism packaged as a wrapper (single-cell inverted list),
|
|
2135
2137
|
so the follow happens at LAYOUT level: each growth paints already pinned (no flash), scrolling
|
|
2136
2138
|
up releases it, and short content still reads top-down. Wrap a streaming `AgentRun` with it in
|
|
2137
|
-
any bounded container (a dialog, a drawer, a panel — put the height bound on `style`)
|
|
2139
|
+
any bounded container (a dialog, a drawer, a panel — put the height bound on `style`) — but ONLY
|
|
2140
|
+
one you framed yourself with `collapseProcess={false}`, or it nests a scroller in a scroller;
|
|
2138
2141
|
`AgentProgress`'s expanded panel uses it. Inherits the inverted pattern's quirks (scrollbar
|
|
2139
2142
|
thumb runs opposite; keyboard paging inverted) — same as chat. A full-page chat list is
|
|
2140
2143
|
already inverted and doesn't need it.
|
|
@@ -2231,7 +2234,7 @@ component rather than showing it at zero.
|
|
|
2231
2234
|
or test DECLARES one rather than mapping from `ClarifyWizardQuestion` (an option is a label +
|
|
2232
2235
|
description; the answer's value IS the label, the `ask_user_choice` wire shape). Seams:
|
|
2233
2236
|
`labelForCall` / `renderToolOutput`; no empty-state slot by design (`AgentRun` owns the
|
|
2234
|
-
localized "Starting
|
|
2237
|
+
localized "Starting" row). Worked example: `tpl_item_list`'s intake dialog.
|
|
2235
2238
|
- **`choice_list`** — `ChoiceList` + `ChoiceOption`: selectable answer options as
|
|
2236
2239
|
divider-separated rows (no bordered cards) with a per-row focus ring + hover wash; the
|
|
2237
2240
|
agent's quick-reply surface. `allowCustom` appends an always-visible borderless multiline field whose
|
package/package.json
CHANGED
package/src/agent_run.tsx
CHANGED
|
@@ -8,7 +8,6 @@ import { Callout, CalloutActions, CalloutText } from "./callout";
|
|
|
8
8
|
import { Markdown } from "./markdown";
|
|
9
9
|
import { JsonPanel, stringifyData } from "./json_panel";
|
|
10
10
|
import { PressableHighlight } from "./pressable_highlight";
|
|
11
|
-
import { FollowScroll } from "./follow_scroll";
|
|
12
11
|
import { Marker, type StepStatus } from "./stepper";
|
|
13
12
|
import { NODE } from "./stepper_layout";
|
|
14
13
|
import { AnimationFadeIn } from "./animation_fade_in";
|
|
@@ -86,7 +85,7 @@ export interface AgentRunProps {
|
|
|
86
85
|
// ── Tool → display meta ───────────────────────────────────────────────────────
|
|
87
86
|
// The ICON per platform tool. The LABEL lives in the locale (`agentRun.tools`)
|
|
88
87
|
// because it is language, and this component already renders localized chrome
|
|
89
|
-
// around these rows — an English tool row under a Vietnamese "Đang suy
|
|
88
|
+
// around these rows — an English tool row under a Vietnamese "Đang suy nghĩ"
|
|
90
89
|
// was the kit contradicting itself. An icon is not language, so it stays here.
|
|
91
90
|
// A tool neither map knows falls back to a prettified name + a neutral icon.
|
|
92
91
|
const TOOL_ICONS: Record<string, IconName> = {
|
|
@@ -149,6 +148,37 @@ function stepLabel(
|
|
|
149
148
|
|
|
150
149
|
const INK = colors.zinc[700];
|
|
151
150
|
|
|
151
|
+
// The "still working" breath, and the ONE rhythm every unsettled row shares.
|
|
152
|
+
// Half-cycle 750ms — so a full breath is the 1500ms the `Marker`'s live halo
|
|
153
|
+
// already runs, and a row's dot and its label breathe together instead of
|
|
154
|
+
// drifting apart into two unrelated animations on one line. The floor is
|
|
155
|
+
// `Skeleton`'s: the kit says "in progress" with an opacity rhythm, in one
|
|
156
|
+
// vocabulary, whether the thing pulsing is a placeholder tile or a live label.
|
|
157
|
+
const BREATH_HALF = 750;
|
|
158
|
+
const BREATH_FLOOR = 0.6;
|
|
159
|
+
|
|
160
|
+
/** Breathe `children` while the work behind them is unsettled. Settled, it
|
|
161
|
+
* holds full opacity and runs no animation — a finished row is not working,
|
|
162
|
+
* and neither is one parked `awaiting` a human decision. */
|
|
163
|
+
function Breathing({ active, children }: { active: boolean; children: ReactNode }) {
|
|
164
|
+
const v = useRef(new Animated.Value(1)).current;
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (!active) {
|
|
167
|
+
v.setValue(1);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const loop = Animated.loop(
|
|
171
|
+
Animated.sequence([
|
|
172
|
+
Animated.timing(v, { toValue: BREATH_FLOOR, duration: BREATH_HALF, useNativeDriver: true }),
|
|
173
|
+
Animated.timing(v, { toValue: 1, duration: BREATH_HALF, useNativeDriver: true }),
|
|
174
|
+
]),
|
|
175
|
+
);
|
|
176
|
+
loop.start();
|
|
177
|
+
return () => loop.stop();
|
|
178
|
+
}, [active, v]);
|
|
179
|
+
return <Animated.View style={{ opacity: v }}>{children}</Animated.View>;
|
|
180
|
+
}
|
|
181
|
+
|
|
152
182
|
/**
|
|
153
183
|
* A live feed of an AI agent's work as a TIMELINE — the prose it streams and the
|
|
154
184
|
* tools it calls, in the order they happened. While the agent is mid-tools the
|
|
@@ -197,9 +227,9 @@ export function AgentRun(props: AgentRunProps) {
|
|
|
197
227
|
// answer, and hiding THAT behind a row until settle would be the real loss.
|
|
198
228
|
//
|
|
199
229
|
// SETTLED, the collapse covers the work and the answer stands below it.
|
|
200
|
-
// Expanding
|
|
201
|
-
//
|
|
202
|
-
//
|
|
230
|
+
// Expanding is the same either way — the timeline opens IN FULL, and what
|
|
231
|
+
// keeps the newest row in view is the host's own scroller, which every host
|
|
232
|
+
// of a live run already has.
|
|
203
233
|
//
|
|
204
234
|
// The settled clauses are cases where collapsing would hide something needed:
|
|
205
235
|
// · no answer — the run stopped ON a tool call, so the fold would leave an
|
|
@@ -211,7 +241,7 @@ export function AgentRun(props: AgentRunProps) {
|
|
|
211
241
|
const folded = managed && (streaming ? process.length > 0 : process.length > 1 && steps.length > 0 && result.length > 0);
|
|
212
242
|
|
|
213
243
|
// What the row SAYS right now: the tool in flight, or — when the model is
|
|
214
|
-
// writing rather than calling — "Thinking
|
|
244
|
+
// writing rather than calling — "Thinking". Naming the last finished action
|
|
215
245
|
// while the agent composes prose reports a moment that has passed.
|
|
216
246
|
const thinking = streaming && !lastRunningStep(segments);
|
|
217
247
|
|
|
@@ -415,29 +445,19 @@ function StepRow({ s, label, renderToolOutput }: { s: AgentStep; label: string;
|
|
|
415
445
|
|
|
416
446
|
// Thinking — a muted, COLLAPSED "Thinking" disclosure (revealed on press), so the
|
|
417
447
|
// agent's reasoning never crowds the answer. Pulses while it's still streaming.
|
|
418
|
-
/** The empty-live feed row: the live dot's halo plus a BREATHING label
|
|
419
|
-
*
|
|
420
|
-
* reads at a glance even before the first token. */
|
|
448
|
+
/** The empty-live feed row: the live dot's halo plus a BREATHING label, so
|
|
449
|
+
* "work has started" reads at a glance even before the first token. */
|
|
421
450
|
function StartingRow({ label }: { label: string }) {
|
|
422
|
-
const v = useRef(new Animated.Value(0.45)).current;
|
|
423
|
-
useEffect(() => {
|
|
424
|
-
const loop = Animated.loop(
|
|
425
|
-
Animated.sequence([
|
|
426
|
-
Animated.timing(v, { toValue: 1, duration: 700, useNativeDriver: false }),
|
|
427
|
-
Animated.timing(v, { toValue: 0.45, duration: 700, useNativeDriver: false }),
|
|
428
|
-
]),
|
|
429
|
-
);
|
|
430
|
-
loop.start();
|
|
431
|
-
return () => loop.stop();
|
|
432
|
-
}, [v]);
|
|
433
451
|
return (
|
|
434
452
|
<View style={styles.row}>
|
|
435
453
|
<View style={styles.dotCol}>
|
|
436
454
|
<Marker status="current" color={colors.zinc[400]} live />
|
|
437
455
|
</View>
|
|
438
|
-
<
|
|
439
|
-
<
|
|
440
|
-
|
|
456
|
+
<View style={styles.rowBody}>
|
|
457
|
+
<Breathing active>
|
|
458
|
+
<Text size="sm" color="muted">{label}</Text>
|
|
459
|
+
</Breathing>
|
|
460
|
+
</View>
|
|
441
461
|
</View>
|
|
442
462
|
);
|
|
443
463
|
}
|
|
@@ -452,9 +472,11 @@ function ReasoningDisclosure(props: { text: string; streaming?: boolean; expande
|
|
|
452
472
|
<Marker status={streaming ? "current" : "done"} color={colors.zinc[400]} live={!!streaming} />
|
|
453
473
|
</View>
|
|
454
474
|
<View style={styles.rowBody}>
|
|
455
|
-
<
|
|
456
|
-
|
|
457
|
-
|
|
475
|
+
<Breathing active={!!streaming}>
|
|
476
|
+
<Text size="sm" color="muted">
|
|
477
|
+
{streaming ? locale.agentRun.thinkingStreaming : locale.agentRun.thinking}
|
|
478
|
+
</Text>
|
|
479
|
+
</Breathing>
|
|
458
480
|
</View>
|
|
459
481
|
{chevron(expanded ? "up" : "down")}
|
|
460
482
|
</PressableHighlight>
|
|
@@ -514,7 +536,7 @@ function WorkSummary(props: {
|
|
|
514
536
|
const { steps, live, thinking, expanded, onToggle, labelForCall, summarizeRun, children } = props;
|
|
515
537
|
const locale = useLoticsLocale();
|
|
516
538
|
const final = steps[steps.length - 1];
|
|
517
|
-
// "Thinking
|
|
539
|
+
// "Thinking" WINS over a host summary while the model writes: `summarizeRun`
|
|
518
540
|
// names what the run DID, and mid-compose that is a moment already past.
|
|
519
541
|
const label = thinking
|
|
520
542
|
? locale.agentRun.thinkingStreaming
|
|
@@ -537,32 +559,30 @@ function WorkSummary(props: {
|
|
|
537
559
|
trailing={chevron(expanded ? "up" : "down")}
|
|
538
560
|
>
|
|
539
561
|
{/* Keyed by what the row is SAYING, so a new call (or the switch to
|
|
540
|
-
"Thinking
|
|
562
|
+
"Thinking") rises + fades into the SAME row — the label swaps, the
|
|
541
563
|
row does not move. Exactly what a settled `ToolGroup` does mid-burst,
|
|
542
|
-
held for the whole run.
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
564
|
+
held for the whole run.
|
|
565
|
+
The breath sits OUTSIDE that key: inside it, the rhythm would restart
|
|
566
|
+
from full opacity on every label swap and fight the fade's own — two
|
|
567
|
+
animations writing one opacity. Outside, the row breathes steadily
|
|
568
|
+
for the whole run while its label changes underneath. */}
|
|
569
|
+
<Breathing active={live}>
|
|
570
|
+
<AnimationFadeIn key={live ? label : "settled"} translateY={4}>
|
|
571
|
+
<SummaryLabel label={label} />
|
|
572
|
+
</AnimationFadeIn>
|
|
573
|
+
</Breathing>
|
|
546
574
|
</ActivityRow>
|
|
547
575
|
{/* The run's own 10px rhythm, not the group's 2px: what rolls out here is
|
|
548
576
|
whole segments — prose, thinking, tool groups — not sibling step rows.
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
the
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
<FollowScroll style={styles.liveFrame}>
|
|
559
|
-
<View style={{ gap: 10 }}>{children}</View>
|
|
560
|
-
</FollowScroll>
|
|
561
|
-
</View>
|
|
562
|
-
) : (
|
|
563
|
-
<View testID="agent-run-work" style={styles.work}>{children}</View>
|
|
564
|
-
)
|
|
565
|
-
) : null}
|
|
577
|
+
It opens IN FULL, live or settled, and the HOST owns the scrolling. A
|
|
578
|
+
self-imposed window here was a scroller nested inside whatever scroller
|
|
579
|
+
the run already sits in — in chat, an inverted list inside an inverted
|
|
580
|
+
list — which paints stale content over its neighbours and captures the
|
|
581
|
+
wheel from the surface the reader is actually on. A component in
|
|
582
|
+
document flow cannot know whether it is inside a scroll container, so
|
|
583
|
+
it must not act as though it were one; a host that needs a bound frames
|
|
584
|
+
the run itself (`collapseProcess={false}` + `FollowScroll`). */}
|
|
585
|
+
{expanded ? <View testID="agent-run-work" style={styles.work}>{children}</View> : null}
|
|
566
586
|
</View>
|
|
567
587
|
);
|
|
568
588
|
}
|
|
@@ -597,9 +617,11 @@ function ToolGroup(props: {
|
|
|
597
617
|
return (
|
|
598
618
|
<View style={styles.group}>
|
|
599
619
|
<ActivityRow markerStatus="current" live>
|
|
600
|
-
<
|
|
601
|
-
<
|
|
602
|
-
|
|
620
|
+
<Breathing active>
|
|
621
|
+
<AnimationFadeIn key={current.id} translateY={4}>
|
|
622
|
+
<StepBody label={resolve(current)} />
|
|
623
|
+
</AnimationFadeIn>
|
|
624
|
+
</Breathing>
|
|
603
625
|
</ActivityRow>
|
|
604
626
|
</View>
|
|
605
627
|
);
|
|
@@ -654,12 +676,6 @@ const styles = StyleSheet.create({
|
|
|
654
676
|
// The folded work, opened: segment spacing (the run's own gap), and a small
|
|
655
677
|
// lead-in under the header so the rolled-out work reads as its content.
|
|
656
678
|
work: { gap: 10, paddingTop: 6 },
|
|
657
|
-
// The cap on work OPENED mid-run. ~5 rows: enough to read what is happening,
|
|
658
|
-
// short enough that opening a p90 94s run does not push the composer off the
|
|
659
|
-
// screen for the rest of it. No border and no background — the run sits flush
|
|
660
|
-
// in the consumer's gutter by law, and a frame that appeared only while
|
|
661
|
-
// streaming would be the loudest thing on the surface.
|
|
662
|
-
liveFrame: { maxHeight: 220 },
|
|
663
679
|
row: {
|
|
664
680
|
flexDirection: "row",
|
|
665
681
|
alignItems: "center",
|
package/src/agent_run_pane.tsx
CHANGED
|
@@ -174,14 +174,16 @@ export function AgentRunPane<TLanding>(props: AgentRunPaneProps<TLanding>) {
|
|
|
174
174
|
// 24` here is a fifth copy of a number that has to agree with four others.
|
|
175
175
|
<FollowScroll contentContainerStyle={{ paddingBottom: 24, paddingHorizontal: gutter }}>
|
|
176
176
|
{/* No empty-state slot on purpose. `AgentRun` renders its own breathing
|
|
177
|
-
"Starting
|
|
177
|
+
"Starting" row while streaming with zero parts, localized through the
|
|
178
178
|
`agentRun` locale slice — and ai_patterns states the law outright:
|
|
179
179
|
never hand-roll a placeholder in front of the feed. A slot here would
|
|
180
180
|
invite exactly that, and every app would localize it again. */}
|
|
181
|
-
{/* collapseProcess=false:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
{/* collapseProcess=false: the run IS this pane's subject. A dialog opened
|
|
182
|
+
to watch one run, that then folds it behind a row the reader has to
|
|
183
|
+
press, hides the only thing it exists to show — the fold is for a run
|
|
184
|
+
sharing a surface with other content (a chat thread), not for one that
|
|
185
|
+
owns the surface. The FollowScroll above is the bound, sized by the
|
|
186
|
+
dialog; taking the framing job is what earns the right to add one. */}
|
|
185
187
|
<AgentRun
|
|
186
188
|
parts={run.parts}
|
|
187
189
|
state={run.status === "error" ? "error" : run.status === "streaming" ? "streaming" : "done"}
|
package/src/locale.tsx
CHANGED
|
@@ -460,9 +460,9 @@ export const en: LoticsLocale = {
|
|
|
460
460
|
scrollToBottom: { tooltip: "Scroll to bottom" },
|
|
461
461
|
textInputField: { clear: "Clear" },
|
|
462
462
|
agentRun: {
|
|
463
|
-
starting: "Starting
|
|
463
|
+
starting: "Starting",
|
|
464
464
|
thinking: "Thinking",
|
|
465
|
-
thinkingStreaming: "Thinking
|
|
465
|
+
thinkingStreaming: "Thinking",
|
|
466
466
|
input: "Input",
|
|
467
467
|
error: "Error",
|
|
468
468
|
output: "Output",
|
|
@@ -667,9 +667,9 @@ export const vi: LoticsLocale = {
|
|
|
667
667
|
scrollToBottom: { tooltip: "Cuộn xuống cuối" },
|
|
668
668
|
textInputField: { clear: "Xóa" },
|
|
669
669
|
agentRun: {
|
|
670
|
-
starting: "Đang bắt đầu
|
|
670
|
+
starting: "Đang bắt đầu",
|
|
671
671
|
thinking: "Suy nghĩ",
|
|
672
|
-
thinkingStreaming: "Đang suy
|
|
672
|
+
thinkingStreaming: "Đang suy nghĩ",
|
|
673
673
|
input: "Đầu vào",
|
|
674
674
|
error: "Lỗi",
|
|
675
675
|
output: "Kết quả",
|