Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Q
qjclaw-dmg
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
AI-甘富林
qjclaw-dmg
Commits
3d778a92
Commit
3d778a92
authored
Mar 30, 2026
by
AI-甘富林
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(ui): streamline chat composer and inline trace rendering
parent
a4d243ae
Changes
3
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
1434 additions
and
208 deletions
+1434
-208
App.tsx
apps/ui/src/App.tsx
+805
-189
styles.css
apps/ui/src/styles.css
+522
-14
index.ts
packages/gateway-client/src/index.ts
+107
-5
No files found.
apps/ui/src/App.tsx
View file @
3d778a92
This diff is collapsed.
Click to expand it.
apps/ui/src/styles.css
View file @
3d778a92
This diff is collapsed.
Click to expand it.
packages/gateway-client/src/index.ts
View file @
3d778a92
import
{
randomUUID
}
from
"node:crypto"
;
import
{
randomUUID
}
from
"node:crypto"
;
import
WebSocket
from
"ws"
;
import
WebSocket
from
"ws"
;
import
type
{
import
type
{
ChatMessage
,
ChatMessage
,
GatewayHealth
,
GatewayHealth
,
GatewayStatus
,
GatewayStatus
,
LogEntry
,
LogEntry
,
MessageRole
,
PromptResult
,
PromptResult
,
SessionSummary
SessionSummary
}
from
"@qjclaw/shared-types"
;
}
from
"@qjclaw/shared-types"
;
...
@@ -48,6 +49,7 @@ interface PendingChatRun {
...
@@ -48,6 +49,7 @@ interface PendingChatRun {
sessionKey
:
string
;
sessionKey
:
string
;
accumulatedText
:
string
;
accumulatedText
:
string
;
onDelta
?:
(
value
:
GatewayPromptStreamDelta
)
=>
void
;
onDelta
?:
(
value
:
GatewayPromptStreamDelta
)
=>
void
;
onStatus
?:
(
value
:
GatewayPromptStreamStatus
)
=>
void
;
onCompleted
?:
(
value
:
{
sessionId
:
string
;
runId
:
string
;
reply
:
ChatMessage
})
=>
void
;
onCompleted
?:
(
value
:
{
sessionId
:
string
;
runId
:
string
;
reply
:
ChatMessage
})
=>
void
;
onError
?:
(
value
:
{
sessionId
:
string
;
runId
?:
string
;
error
:
Error
})
=>
void
;
onError
?:
(
value
:
{
sessionId
:
string
;
runId
?:
string
;
error
:
Error
})
=>
void
;
}
}
...
@@ -100,7 +102,7 @@ interface SessionsListResult {
...
@@ -100,7 +102,7 @@ interface SessionsListResult {
interface
ChatHistoryResult
{
interface
ChatHistoryResult
{
sessionKey
?:
string
;
sessionKey
?:
string
;
messages
?:
Array
<
{
messages
?:
Array
<
{
role
?:
"system"
|
"user"
|
"assistant"
;
role
?:
string
;
timestamp
?:
number
;
timestamp
?:
number
;
content
?:
Array
<
{
type
?:
string
;
text
?:
string
}
>
;
content
?:
Array
<
{
type
?:
string
;
text
?:
string
}
>
;
}
>
;
}
>
;
...
@@ -119,9 +121,18 @@ export interface GatewayPromptStreamDelta {
...
@@ -119,9 +121,18 @@ export interface GatewayPromptStreamDelta {
fullText
?:
string
;
fullText
?:
string
;
}
}
export
interface
GatewayPromptStreamStatus
{
sessionId
:
string
;
runId
?:
string
;
stage
:
string
;
label
:
string
;
detail
?:
string
;
}
export
interface
GatewayPromptStreamHandlers
{
export
interface
GatewayPromptStreamHandlers
{
onStarted
?:
(
value
:
GatewayPromptStreamStart
)
=>
void
;
onStarted
?:
(
value
:
GatewayPromptStreamStart
)
=>
void
;
onDelta
?:
(
value
:
GatewayPromptStreamDelta
)
=>
void
;
onDelta
?:
(
value
:
GatewayPromptStreamDelta
)
=>
void
;
onStatus
?:
(
value
:
GatewayPromptStreamStatus
)
=>
void
;
onCompleted
?:
(
value
:
{
sessionId
:
string
;
runId
:
string
;
reply
:
ChatMessage
})
=>
void
;
onCompleted
?:
(
value
:
{
sessionId
:
string
;
runId
:
string
;
reply
:
ChatMessage
})
=>
void
;
onError
?:
(
value
:
{
sessionId
:
string
;
runId
?:
string
;
error
:
Error
})
=>
void
;
onError
?:
(
value
:
{
sessionId
:
string
;
runId
?:
string
;
error
:
Error
})
=>
void
;
}
}
...
@@ -410,7 +421,7 @@ export class GatewayClient {
...
@@ -410,7 +421,7 @@ export class GatewayClient {
const
result
=
(
await
this
.
request
(
"chat.history"
,
{
sessionKey
,
limit
:
100
}))
as
ChatHistoryResult
;
const
result
=
(
await
this
.
request
(
"chat.history"
,
{
sessionKey
,
limit
:
100
}))
as
ChatHistoryResult
;
const
messages
=
(
result
.
messages
??
[]).
map
((
message
,
messageIndex
)
=>
({
const
messages
=
(
result
.
messages
??
[]).
map
((
message
,
messageIndex
)
=>
({
id
:
`
${
sessionKey
}
:
${
message
.
timestamp
??
messageIndex
}:
$
{
messageIndex
}
`,
id
:
`
${
sessionKey
}
:
${
message
.
timestamp
??
messageIndex
}:
$
{
messageIndex
}
`,
role:
message.role ?? "assistant"
,
role:
this.normalizeChatRole(message.role)
,
content: this.flattenContent(message.content),
content: this.flattenContent(message.content),
createdAt: new Date(message.timestamp ?? Date.now()).toISOString()
createdAt: new Date(message.timestamp ?? Date.now()).toISOString()
}));
}));
...
@@ -492,7 +503,8 @@ export class GatewayClient {
...
@@ -492,7 +503,8 @@ export class GatewayClient {
if (runId) {
if (runId) {
this.emitChatDelta(runId, payload);
this.emitChatDelta(runId, payload);
if (state === "final") {
if (state === "final") {
this.completeChatRun(runId, this.buildChatMessage(runId, payload));
const reply = await this.resolveCompletedChatReply(runId, payload);
this.completeChatRun(runId, reply);
}
}
}
}
}
}
...
@@ -511,6 +523,11 @@ export class GatewayClient {
...
@@ -511,6 +523,11 @@ export class GatewayClient {
if (runId) {
if (runId) {
this.emitChatDelta(runId, payload.data ?? payload);
this.emitChatDelta(runId, payload.data ?? payload);
}
}
} else if (runId) {
const status = this.describeAgentStatus(payload, stream);
if (status) {
this.emitChatStatus(runId, status);
}
}
}
}
}
...
@@ -614,6 +631,7 @@ export class GatewayClient {
...
@@ -614,6 +631,7 @@ export class GatewayClient {
sessionKey
,
sessionKey
,
accumulatedText
:
""
,
accumulatedText
:
""
,
onDelta
:
handlers
.
onDelta
,
onDelta
:
handlers
.
onDelta
,
onStatus
:
handlers
.
onStatus
,
onCompleted
:
handlers
.
onCompleted
,
onCompleted
:
handlers
.
onCompleted
,
onError
:
handlers
.
onError
onError
:
handlers
.
onError
});
});
...
@@ -679,6 +697,80 @@ export class GatewayClient {
...
@@ -679,6 +697,80 @@ export class GatewayClient {
});
});
}
}
private
emitChatStatus
(
runId
:
string
,
status
:
Omit
<
GatewayPromptStreamStatus
,
"sessionId"
|
"runId"
>
):
void
{
const
pending
=
this
.
pendingChatRuns
.
get
(
runId
);
if
(
!
pending
)
{
return
;
}
this
.
refreshChatRunTimer
(
runId
);
pending
.
onStatus
?.({
sessionId
:
pending
.
sessionKey
,
runId
,
...
status
});
}
private
async
resolveCompletedChatReply
(
runId
:
string
,
payload
:
Record
<
string
,
unknown
>
):
Promise
<
ChatMessage
>
{
const
reply
=
this
.
buildChatMessage
(
runId
,
payload
);
if
(
reply
.
content
.
trim
())
{
return
reply
;
}
const
pending
=
this
.
pendingChatRuns
.
get
(
runId
);
if
(
!
pending
)
{
return
reply
;
}
try
{
const
history
=
await
this
.
listMessages
(
pending
.
sessionKey
);
const
assistant
=
[...
history
].
reverse
().
find
((
message
)
=>
message
.
role
===
"assistant"
&&
message
.
content
.
trim
());
if
(
assistant
)
{
return
assistant
;
}
}
catch
{
}
return
reply
;
}
private
describeAgentStatus
(
payload
:
Record
<
string
,
unknown
>
,
stream
:
string
):
Omit
<
GatewayPromptStreamStatus
,
"sessionId"
|
"runId"
>
|
null
{
const
normalizedStage
=
stream
.
trim
().
toLowerCase
();
const
toolName
=
this
.
findStringDeep
(
payload
,
[
"toolName"
,
"tool_name"
,
"name"
]);
const
detail
=
this
.
extractTextCandidate
(
payload
.
data
)
??
this
.
extractTextCandidate
(
payload
);
const
label
=
this
.
buildStatusLabel
(
normalizedStage
,
toolName
);
const
compactDetail
=
detail
&&
detail
!==
label
?
detail
.
slice
(
0
,
240
)
:
undefined
;
if
(
!
label
&&
!
compactDetail
)
{
return
null
;
}
return
{
stage
:
stream
,
label
:
label
||
"
\
u6b63
\
u5728
\
u6574
\
u7406
\
u4e2d
\
u95f4
\
u7ed3
\
u679c"
,
detail
:
compactDetail
};
}
private
buildStatusLabel
(
stage
:
string
,
toolName
?:
string
):
string
{
if
(
stage
.
includes
(
"reason"
)
||
stage
.
includes
(
"think"
))
{
return
"
\
u6b63
\
u5728
\
u7406
\
u89e3
\
u4f60
\
u7684
\
u95ee
\
u9898"
;
}
if
(
stage
.
includes
(
"tool"
)
&&
stage
.
includes
(
"result"
))
{
return
toolName
?
`
${
toolName
}
\u5df2\u8fd4\u56de\u4fe1\u606f`
:
"
\
u5df2
\
u62ff
\
u5230
\
u8865
\
u5145
\
u4fe1
\
u606f"
;
}
if
(
stage
.
includes
(
"tool"
)
||
stage
.
includes
(
"call"
))
{
return
toolName
?
`\u6b63\u5728\u8c03\u7528
${
toolName
}
`
:
"
\
u6b63
\
u5728
\
u8c03
\
u7528
\
u8f85
\
u52a9
\
u80fd
\
u529b"
;
}
if
(
stage
.
includes
(
"search"
)
||
stage
.
includes
(
"fetch"
)
||
stage
.
includes
(
"browser"
)
||
stage
.
includes
(
"web"
))
{
return
toolName
?
`\u6b63\u5728\u901a\u8fc7
${
toolName
}
\u67e5\u627e\u4fe1\u606f`
:
"
\
u6b63
\
u5728
\
u67e5
\
u627e
\
u8865
\
u5145
\
u4fe1
\
u606f"
;
}
if
(
stage
.
includes
(
"plan"
)
||
stage
.
includes
(
"route"
))
{
return
"
\
u6b63
\
u5728
\
u5b89
\
u6392
\
u5904
\
u7406
\
u6b65
\
u9aa4"
;
}
return
toolName
?
`\u6b63\u5728\u6574\u7406
${
toolName
}
\u8fd4\u56de\u7684\u4fe1\u606f`
:
"
\
u6b63
\
u5728
\
u6574
\
u7406
\
u4e2d
\
u95f4
\
u7ed3
\
u679c"
;
}
private
completeChatRun
(
runId
:
string
,
reply
:
ChatMessage
):
void
{
private
completeChatRun
(
runId
:
string
,
reply
:
ChatMessage
):
void
{
const
pending
=
this
.
pendingChatRuns
.
get
(
runId
);
const
pending
=
this
.
pendingChatRuns
.
get
(
runId
);
if
(
!
pending
)
{
if
(
!
pending
)
{
...
@@ -717,6 +809,13 @@ export class GatewayClient {
...
@@ -717,6 +809,13 @@ export class GatewayClient {
};
};
}
}
private normalizeChatRole(role: unknown): MessageRole {
if (role === "system" || role === "user" || role === "assistant" || role === "tool" || role === "toolResult") {
return role;
}
return "system";
}
private extractTextCandidate(value: unknown): string | undefined {
private extractTextCandidate(value: unknown): string | undefined {
if (typeof value === "string") {
if (typeof value === "string") {
const normalized = value.replace(/\r\n/g, "\n");
const normalized = value.replace(/\r\n/g, "\n");
...
@@ -993,7 +1092,7 @@ export class GatewayClient {
...
@@ -993,7 +1092,7 @@ export class GatewayClient {
private stripStructuredLogPrefix(message: string): string {
private stripStructuredLogPrefix(message: string): string {
return message
return message
.replace(LOG_PREFIX_PATTERN, "")
.replace(LOG_PREFIX_PATTERN, "")
.replace(/
闁跨喓绁
?/g, "ok ")
.replace(/
闂佽法鍠撶粊
?/g, "ok ")
.replace(/[?]{2,}/g, "")
.replace(/[?]{2,}/g, "")
.replace(/\s+/g, " ")
.replace(/\s+/g, " ")
.trim();
.trim();
...
@@ -1043,3 +1142,6 @@ export class GatewayClient {
...
@@ -1043,3 +1142,6 @@ export class GatewayClient {
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment