@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.
Files changed (48) hide show
  1. package/.env +2 -0
  2. package/.env.example +2 -0
  3. package/dist/assets/index-7vem5Peg.css +1 -0
  4. package/dist/assets/index-CZ6k2dGe.js +235 -0
  5. package/dist/index.html +3 -5
  6. package/index.html +1 -3
  7. package/package.json +10 -2
  8. package/src/artifacts/ArtifactViewer.tsx +158 -0
  9. package/src/artifacts/ArtifactsContext.tsx +99 -0
  10. package/src/artifacts/SourceCodeViewer.tsx +15 -0
  11. package/src/chat/Chat.tsx +102 -27
  12. package/src/chat/FileUpload/index.ts +3 -7
  13. package/src/chat/components/FileList.tsx +16 -12
  14. package/src/chat/components/HistoryList.tsx +39 -137
  15. package/src/chat/components/JsonEditorPopup.tsx +57 -45
  16. package/src/chat/components/JsonToMessage/JsonToMessage.tsx +20 -14
  17. package/src/chat/components/JsonToMessage/JsonToMessageButton.tsx +9 -16
  18. package/src/chat/components/JsonToMessage/index.tsx +1 -1
  19. package/src/chat/components/MessageAI.tsx +5 -4
  20. package/src/chat/components/MessageBox.tsx +21 -19
  21. package/src/chat/components/MessageHuman.tsx +13 -10
  22. package/src/chat/components/MessageTool.tsx +71 -30
  23. package/src/chat/components/UsageMetadata.tsx +41 -22
  24. package/src/chat/context/ChatContext.tsx +14 -5
  25. package/src/chat/store/index.ts +25 -2
  26. package/src/chat/tools/ask_user_for_approve.tsx +80 -0
  27. package/src/chat/tools/create_artifacts.tsx +50 -0
  28. package/src/chat/tools/index.ts +5 -0
  29. package/src/chat/tools/update_plan.tsx +75 -0
  30. package/src/chat/tools/web_search_tool.tsx +89 -0
  31. package/src/graph/index.tsx +9 -6
  32. package/src/index.ts +1 -0
  33. package/src/login/Login.tsx +155 -47
  34. package/src/memory/BaseDB.ts +92 -0
  35. package/src/memory/db.ts +232 -0
  36. package/src/memory/fulltext-search.ts +191 -0
  37. package/src/memory/index.ts +4 -0
  38. package/src/memory/tools.ts +170 -0
  39. package/test/main.tsx +2 -2
  40. package/vite.config.ts +7 -1
  41. package/dist/assets/index-BWndsYW1.js +0 -214
  42. package/dist/assets/index-LcgERueJ.css +0 -1
  43. package/src/chat/chat.css +0 -406
  44. package/src/chat/components/FileList.css +0 -129
  45. package/src/chat/components/JsonEditorPopup.css +0 -81
  46. package/src/chat/components/JsonToMessage/JsonToMessage.css +0 -104
  47. package/src/chat/tools.ts +0 -33
  48. 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="history-list">
14
- <div className="history-header">
15
- <div className="header-left">
16
- <h3>历史记录</h3>
17
- <button className="refresh-button" onClick={refreshHistoryList} title="刷新列表">
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 className="close-button" onClick={onClose} title="关闭">
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="history-content">
34
+ <div className="flex-1 overflow-y-auto p-4">
26
35
  <div
27
- className="history-items"
36
+ className="flex flex-col gap-3 cursor-pointer"
28
37
  onClick={() => {
29
38
  createNewChat();
30
39
  }}
31
40
  >
32
- <div className="history-item">
33
- <div className="history-title"> New Chat</div>
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="empty-history">暂无历史记录</div>
46
+ <div className="text-center text-gray-500 py-8">暂无历史记录</div>
38
47
  ) : (
39
- <div className="history-items">
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 className={`history-item ${thread.thread_id === currentChatId ? "active" : ""}`} key={thread.thread_id}>
44
- <div className="history-info">
45
- <div className="history-title">{getHistoryContent(thread)}</div>
46
- <div className="history-meta">
47
- <span className="history-time">{formatTime(new Date(thread.created_at))}</span>
48
- <span className="history-status">{thread.status}</span>
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="history-actions">
65
+ <div className="flex gap-2 shrink-0">
52
66
  <button
53
- className="action-button"
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="action-button"
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 'react';
2
- import './JsonEditorPopup.css';
1
+ import React, { useState, useEffect } from "react";
3
2
 
4
3
  interface JsonEditorPopupProps {
5
- isOpen: boolean;
6
- initialJson: object;
7
- onClose: () => void;
8
- onSave: (jsonData: object) => void;
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
- const [jsonString, setJsonString] = useState('');
13
- const [error, setError] = useState<string | null>(null);
11
+ const [jsonString, setJsonString] = useState("");
12
+ const [error, setError] = useState<string | null>(null);
14
13
 
15
- useEffect(() => {
16
- setJsonString(JSON.stringify(initialJson, null, 2));
17
- setError(null); // Reset error when initialJson changes or popup opens
18
- }, [initialJson, isOpen]);
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
- if (!isOpen) {
21
- return null;
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
- return (
36
- <div className="json-editor-popup-overlay">
37
- <div className="json-editor-popup-content">
38
- <h2>编辑 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
- />
47
- {error && <p className="error-message">{error}</p>}
48
- <div className="popup-actions">
49
- <button onClick={onClose} className="cancel-button">取消</button>
50
- <button onClick={handleSave} className="save-button">保存</button>
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
- </div>
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="json-to-message-overlay">
85
- <div className="json-to-message-content">
86
- <div className="json-to-message-header">
87
- <h2>JSON 消息预览</h2>
88
- <button onClick={onClose} className="close-button">
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="json-to-message-body">
94
- <div className="json-editor-pane">
95
- <textarea value={jsonString} onChange={(e) => setJsonString(e.target.value)} rows={20} placeholder="输入JSON消息格式..." />
96
- {error && <p className="error-message">{error}</p>}
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="message-preview-pane">
100
- <div className="preview-container chat-messages">
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="no-preview">请输入有效的JSON以查看消息预览</div>
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
- interface JsonToMessageButtonProps {
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 className={`history-button ${className}`} onClick={openModal}>
19
- {buttonText}
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;
@@ -1,5 +1,5 @@
1
1
  import JsonToMessage from "./JsonToMessage";
2
- import JsonToMessageButton from "./JsonToMessageButton";
2
+ import { JsonToMessageButton } from "./JsonToMessageButton";
3
3
 
4
4
  export { JsonToMessage, JsonToMessageButton };
5
5
  export default JsonToMessage;
@@ -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="message ai">
14
- <div className="message-content">
15
- <div className="message-title">{message.name}</div>
16
- <div className="message-text markdown-body">
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.type === "human" ? (
22
- <MessageHuman content={message.content} key={message.unique_id} />
23
- ) : message.type === "tool" ? (
24
- <MessageTool
25
- key={message.unique_id}
26
- message={message}
27
- client={client!}
28
- getMessageContent={getMessageContent}
29
- formatTokens={formatTokens}
30
- isCollapsed={collapsedTools.includes(message.id!)}
31
- onToggleCollapse={() => toggleToolCollapse(message.id!)}
32
- />
33
- ) : (
34
- <MessageAI key={message.unique_id} message={message} />
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="message-text">{content}</div>;
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="message-text">
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="message-image">
24
- <img src={item.image_url.url} alt="用户上传的图片" style={{ maxWidth: "200px", borderRadius: "4px" }} />
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="message-audio">
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="message-text">
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="message-text">{JSON.stringify(content)}</div>;
46
+ return <div className="text-white whitespace-pre-wrap">{JSON.stringify(content)}</div>;
46
47
  };
47
48
 
48
49
  return (
49
- <div className="message human">
50
- <div className="message-content">{renderContent()}</div>
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
  };