@springbrand/message-panel 0.1.3-alpha.3 → 0.1.3-alpha.5

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.
@@ -46,10 +46,10 @@ pnpm dev:cloud-os # http://localhost:9992
46
46
  shadcn 主题正面冲突。
47
47
  4. **作用域化主题**。只有 kumo 命名空间的 token 进 `@theme`;通用 token(字体 /
48
48
  圆角 / 字号 / 缓动)挂在 `.cloud-os-root` 上靠继承生效,宿主主题分毫不动。
49
- 5. **删掉的东西**:capsule(URL → 资源胶囊)、行内 slash picker、ComposerMirror、
50
- GatekeeperModal、Yjs 草稿 / 「接受改动」那一整套。它们都直挂 gadgets 的 capnweb
51
- RPC,UIMessage 协议下没有对应物。slash 命令退成「+」菜单里的一组条目 —— 能力
52
- 保住,但没有边打字边过滤的行内补全。
49
+ 5. **删掉的东西**:capsule(URL → 资源胶囊)、ComposerMirror、GatekeeperModal、
50
+ Yjs 草稿 / 「接受改动」那一整套。它们都直挂 gadgets 的 capnweb RPC,
51
+ UIMessage 协议下没有对应物。命令补全使用 package 内的 assistant-ui
52
+ `ComposerTriggerPopover`,宿主只传命令数据。
53
53
  6. **窄栏适配**。授权卡 / 提问卡 / 定时卡在上游是「图标 + 正文 + 右侧动作」一行到底;
54
54
  聊天栏默认只有 420px,一行放不下会把正文挤成 0 宽,所以改成允许换行。
55
55
 
@@ -0,0 +1,35 @@
1
+ import { Diamond, X } from "@phosphor-icons/react";
2
+
3
+ export interface CapabilityChipProps {
4
+ kind: "skill" | "plan";
5
+ label: string;
6
+ removable?: boolean;
7
+ onRemove?: () => void;
8
+ }
9
+
10
+ export function CapabilityChip({
11
+ kind,
12
+ label,
13
+ removable = false,
14
+ onRemove,
15
+ }: CapabilityChipProps) {
16
+ return (
17
+ <span
18
+ data-capability-kind={kind}
19
+ className="inline-flex max-w-full items-center gap-1 rounded-full border border-kumo-line bg-kumo-elevated px-2 py-1 text-[12px] leading-4 text-kumo-subtle"
20
+ >
21
+ <Diamond size={11} weight="duotone" className="shrink-0 text-kumo-brand" />
22
+ <span className="truncate">{label}</span>
23
+ {removable && onRemove && (
24
+ <button
25
+ type="button"
26
+ onClick={onRemove}
27
+ aria-label={`Remove ${label}`}
28
+ className="-mr-1 grid size-5 shrink-0 cursor-pointer place-items-center rounded-full text-kumo-inactive hover:bg-kumo-tint hover:text-kumo-default focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kumo-brand/40"
29
+ >
30
+ <X size={10} weight="bold" />
31
+ </button>
32
+ )}
33
+ </span>
34
+ );
35
+ }
@@ -36,6 +36,7 @@ import {
36
36
  } from "./rich-blocks";
37
37
  import { ThinkingTraceRow, ToolGroupRow } from "./tool-rows";
38
38
  import { CloudOsActivityIndicator } from "./activity-indicator";
39
+ import { CapabilityChip } from "../capability-chip";
39
40
  import {
40
41
  buildCloudOsEntries,
41
42
  deriveTurnActivity,
@@ -127,6 +128,17 @@ function UserBubble({
127
128
  }) {
128
129
  return (
129
130
  <div className="group/message relative flex flex-col items-end">
131
+ {entry.capabilities.length > 0 && (
132
+ <div className="mb-1.5 flex max-w-[min(680px,78%)] flex-wrap justify-end gap-1.5">
133
+ {entry.capabilities.map((capability) => (
134
+ <CapabilityChip
135
+ key={`${capability.kind}:${capability.name}`}
136
+ kind={capability.kind}
137
+ label={capability.label}
138
+ />
139
+ ))}
140
+ </div>
141
+ )}
130
142
  {entry.attachments.length > 0 && (
131
143
  <div className="mb-1.5 flex max-w-[min(680px,78%)] flex-wrap justify-end gap-2">
132
144
  {entry.attachments.map((attachment, index) =>
@@ -29,6 +29,13 @@ export interface CloudOsMessageMetadata extends UnknownRecord {
29
29
  turnDurationMs?: number;
30
30
  turnStartedAt?: number;
31
31
  turnStatus?: string;
32
+ requestedCapabilities?: unknown;
33
+ }
34
+
35
+ export interface CloudOsRequestedCapability {
36
+ kind: "skill" | "plan";
37
+ name: string;
38
+ label: string;
32
39
  }
33
40
 
34
41
  export interface CloudOsAttachment {
@@ -80,6 +87,7 @@ export type CloudOsEntry =
80
87
  messageId: string;
81
88
  text: string;
82
89
  attachments: CloudOsAttachment[];
90
+ capabilities: CloudOsRequestedCapability[];
83
91
  authorName?: string;
84
92
  timestamp?: number;
85
93
  }
@@ -126,6 +134,31 @@ export function cloudOsMetadata(message: UIMessage): CloudOsMessageMetadata {
126
134
  return recordOf(message.metadata) as CloudOsMessageMetadata;
127
135
  }
128
136
 
137
+ export function requestedCapabilitiesOf(
138
+ metadata: CloudOsMessageMetadata,
139
+ ): CloudOsRequestedCapability[] {
140
+ if (!Array.isArray(metadata.requestedCapabilities)) return [];
141
+ const seen = new Set<string>();
142
+ return metadata.requestedCapabilities.flatMap((value) => {
143
+ const capability = recordOf(value);
144
+ if (
145
+ (capability.kind !== "skill" && capability.kind !== "plan") ||
146
+ typeof capability.name !== "string" ||
147
+ !capability.name.trim() ||
148
+ typeof capability.label !== "string" ||
149
+ !capability.label.trim()
150
+ ) return [];
151
+ const key = `${capability.kind}:${capability.name}`;
152
+ if (seen.has(key)) return [];
153
+ seen.add(key);
154
+ return [{
155
+ kind: capability.kind,
156
+ name: capability.name,
157
+ label: capability.label,
158
+ }];
159
+ });
160
+ }
161
+
129
162
  export function messageText(message: UIMessage): string {
130
163
  return message.parts
131
164
  .filter(
@@ -488,6 +521,7 @@ export function buildCloudOsEntries({
488
521
  messageId: message.id,
489
522
  text,
490
523
  attachments,
524
+ capabilities: requestedCapabilitiesOf(metadata),
491
525
  authorName: metadata.authorDisplayName,
492
526
  timestamp:
493
527
  nonNegativeNumber(metadata.createdAt) ??
@@ -8,9 +8,21 @@ import {
8
8
  Terminal,
9
9
  X,
10
10
  } from "@phosphor-icons/react";
11
+ import {
12
+ AssistantRuntimeProvider,
13
+ ComposerPrimitive,
14
+ unstable_useSlashCommandAdapter,
15
+ unstable_useTriggerPopoverAriaProps,
16
+ unstable_useTriggerPopoverTriggers,
17
+ useExternalStoreRuntime,
18
+ type AssistantRuntime,
19
+ type ThreadMessage,
20
+ } from "@assistant-ui/react";
21
+ import { ComposerTriggerPopover } from "@springbrand/message-panel/composer";
11
22
  import {
12
23
  useCallback,
13
24
  useEffect,
25
+ useMemo,
14
26
  useRef,
15
27
  useState,
16
28
  type DragEvent as ReactDragEvent,
@@ -19,6 +31,7 @@ import {
19
31
  import { DropdownMenu } from "../primitives/dropdown-menu";
20
32
  import { Tooltip } from "../primitives/tooltip";
21
33
  import { WorkshopIconButton } from "../primitives/workshop-controls";
34
+ import { CapabilityChip } from "../capability-chip";
22
35
 
23
36
  const isComposingKeyEvent = (event: {
24
37
  isComposing?: boolean;
@@ -32,9 +45,10 @@ const isComposingKeyEvent = (event: {
32
45
  * 附件缩略图行、底部左「+ / 添加资源」右「模型 / 发送-停止」的布局、
33
46
  * draftUpdateBanner 插槽、blockedReason 占位文案、Enter 发送 / Shift+Enter 换行。
34
47
  *
35
- * 删掉:capsule(URL → 资源胶囊)、slash command picker、ComposerMirror、
36
- * GatekeeperModal —— 这四样都直挂 gadgets 的 capnweb RPC,在 UIMessage 协议下
37
- * 没有对应物。「添加资源」按钮保留成一个可选回调,宿主不给就不渲染。
48
+ * 删掉:capsule(URL → 资源胶囊)、ComposerMirror、GatekeeperModal —— 这三样都直挂
49
+ * gadgets 的 capnweb RPC,在 UIMessage 协议下没有对应物。「添加资源」按钮保留成一个
50
+ * 可选回调,宿主不给就不渲染。Command picker 使用 package 内的
51
+ * assistant-ui ComposerTriggerPopover。
38
52
  */
39
53
 
40
54
  export interface CloudOsModelOption {
@@ -43,10 +57,15 @@ export interface CloudOsModelOption {
43
57
  }
44
58
 
45
59
  export interface CloudOsComposerCommand {
60
+ prefix?: "/" | "$";
46
61
  name: string;
47
62
  description: string;
48
- /** 选中后填进输入框的起手文本。 */
49
- prompt: string;
63
+ }
64
+
65
+ export interface CloudOsCapabilityView {
66
+ kind: "skill" | "plan";
67
+ name: string;
68
+ label: string;
50
69
  }
51
70
 
52
71
  export interface CloudOsAttachmentView {
@@ -82,11 +101,11 @@ export interface CloudOsChatInputProps {
82
101
  * 传了就以它为准来决定显示发送键还是停止键。
83
102
  */
84
103
  turnActive?: boolean;
85
- /**
86
- * 可用的斜杠命令。cloudflare-os 原版是一个直连 RPC 的行内 picker;这里退成
87
- * 「+」菜单里的一组条目 —— 能力保住,但**没有**边打字边过滤的行内补全。
88
- */
104
+ /** 可用的斜杠命令,也会显示在「+」菜单中。 */
89
105
  commands?: readonly CloudOsComposerCommand[];
106
+ capabilities?: readonly CloudOsCapabilityView[];
107
+ onCommandSelect?: (command: CloudOsComposerCommand) => void;
108
+ onCapabilityRemove?: (kind: CloudOsCapabilityView["kind"], name: string) => void;
90
109
  /** 有值时渲染「添加资源」按钮。 */
91
110
  onAttachResource?: () => void;
92
111
  attachLabel?: string;
@@ -106,6 +125,10 @@ export interface CloudOsChatInputProps {
106
125
 
107
126
  const MAX_PENDING_ATTACHMENTS = 5;
108
127
 
128
+ function commandLabel(command: CloudOsComposerCommand): string {
129
+ return `${command.prefix ?? "/"}${command.name.replaceAll("-", " ")}`;
130
+ }
131
+
109
132
  function autoResizeTextarea(
110
133
  textarea: HTMLTextAreaElement,
111
134
  minRows: number,
@@ -123,7 +146,7 @@ function autoResizeTextarea(
123
146
  textarea.style.overflow = textarea.scrollHeight > maxH ? "auto" : "hidden";
124
147
  }
125
148
 
126
- export function CloudOsChatInput({
149
+ function CloudOsChatInputContent({
127
150
  value,
128
151
  onChange,
129
152
  onSubmit,
@@ -141,6 +164,9 @@ export function CloudOsChatInput({
141
164
  submissionBlocked = false,
142
165
  turnActive,
143
166
  commands = [],
167
+ capabilities = [],
168
+ onCommandSelect,
169
+ onCapabilityRemove,
144
170
  onAttachResource,
145
171
  attachLabel,
146
172
  showThinkingTraces,
@@ -152,7 +178,8 @@ export function CloudOsChatInput({
152
178
  maxRows = 6,
153
179
  autoFocus = false,
154
180
  footnote,
155
- }: CloudOsChatInputProps) {
181
+ runtime,
182
+ }: CloudOsChatInputProps & { runtime: AssistantRuntime }) {
156
183
  const textareaRef = useRef<HTMLTextAreaElement | null>(null);
157
184
  const attachmentInputRef = useRef<HTMLInputElement>(null);
158
185
  const dragDepthRef = useRef(0);
@@ -189,6 +216,73 @@ export function CloudOsChatInput({
189
216
  if (element) autoResizeTextarea(element, minRows, maxRows);
190
217
  }, [maxRows, minRows, value]);
191
218
 
219
+ const selectCommand = useCallback((
220
+ command: CloudOsComposerCommand,
221
+ clearTrigger: boolean,
222
+ ) => {
223
+ onCommandSelect?.(command);
224
+ if (clearTrigger) {
225
+ runtime.thread.composer.setText("");
226
+ onChange("");
227
+ }
228
+ requestAnimationFrame(() => textareaRef.current?.focus());
229
+ }, [onChange, onCommandSelect, runtime]);
230
+
231
+ const slashCommands = useMemo(
232
+ () => commands
233
+ .filter((command) => (command.prefix ?? "/") === "/")
234
+ .map((command) => ({
235
+ id: command.name,
236
+ label: commandLabel(command),
237
+ description: command.description,
238
+ execute: () => selectCommand(command, true),
239
+ })),
240
+ [commands, selectCommand],
241
+ );
242
+ const skillCommands = useMemo(
243
+ () => commands
244
+ .filter((command) => command.prefix === "$")
245
+ .map((command) => ({
246
+ id: command.name,
247
+ label: commandLabel(command),
248
+ description: command.description,
249
+ execute: () => selectCommand(command, true),
250
+ })),
251
+ [commands, selectCommand],
252
+ );
253
+ const slashAdapter = unstable_useSlashCommandAdapter({
254
+ commands: slashCommands,
255
+ removeOnExecute: true,
256
+ });
257
+ const skillAdapter = unstable_useSlashCommandAdapter({
258
+ commands: skillCommands,
259
+ removeOnExecute: true,
260
+ });
261
+ // assistant-ui only re-runs a TriggerPopover search when the adapter changes.
262
+ const slash = useMemo(() => ({
263
+ ...slashAdapter,
264
+ adapter: { ...slashAdapter.adapter },
265
+ }), [slashAdapter, slashCommands]);
266
+ const skills = useMemo(() => ({
267
+ ...skillAdapter,
268
+ adapter: { ...skillAdapter.adapter },
269
+ }), [skillAdapter, skillCommands]);
270
+ const triggers = unstable_useTriggerPopoverTriggers();
271
+ const triggerAria = unstable_useTriggerPopoverAriaProps();
272
+
273
+ const setTriggerCursor = useCallback(
274
+ (position: number) => {
275
+ for (const trigger of triggers.values()) {
276
+ trigger.resource.setCursorPosition(position);
277
+ }
278
+ },
279
+ [triggers],
280
+ );
281
+
282
+ useEffect(() => {
283
+ setTriggerCursor(textareaRef.current?.selectionStart ?? value.length);
284
+ }, [commands, setTriggerCursor, value]);
285
+
192
286
  const addFiles = useCallback(
193
287
  (files: readonly File[]) => {
194
288
  if (files.length === 0) return;
@@ -224,6 +318,20 @@ export function CloudOsChatInput({
224
318
  return (
225
319
  // isolation: isolate 把输入框内部的 z-index 关起来,免得盖到 portal 出去的模型下拉。
226
320
  <div className="cos-chat-input-root relative isolate px-4 py-4">
321
+ <ComposerTriggerPopover
322
+ char="/"
323
+ {...slash}
324
+ aria-label="Commands"
325
+ emptyItemsLabel="No matching commands"
326
+ className="!w-72 !border-kumo-line/70 !bg-kumo-base !text-kumo-default themed-floating-shadow-lg"
327
+ />
328
+ <ComposerTriggerPopover
329
+ char="$"
330
+ {...skills}
331
+ aria-label="Skills"
332
+ emptyItemsLabel="No matching skills"
333
+ className="!w-72 !border-kumo-line/70 !bg-kumo-base !text-kumo-default themed-floating-shadow-lg"
334
+ />
227
335
  <input
228
336
  ref={attachmentInputRef}
229
337
  type="file"
@@ -276,7 +384,22 @@ export function CloudOsChatInput({
276
384
 
277
385
  {banner}
278
386
 
279
- <div className="relative px-4 pb-1 pt-3">
387
+ <div className="relative flex flex-nowrap items-center gap-2 px-4 pb-1 pt-3">
388
+ {capabilities.length > 0 && (
389
+ <div className="flex max-w-[65%] shrink-0 flex-nowrap gap-1.5 overflow-x-auto">
390
+ {capabilities.map((capability) => (
391
+ <CapabilityChip
392
+ key={`${capability.kind}:${capability.name}`}
393
+ kind={capability.kind}
394
+ label={capability.label}
395
+ removable
396
+ onRemove={() =>
397
+ onCapabilityRemove?.(capability.kind, capability.name)
398
+ }
399
+ />
400
+ ))}
401
+ </div>
402
+ )}
280
403
  <textarea
281
404
  ref={(element) => {
282
405
  textareaRef.current = element;
@@ -294,9 +417,17 @@ export function CloudOsChatInput({
294
417
  : (placeholder ?? "Ask a follow-up…")
295
418
  }
296
419
  onChange={(event) => {
297
- onChange(event.target.value);
420
+ const next = event.target.value;
421
+ runtime.thread.composer.setText(next);
422
+ onChange(next);
423
+ setTriggerCursor(event.target.selectionStart ?? next.length);
298
424
  autoResizeTextarea(event.target, minRows, maxRows);
299
425
  }}
426
+ onSelect={(event) =>
427
+ setTriggerCursor(
428
+ event.currentTarget.selectionStart ?? value.length,
429
+ )
430
+ }
300
431
  onPaste={(event) => {
301
432
  const files = Array.from(event.clipboardData.items)
302
433
  .filter((item) => item.kind === "file")
@@ -308,6 +439,9 @@ export function CloudOsChatInput({
308
439
  }
309
440
  }}
310
441
  onKeyDown={(event) => {
442
+ for (const trigger of triggers.values()) {
443
+ if (trigger.resource.handleKeyDown(event)) return;
444
+ }
311
445
  // Enter 发送(按住 Shift 换行); IME 组合中不发送
312
446
  if (
313
447
  event.key === "Enter" &&
@@ -318,7 +452,8 @@ export function CloudOsChatInput({
318
452
  if (canSend) onSubmit();
319
453
  }
320
454
  }}
321
- className="relative z-[1] w-full resize-none border-none bg-transparent p-0 text-[14px] leading-[22px] tracking-[-0.25px] text-kumo-default outline-none placeholder:text-kumo-inactive disabled:cursor-not-allowed"
455
+ className="relative z-[1] min-w-0 flex-1 resize-none border-none bg-transparent p-0 text-[14px] leading-[22px] tracking-[-0.25px] text-kumo-default outline-none placeholder:text-kumo-inactive disabled:cursor-not-allowed"
456
+ {...triggerAria}
322
457
  />
323
458
  </div>
324
459
 
@@ -404,20 +539,15 @@ export function CloudOsChatInput({
404
539
  <>
405
540
  {commands.map((command) => (
406
541
  <DropdownMenu.Item
407
- key={command.name}
408
- onClick={() => {
409
- onChange(command.prompt);
410
- requestAnimationFrame(() =>
411
- textareaRef.current?.focus(),
412
- );
413
- }}
542
+ key={`${command.prefix ?? "/"}${command.name}`}
543
+ onClick={() => selectCommand(command, false)}
414
544
  className="!h-auto rounded-xl !px-2 !py-1.5 text-[12px] leading-4 font-normal tracking-[-0.15px] text-kumo-subtle transition-colors data-highlighted:bg-kumo-tint/70 data-highlighted:text-kumo-default"
415
545
  >
416
546
  <span className="mr-2 inline-flex h-4 w-4 items-center justify-center text-kumo-inactive">
417
547
  <Terminal size={14} />
418
548
  </span>
419
549
  <span className="min-w-0 flex-1 truncate">
420
- /{command.name}
550
+ {commandLabel(command)}
421
551
  </span>
422
552
  </DropdownMenu.Item>
423
553
  ))}
@@ -539,3 +669,24 @@ export function CloudOsChatInput({
539
669
  </div>
540
670
  );
541
671
  }
672
+
673
+ const EMPTY_MESSAGES: readonly ThreadMessage[] = [];
674
+
675
+ export function CloudOsChatInput(props: CloudOsChatInputProps) {
676
+ const runtime = useExternalStoreRuntime<ThreadMessage>({
677
+ messages: EMPTY_MESSAGES,
678
+ onNew: async () => undefined,
679
+ });
680
+
681
+ useEffect(() => {
682
+ runtime.thread.composer.setText(props.value);
683
+ }, [props.value, runtime]);
684
+
685
+ return (
686
+ <AssistantRuntimeProvider runtime={runtime}>
687
+ <ComposerPrimitive.Unstable_TriggerPopoverRoot>
688
+ <CloudOsChatInputContent {...props} runtime={runtime} />
689
+ </ComposerPrimitive.Unstable_TriggerPopoverRoot>
690
+ </AssistantRuntimeProvider>
691
+ );
692
+ }
package/cloud-os/index.ts CHANGED
@@ -11,6 +11,8 @@
11
11
  */
12
12
 
13
13
  export { CloudOsRoot } from "./layout/cloud-os-root";
14
+ export { CapabilityChip } from "./capability-chip";
15
+ export type { CapabilityChipProps } from "./capability-chip";
14
16
  export { CloudOsWorkspaceSplit } from "./layout/cloud-os-workspace-split";
15
17
  export type { CloudOsWorkspaceSplitProps } from "./layout/cloud-os-workspace-split";
16
18
 
@@ -23,6 +25,7 @@ export type {
23
25
  export { CloudOsChatInput } from "./composer/cloud-os-chat-input";
24
26
  export type {
25
27
  CloudOsAttachmentView,
28
+ CloudOsCapabilityView,
26
29
  CloudOsChatInputProps,
27
30
  CloudOsComposerCommand,
28
31
  CloudOsModelOption,
@@ -70,6 +73,7 @@ export {
70
73
  formatClockTime,
71
74
  formatFullTimestamp,
72
75
  messageText,
76
+ requestedCapabilitiesOf,
73
77
  rhythmTopClass,
74
78
  } from "./chat/transcript-model";
75
79
  export type {
@@ -77,6 +81,7 @@ export type {
77
81
  CloudOsActivity,
78
82
  CloudOsAttachment,
79
83
  CloudOsEntry,
84
+ CloudOsRequestedCapability,
80
85
  ParallelTool,
81
86
  PlanStep,
82
87
  SubAgentView,
@@ -10,7 +10,7 @@ import {
10
10
  /**
11
11
  * 左聊天 / 右工作区的双栏骨架。从 cloudflare-os-main `GadgetEditor.tsx` 的 BODY
12
12
  * 段拷来:同一条 1px 的 kumo-line 分隔即拖拽把手、同一套 pointer capture 拖拽
13
- * (拖过 iframe 也不丢)、同一个 200ms 的收起/展开过渡、同一条顶部推进条。
13
+ * (拖过 iframe 也不丢)、同一个 200ms 的收起/展开过渡。
14
14
  *
15
15
  * 适配点:原文件把左右两侧的内容写死成 ChatInterface / GadgetUI,这里换成
16
16
  * children 插槽;chatWidth 的 localStorage key 换成 cloud-os 自己的。
@@ -59,8 +59,6 @@ export interface CloudOsWorkspaceSplitProps {
59
59
  workspace: ReactNode;
60
60
  /** 关掉右栏时聊天占满整宽。 */
61
61
  workspaceOpen: boolean;
62
- /** 顶部那条推进条:回合进行中显示。 */
63
- isAgentActive?: boolean;
64
62
  /** 右栏收起时依然保留的窄边栏宽度(原文件的 Outputs rail)。 */
65
63
  railWidth?: number;
66
64
  rail?: ReactNode;
@@ -70,7 +68,6 @@ export function CloudOsWorkspaceSplit({
70
68
  chat,
71
69
  workspace,
72
70
  workspaceOpen,
73
- isAgentActive = false,
74
71
  railWidth = 0,
75
72
  rail,
76
73
  }: CloudOsWorkspaceSplitProps) {
@@ -188,17 +185,6 @@ export function CloudOsWorkspaceSplit({
188
185
  ref={containerRef}
189
186
  className="relative flex h-full min-h-0 flex-1 overflow-hidden bg-kumo-base"
190
187
  >
191
- {isAgentActive && (
192
- <div
193
- className="absolute left-0 z-10 h-0"
194
- style={{ top: 0, right: railWidth }}
195
- >
196
- <div className="absolute left-0 right-0 h-0.5 overflow-hidden bg-kumo-fill">
197
- <div className="cos-progress-sweep absolute inset-y-0 w-1/3 bg-kumo-brand" />
198
- </div>
199
- </div>
200
- )}
201
-
202
188
  {/* ── 左:聊天 ──────────────────────────────────────────────────────── */}
203
189
  <div
204
190
  className={`flex h-full min-h-0 flex-shrink-0 flex-col ${transitionClass} ${
@@ -356,23 +356,6 @@
356
356
  background: transparent;
357
357
  }
358
358
 
359
- /* ── 顶部推进条(isAgentActive)──────────────────────────────────────────── */
360
- @keyframes cos-thinking {
361
- 0% {
362
- left: -33%;
363
- }
364
- 50% {
365
- left: 100%;
366
- }
367
- 100% {
368
- left: -33%;
369
- }
370
- }
371
-
372
- .cos-progress-sweep {
373
- animation: cos-thinking 1.5s ease-in-out infinite;
374
- }
375
-
376
359
  /* ============================================================================
377
360
  * 以下整段来自 ChatInterface.module.css —— CSS Module 类名改成 cos- 全局前缀
378
361
  * ========================================================================== */
@@ -550,9 +533,6 @@
550
533
  background: none;
551
534
  -webkit-text-fill-color: currentColor;
552
535
  }
553
- .cos-progress-sweep {
554
- animation: none;
555
- }
556
536
  }
557
537
 
558
538
  /* ── 观察类 markdown(行内、紧凑)────────────────────────────────────────── */
@@ -283,7 +283,6 @@ export function CloudOsChatShowcase() {
283
283
 
284
284
  <CloudOsWorkspaceSplit
285
285
  workspaceOpen={workspaceOpen}
286
- isAgentActive={status === "submitted" || status === "streaming"}
287
286
  chat={
288
287
  <div className="flex h-full min-h-0 flex-col bg-kumo-base">
289
288
  <aside
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/message-panel",
3
- "version": "0.1.3-alpha.3",
3
+ "version": "0.1.3-alpha.5",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -156,7 +156,7 @@ function Items({
156
156
  {item.label}
157
157
  </span>
158
158
  {item.description && (
159
- <span className="ms-5.5 text-xs leading-tight text-muted-foreground">
159
+ <span className="ms-5.5 w-full truncate text-xs leading-tight text-muted-foreground">
160
160
  {item.description}
161
161
  </span>
162
162
  )}