@morlay/ui-conversation-message-actions 0.1.3 → 0.1.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.
- package/dist/client.cjs +16461 -881
- package/dist/{rolldown-runtime-BMI-E3GI.mjs → rolldown-runtime-Dr7-SnC6.mjs} +2 -1
- package/dist/testing.mjs +1 -1
- package/package.json +24 -24
- package/src/client/chat-node/MessageItem.tsx +37 -18
- package/src/client/chat-node/register.ts +1 -1
- package/src/client/controller.ts +3 -2
- package/src/client/index.ts +4 -7
- package/src/client/chat-node/MessageIconActions.styles.ts +0 -76
- package/src/client/chat-node/MessageIconActions.tsx +0 -166
- package/src/client/chat-node/MessageItem.styles.ts +0 -248
- package/src/client/chat-node/message-chrome.ts +0 -44
- package/src/client/chat-node/use-calendar-day.ts +0 -19
- package/src/client/composer-stats/StatsPills.styles.ts +0 -57
- package/src/client/composer-stats/StatsPills.tsx +0 -362
- package/src/client/composer-stats/register.ts +0 -17
- package/src/client/composer-stats/stat-dialog.styles.ts +0 -70
- package/src/client/composer-stats/stat-dialog.ts +0 -52
- package/src/client/composer-stats/token-format.ts +0 -84
- package/src/client/composer-stats/turn-reading.ts +0 -35
|
@@ -34,6 +34,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
34
34
|
}
|
|
35
35
|
return to;
|
|
36
36
|
};
|
|
37
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
37
38
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
38
39
|
value: mod,
|
|
39
40
|
enumerable: true
|
|
@@ -41,4 +42,4 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
41
42
|
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
42
43
|
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
43
44
|
//#endregion
|
|
44
|
-
export {
|
|
45
|
+
export { __require as a, __reExport as i, __esmMin as n, __toCommonJS as o, __exportAll as r, __toESM as s, __commonJSMin as t };
|
package/dist/testing.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as __require, n as __esmMin, o as __toCommonJS, r as __exportAll, s as __toESM, t as __commonJSMin } from "./rolldown-runtime-Dr7-SnC6.mjs";
|
|
2
2
|
import { t as SESSION_EDITOR_PATHS } from "./shared-DG86bLrc.mjs";
|
|
3
3
|
import { SessionEditor } from "./index.mjs";
|
|
4
4
|
import { Context, Service } from "@deepseek-ai/cordis";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morlay/ui-conversation-message-actions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Rewind / retry / recall / fork orchestration (host) plus the browser half — replaces the conversation.chat.node user / steering rendering and overrides the conversation.composer.dock stats row.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"conversation-chat-node",
|
|
@@ -38,12 +38,13 @@
|
|
|
38
38
|
"./locale/*.json": "./locale/*.json"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@morlay/session-branch": "0.1.
|
|
41
|
+
"@morlay/session-branch": "0.1.5"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@deepseek-ai/dsh-
|
|
45
|
-
"@
|
|
46
|
-
"@morlay/
|
|
44
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.2",
|
|
45
|
+
"@deepseek-ai/dsh-session-stats": "0.2.0-rc.2",
|
|
46
|
+
"@morlay/dsh-client-ui-primitives": "0.0.8",
|
|
47
|
+
"@morlay/session-rdb": "0.1.5",
|
|
47
48
|
"@types/react": "~18.3.1",
|
|
48
49
|
"@types/react-dom": "~18.3.0",
|
|
49
50
|
"react": "^18.2.0",
|
|
@@ -51,24 +52,23 @@
|
|
|
51
52
|
},
|
|
52
53
|
"peerDependencies": {
|
|
53
54
|
"@deepseek-ai/cordis": "^4.0.4",
|
|
54
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.2.0-rc.
|
|
55
|
-
"@deepseek-ai/dsh-attachment": "^0.2.0-rc.
|
|
56
|
-
"@deepseek-ai/dsh-client-connection": "^0.2.0-rc.
|
|
57
|
-
"@deepseek-ai/dsh-client-store": "^0.2.0-rc.
|
|
58
|
-
"@deepseek-ai/dsh-client-ui-attachment": "^0.2.0-rc.
|
|
59
|
-
"@deepseek-ai/dsh-client-ui-chat": "^0.2.0-rc.
|
|
60
|
-
"@deepseek-ai/dsh-client-ui-
|
|
61
|
-
"@deepseek-ai/dsh-client-ui-
|
|
62
|
-
"@deepseek-ai/dsh-client-ui-
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-
|
|
64
|
-
"@deepseek-ai/dsh-
|
|
65
|
-
"@deepseek-ai/dsh-
|
|
66
|
-
"@deepseek-ai/dsh-
|
|
67
|
-
"@deepseek-ai/dsh-session
|
|
68
|
-
"@deepseek-ai/dsh-session-
|
|
69
|
-
"@deepseek-ai/dsh-
|
|
70
|
-
"@
|
|
71
|
-
"@morlay/dsh-client-ui-primitives": "0.0.6"
|
|
55
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.2.0-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-attachment": "^0.2.0-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-client-connection": "^0.2.0-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-client-store": "^0.2.0-rc.2",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-attachment": "^0.2.0-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-chat": "^0.2.0-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.2.0-rc.2",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.2.0-rc.2",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.2.0-rc.2",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.2",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-workspace": "^0.2.0-rc.2",
|
|
66
|
+
"@deepseek-ai/dsh-invariants": "^0.2.0-rc.2",
|
|
67
|
+
"@deepseek-ai/dsh-llm": "^0.2.0-rc.2",
|
|
68
|
+
"@deepseek-ai/dsh-session": "^0.2.0-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-session-persistence": "^0.2.0-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-session-projection": "^0.2.0-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-token-meter": "^0.2.0-rc.2"
|
|
72
72
|
},
|
|
73
73
|
"dsh": {
|
|
74
74
|
"client": {
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"@deepseek-ai/dsh-client-connection",
|
|
78
78
|
"@deepseek-ai/dsh-client-store",
|
|
79
79
|
"@deepseek-ai/dsh-client-ui-workspace",
|
|
80
|
-
"@
|
|
80
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
81
81
|
"@deepseek-ai/dsh-client-ui-chat"
|
|
82
82
|
]
|
|
83
83
|
}
|
|
@@ -2,21 +2,28 @@
|
|
|
2
2
|
// 在消息动作行上提供 edit / retry;其余 key 由官方内置渲染器处理。
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
+
Bubble,
|
|
6
|
+
Button,
|
|
7
|
+
IconButton,
|
|
8
|
+
IconEditOutlineRegular,
|
|
9
|
+
IconRefreshOutlineRegular,
|
|
10
|
+
JsonBlock,
|
|
5
11
|
markdownLabels,
|
|
12
|
+
Modal,
|
|
6
13
|
ReferenceMarkdown,
|
|
7
|
-
|
|
14
|
+
Row,
|
|
15
|
+
Stack,
|
|
8
16
|
type ReferenceActions,
|
|
9
17
|
} from "@morlay/dsh-client-ui-primitives/client";
|
|
10
18
|
import { memo, useMemo, useState } from "react";
|
|
11
19
|
import type { ReactNode } from "react";
|
|
12
20
|
import type { InjectFace } from "@deepseek-ai/dsh-client-ui-slots";
|
|
13
|
-
import { Button, Modal } from "@deepseek-ai/dsh-client-ui-primitives";
|
|
14
21
|
import type { UserMessageNode } from "@deepseek-ai/dsh-client-ui-chat/client";
|
|
15
|
-
import { JsonBlock } from "@deepseek-ai/dsh-client-ui-primitives";
|
|
16
22
|
import type { ChatNodeViewProps, ChatViewSlotProps } from "@deepseek-ai/dsh-client-ui-chat/client";
|
|
17
|
-
import type { RenderMessageImages } from "@
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
import type { RenderMessageImages } from "@deepseek-ai/dsh-client-ui-conversation/client";
|
|
24
|
+
// 动作行整条用上游那份:复制 / 分支 / 时间标签的悬停显形都归它,我们只经它的 `extraActions` 注入自己的两个入口。
|
|
25
|
+
// 走 vendor 源码的相对路径(与 `dsh-subagent` 接上游源码同一种写法),不经包出口。
|
|
26
|
+
import { MessageIconActions } from "../../../../../../vendor/deepseek-harness/packages/client/ui-chat/src/client/chat/MessageIconActions.tsx";
|
|
20
27
|
import type { EditableMessageBlock } from "../../shared.ts";
|
|
21
28
|
import type { SessionEditorFace } from "../controller.ts";
|
|
22
29
|
|
|
@@ -63,11 +70,11 @@ function UserStyleBubble({
|
|
|
63
70
|
const labels = useMemo(() => markdownLabels(t), [t]);
|
|
64
71
|
const showBubble = text !== "" || rest.length > 0;
|
|
65
72
|
return (
|
|
66
|
-
<
|
|
67
|
-
<
|
|
73
|
+
<Stack align="end" gap={6} data-time-hover-root>
|
|
74
|
+
<Stack align="end" gap={8} narrow>
|
|
68
75
|
{renderMessageImages({ images, align: "end" })}
|
|
69
76
|
{showBubble && (
|
|
70
|
-
<
|
|
77
|
+
<Bubble>
|
|
71
78
|
<ReferenceMarkdown text={text} labels={labels} actions={references} />
|
|
72
79
|
{rest.map((block, i) => (
|
|
73
80
|
<JsonBlock
|
|
@@ -77,11 +84,11 @@ function UserStyleBubble({
|
|
|
77
84
|
truncatedLabel={truncated}
|
|
78
85
|
/>
|
|
79
86
|
))}
|
|
80
|
-
</
|
|
87
|
+
</Bubble>
|
|
81
88
|
)}
|
|
82
|
-
</
|
|
89
|
+
</Stack>
|
|
83
90
|
{actions?.(text)}
|
|
84
|
-
</
|
|
91
|
+
</Stack>
|
|
85
92
|
);
|
|
86
93
|
}
|
|
87
94
|
|
|
@@ -147,7 +154,7 @@ export const UserMessageNodeView = memo(function UserMessageNodeView({
|
|
|
147
154
|
closeLabel="关闭"
|
|
148
155
|
description="将撤回该消息及其之后的对话内容到输入框,请修改后重新发送。"
|
|
149
156
|
footer={
|
|
150
|
-
<
|
|
157
|
+
<Row gap={8} justify="end">
|
|
151
158
|
<Button variant="outline" onClick={() => setConfirmingRecall(null)}>
|
|
152
159
|
取消
|
|
153
160
|
</Button>
|
|
@@ -161,7 +168,7 @@ export const UserMessageNodeView = memo(function UserMessageNodeView({
|
|
|
161
168
|
>
|
|
162
169
|
撤回并编辑
|
|
163
170
|
</Button>
|
|
164
|
-
</
|
|
171
|
+
</Row>
|
|
165
172
|
}
|
|
166
173
|
/>
|
|
167
174
|
)}
|
|
@@ -173,7 +180,7 @@ export const UserMessageNodeView = memo(function UserMessageNodeView({
|
|
|
173
180
|
closeLabel="关闭"
|
|
174
181
|
description={`将重新生成第 ${turn} 轮的回复,并抛弃该回合之后的内容。`}
|
|
175
182
|
footer={
|
|
176
|
-
<
|
|
183
|
+
<Row gap={8} justify="end">
|
|
177
184
|
<Button variant="outline" onClick={() => setConfirmingRetry(false)}>
|
|
178
185
|
取消
|
|
179
186
|
</Button>
|
|
@@ -186,7 +193,7 @@ export const UserMessageNodeView = memo(function UserMessageNodeView({
|
|
|
186
193
|
>
|
|
187
194
|
确认重试
|
|
188
195
|
</Button>
|
|
189
|
-
</
|
|
196
|
+
</Row>
|
|
190
197
|
}
|
|
191
198
|
/>
|
|
192
199
|
)}
|
|
@@ -201,8 +208,20 @@ export const UserMessageNodeView = memo(function UserMessageNodeView({
|
|
|
201
208
|
time={data.time}
|
|
202
209
|
clock="start"
|
|
203
210
|
t={t}
|
|
204
|
-
|
|
205
|
-
|
|
211
|
+
extraActions={
|
|
212
|
+
<>
|
|
213
|
+
{onEdit === undefined ? null : (
|
|
214
|
+
<IconButton shape="circle" label="编辑" onClick={onEdit}>
|
|
215
|
+
<IconEditOutlineRegular />
|
|
216
|
+
</IconButton>
|
|
217
|
+
)}
|
|
218
|
+
{onRetry === undefined ? null : (
|
|
219
|
+
<IconButton shape="circle" label="重试此回合" onClick={onRetry}>
|
|
220
|
+
<IconRefreshOutlineRegular />
|
|
221
|
+
</IconButton>
|
|
222
|
+
)}
|
|
223
|
+
</>
|
|
224
|
+
}
|
|
206
225
|
/>
|
|
207
226
|
)}
|
|
208
227
|
/>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Context } from "@deepseek-ai/cordis";
|
|
2
2
|
import type { SessionId } from "@deepseek-ai/dsh-session";
|
|
3
|
-
import "@
|
|
3
|
+
import type {} from "@deepseek-ai/dsh-client-ui-conversation/client";
|
|
4
4
|
import type {} from "@deepseek-ai/dsh-client-ui-chat/client";
|
|
5
5
|
import type {} from "@deepseek-ai/dsh-client-ui-renderer/client";
|
|
6
6
|
import { UserMessageNodeView } from "./MessageItem.tsx";
|
package/src/client/controller.ts
CHANGED
|
@@ -123,6 +123,7 @@ export class SessionEditorController {
|
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// 撤回回填:写官方输入契约的草稿(`setDraft`)。拿不到输入面(会话没打开 / 面未就绪)就只撤回、不回填。
|
|
126
127
|
private setComposerDraft(text: string): void {
|
|
127
128
|
if (this.sessions.binding(this.sessionId) === undefined) return;
|
|
128
129
|
const scoped = this.sessions.scope(this.sessionId);
|
|
@@ -130,10 +131,10 @@ export class SessionEditorController {
|
|
|
130
131
|
const conversation = scoped.get("conversation") as
|
|
131
132
|
| {
|
|
132
133
|
input?: {
|
|
133
|
-
for(ctx: ClientContext): {
|
|
134
|
+
for(ctx: ClientContext): { setDraft(draft: string): void };
|
|
134
135
|
};
|
|
135
136
|
}
|
|
136
137
|
| undefined;
|
|
137
|
-
conversation?.input?.for(scoped).
|
|
138
|
+
conversation?.input?.for(scoped).setDraft(text);
|
|
138
139
|
}
|
|
139
140
|
}
|
package/src/client/index.ts
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
-
import type {} from "@
|
|
2
|
+
import type {} from "@deepseek-ai/dsh-client-ui-conversation/client";
|
|
3
3
|
import type {} from "@deepseek-ai/dsh-client-ui-chat/client";
|
|
4
4
|
import type {} from "@deepseek-ai/dsh-client-ui-renderer/client";
|
|
5
5
|
import type { SessionId } from "@deepseek-ai/dsh-session";
|
|
6
6
|
import { SessionEditorController } from "./controller.ts";
|
|
7
7
|
import { registerChatNodeRenderers } from "./chat-node/register.ts";
|
|
8
|
-
import {
|
|
9
|
-
import { styling } from "@morlay/dsh-client-ui-primitives/client";
|
|
10
|
-
import { globals as messageIconGlobals } from "./chat-node/MessageIconActions.styles.ts";
|
|
8
|
+
import { apply as installUiPrimitives } from "@morlay/dsh-client-ui-primitives/client";
|
|
11
9
|
|
|
12
10
|
export const inject = ["slots", "conversation", "connection", "sessions", "uiWorkspace"];
|
|
13
11
|
|
|
14
12
|
export function apply(ctx: Context): void {
|
|
15
|
-
|
|
13
|
+
// 基础面随本包 inline(不再是装配行):装上它提供的字典、字段槽与按行配置页;多份副本只装一次。
|
|
14
|
+
installUiPrimitives(ctx);
|
|
16
15
|
|
|
17
16
|
const controllers = new Map<SessionId, SessionEditorController>();
|
|
18
17
|
const controllerFor = (sessionId: SessionId): SessionEditorController => {
|
|
@@ -25,6 +24,4 @@ export function apply(ctx: Context): void {
|
|
|
25
24
|
};
|
|
26
25
|
|
|
27
26
|
registerChatNodeRenderers(ctx, controllerFor);
|
|
28
|
-
|
|
29
|
-
ctx.slots.inject("conversation.composer.dock", () => registerComposerStats(ctx.slots));
|
|
30
27
|
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import type { CSSProps } from "@morlay/dsh-client-ui-primitives/client";
|
|
2
|
-
|
|
3
|
-
export const styles = {
|
|
4
|
-
actions: {
|
|
5
|
-
display: "flex",
|
|
6
|
-
alignItems: "center",
|
|
7
|
-
gap: "10px",
|
|
8
|
-
height: "28px",
|
|
9
|
-
},
|
|
10
|
-
timeStart: {
|
|
11
|
-
paddingRight: "12px",
|
|
12
|
-
fontSize: "14px",
|
|
13
|
-
lineHeight: "24px",
|
|
14
|
-
color: "var(--dsw-alias-label-tertiary)",
|
|
15
|
-
whiteSpace: "nowrap",
|
|
16
|
-
},
|
|
17
|
-
timeEnd: {
|
|
18
|
-
paddingLeft: "12px",
|
|
19
|
-
fontSize: "14px",
|
|
20
|
-
lineHeight: "24px",
|
|
21
|
-
color: "var(--dsw-alias-label-tertiary)",
|
|
22
|
-
whiteSpace: "nowrap",
|
|
23
|
-
"&)": {
|
|
24
|
-
"@media (hover: hover)": {
|
|
25
|
-
opacity: "1",
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
runTimeDot: {
|
|
30
|
-
margin: "0 10px",
|
|
31
|
-
},
|
|
32
|
-
action: {
|
|
33
|
-
display: "inline-flex",
|
|
34
|
-
alignItems: "center",
|
|
35
|
-
justifyContent: "center",
|
|
36
|
-
width: "28px",
|
|
37
|
-
height: "28px",
|
|
38
|
-
padding: "6px",
|
|
39
|
-
border: "none",
|
|
40
|
-
borderRadius: "28px",
|
|
41
|
-
background: "transparent",
|
|
42
|
-
color: "var(--dsw-alias-label-tertiary)",
|
|
43
|
-
cursor: "pointer",
|
|
44
|
-
"&:hover": {
|
|
45
|
-
background: "var(--dsw-alias-interactive-bg-hover)",
|
|
46
|
-
color: "var(--dsw-alias-label-secondary)",
|
|
47
|
-
},
|
|
48
|
-
"&[data-unavailable]": {
|
|
49
|
-
cursor: "default",
|
|
50
|
-
opacity: "0.4",
|
|
51
|
-
},
|
|
52
|
-
"&[data-unavailable]:hover": {
|
|
53
|
-
background: "transparent",
|
|
54
|
-
color: "var(--dsw-alias-label-tertiary)",
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
visuallyHidden: {
|
|
58
|
-
position: "absolute",
|
|
59
|
-
width: "1px",
|
|
60
|
-
height: "1px",
|
|
61
|
-
overflow: "hidden",
|
|
62
|
-
clip: "rect(0 0 0 0)",
|
|
63
|
-
whiteSpace: "nowrap",
|
|
64
|
-
},
|
|
65
|
-
} satisfies Record<string, CSSProps>;
|
|
66
|
-
|
|
67
|
-
export const globals = {
|
|
68
|
-
"[data-time-hover-root] [data-time-label]": {
|
|
69
|
-
opacity: "0",
|
|
70
|
-
transition: "opacity 80ms ease",
|
|
71
|
-
},
|
|
72
|
-
"[data-time-hover-root]:hover :is(.timeStart, .timeEnd),\n [data-time-hover-root]:focus-within :is(.timeStart, .timeEnd)":
|
|
73
|
-
{
|
|
74
|
-
opacity: "1",
|
|
75
|
-
},
|
|
76
|
-
} satisfies Record<string, CSSProps>;
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
// Shared IconActions chrome for user and assistant messages: copy
|
|
2
|
-
// live, optional branch wiring, and an optional date-aware clock.
|
|
3
|
-
|
|
4
|
-
import { styling } from "@morlay/dsh-client-ui-primitives/client";
|
|
5
|
-
import { useCallback, useEffect, useId, useRef, useState, type ReactNode } from "react";
|
|
6
|
-
import {
|
|
7
|
-
IconBranchOutlineRegular,
|
|
8
|
-
IconCheckOutlineRegular,
|
|
9
|
-
IconCopyOutlineRegular,
|
|
10
|
-
IconEditOutlineRegular,
|
|
11
|
-
IconRefreshOutlineRegular,
|
|
12
|
-
Tooltip,
|
|
13
|
-
writeClipboard,
|
|
14
|
-
} from "@deepseek-ai/dsh-client-ui-primitives";
|
|
15
|
-
import type { ChatViewSlotProps } from "@deepseek-ai/dsh-client-ui-chat/client";
|
|
16
|
-
import { formatMessageClock } from "./message-chrome.ts";
|
|
17
|
-
import { useCalendarDay } from "./use-calendar-day.ts";
|
|
18
|
-
import { styles } from "./MessageIconActions.styles.ts";
|
|
19
|
-
|
|
20
|
-
export interface MessageIconActionsProps {
|
|
21
|
-
text: string;
|
|
22
|
-
|
|
23
|
-
time?: number | undefined;
|
|
24
|
-
|
|
25
|
-
clock: "start" | "end";
|
|
26
|
-
|
|
27
|
-
onBranch?: (() => void) | undefined;
|
|
28
|
-
|
|
29
|
-
branchUnavailable?: boolean | undefined;
|
|
30
|
-
|
|
31
|
-
className?: string | undefined;
|
|
32
|
-
|
|
33
|
-
extraActions?: ReactNode;
|
|
34
|
-
|
|
35
|
-
onEdit?: (() => void) | undefined;
|
|
36
|
-
|
|
37
|
-
onRetry?: (() => void) | undefined;
|
|
38
|
-
|
|
39
|
-
t: ChatViewSlotProps["t"];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function MessageIconActions({
|
|
43
|
-
text,
|
|
44
|
-
time,
|
|
45
|
-
clock,
|
|
46
|
-
onBranch,
|
|
47
|
-
branchUnavailable = false,
|
|
48
|
-
className,
|
|
49
|
-
extraActions,
|
|
50
|
-
onEdit,
|
|
51
|
-
onRetry,
|
|
52
|
-
t,
|
|
53
|
-
}: MessageIconActionsProps) {
|
|
54
|
-
const day = useCalendarDay();
|
|
55
|
-
const reasonId = useId();
|
|
56
|
-
// Same success chrome as CodeBlock: a short check swap after the write,
|
|
57
|
-
// gated so re-clicks during the window neither re-copy nor stack timers.
|
|
58
|
-
const [copied, setCopied] = useState(false);
|
|
59
|
-
const copyPending = useRef(false);
|
|
60
|
-
const copyTimer = useRef<number | null>(null);
|
|
61
|
-
const copyEpoch = useRef(0);
|
|
62
|
-
useEffect(
|
|
63
|
-
() => () => {
|
|
64
|
-
copyEpoch.current += 1;
|
|
65
|
-
copyPending.current = false;
|
|
66
|
-
if (copyTimer.current !== null) clearTimeout(copyTimer.current);
|
|
67
|
-
},
|
|
68
|
-
[],
|
|
69
|
-
);
|
|
70
|
-
const onCopy = useCallback(() => {
|
|
71
|
-
if (copied || copyPending.current) return;
|
|
72
|
-
const epoch = copyEpoch.current;
|
|
73
|
-
copyPending.current = true;
|
|
74
|
-
void writeClipboard(text).then((ok) => {
|
|
75
|
-
if (epoch !== copyEpoch.current) return;
|
|
76
|
-
copyPending.current = false;
|
|
77
|
-
if (!ok) return;
|
|
78
|
-
setCopied(true);
|
|
79
|
-
copyTimer.current = window.setTimeout(() => {
|
|
80
|
-
copyTimer.current = null;
|
|
81
|
-
setCopied(false);
|
|
82
|
-
}, 1000);
|
|
83
|
-
});
|
|
84
|
-
}, [copied, text]);
|
|
85
|
-
// 只剩时钟:耗时 / TTFT / tok-s 的读数在 0.1.7 已不属于消息 chrome(上游把它们收进
|
|
86
|
-
// 会话统计弹窗与轮次过程节点),上游那套聊天文案表也不再提供这三个 key。
|
|
87
|
-
const clockEl =
|
|
88
|
-
time === undefined ? null : (
|
|
89
|
-
<span
|
|
90
|
-
data-time-label=""
|
|
91
|
-
{...styling.props(clock === "start" ? styles.timeStart : styles.timeEnd)}
|
|
92
|
-
>
|
|
93
|
-
{formatMessageClock(time, t, day)}
|
|
94
|
-
</span>
|
|
95
|
-
);
|
|
96
|
-
// 外部 className 需要拼接:给 actions 一个真实类名(而不是 data-css 属性)。
|
|
97
|
-
const actionsClass = styling.className(styles.actions);
|
|
98
|
-
return (
|
|
99
|
-
<div
|
|
100
|
-
data-time-hover-root=""
|
|
101
|
-
className={className === undefined ? actionsClass : `${actionsClass} ${className}`}
|
|
102
|
-
>
|
|
103
|
-
{clock === "start" ? clockEl : null}
|
|
104
|
-
<Tooltip label={copied ? t("copied" as never) : t("copy" as never)} side="bottom">
|
|
105
|
-
<button
|
|
106
|
-
type="button"
|
|
107
|
-
{...styling.props(styles.action)}
|
|
108
|
-
aria-label={copied ? t("copied" as never) : t("copy" as never)}
|
|
109
|
-
onClick={onCopy}
|
|
110
|
-
>
|
|
111
|
-
{copied ? <IconCheckOutlineRegular /> : <IconCopyOutlineRegular />}
|
|
112
|
-
</button>
|
|
113
|
-
</Tooltip>
|
|
114
|
-
{extraActions}
|
|
115
|
-
{onEdit !== undefined && (
|
|
116
|
-
<Tooltip label="编辑" side="bottom">
|
|
117
|
-
<button
|
|
118
|
-
type="button"
|
|
119
|
-
{...styling.props(styles.action)}
|
|
120
|
-
aria-label="编辑"
|
|
121
|
-
onClick={onEdit}
|
|
122
|
-
>
|
|
123
|
-
<IconEditOutlineRegular />
|
|
124
|
-
</button>
|
|
125
|
-
</Tooltip>
|
|
126
|
-
)}
|
|
127
|
-
{onRetry !== undefined && (
|
|
128
|
-
<Tooltip label="重试此回合" side="bottom">
|
|
129
|
-
<button
|
|
130
|
-
type="button"
|
|
131
|
-
{...styling.props(styles.action)}
|
|
132
|
-
aria-label="重试此回合"
|
|
133
|
-
onClick={onRetry}
|
|
134
|
-
>
|
|
135
|
-
<IconRefreshOutlineRegular />
|
|
136
|
-
</button>
|
|
137
|
-
</Tooltip>
|
|
138
|
-
)}
|
|
139
|
-
{onBranch !== undefined && (
|
|
140
|
-
<Tooltip
|
|
141
|
-
label={branchUnavailable ? t("message.branchUnavailable") : t("message.branch")}
|
|
142
|
-
side="bottom"
|
|
143
|
-
>
|
|
144
|
-
{/* Native disabled buttons do not deliver the hover/focus events Tooltip needs. */}
|
|
145
|
-
<button
|
|
146
|
-
type="button"
|
|
147
|
-
{...styling.props(styles.action)}
|
|
148
|
-
aria-label={t("message.branch")}
|
|
149
|
-
aria-disabled={branchUnavailable || undefined}
|
|
150
|
-
aria-describedby={branchUnavailable ? reasonId : undefined}
|
|
151
|
-
data-unavailable={branchUnavailable || undefined}
|
|
152
|
-
onClick={branchUnavailable ? undefined : onBranch}
|
|
153
|
-
>
|
|
154
|
-
<IconBranchOutlineRegular />
|
|
155
|
-
</button>
|
|
156
|
-
</Tooltip>
|
|
157
|
-
)}
|
|
158
|
-
{onBranch !== undefined && branchUnavailable && (
|
|
159
|
-
<span id={reasonId} {...styling.props(styles.visuallyHidden)}>
|
|
160
|
-
{t("message.branchUnavailable")}
|
|
161
|
-
</span>
|
|
162
|
-
)}
|
|
163
|
-
{clock === "end" ? clockEl : null}
|
|
164
|
-
</div>
|
|
165
|
-
);
|
|
166
|
-
}
|