Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input

# Conflicts:
#	docs/event-producer-consumer.md
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	packages/client/runtime/README.i18n.yaml
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/host/apiproxy/README.i18n.yaml
This commit is contained in:
creatixchu
2026-07-31 14:51:31 +08:00
186 changed files with 3495 additions and 744 deletions
@@ -66,33 +66,45 @@
border-left: 1px solid var(--dsw-alias-border-l2);
}
/* Turn loader: one row of four 2.5px pixels (StateDot blue) chasing left to
right with a stepped trail — flat keyframe holds, no tweening. Phase
offsets come from per-rect animation-delay (index * -250ms) set inline
by the component. */
.turnDots {
/* Turn activity keeps the former loader's one-line footprint. A pale
brand-blue band sweeps from left to right; reduced-motion keeps it static. */
.turnStatus {
align-self: flex-start;
flex: none;
display: flex;
display: inline-flex;
align-items: center;
/* One message line box: the dots center inside the text line height. */
height: 26px;
/* Same pin as StateDot: ongoing blue has no alias token (business-primary
is the 500 step, not this 450). */
color: var(--dsw-static-deepseek-450);
font: var(--dsw-font-s-strong-14);
white-space: nowrap;
background: linear-gradient(
90deg,
var(--dsw-static-deepseek-500) 0%,
var(--dsw-static-deepseek-500) 40%,
var(--dsw-static-deepseek-200) 50%,
var(--dsw-static-deepseek-500) 60%,
var(--dsw-static-deepseek-500) 100%
);
background-position: 100% 0;
background-size: 250% 100%;
background-clip: text;
color: transparent;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: dsh-turn-status-shimmer 1.8s linear infinite;
}
.turnDotCell {
fill: currentColor;
opacity: 0.15;
animation: dsh-turn-dots-chase 1s infinite;
@keyframes dsh-turn-status-shimmer {
to {
background-position: 0 0;
}
}
@keyframes dsh-turn-dots-chase {
0%, 24.9% { opacity: 1; }
25%, 49.9% { opacity: 0.6; }
50%, 74.9% { opacity: 0.35; }
75%, 100% { opacity: 0.15; }
@media (prefers-reduced-motion: reduce) {
.turnStatus {
background-position: 0 0;
background-size: 100% 100%;
animation: none;
}
}
.hint {
@@ -205,36 +205,11 @@ const CommandRow = memo(function CommandRow({ renderSlot, node, t }: {
)
})
/** Turn loader: one row of four 2.5px pixels (half a notch above the StateDot
* 2px cell, same blue) chasing left to right with a stepped trail — flat
* keyframe holds, no tweening, no rotation. Phase offsets come from
* per-rect animation-delay. */
const LOADER_CELLS = [0, 5, 10, 15] as const
function TurnDots() {
/** Turn-level model activity label retained across first-token, tool, and streaming phases. */
function TurnStatus() {
return (
/* The wrapper is a 26px line box (message line height) so the loader
occupies one text line and centers the dots inside it. */
<div className={css.turnDots} aria-hidden="true">
<svg
width="17.5"
height="2.5"
viewBox="0 0 17.5 2.5"
shapeRendering="crispEdges"
>
{LOADER_CELLS.map((x, index) => (
<rect
key={x}
className={css.turnDotCell}
x={x}
y="0"
width="2.5"
height="2.5"
/* Negative delay phases the chase so every cell animates from mount. */
style={{ animationDelay: `${(index - LOADER_CELLS.length) * 250}ms` }}
/>
))}
</svg>
<div className={css.turnStatus} role="status" aria-live="polite">
Deep diving...
</div>
)
}
@@ -496,7 +471,7 @@ export function ChatView({
double-render the same wait. */}
{/* Turn-level loading signal: rides the whole running turn (first-token
wait, tool execution, streaming) so it never flickers per step. */}
{running && <TurnDots />}
{running && <TurnStatus />}
</div>
{!atBottom && (
<div className={css.toBottomSlot}>
@@ -107,6 +107,10 @@
border-radius: 8px;
}
.row + .row {
box-shadow: inset 0 1px 0 var(--dsw-alias-border-l1);
}
.preview,
.editor {
flex: 1 1 auto;