@langgraph-js/ui 1.4.0 → 1.6.0
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/.env +2 -0
- package/.env.example +2 -0
- package/dist/assets/index-7vem5Peg.css +1 -0
- package/dist/assets/index-CZ6k2dGe.js +235 -0
- package/dist/index.html +3 -5
- package/index.html +1 -3
- package/package.json +10 -2
- package/src/artifacts/ArtifactViewer.tsx +158 -0
- package/src/artifacts/ArtifactsContext.tsx +99 -0
- package/src/artifacts/SourceCodeViewer.tsx +15 -0
- package/src/chat/Chat.tsx +102 -27
- package/src/chat/FileUpload/index.ts +3 -7
- package/src/chat/components/FileList.tsx +16 -12
- package/src/chat/components/HistoryList.tsx +39 -137
- package/src/chat/components/JsonEditorPopup.tsx +57 -45
- package/src/chat/components/JsonToMessage/JsonToMessage.tsx +20 -14
- package/src/chat/components/JsonToMessage/JsonToMessageButton.tsx +9 -16
- package/src/chat/components/JsonToMessage/index.tsx +1 -1
- package/src/chat/components/MessageAI.tsx +5 -4
- package/src/chat/components/MessageBox.tsx +21 -19
- package/src/chat/components/MessageHuman.tsx +13 -10
- package/src/chat/components/MessageTool.tsx +71 -30
- package/src/chat/components/UsageMetadata.tsx +41 -22
- package/src/chat/context/ChatContext.tsx +14 -5
- package/src/chat/store/index.ts +25 -2
- package/src/chat/tools/ask_user_for_approve.tsx +80 -0
- package/src/chat/tools/create_artifacts.tsx +50 -0
- package/src/chat/tools/index.ts +5 -0
- package/src/chat/tools/update_plan.tsx +75 -0
- package/src/chat/tools/web_search_tool.tsx +89 -0
- package/src/graph/index.tsx +9 -6
- package/src/index.ts +1 -0
- package/src/login/Login.tsx +155 -47
- package/src/memory/BaseDB.ts +92 -0
- package/src/memory/db.ts +232 -0
- package/src/memory/fulltext-search.ts +191 -0
- package/src/memory/index.ts +4 -0
- package/src/memory/tools.ts +170 -0
- package/test/main.tsx +2 -2
- package/vite.config.ts +7 -1
- package/dist/assets/index-BWndsYW1.js +0 -214
- package/dist/assets/index-LcgERueJ.css +0 -1
- package/src/chat/chat.css +0 -406
- package/src/chat/components/FileList.css +0 -129
- package/src/chat/components/JsonEditorPopup.css +0 -81
- package/src/chat/components/JsonToMessage/JsonToMessage.css +0 -104
- package/src/chat/tools.ts +0 -33
- package/src/login/Login.css +0 -93
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { useChat } from "../context/ChatContext";
|
|
3
3
|
import { getHistoryContent } from "@langgraph-js/sdk";
|
|
4
|
+
import { RefreshCw, X, RotateCcw, Trash2 } from "lucide-react";
|
|
4
5
|
|
|
5
6
|
interface HistoryListProps {
|
|
6
7
|
onClose: () => void;
|
|
@@ -10,62 +11,75 @@ interface HistoryListProps {
|
|
|
10
11
|
const HistoryList: React.FC<HistoryListProps> = ({ onClose, formatTime }) => {
|
|
11
12
|
const { historyList, currentChatId, refreshHistoryList, createNewChat, deleteHistoryChat, toHistoryChat } = useChat();
|
|
12
13
|
return (
|
|
13
|
-
<div className="
|
|
14
|
-
<div className="
|
|
15
|
-
<div className="
|
|
16
|
-
<h3>历史记录</h3>
|
|
17
|
-
<button
|
|
18
|
-
|
|
14
|
+
<div className=" bg-white rounded-lg shadow-md h-full flex flex-col border-r">
|
|
15
|
+
<div className="p-4 border-b border-gray-200 flex justify-between items-center h-16">
|
|
16
|
+
<div className="flex items-center gap-3">
|
|
17
|
+
<h3 className="m-0 text-lg text-gray-800">历史记录</h3>
|
|
18
|
+
<button
|
|
19
|
+
className="p-1.5 text-base rounded bg-blue-100 hover:bg-blue-200 transition-all duration-200 flex items-center justify-center hover:scale-110 group"
|
|
20
|
+
onClick={refreshHistoryList}
|
|
21
|
+
title="刷新列表"
|
|
22
|
+
>
|
|
23
|
+
<RefreshCw className="w-4 h-4 text-blue-600 group-hover:text-blue-700 group-hover:rotate-180 transition-all duration-300" />
|
|
19
24
|
</button>
|
|
20
25
|
</div>
|
|
21
|
-
<button
|
|
22
|
-
|
|
26
|
+
<button
|
|
27
|
+
className="p-1.5 text-base rounded bg-red-100 hover:bg-red-200 transition-all duration-200 flex items-center justify-center hover:scale-110 group"
|
|
28
|
+
onClick={onClose}
|
|
29
|
+
title="关闭"
|
|
30
|
+
>
|
|
31
|
+
<X className="w-4 h-4 text-red-600 group-hover:text-red-700" />
|
|
23
32
|
</button>
|
|
24
33
|
</div>
|
|
25
|
-
<div className="
|
|
34
|
+
<div className="flex-1 overflow-y-auto p-4">
|
|
26
35
|
<div
|
|
27
|
-
className="
|
|
36
|
+
className="flex flex-col gap-3 cursor-pointer"
|
|
28
37
|
onClick={() => {
|
|
29
38
|
createNewChat();
|
|
30
39
|
}}
|
|
31
40
|
>
|
|
32
|
-
<div className="
|
|
33
|
-
<div className="
|
|
41
|
+
<div className="flex justify-between items-center p-3 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors duration-200">
|
|
42
|
+
<div className="text-sm text-gray-800 truncate">New Chat</div>
|
|
34
43
|
</div>
|
|
35
44
|
</div>
|
|
36
45
|
{historyList.length === 0 ? (
|
|
37
|
-
<div className="
|
|
46
|
+
<div className="text-center text-gray-500 py-8">暂无历史记录</div>
|
|
38
47
|
) : (
|
|
39
|
-
<div className="
|
|
48
|
+
<div className="flex flex-col gap-3 mt-3">
|
|
40
49
|
{historyList
|
|
41
50
|
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
|
|
42
51
|
.map((thread) => (
|
|
43
|
-
<div
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
<div
|
|
53
|
+
className={`flex justify-between items-center p-3 rounded-lg transition-colors duration-200 ${
|
|
54
|
+
thread.thread_id === currentChatId ? "bg-blue-50 border border-blue-200" : "bg-gray-50 hover:bg-gray-100"
|
|
55
|
+
}`}
|
|
56
|
+
key={thread.thread_id}
|
|
57
|
+
>
|
|
58
|
+
<div className="flex-1 min-w-0 mr-2">
|
|
59
|
+
<div className="text-sm text-gray-800 mb-1 truncate max-w-[180px]">{getHistoryContent(thread)}</div>
|
|
60
|
+
<div className="flex gap-3 text-xs text-gray-500">
|
|
61
|
+
<span className="truncate max-w-[100px]">{formatTime(new Date(thread.created_at))}</span>
|
|
62
|
+
<span className="truncate max-w-[60px]">{thread.status}</span>
|
|
49
63
|
</div>
|
|
50
64
|
</div>
|
|
51
|
-
<div className="
|
|
65
|
+
<div className="flex gap-2 shrink-0">
|
|
52
66
|
<button
|
|
53
|
-
className="
|
|
67
|
+
className="p-1.5 text-base rounded bg-green-100 hover:bg-green-200 transition-all duration-200 flex items-center justify-center hover:scale-110 group"
|
|
54
68
|
onClick={() => {
|
|
55
69
|
toHistoryChat(thread);
|
|
56
70
|
}}
|
|
57
71
|
title="恢复对话"
|
|
58
72
|
>
|
|
59
|
-
|
|
73
|
+
<RotateCcw className="w-4 h-4 text-green-600 group-hover:text-green-700 group-hover:-rotate-180 transition-all duration-300" />
|
|
60
74
|
</button>
|
|
61
75
|
<button
|
|
62
|
-
className="
|
|
76
|
+
className="p-1.5 text-base rounded bg-red-100 hover:bg-red-200 transition-all duration-200 flex items-center justify-center hover:scale-110 group"
|
|
63
77
|
onClick={async () => {
|
|
64
78
|
await deleteHistoryChat(thread);
|
|
65
79
|
}}
|
|
66
80
|
title="删除对话"
|
|
67
81
|
>
|
|
68
|
-
|
|
82
|
+
<Trash2 className="w-4 h-4 text-red-600 group-hover:text-red-700" />
|
|
69
83
|
</button>
|
|
70
84
|
</div>
|
|
71
85
|
</div>
|
|
@@ -73,118 +87,6 @@ const HistoryList: React.FC<HistoryListProps> = ({ onClose, formatTime }) => {
|
|
|
73
87
|
</div>
|
|
74
88
|
)}
|
|
75
89
|
</div>
|
|
76
|
-
<style>{`
|
|
77
|
-
.history-list {
|
|
78
|
-
background: #fff;
|
|
79
|
-
border-radius: 8px;
|
|
80
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
81
|
-
height: 100%;
|
|
82
|
-
display: flex;
|
|
83
|
-
flex-direction: column;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.history-header {
|
|
87
|
-
padding: 16px;
|
|
88
|
-
border-bottom: 1px solid #eee;
|
|
89
|
-
display: flex;
|
|
90
|
-
justify-content: space-between;
|
|
91
|
-
align-items: center;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.header-left {
|
|
95
|
-
display: flex;
|
|
96
|
-
align-items: center;
|
|
97
|
-
gap: 12px;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.history-header h3 {
|
|
101
|
-
margin: 0;
|
|
102
|
-
font-size: 18px;
|
|
103
|
-
color: #333;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.history-content {
|
|
107
|
-
flex: 1;
|
|
108
|
-
overflow-y: auto;
|
|
109
|
-
padding: 16px;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.history-items {
|
|
113
|
-
display: flex;
|
|
114
|
-
flex-direction: column;
|
|
115
|
-
gap: 12px;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.history-item {
|
|
119
|
-
display: flex;
|
|
120
|
-
justify-content: space-between;
|
|
121
|
-
align-items: center;
|
|
122
|
-
padding: 12px;
|
|
123
|
-
border-radius: 6px;
|
|
124
|
-
background: #f8f9fa;
|
|
125
|
-
transition: all 0.2s ease;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.history-item:hover {
|
|
129
|
-
background: #f0f2f5;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
.history-item.active {
|
|
133
|
-
background: #e6f7ff;
|
|
134
|
-
border: 1px solid #91d5ff;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.history-info {
|
|
138
|
-
flex: 1;
|
|
139
|
-
min-width: 0;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.history-title {
|
|
143
|
-
font-size: 14px;
|
|
144
|
-
color: #333;
|
|
145
|
-
margin-bottom: 4px;
|
|
146
|
-
white-space: nowrap;
|
|
147
|
-
overflow: hidden;
|
|
148
|
-
text-overflow: ellipsis;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.history-meta {
|
|
152
|
-
display: flex;
|
|
153
|
-
gap: 12px;
|
|
154
|
-
color: #666;
|
|
155
|
-
font-size: 12px;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.history-actions {
|
|
159
|
-
display: flex;
|
|
160
|
-
gap: 8px;
|
|
161
|
-
margin-left: 12px;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.action-button, .close-button, .refresh-button {
|
|
165
|
-
background: none;
|
|
166
|
-
border: none;
|
|
167
|
-
cursor: pointer;
|
|
168
|
-
padding: 6px;
|
|
169
|
-
font-size: 16px;
|
|
170
|
-
border-radius: 4px;
|
|
171
|
-
transition: all 0.2s ease;
|
|
172
|
-
display: flex;
|
|
173
|
-
align-items: center;
|
|
174
|
-
justify-content: center;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.action-button:hover, .close-button:hover, .refresh-button:hover {
|
|
178
|
-
background: rgba(0, 0, 0, 0.05);
|
|
179
|
-
transform: scale(1.1);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
.empty-history {
|
|
183
|
-
text-align: center;
|
|
184
|
-
color: #999;
|
|
185
|
-
padding: 32px 0;
|
|
186
|
-
}
|
|
187
|
-
`}</style>
|
|
188
90
|
</div>
|
|
189
91
|
);
|
|
190
92
|
};
|
|
@@ -1,57 +1,69 @@
|
|
|
1
|
-
import React, { useState, useEffect } from
|
|
2
|
-
import './JsonEditorPopup.css';
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
3
2
|
|
|
4
3
|
interface JsonEditorPopupProps {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
initialJson: object;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
onSave: (jsonData: object) => void;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
const JsonEditorPopup: React.FC<JsonEditorPopupProps> = ({ isOpen, initialJson, onClose, onSave }) => {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const [jsonString, setJsonString] = useState("");
|
|
12
|
+
const [error, setError] = useState<string | null>(null);
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
setJsonString(JSON.stringify(initialJson, null, 2));
|
|
16
|
+
setError(null); // Reset error when initialJson changes or popup opens
|
|
17
|
+
}, [initialJson, isOpen]);
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const handleSave = () => {
|
|
25
|
-
try {
|
|
26
|
-
const parsedJson = JSON.parse(jsonString);
|
|
27
|
-
onSave(parsedJson);
|
|
28
|
-
onClose();
|
|
29
|
-
} catch (e) {
|
|
30
|
-
setError('JSON 格式无效,请检查后重试。');
|
|
31
|
-
console.error("Invalid JSON format:", e);
|
|
19
|
+
if (!isOpen) {
|
|
20
|
+
return null;
|
|
32
21
|
}
|
|
33
|
-
};
|
|
34
22
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
23
|
+
const handleSave = () => {
|
|
24
|
+
try {
|
|
25
|
+
const parsedJson = JSON.parse(jsonString);
|
|
26
|
+
onSave(parsedJson);
|
|
27
|
+
onClose();
|
|
28
|
+
} catch (e) {
|
|
29
|
+
setError("JSON 格式无效,请检查后重试。");
|
|
30
|
+
console.error("Invalid JSON format:", e);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
36
|
+
<div className="bg-white rounded-lg shadow-xl w-full max-w-2xl mx-4">
|
|
37
|
+
<div className="p-6">
|
|
38
|
+
<h2 className="text-xl font-semibold text-gray-900 mb-4">编辑 Extra Parameters</h2>
|
|
39
|
+
<textarea
|
|
40
|
+
value={jsonString}
|
|
41
|
+
onChange={(e) => {
|
|
42
|
+
setJsonString(e.target.value);
|
|
43
|
+
setError(null); // Clear error on edit
|
|
44
|
+
}}
|
|
45
|
+
rows={15}
|
|
46
|
+
className="w-full p-3 border border-gray-300 rounded-lg font-mono text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
47
|
+
/>
|
|
48
|
+
{error && <p className="mt-2 text-sm text-red-600">{error}</p>}
|
|
49
|
+
<div className="flex justify-end gap-3 mt-4">
|
|
50
|
+
<button
|
|
51
|
+
onClick={onClose}
|
|
52
|
+
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
53
|
+
>
|
|
54
|
+
取消
|
|
55
|
+
</button>
|
|
56
|
+
<button
|
|
57
|
+
onClick={handleSave}
|
|
58
|
+
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
59
|
+
>
|
|
60
|
+
保存
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
51
65
|
</div>
|
|
52
|
-
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
66
|
+
);
|
|
55
67
|
};
|
|
56
68
|
|
|
57
|
-
export default JsonEditorPopup;
|
|
69
|
+
export default JsonEditorPopup;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { MessagesBox } from "../MessageBox";
|
|
3
|
-
import "./JsonToMessage.css";
|
|
4
3
|
import { RenderMessage } from "@langgraph-js/sdk";
|
|
4
|
+
import { X } from "lucide-react";
|
|
5
5
|
|
|
6
6
|
interface JsonToMessageProps {
|
|
7
7
|
isOpen: boolean;
|
|
@@ -81,27 +81,33 @@ const JsonToMessage: React.FC<JsonToMessageProps> = ({ isOpen, onClose, initialJ
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
return (
|
|
84
|
-
<div className="
|
|
85
|
-
<div className="
|
|
86
|
-
<div className="
|
|
87
|
-
<h2>JSON 消息预览</h2>
|
|
88
|
-
<button onClick={onClose} className="
|
|
89
|
-
|
|
84
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
85
|
+
<div className="bg-white rounded-lg shadow-xl w-[90%] max-w-[1200px] h-[80vh] max-h-[800px] flex flex-col overflow-hidden">
|
|
86
|
+
<div className="flex justify-between items-center p-4 border-b border-gray-200">
|
|
87
|
+
<h2 className="text-xl font-semibold text-gray-900">JSON 消息预览</h2>
|
|
88
|
+
<button onClick={onClose} className="p-1.5 text-gray-500 hover:text-gray-900 transition-colors rounded-lg hover:bg-gray-100">
|
|
89
|
+
<X className="w-5 h-5" />
|
|
90
90
|
</button>
|
|
91
91
|
</div>
|
|
92
92
|
|
|
93
|
-
<div className="
|
|
94
|
-
<div className="
|
|
95
|
-
<textarea
|
|
96
|
-
|
|
93
|
+
<div className="flex flex-1 overflow-hidden">
|
|
94
|
+
<div className="flex-1 p-4 flex flex-col border-r border-gray-200">
|
|
95
|
+
<textarea
|
|
96
|
+
value={jsonString}
|
|
97
|
+
onChange={(e) => setJsonString(e.target.value)}
|
|
98
|
+
rows={20}
|
|
99
|
+
placeholder="输入JSON消息格式..."
|
|
100
|
+
className="flex-1 font-mono text-sm p-3 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
|
101
|
+
/>
|
|
102
|
+
{error && <p className="mt-2 text-sm text-red-600">{error}</p>}
|
|
97
103
|
</div>
|
|
98
104
|
|
|
99
|
-
<div className="
|
|
100
|
-
<div className="
|
|
105
|
+
<div className="flex-1 p-4 overflow-y-auto bg-gray-50">
|
|
106
|
+
<div className="bg-white rounded-lg p-4 min-h-full">
|
|
101
107
|
{previewMessages?.length > 0 ? (
|
|
102
108
|
<MessagesBox renderMessages={previewMessages} collapsedTools={[]} toggleToolCollapse={() => {}} client={null as any} />
|
|
103
109
|
) : (
|
|
104
|
-
<div className="
|
|
110
|
+
<div className="flex items-center justify-center h-full text-gray-500 italic">请输入有效的JSON以查看消息预览</div>
|
|
105
111
|
)}
|
|
106
112
|
</div>
|
|
107
113
|
</div>
|
|
@@ -1,27 +1,20 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
2
|
import JsonToMessage from "./JsonToMessage";
|
|
3
|
+
import { FileJson } from "lucide-react";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
buttonText?: string;
|
|
6
|
-
initialJson?: string | object;
|
|
7
|
-
className?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const JsonToMessageButton: React.FC<JsonToMessageButtonProps> = ({ buttonText = "JSON 预览", initialJson, className = "" }) => {
|
|
5
|
+
export const JsonToMessageButton: React.FC = () => {
|
|
11
6
|
const [isOpen, setIsOpen] = useState(false);
|
|
12
7
|
|
|
13
|
-
const openModal = () => setIsOpen(true);
|
|
14
|
-
const closeModal = () => setIsOpen(false);
|
|
15
|
-
|
|
16
8
|
return (
|
|
17
9
|
<>
|
|
18
|
-
<button
|
|
19
|
-
{
|
|
10
|
+
<button
|
|
11
|
+
onClick={() => setIsOpen(true)}
|
|
12
|
+
className="px-3 py-1.5 text-sm font-medium text-gray-700 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 flex items-center gap-1.5"
|
|
13
|
+
>
|
|
14
|
+
<FileJson className="w-4 h-4" />
|
|
15
|
+
JSON 消息
|
|
20
16
|
</button>
|
|
21
|
-
|
|
22
|
-
<JsonToMessage isOpen={isOpen} onClose={closeModal} initialJson={initialJson} />
|
|
17
|
+
<JsonToMessage isOpen={isOpen} onClose={() => setIsOpen(false)} />
|
|
23
18
|
</>
|
|
24
19
|
);
|
|
25
20
|
};
|
|
26
|
-
|
|
27
|
-
export default JsonToMessageButton;
|
|
@@ -4,16 +4,17 @@ import { UsageMetadata } from "./UsageMetadata";
|
|
|
4
4
|
import { getMessageContent } from "@langgraph-js/sdk";
|
|
5
5
|
import Markdown from "react-markdown";
|
|
6
6
|
import remarkGfm from "remark-gfm";
|
|
7
|
+
|
|
7
8
|
interface MessageAIProps {
|
|
8
9
|
message: RenderMessage;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
const MessageAI: React.FC<MessageAIProps> = ({ message }) => {
|
|
12
13
|
return (
|
|
13
|
-
<div className="
|
|
14
|
-
<div className="
|
|
15
|
-
<div className="
|
|
16
|
-
<div className="
|
|
14
|
+
<div className="flex flex-col w-[80%] bg-white rounded-lg shadow-sm border border-gray-200">
|
|
15
|
+
<div className="flex flex-col p-4">
|
|
16
|
+
<div className="text-sm font-medium text-gray-700 mb-2">{message.name}</div>
|
|
17
|
+
<div className="markdown-body max-w-none">
|
|
17
18
|
<Markdown remarkPlugins={[remarkGfm]}>{getMessageContent(message.content)}</Markdown>
|
|
18
19
|
</div>
|
|
19
20
|
<UsageMetadata response_metadata={message.response_metadata as any} usage_metadata={message.usage_metadata || {}} spend_time={message.spend_time} id={message.id} />
|
|
@@ -3,6 +3,7 @@ import MessageHuman from "./MessageHuman";
|
|
|
3
3
|
import MessageAI from "./MessageAI";
|
|
4
4
|
import MessageTool from "./MessageTool";
|
|
5
5
|
import { formatTokens, getMessageContent, LangGraphClient, RenderMessage } from "@langgraph-js/sdk";
|
|
6
|
+
import { motion } from "motion/react";
|
|
6
7
|
|
|
7
8
|
export const MessagesBox = ({
|
|
8
9
|
renderMessages,
|
|
@@ -16,24 +17,25 @@ export const MessagesBox = ({
|
|
|
16
17
|
client: LangGraphClient;
|
|
17
18
|
}) => {
|
|
18
19
|
return (
|
|
19
|
-
|
|
20
|
-
{renderMessages.map((message) =>
|
|
21
|
-
message.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
20
|
+
<div className="flex flex-col gap-4 w-full">
|
|
21
|
+
{renderMessages.map((message, index) => (
|
|
22
|
+
<motion.div key={message.unique_id} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3, delay: index * 0.1 }}>
|
|
23
|
+
{message.type === "human" ? (
|
|
24
|
+
<MessageHuman content={message.content} />
|
|
25
|
+
) : message.type === "tool" ? (
|
|
26
|
+
<MessageTool
|
|
27
|
+
message={message}
|
|
28
|
+
client={client!}
|
|
29
|
+
getMessageContent={getMessageContent}
|
|
30
|
+
formatTokens={formatTokens}
|
|
31
|
+
isCollapsed={collapsedTools.includes(message.id!)}
|
|
32
|
+
onToggleCollapse={() => toggleToolCollapse(message.id!)}
|
|
33
|
+
/>
|
|
34
|
+
) : (
|
|
35
|
+
<MessageAI message={message} />
|
|
36
|
+
)}
|
|
37
|
+
</motion.div>
|
|
38
|
+
))}
|
|
39
|
+
</div>
|
|
38
40
|
);
|
|
39
41
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
|
|
2
3
|
interface MessageHumanProps {
|
|
3
4
|
content: string | any[];
|
|
4
5
|
}
|
|
@@ -6,7 +7,7 @@ interface MessageHumanProps {
|
|
|
6
7
|
const MessageHuman: React.FC<MessageHumanProps> = ({ content }) => {
|
|
7
8
|
const renderContent = () => {
|
|
8
9
|
if (typeof content === "string") {
|
|
9
|
-
return <div className="
|
|
10
|
+
return <div className="text-white whitespace-pre-wrap">{content}</div>;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
if (Array.isArray(content)) {
|
|
@@ -14,27 +15,27 @@ const MessageHuman: React.FC<MessageHumanProps> = ({ content }) => {
|
|
|
14
15
|
switch (item.type) {
|
|
15
16
|
case "text":
|
|
16
17
|
return (
|
|
17
|
-
<div key={index} className="
|
|
18
|
+
<div key={index} className="text-white">
|
|
18
19
|
{item.text}
|
|
19
20
|
</div>
|
|
20
21
|
);
|
|
21
22
|
case "image_url":
|
|
22
23
|
return (
|
|
23
|
-
<div key={index} className="
|
|
24
|
-
<img src={item.image_url.url} alt="用户上传的图片"
|
|
24
|
+
<div key={index} className="mt-2">
|
|
25
|
+
<img src={item.image_url.url} alt="用户上传的图片" className="max-w-[200px] rounded" />
|
|
25
26
|
</div>
|
|
26
27
|
);
|
|
27
28
|
case "audio":
|
|
28
29
|
return (
|
|
29
|
-
<div key={index} className="
|
|
30
|
-
<audio controls src={item.audio_url}>
|
|
30
|
+
<div key={index} className="mt-2">
|
|
31
|
+
<audio controls src={item.audio_url} className="w-full">
|
|
31
32
|
您的浏览器不支持音频播放
|
|
32
33
|
</audio>
|
|
33
34
|
</div>
|
|
34
35
|
);
|
|
35
36
|
default:
|
|
36
37
|
return (
|
|
37
|
-
<div key={index} className="
|
|
38
|
+
<div key={index} className="text-white whitespace-pre-wrap">
|
|
38
39
|
{JSON.stringify(item)}
|
|
39
40
|
</div>
|
|
40
41
|
);
|
|
@@ -42,12 +43,14 @@ const MessageHuman: React.FC<MessageHumanProps> = ({ content }) => {
|
|
|
42
43
|
});
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
return <div className="
|
|
46
|
+
return <div className="text-white whitespace-pre-wrap">{JSON.stringify(content)}</div>;
|
|
46
47
|
};
|
|
47
48
|
|
|
48
49
|
return (
|
|
49
|
-
<div className="
|
|
50
|
-
<div className="
|
|
50
|
+
<div className="flex flex-row w-full justify-end">
|
|
51
|
+
<div className="flex flex-col w-fit bg-blue-500 rounded-lg text-white border border-blue-100">
|
|
52
|
+
<div className="flex flex-col p-4 ">{renderContent()}</div>
|
|
53
|
+
</div>
|
|
51
54
|
</div>
|
|
52
55
|
);
|
|
53
56
|
};
|