Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files
aworld/cmd/web/webui/src/pages/components/BubbleItem/index.less
ADDED
File without changes
|
aworld/cmd/web/webui/src/pages/components/BubbleItem/index.tsx
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
import ReactMarkdown from "react-markdown";
|
3 |
+
import { extractToolCards } from "./utils";
|
4 |
+
import CardDefault from "./cardDefault";
|
5 |
+
import CardLinkList from "./cardLinkList";
|
6 |
+
|
7 |
+
interface BubbleItemProps {
|
8 |
+
data: string;
|
9 |
+
}
|
10 |
+
|
11 |
+
const BubbleItem: React.FC<BubbleItemProps> = ({ data }) => {
|
12 |
+
const { segments } = extractToolCards(data);
|
13 |
+
console.log(segments)
|
14 |
+
return (
|
15 |
+
<div className="card">
|
16 |
+
{segments.map((segment, index) => {
|
17 |
+
if (segment.type === 'text') {
|
18 |
+
return (
|
19 |
+
<ReactMarkdown key={`text-${index}`}>
|
20 |
+
{segment.content}
|
21 |
+
</ReactMarkdown>
|
22 |
+
);
|
23 |
+
} else if (segment.type === 'tool_card') {
|
24 |
+
const cardType = segment.data?.card_type;
|
25 |
+
if (cardType === 'tool_call_card_link_list') {
|
26 |
+
return <CardLinkList key={`tool-${index}`} data={segment.data} />;
|
27 |
+
} else {
|
28 |
+
return <CardDefault key={`tool-${index}`} data={segment.data} />;
|
29 |
+
}
|
30 |
+
}
|
31 |
+
})}
|
32 |
+
</div>
|
33 |
+
);
|
34 |
+
};
|
35 |
+
|
36 |
+
export default BubbleItem;
|
aworld/cmd/web/webui/src/pages/components/BubbleItem/utils.ts
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export interface ToolCardData {
|
2 |
+
tool_type: string;
|
3 |
+
tool_name: string;
|
4 |
+
function_name: string;
|
5 |
+
tool_call_id: string;
|
6 |
+
arguments: string;
|
7 |
+
results: string;
|
8 |
+
card_type: string;
|
9 |
+
card_data: any;
|
10 |
+
}
|
11 |
+
|
12 |
+
type ContentSegment =
|
13 |
+
| { type: 'text'; content: string }
|
14 |
+
| { type: 'tool_card'; data: ToolCardData; raw: string };
|
15 |
+
|
16 |
+
export interface ParsedContent {
|
17 |
+
segments: ContentSegment[];
|
18 |
+
}
|
19 |
+
|
20 |
+
export const extractToolCards = (content: string): ParsedContent => {
|
21 |
+
const toolCardRegex = /(.*?)(```tool_card\s*({[\s\S]*?})\s*```)/gs;
|
22 |
+
const segments: ContentSegment[] = [];
|
23 |
+
let lastIndex = 0;
|
24 |
+
|
25 |
+
let match;
|
26 |
+
while ((match = toolCardRegex.exec(content)) !== null) {
|
27 |
+
const [, textBefore, fullToolCard, toolCardJson] = match;
|
28 |
+
|
29 |
+
// 添加文本内容
|
30 |
+
if (textBefore) {
|
31 |
+
segments.push({
|
32 |
+
type: 'text',
|
33 |
+
content: textBefore.trim()
|
34 |
+
});
|
35 |
+
}
|
36 |
+
|
37 |
+
// 添加工具卡片
|
38 |
+
try {
|
39 |
+
segments.push({
|
40 |
+
type: 'tool_card',
|
41 |
+
data: JSON.parse(toolCardJson),
|
42 |
+
raw: fullToolCard.trim()
|
43 |
+
});
|
44 |
+
} catch (e) {
|
45 |
+
console.error('Failed to parse tool_card JSON:', e);
|
46 |
+
// 如果解析失败,仍保留原始文本
|
47 |
+
segments.push({
|
48 |
+
type: 'text',
|
49 |
+
content: fullToolCard.trim()
|
50 |
+
});
|
51 |
+
}
|
52 |
+
|
53 |
+
lastIndex = toolCardRegex.lastIndex;
|
54 |
+
}
|
55 |
+
|
56 |
+
// 添加最后剩余的文本内容
|
57 |
+
const remainingText = content.slice(lastIndex);
|
58 |
+
if (remainingText.trim()) {
|
59 |
+
segments.push({
|
60 |
+
type: 'text',
|
61 |
+
content: remainingText.trim()
|
62 |
+
});
|
63 |
+
}
|
64 |
+
|
65 |
+
return { segments };
|
66 |
+
};
|