@tangle-network/ui 11.51.1 → 11.51.3
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/CHANGELOG.md +12 -0
- package/README.md +4 -0
- package/dist/chat/speaker.d.ts +3 -42
- package/dist/chat/speaker.js +1 -42
- package/dist/hooks/use-auto-scroll.d.ts +1 -4
- package/dist/hooks/use-auto-scroll.js +7 -41
- package/dist/hooks/use-event-stream.d.ts +15 -0
- package/dist/hooks/use-event-stream.js +71 -0
- package/dist/hooks/use-follow-tail.d.ts +14 -0
- package/dist/hooks/use-follow-tail.js +69 -0
- package/dist/hooks/use-sse-stream.js +36 -33
- package/dist/hooks.d.ts +3 -1
- package/dist/hooks.js +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/package.json +3 -2
- package/src/chat/message-author.test.tsx +0 -17
- package/src/chat/speaker.tsx +2 -65
- package/src/hooks/index.ts +3 -0
- package/src/hooks/use-auto-scroll.ts +9 -58
- package/src/hooks/use-event-stream.ts +71 -0
- package/src/hooks/use-follow-tail.ts +32 -0
- package/src/hooks/use-sse-stream.ts +24 -55
- package/src/chat/chat-container.synthetic.test.tsx +0 -171
- package/src/chat/chat-container.test.tsx +0 -189
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { render, screen } from "@testing-library/react"
|
|
2
|
-
import { beforeAll, describe, expect, it, vi } from "vitest"
|
|
3
|
-
import type { SessionMessage } from "../types/message"
|
|
4
|
-
import type { SessionPart, ToolPart } from "../types/parts"
|
|
5
|
-
import { ChatContainer } from "./chat-container"
|
|
6
|
-
|
|
7
|
-
// jsdom has no layout engine; the auto-scroll hook calls scrollIntoView.
|
|
8
|
-
beforeAll(() => {
|
|
9
|
-
Element.prototype.scrollIntoView = vi.fn()
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
const messages: SessionMessage[] = [
|
|
13
|
-
{ id: "m1", role: "assistant" },
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
const editPart: ToolPart = {
|
|
17
|
-
type: "tool",
|
|
18
|
-
id: "tool-1",
|
|
19
|
-
tool: "edit",
|
|
20
|
-
state: {
|
|
21
|
-
status: "completed",
|
|
22
|
-
input: { path: "src/batch-writer.ts" },
|
|
23
|
-
output: "Applied 1 edit to src/batch-writer.ts",
|
|
24
|
-
},
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const partMap: Record<string, SessionPart[]> = { m1: [editPart] }
|
|
28
|
-
|
|
29
|
-
describe("ChatContainer timeline tool rendering", () => {
|
|
30
|
-
it("shows thinking as soon as a submitted user message is streaming", () => {
|
|
31
|
-
render(
|
|
32
|
-
<ChatContainer
|
|
33
|
-
messages={[{ id: "user-1", role: "user" }]}
|
|
34
|
-
partMap={{
|
|
35
|
-
"user-1": [{ type: "text", text: "hello" }],
|
|
36
|
-
}}
|
|
37
|
-
isStreaming
|
|
38
|
-
presentation="timeline"
|
|
39
|
-
/>,
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
expect(
|
|
43
|
-
screen.getByRole("status", { name: "Agent is thinking" }),
|
|
44
|
-
).toBeInTheDocument()
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it("does not let an earlier assistant reply hide the next turn's thinking state", () => {
|
|
48
|
-
render(
|
|
49
|
-
<ChatContainer
|
|
50
|
-
messages={[
|
|
51
|
-
{ id: "user-1", role: "user" },
|
|
52
|
-
{ id: "assistant-1", role: "assistant" },
|
|
53
|
-
{ id: "user-2", role: "user" },
|
|
54
|
-
]}
|
|
55
|
-
partMap={{
|
|
56
|
-
"user-1": [{ type: "text", text: "first" }],
|
|
57
|
-
"assistant-1": [{ type: "text", text: "done" }],
|
|
58
|
-
"user-2": [{ type: "text", text: "second" }],
|
|
59
|
-
}}
|
|
60
|
-
isStreaming
|
|
61
|
-
presentation="timeline"
|
|
62
|
-
/>,
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
expect(
|
|
66
|
-
screen.getByRole("status", { name: "Agent is thinking" }),
|
|
67
|
-
).toBeInTheDocument()
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it("replaces the generic thinking row when the current reply has text", () => {
|
|
71
|
-
render(
|
|
72
|
-
<ChatContainer
|
|
73
|
-
messages={[
|
|
74
|
-
{ id: "user-1", role: "user" },
|
|
75
|
-
{ id: "assistant-1", role: "assistant" },
|
|
76
|
-
]}
|
|
77
|
-
partMap={{
|
|
78
|
-
"user-1": [{ type: "text", text: "hello" }],
|
|
79
|
-
"assistant-1": [{ type: "text", text: "working on it" }],
|
|
80
|
-
}}
|
|
81
|
-
isStreaming
|
|
82
|
-
presentation="timeline"
|
|
83
|
-
/>,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
expect(
|
|
87
|
-
screen.queryByRole("status", { name: "Agent is thinking" }),
|
|
88
|
-
).not.toBeInTheDocument()
|
|
89
|
-
expect(screen.getByText("working on it")).toBeInTheDocument()
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it("shows a clean tool detail (the file path), not the raw input JSON", () => {
|
|
93
|
-
render(
|
|
94
|
-
<ChatContainer
|
|
95
|
-
messages={messages}
|
|
96
|
-
partMap={partMap}
|
|
97
|
-
isStreaming={false}
|
|
98
|
-
presentation="timeline"
|
|
99
|
-
/>,
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
expect(screen.getByText("src/batch-writer.ts")).toBeInTheDocument()
|
|
103
|
-
// The raw `{ "path": ... }` input JSON must not leak into the summary row.
|
|
104
|
-
expect(screen.queryByText(/"path"/)).not.toBeInTheDocument()
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it("titles the tool row with the bare verb and prints the path once", () => {
|
|
108
|
-
const { container } = render(
|
|
109
|
-
<ChatContainer
|
|
110
|
-
messages={messages}
|
|
111
|
-
partMap={partMap}
|
|
112
|
-
isStreaming={false}
|
|
113
|
-
presentation="timeline"
|
|
114
|
-
/>,
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
expect(screen.getByText("Edit")).toBeInTheDocument()
|
|
118
|
-
expect(screen.getAllByText("src/batch-writer.ts")).toHaveLength(1)
|
|
119
|
-
expect(container.textContent).not.toContain("Edit src/batch-writer.ts")
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it("stacks consecutive tool calls with no visible group header", () => {
|
|
123
|
-
const readPart: ToolPart = {
|
|
124
|
-
type: "tool",
|
|
125
|
-
id: "tool-2",
|
|
126
|
-
tool: "read",
|
|
127
|
-
state: { status: "completed", input: { path: "src/lib/jitter.ts" }, output: "ok" },
|
|
128
|
-
}
|
|
129
|
-
render(
|
|
130
|
-
<ChatContainer
|
|
131
|
-
messages={messages}
|
|
132
|
-
partMap={{ m1: [editPart, readPart] }}
|
|
133
|
-
isStreaming={false}
|
|
134
|
-
presentation="timeline"
|
|
135
|
-
/>,
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
expect(screen.queryByText(/tool activity/i)).not.toBeInTheDocument()
|
|
139
|
-
expect(screen.getByRole("group", { name: "Tool activity" })).toBeInTheDocument()
|
|
140
|
-
expect(screen.getByText("Edit")).toBeInTheDocument()
|
|
141
|
-
expect(screen.getByText("Read")).toBeInTheDocument()
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it("shimmers a running tool's verb and shows no spinner", () => {
|
|
145
|
-
const running: ToolPart = {
|
|
146
|
-
type: "tool",
|
|
147
|
-
id: "tool-3",
|
|
148
|
-
tool: "bash",
|
|
149
|
-
state: { status: "running", input: { command: "pnpm build" }, time: { start: 1 } },
|
|
150
|
-
}
|
|
151
|
-
const { container } = render(
|
|
152
|
-
<ChatContainer
|
|
153
|
-
messages={[{ id: "user-1", role: "user" }, { id: "m1", role: "assistant" }]}
|
|
154
|
-
partMap={{ "user-1": [{ type: "text", text: "build" }], m1: [running] }}
|
|
155
|
-
isStreaming
|
|
156
|
-
presentation="timeline"
|
|
157
|
-
/>,
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
const shimmer = container.querySelector(".tangle-text-shimmer")
|
|
161
|
-
expect(shimmer).not.toBeNull()
|
|
162
|
-
expect(shimmer!.textContent).toBe("Shell")
|
|
163
|
-
expect(container.querySelector(".animate-spin")).toBeNull()
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it("threads renderTimelineToolActions to the timeline tool item with its source part", () => {
|
|
167
|
-
const renderTimelineToolActions = vi.fn((part: ToolPart) => (
|
|
168
|
-
<button type="button">Open {part.id}</button>
|
|
169
|
-
))
|
|
170
|
-
|
|
171
|
-
render(
|
|
172
|
-
<ChatContainer
|
|
173
|
-
messages={messages}
|
|
174
|
-
partMap={partMap}
|
|
175
|
-
isStreaming={false}
|
|
176
|
-
presentation="timeline"
|
|
177
|
-
renderTimelineToolActions={renderTimelineToolActions}
|
|
178
|
-
/>,
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
expect(renderTimelineToolActions).toHaveBeenCalled()
|
|
182
|
-
expect(renderTimelineToolActions.mock.calls[0][0]).toMatchObject({
|
|
183
|
-
id: "tool-1",
|
|
184
|
-
})
|
|
185
|
-
expect(
|
|
186
|
-
screen.getByRole("button", { name: /open tool-1/i }),
|
|
187
|
-
).toBeInTheDocument()
|
|
188
|
-
})
|
|
189
|
-
})
|