@springbrand/message-panel 0.1.3-alpha.2 → 0.1.3-alpha.22
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/cloud-os/README.md +6 -6
- package/cloud-os/assets/followup-arrow.svg +3 -0
- package/cloud-os/assets/loading-corner.svg +3 -0
- package/cloud-os/assets/loading-mark.svg +16 -0
- package/cloud-os/assets/loading-spark.svg +3 -0
- package/cloud-os/assets/model-selected.svg +5 -0
- package/cloud-os/capability-chip.tsx +35 -0
- package/cloud-os/chat/activity-indicator.tsx +29 -0
- package/cloud-os/chat/cloud-os-chat-messages.tsx +130 -70
- package/cloud-os/chat/markdown-message.tsx +85 -40
- package/cloud-os/chat/rich-blocks.tsx +494 -190
- package/cloud-os/chat/tool-presentation.ts +67 -6
- package/cloud-os/chat/tool-rows.tsx +87 -43
- package/cloud-os/chat/transcript-model.ts +215 -23
- package/cloud-os/composer/cloud-os-chat-input.tsx +242 -127
- package/cloud-os/composer/cloud-os-model-select.tsx +105 -0
- package/cloud-os/file-view.tsx +266 -0
- package/cloud-os/index.ts +29 -2
- package/cloud-os/layout/cloud-os-workspace-split.tsx +107 -24
- package/cloud-os/primitives/workshop-controls.tsx +2 -2
- package/cloud-os/styles/cloud-os.css +93 -19
- package/demo/camel-chat-showcase.tsx +21 -13
- package/demo/chat-scenarios.ts +100 -40
- package/demo/cloud-os-chat-showcase.tsx +51 -17
- package/demo/fixtures.ts +4 -5
- package/demo/message-panel-gallery.tsx +16 -2
- package/package.json +3 -4
- package/src/camel/camel-chat-messages.tsx +82 -77
- package/src/camel/camel-prompt-input.tsx +2 -1
- package/src/camel/camel-tool-presentation.tsx +19 -15
- package/src/camel/camel-turn.ts +1 -17
- package/src/chat-summary-panel.tsx +1 -1
- package/src/composer/chat-composer.tsx +2 -1
- package/src/composer/composer-trigger-popover.tsx +1 -1
- package/src/composer/composer.tsx +2 -1
- package/src/composer/index.ts +1 -0
- package/src/composer/key-rules.ts +12 -0
- package/src/contracts.ts +0 -2
- package/src/message-panel.tsx +0 -23
- package/src/parts/index.tsx +102 -63
- package/src/styles/index.css +4 -1
package/cloud-os/README.md
CHANGED
|
@@ -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 → 资源胶囊)
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
|
@@ -57,8 +57,8 @@ pnpm dev:cloud-os # http://localhost:9992
|
|
|
57
57
|
|
|
58
58
|
- `update_plan` 是**整份快照覆盖**,不是增量事件。整条会话只保留最后一份计划快照
|
|
59
59
|
(`dropSupersededPlans`),否则同一份计划会被画上四遍。
|
|
60
|
-
- `ask_user`
|
|
61
|
-
|
|
60
|
+
- `ask_user` 的「已回答」只取该 Tool Part 的 `output.answers[]`;
|
|
61
|
+
不扫描后续用户消息,问题与答案按数组下标一一对应。
|
|
62
62
|
- `approval-requested` 的工具是否渲染成原位授权卡,由宿主的 `approvalIdOf` 决定;
|
|
63
63
|
返回 `undefined` 就退回普通工具行(本仓的持久化审批走输入框上方那条队列)。
|
|
64
64
|
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="11.6667" height="8.33333" viewBox="0 0 11.6667 8.33333" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path id="Vector (Stroke)" d="M3.92575 0.159107C4.14614 -0.0530669 4.50354 -0.0530047 4.72403 0.159107C4.94441 0.371284 4.94435 0.715363 4.72403 0.927642L1.92639 3.62317H11.1021C11.4139 3.62317 11.6667 3.8665 11.6667 4.16667C11.6667 4.46683 11.4139 4.71016 11.1021 4.71016H1.92639L4.72403 7.40569C4.94435 7.61797 4.94441 7.96205 4.72403 8.17423C4.50354 8.38634 4.14614 8.3864 3.92575 8.17423L0.165189 4.55093C-0.0550631 4.33872 -0.0550631 3.99461 0.165189 3.7824L3.92575 0.159107Z" fill="black" fill-opacity="0.5"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="21.8891" height="21.9254" viewBox="0 0 21.8891 21.9254" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path id="Vector" d="M11.6017 6.11877H0.752769C0.337056 6.11877 3.87367e-05 5.78171 3.87367e-05 5.36611L0 0.849598C0 0.433872 0.337004 0.0968018 0.75273 0.0968018H11.5558L15.6704 0.00163975C19.0193 -0.0757287 21.7878 2.59451 21.8311 5.94405L21.8891 10.4301V21.1726C21.8891 21.5883 21.552 21.9254 21.1363 21.9254H16.6199C16.2042 21.9254 15.8671 21.5883 15.8671 21.1726V10.3695L15.7581 7.55744C15.7267 6.74739 15.0595 6.10781 14.249 6.11039L11.6017 6.11877Z" fill="#FF9938"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="37.5518" height="37.4985" viewBox="0 0 37.5518 37.4985" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g id="Group" filter="url(#filter0_f_0_4)">
|
|
3
|
+
<path id="Vector" d="M31.8173 8.87482C32.1113 9.16882 32.1113 9.64541 31.8173 9.93941L24.16 17.5967H35.009C35.4247 17.5967 35.7616 17.9338 35.7616 18.3494L35.7618 22.8659C35.7618 23.2816 35.4247 23.6185 35.009 23.6185H24.2059L31.8173 31.2299C32.1113 31.5239 32.1113 32.0005 31.8173 32.2945L28.6236 35.488C28.3298 35.782 27.8532 35.782 27.5592 35.488L22.4352 30.3641C22.2939 30.2227 22.2146 30.0309 22.2148 29.831L22.216 28.8867L22.1335 21.8329C22.0894 18.0581 18.9682 15.0475 15.1943 15.1395L8.62867 15.2995L7.68452 15.3007C7.48456 15.3009 7.29271 15.2216 7.15132 15.0802L2.0105 9.93941C1.71653 9.64541 1.71653 9.16882 2.0105 8.87482L5.20408 5.68127C5.49804 5.38731 5.97465 5.38731 6.26861 5.68127L13.8727 13.2853V2.54284C13.8727 2.12712 14.2096 1.79009 14.6254 1.79008L19.1417 1.79002C19.5575 1.79002 19.8945 2.12707 19.8945 2.54279V13.3458L27.5592 5.68127C27.8532 5.38731 28.3298 5.38731 28.6236 5.68127L31.8173 8.87482Z" fill="url(#paint0_linear_0_4)"/>
|
|
4
|
+
</g>
|
|
5
|
+
<defs>
|
|
6
|
+
<filter id="filter0_f_0_4" x="-1.86265e-09" y="0" width="37.5518" height="37.4985" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
7
|
+
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
8
|
+
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
9
|
+
<feGaussianBlur stdDeviation="0.895012" result="effect1_foregroundBlur_0_4"/>
|
|
10
|
+
</filter>
|
|
11
|
+
<linearGradient id="paint0_linear_0_4" x1="30.2205" y1="7.27806" x2="22.5471" y2="31.3091" gradientUnits="userSpaceOnUse">
|
|
12
|
+
<stop stop-color="#FFD078"/>
|
|
13
|
+
<stop offset="1" stop-color="#FF9938"/>
|
|
14
|
+
</linearGradient>
|
|
15
|
+
</defs>
|
|
16
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="9.81853" height="9.8186" viewBox="0 0 9.81853 9.8186" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path id="Vector" d="M4.67857 0.881903C5.85442 -0.293968 7.76085 -0.293968 8.93672 0.881903C10.1125 2.05777 10.1125 3.96426 8.93672 5.14L4.25811 9.8186L0 5.5605L4.67857 0.881903Z" fill="#FF9938"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g id="Icon Variants">
|
|
3
|
+
<path id="Subtract" d="M8.00033 1.33333C11.6821 1.33351 14.6663 4.31854 14.6663 8.00033C14.6662 11.682 11.682 14.6662 8.00033 14.6663C4.31854 14.6663 1.33351 11.6821 1.33333 8.00033C1.33333 4.31843 4.31843 1.33333 8.00033 1.33333ZM11.138 6.19466C10.8776 5.93464 10.4549 5.93442 10.1947 6.19466L7.33236 9.05697L6.13802 7.86165C5.87764 7.60163 5.4549 7.60141 5.19466 7.86165C4.93469 8.1218 4.93502 8.54369 5.19466 8.80404L6.86165 10.471C7.12195 10.7313 7.54367 10.7312 7.80404 10.471L11.138 7.13802C11.3984 6.87767 11.3984 6.45501 11.138 6.19466Z" fill="#FF9938"/>
|
|
4
|
+
</g>
|
|
5
|
+
</svg>
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ThinkingOrb } from "thinking-orbs";
|
|
2
|
+
import { useCloudOsMode } from "../internal/theme-context";
|
|
3
|
+
import type { CloudOsActivity } from "./transcript-model";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 回合存活期间持续显示的全局活动指示。
|
|
7
|
+
*
|
|
8
|
+
* 用 thinking-orbs 的 canvas 球而不是一行 shimmer 文字:动画本身就是「还活着」
|
|
9
|
+
* 的信号,文案只负责说清在干什么(`deriveTurnActivity` 给出的九种之一)。
|
|
10
|
+
*
|
|
11
|
+
* 和 `src/status.tsx` 的 `ThinkingIndicator` 是同一个上游球、同一套 role/aria
|
|
12
|
+
* 形状,但**不共用实现** —— cloud-os 不 import `../src`(见 cloud-os/README.md,
|
|
13
|
+
* 由 check-chat-presentation-boundary.mjs 守着),文案也走 cloud-os 自己的
|
|
14
|
+
* 工具种类表,不是 src 那套工具名正则。
|
|
15
|
+
*/
|
|
16
|
+
export function CloudOsActivityIndicator({ state, label }: CloudOsActivity) {
|
|
17
|
+
// 必须显式传 theme,不能用它的 `auto`:auto 先扫祖先的 data-theme / .dark / .light,
|
|
18
|
+
// 而本仓的浅色**是「没有 .dark」**,扫不到任何标记 → 它回落到
|
|
19
|
+
// prefers-color-scheme,也就是**操作系统**的深浅。系统深色 + 应用浅色时,
|
|
20
|
+
// 球会在浅底上画浅墨,几乎看不见。cloud-os 子树的深浅事实源是这个 context
|
|
21
|
+
// (CloudOsRoot 从宿主的 useThemeMode() 拿到),直接用它。
|
|
22
|
+
const mode = useCloudOsMode();
|
|
23
|
+
return (
|
|
24
|
+
<div className="cos-activity" role="status" aria-label={label}>
|
|
25
|
+
<ThinkingOrb state={state} size={20} theme={mode} aria-hidden="true" />
|
|
26
|
+
<span>{label}</span>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -5,15 +5,16 @@ import {
|
|
|
5
5
|
CaretRight,
|
|
6
6
|
Check,
|
|
7
7
|
Copy,
|
|
8
|
-
File as FileIcon,
|
|
9
8
|
Terminal,
|
|
10
9
|
} from "@phosphor-icons/react";
|
|
11
10
|
import {
|
|
11
|
+
Fragment,
|
|
12
12
|
useCallback,
|
|
13
13
|
useEffect,
|
|
14
14
|
useMemo,
|
|
15
15
|
useRef,
|
|
16
16
|
useState,
|
|
17
|
+
type ReactNode,
|
|
17
18
|
} from "react";
|
|
18
19
|
import { Tooltip } from "../primitives/tooltip";
|
|
19
20
|
import { WorkshopIconButton } from "../primitives/workshop-controls";
|
|
@@ -33,8 +34,15 @@ import {
|
|
|
33
34
|
type ApprovalDecision,
|
|
34
35
|
} from "./rich-blocks";
|
|
35
36
|
import { ThinkingTraceRow, ToolGroupRow } from "./tool-rows";
|
|
37
|
+
import { CloudOsActivityIndicator } from "./activity-indicator";
|
|
38
|
+
import { CapabilityChip } from "../capability-chip";
|
|
39
|
+
import {
|
|
40
|
+
renderFileView,
|
|
41
|
+
type FileRenderer,
|
|
42
|
+
} from "../file-view";
|
|
36
43
|
import {
|
|
37
44
|
buildCloudOsEntries,
|
|
45
|
+
deriveTurnActivity,
|
|
38
46
|
formatClockTime,
|
|
39
47
|
formatFullTimestamp,
|
|
40
48
|
rhythmTopClass,
|
|
@@ -46,9 +54,11 @@ export type CloudOsChatStatus = "ready" | "submitted" | "streaming" | "error";
|
|
|
46
54
|
export interface CloudOsChatMessagesProps {
|
|
47
55
|
messages: readonly UIMessage[];
|
|
48
56
|
status: CloudOsChatStatus;
|
|
49
|
-
|
|
57
|
+
/** 权威 Turn 生命周期;提供时替代本地 status 推断。 */
|
|
58
|
+
turnActive?: boolean;
|
|
50
59
|
isRecovering?: boolean;
|
|
51
60
|
recoveryStatusLabel?: string;
|
|
61
|
+
awaitingApproval?: boolean;
|
|
52
62
|
/** 有 pending steer 时,活动占位说明「正在等当前步骤收尾」。 */
|
|
53
63
|
hasPendingSteer?: boolean;
|
|
54
64
|
/** 首帧就带着待发消息:此时不显示空会话欢迎语。 */
|
|
@@ -58,6 +68,20 @@ export interface CloudOsChatMessagesProps {
|
|
|
58
68
|
/** 把某个 part 映射成待授权 id;返回 undefined 表示不在此处渲染授权。 */
|
|
59
69
|
approvalIdOf?: (part: unknown) => string | undefined;
|
|
60
70
|
approvalsDisabled?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* 用宿主自己的卡片渲染某条待授权。返回 undefined 就退回内置的通用授权行。
|
|
73
|
+
*
|
|
74
|
+
* 存在的理由:授权行只认得工具名和入参,说不出「这次要花多少钱、买的是什么」。
|
|
75
|
+
* 需要在决定之前把这些摆到人面前的宿主,得自己画那张卡。
|
|
76
|
+
*/
|
|
77
|
+
renderApproval?: (approval: {
|
|
78
|
+
approvalId: string;
|
|
79
|
+
toolName: string;
|
|
80
|
+
input: Record<string, unknown>;
|
|
81
|
+
disabled: boolean;
|
|
82
|
+
}) => ReactNode;
|
|
83
|
+
/** 接管消息里的文件视图;未提供时使用包内 FileView。 */
|
|
84
|
+
renderFile?: FileRenderer;
|
|
61
85
|
/** 把消息里的 `sandbox:/workspace/...` 之类地址翻译成可访问 URL。 */
|
|
62
86
|
resolveUrl?: CloudOsUrlResolver;
|
|
63
87
|
/** 消息区宽度是否收敛到 920px 居中(GadgetEditor 里叫 constrainChatWidth)。 */
|
|
@@ -66,7 +90,11 @@ export interface CloudOsChatMessagesProps {
|
|
|
66
90
|
onRetry?: () => void;
|
|
67
91
|
onApprove?: (approvalId: string, decision: ApprovalDecision) => void;
|
|
68
92
|
onSuggestion?: (text: string) => void;
|
|
69
|
-
|
|
93
|
+
/**
|
|
94
|
+
* 把交互卡片上的操作回写成那次工具调用的结果(缺省 → 卡片不可操作)。
|
|
95
|
+
* 卡片不再知道「答案怎么变成消息」—— 它只认自己的 toolCallId。
|
|
96
|
+
*/
|
|
97
|
+
onRespond?: (toolCallId: string, response: unknown) => Promise<boolean>;
|
|
70
98
|
/** 点击定时任务卡片:宿主决定跳到哪(缺省 → 卡片不可点)。 */
|
|
71
99
|
onOpenSchedule?: (scheduleId: string) => void;
|
|
72
100
|
onCopy?: (text: string) => void;
|
|
@@ -98,43 +126,54 @@ function UserBubble({
|
|
|
98
126
|
entry,
|
|
99
127
|
onCopy,
|
|
100
128
|
copied,
|
|
129
|
+
renderFile,
|
|
101
130
|
resolveUrl,
|
|
102
131
|
}: {
|
|
103
132
|
entry: Extract<CloudOsEntry, { type: "user" }>;
|
|
104
133
|
onCopy: (text: string) => void;
|
|
105
134
|
copied: boolean;
|
|
135
|
+
renderFile?: FileRenderer;
|
|
106
136
|
resolveUrl?: CloudOsUrlResolver;
|
|
107
137
|
}) {
|
|
108
138
|
return (
|
|
109
139
|
<div className="group/message relative flex flex-col items-end">
|
|
140
|
+
{entry.capabilities.length > 0 && (
|
|
141
|
+
<div className="mb-1.5 flex max-w-[min(680px,78%)] flex-wrap justify-end gap-1.5">
|
|
142
|
+
{entry.capabilities.map((capability) => (
|
|
143
|
+
<CapabilityChip
|
|
144
|
+
key={`${capability.kind}:${capability.name}`}
|
|
145
|
+
kind={capability.kind}
|
|
146
|
+
label={capability.label}
|
|
147
|
+
/>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
110
151
|
{entry.attachments.length > 0 && (
|
|
111
|
-
<div className="mb-1.5 flex max-w-[min(680px,78%)] flex-wrap justify-end gap-2">
|
|
112
|
-
{entry.attachments.map((attachment, index) =>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
152
|
+
<div className="mb-1.5 flex max-w-[min(680px,78%)] flex-wrap items-start justify-end gap-2">
|
|
153
|
+
{entry.attachments.map((attachment, index) => {
|
|
154
|
+
const view = renderFileView(renderFile, {
|
|
155
|
+
file: {
|
|
156
|
+
...attachment,
|
|
157
|
+
url: resolveUrl?.(attachment.url) ?? attachment.url,
|
|
158
|
+
},
|
|
159
|
+
placement: "user-message",
|
|
160
|
+
});
|
|
161
|
+
return attachment.mediaType?.startsWith("image/") ? (
|
|
162
|
+
<Fragment key={`${attachment.url}:${index}`}>{view}</Fragment>
|
|
120
163
|
) : (
|
|
121
164
|
<span
|
|
122
165
|
key={`${attachment.url}:${index}`}
|
|
123
|
-
className="
|
|
166
|
+
className="flex basis-full justify-end"
|
|
124
167
|
>
|
|
125
|
-
|
|
126
|
-
{attachment.filename ?? "Attachment"}
|
|
168
|
+
{view}
|
|
127
169
|
</span>
|
|
128
|
-
)
|
|
129
|
-
)}
|
|
170
|
+
);
|
|
171
|
+
})}
|
|
130
172
|
</div>
|
|
131
173
|
)}
|
|
132
174
|
{entry.text && (
|
|
133
|
-
<div className="themed-user-bubble-shadow cos-markdown w-fit max-w-[min(680px,78%)] rounded-[
|
|
134
|
-
{
|
|
135
|
-
<div className="whitespace-pre-wrap">
|
|
136
|
-
<MarkdownMessage message={entry.text} resolveUrl={resolveUrl} />
|
|
137
|
-
</div>
|
|
175
|
+
<div className="themed-user-bubble-shadow cos-markdown cos-user-markdown w-fit max-w-[min(680px,78%)] rounded-[12px] border border-transparent bg-kumo-bubble-user px-4 py-2.5 text-[14px] leading-[22px] tracking-[-0.25px] text-kumo-default">
|
|
176
|
+
<MarkdownMessage message={entry.text} resolveUrl={resolveUrl} />
|
|
138
177
|
</div>
|
|
139
178
|
)}
|
|
140
179
|
<div className="mt-0.5 flex items-center justify-end gap-2 pr-1 text-[11px] leading-4 text-kumo-inactive opacity-0 transition-opacity duration-150 ease-out group-hover/message:opacity-100 group-focus-within/message:opacity-100">
|
|
@@ -164,22 +203,25 @@ function UserBubble({
|
|
|
164
203
|
export function CloudOsChatMessages({
|
|
165
204
|
messages,
|
|
166
205
|
status,
|
|
167
|
-
|
|
206
|
+
turnActive,
|
|
168
207
|
isRecovering = false,
|
|
169
208
|
recoveryStatusLabel,
|
|
209
|
+
awaitingApproval = false,
|
|
170
210
|
hasPendingSteer = false,
|
|
171
211
|
startedWithPending = false,
|
|
172
212
|
error,
|
|
173
213
|
showThinkingTraces = true,
|
|
174
214
|
approvalIdOf,
|
|
175
215
|
approvalsDisabled = false,
|
|
216
|
+
renderApproval,
|
|
217
|
+
renderFile,
|
|
176
218
|
resolveUrl,
|
|
177
219
|
constrainWidth = true,
|
|
178
220
|
emptyTitle = "What are we working on?",
|
|
179
221
|
onRetry,
|
|
180
222
|
onApprove,
|
|
181
223
|
onSuggestion,
|
|
182
|
-
|
|
224
|
+
onRespond,
|
|
183
225
|
onOpenSchedule,
|
|
184
226
|
onCopy,
|
|
185
227
|
}: CloudOsChatMessagesProps) {
|
|
@@ -193,7 +235,8 @@ export function CloudOsChatMessages({
|
|
|
193
235
|
ReadonlySet<string>
|
|
194
236
|
>(() => new Set());
|
|
195
237
|
const { copied, copy } = useCopyAction();
|
|
196
|
-
const isActive =
|
|
238
|
+
const isActive =
|
|
239
|
+
turnActive ?? (status === "submitted" || status === "streaming");
|
|
197
240
|
|
|
198
241
|
const entries = useMemo(
|
|
199
242
|
() =>
|
|
@@ -246,14 +289,13 @@ export function CloudOsChatMessages({
|
|
|
246
289
|
element.scrollTo({ top: element.scrollHeight });
|
|
247
290
|
}, [isRecovering, messages, status, showScrollButton]);
|
|
248
291
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
lastEntry.type === "slashCommand");
|
|
292
|
+
const activity = deriveTurnActivity(entries, {
|
|
293
|
+
isActive,
|
|
294
|
+
isRecovering,
|
|
295
|
+
recoveryStatusLabel,
|
|
296
|
+
awaitingApproval,
|
|
297
|
+
hasPendingSteer,
|
|
298
|
+
});
|
|
257
299
|
const copyText = (text: string) => {
|
|
258
300
|
copy(text);
|
|
259
301
|
onCopy?.(text);
|
|
@@ -264,7 +306,7 @@ export function CloudOsChatMessages({
|
|
|
264
306
|
className="relative flex min-h-0 flex-1 flex-col overflow-hidden bg-kumo-base"
|
|
265
307
|
data-cloud-os-transcript=""
|
|
266
308
|
data-status={status}
|
|
267
|
-
aria-busy={isActive ||
|
|
309
|
+
aria-busy={isActive || isRecovering}
|
|
268
310
|
>
|
|
269
311
|
<div
|
|
270
312
|
ref={scrollRef}
|
|
@@ -279,7 +321,7 @@ export function CloudOsChatMessages({
|
|
|
279
321
|
constrainWidth ? "mx-auto w-full max-w-[920px]" : ""
|
|
280
322
|
}`}
|
|
281
323
|
>
|
|
282
|
-
{entries.length === 0 && !startedWithPending &&
|
|
324
|
+
{entries.length === 0 && !startedWithPending && (
|
|
283
325
|
<div className="flex min-h-56 items-center justify-center text-center text-[24px] leading-8 font-semibold tracking-[-0.4px] text-kumo-default">
|
|
284
326
|
{emptyTitle}
|
|
285
327
|
</div>
|
|
@@ -295,6 +337,7 @@ export function CloudOsChatMessages({
|
|
|
295
337
|
entry={entry}
|
|
296
338
|
onCopy={copyText}
|
|
297
339
|
copied={copied === entry.text}
|
|
340
|
+
renderFile={renderFile}
|
|
298
341
|
resolveUrl={resolveUrl}
|
|
299
342
|
/>
|
|
300
343
|
</div>
|
|
@@ -304,7 +347,7 @@ export function CloudOsChatMessages({
|
|
|
304
347
|
if (entry.type === "slashCommand") {
|
|
305
348
|
return (
|
|
306
349
|
<div key={entry.key} className={`${topClass} flex flex-col items-end`}>
|
|
307
|
-
<div className="themed-user-bubble-shadow w-fit max-w-[min(680px,78%)] rounded-[
|
|
350
|
+
<div className="themed-user-bubble-shadow w-fit max-w-[min(680px,78%)] rounded-[12px] border border-transparent bg-kumo-bubble-user px-4 py-2.5 text-[14px] leading-[22px] tracking-[-0.25px] text-kumo-brand">
|
|
308
351
|
<span className="whitespace-pre-wrap font-mono">{entry.text}</span>
|
|
309
352
|
</div>
|
|
310
353
|
</div>
|
|
@@ -395,9 +438,17 @@ export function CloudOsChatMessages({
|
|
|
395
438
|
switch (block.kind) {
|
|
396
439
|
case "reasoning":
|
|
397
440
|
return (
|
|
398
|
-
<ThinkingTraceRow
|
|
441
|
+
<ThinkingTraceRow
|
|
442
|
+
key={block.key}
|
|
443
|
+
reasoning={block.text}
|
|
444
|
+
running={block.running}
|
|
445
|
+
/>
|
|
399
446
|
);
|
|
400
|
-
case "text":
|
|
447
|
+
case "text": {
|
|
448
|
+
const streaming =
|
|
449
|
+
isActive &&
|
|
450
|
+
entryIndex === entries.length - 1 &&
|
|
451
|
+
block === entry.blocks.at(-1);
|
|
401
452
|
return (
|
|
402
453
|
<div
|
|
403
454
|
key={block.key}
|
|
@@ -406,9 +457,11 @@ export function CloudOsChatMessages({
|
|
|
406
457
|
<MarkdownMessage
|
|
407
458
|
message={block.text}
|
|
408
459
|
resolveUrl={resolveUrl}
|
|
460
|
+
streaming={streaming}
|
|
409
461
|
/>
|
|
410
462
|
</div>
|
|
411
463
|
);
|
|
464
|
+
}
|
|
412
465
|
case "toolGroup":
|
|
413
466
|
return (
|
|
414
467
|
<ToolGroupRow
|
|
@@ -432,8 +485,7 @@ export function CloudOsChatMessages({
|
|
|
432
485
|
<AskUserBlock
|
|
433
486
|
key={block.key}
|
|
434
487
|
call={block.call}
|
|
435
|
-
|
|
436
|
-
onAnswer={(payload) => onAnswer?.(payload)}
|
|
488
|
+
onRespond={onRespond}
|
|
437
489
|
/>
|
|
438
490
|
);
|
|
439
491
|
case "suggestions":
|
|
@@ -452,7 +504,18 @@ export function CloudOsChatMessages({
|
|
|
452
504
|
onOpen={onOpenSchedule}
|
|
453
505
|
/>
|
|
454
506
|
);
|
|
455
|
-
case "approval":
|
|
507
|
+
case "approval": {
|
|
508
|
+
const custom = renderApproval?.({
|
|
509
|
+
approvalId: block.approvalId,
|
|
510
|
+
toolName: block.call.toolName,
|
|
511
|
+
input: block.call.input,
|
|
512
|
+
disabled: approvalsDisabled,
|
|
513
|
+
});
|
|
514
|
+
if (custom !== undefined && custom !== null) {
|
|
515
|
+
return (
|
|
516
|
+
<Fragment key={block.key}>{custom}</Fragment>
|
|
517
|
+
);
|
|
518
|
+
}
|
|
456
519
|
return (
|
|
457
520
|
<ApprovalBlock
|
|
458
521
|
key={block.key}
|
|
@@ -462,6 +525,7 @@ export function CloudOsChatMessages({
|
|
|
462
525
|
onApprove={(id, decision) => onApprove?.(id, decision)}
|
|
463
526
|
/>
|
|
464
527
|
);
|
|
528
|
+
}
|
|
465
529
|
case "parallel":
|
|
466
530
|
return (
|
|
467
531
|
<ParallelBlock
|
|
@@ -476,13 +540,29 @@ export function CloudOsChatMessages({
|
|
|
476
540
|
);
|
|
477
541
|
case "image":
|
|
478
542
|
return (
|
|
479
|
-
<
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
543
|
+
<Fragment key={block.key}>
|
|
544
|
+
{renderFileView(renderFile, {
|
|
545
|
+
file: {
|
|
546
|
+
url: resolveUrl?.(block.src) ?? block.src,
|
|
547
|
+
filename: block.alt,
|
|
548
|
+
mediaType: block.mediaType ?? "image/*",
|
|
549
|
+
},
|
|
550
|
+
placement: "assistant-message",
|
|
551
|
+
})}
|
|
552
|
+
</Fragment>
|
|
553
|
+
);
|
|
554
|
+
case "file":
|
|
555
|
+
return (
|
|
556
|
+
<Fragment key={block.key}>
|
|
557
|
+
{renderFileView(renderFile, {
|
|
558
|
+
file: {
|
|
559
|
+
...block.file,
|
|
560
|
+
url:
|
|
561
|
+
resolveUrl?.(block.file.url) ?? block.file.url,
|
|
562
|
+
},
|
|
563
|
+
placement: "assistant-message",
|
|
564
|
+
})}
|
|
565
|
+
</Fragment>
|
|
486
566
|
);
|
|
487
567
|
case "error":
|
|
488
568
|
return (
|
|
@@ -543,19 +623,9 @@ export function CloudOsChatMessages({
|
|
|
543
623
|
);
|
|
544
624
|
})}
|
|
545
625
|
|
|
546
|
-
{
|
|
547
|
-
<div className="mt-4 flex max-w-[860px] items-center gap-3 px-1.5 py-1 text-[14px] leading-5 tracking-[-0.25px]">
|
|
548
|
-
<span className="cos-thinking-shimmer">
|
|
549
|
-
{recoveryStatusLabel ?? "Reconnecting…"}
|
|
550
|
-
</span>
|
|
551
|
-
</div>
|
|
552
|
-
)}
|
|
553
|
-
|
|
554
|
-
{awaitingFirstOutput && (
|
|
626
|
+
{activity && (
|
|
555
627
|
<div className="mt-5 inline-flex px-1.5 py-1 text-[14px] leading-5 tracking-[-0.25px]">
|
|
556
|
-
<
|
|
557
|
-
{hasPendingSteer ? "Waiting for the current step to finish…" : "Thinking"}
|
|
558
|
-
</span>
|
|
628
|
+
<CloudOsActivityIndicator {...activity} />
|
|
559
629
|
</div>
|
|
560
630
|
)}
|
|
561
631
|
|
|
@@ -591,16 +661,6 @@ export function CloudOsChatMessages({
|
|
|
591
661
|
<ArrowDown size={15} />
|
|
592
662
|
</WorkshopIconButton>
|
|
593
663
|
|
|
594
|
-
{isHydrating && (
|
|
595
|
-
<div className="absolute inset-0 z-30 grid place-items-center bg-kumo-base/90">
|
|
596
|
-
<div className="flex flex-col items-center gap-3">
|
|
597
|
-
<div className="h-6 w-6 animate-spin rounded-full border-2 border-kumo-brand border-t-transparent" />
|
|
598
|
-
<p className="m-0 text-[13px] leading-[18px] text-kumo-subtle">
|
|
599
|
-
Loading conversation…
|
|
600
|
-
</p>
|
|
601
|
-
</div>
|
|
602
|
-
</div>
|
|
603
|
-
)}
|
|
604
664
|
</div>
|
|
605
665
|
);
|
|
606
666
|
}
|