@wayofmono/wo-web-ui 1.0.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/package.json +33 -0
- package/src/components/chat-container.tsx +137 -0
- package/src/components/chat-input.tsx +91 -0
- package/src/components/message-bubble.tsx +44 -0
- package/src/components/session-list.tsx +112 -0
- package/src/components/tool-call-card.tsx +94 -0
- package/src/index.ts +10 -0
- package/src/theme.ts +35 -0
- package/src/types.ts +56 -0
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wayofmono/wo-web-ui",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Web UI components for WayOfMono — chat interface, session management, and tool call visualization",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.ts",
|
|
11
|
+
"import": "./src/index.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"react": "^19.1.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/react": "^19.1.2",
|
|
23
|
+
"@types/react-dom": "^19.1.2",
|
|
24
|
+
"typescript": "^6.0.3"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"typecheck": "tsc --noEmit"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { useRef, useEffect } from "react";
|
|
2
|
+
import type { ChatMessage, SessionInfo } from "../types.js";
|
|
3
|
+
import { getTheme, type ThemeConfig } from "../theme.js";
|
|
4
|
+
import { MessageBubble } from "./message-bubble.js";
|
|
5
|
+
import { ChatInput } from "./chat-input.js";
|
|
6
|
+
import { SessionList } from "./session-list.js";
|
|
7
|
+
import { ToolCallCard } from "./tool-call-card.js";
|
|
8
|
+
|
|
9
|
+
export interface ChatContainerProps {
|
|
10
|
+
messages: ChatMessage[];
|
|
11
|
+
sessions?: SessionInfo[];
|
|
12
|
+
activeSessionId?: string;
|
|
13
|
+
onSend: (text: string) => void;
|
|
14
|
+
onSelectSession?: (id: string) => void;
|
|
15
|
+
onCreateSession?: () => void;
|
|
16
|
+
themeMode?: "dark" | "light";
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const containerStyle = (theme: ThemeConfig): React.CSSProperties => ({
|
|
21
|
+
display: "flex",
|
|
22
|
+
height: "100%",
|
|
23
|
+
backgroundColor: theme.background,
|
|
24
|
+
color: theme.text,
|
|
25
|
+
fontFamily:
|
|
26
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const mainStyle: React.CSSProperties = {
|
|
30
|
+
flex: 1,
|
|
31
|
+
display: "flex",
|
|
32
|
+
flexDirection: "column",
|
|
33
|
+
overflow: "hidden",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const messagesStyle: React.CSSProperties = {
|
|
37
|
+
flex: 1,
|
|
38
|
+
overflowY: "auto",
|
|
39
|
+
padding: "16px",
|
|
40
|
+
display: "flex",
|
|
41
|
+
flexDirection: "column",
|
|
42
|
+
gap: "12px",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const emptyStyle: React.CSSProperties = {
|
|
46
|
+
flex: 1,
|
|
47
|
+
display: "flex",
|
|
48
|
+
alignItems: "center",
|
|
49
|
+
justifyContent: "center",
|
|
50
|
+
color: "var(--text-secondary, #94a3b8)",
|
|
51
|
+
fontSize: "14px",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export function ChatContainer({
|
|
55
|
+
messages,
|
|
56
|
+
sessions,
|
|
57
|
+
activeSessionId,
|
|
58
|
+
onSend,
|
|
59
|
+
onSelectSession,
|
|
60
|
+
onCreateSession,
|
|
61
|
+
themeMode = "dark",
|
|
62
|
+
disabled,
|
|
63
|
+
}: ChatContainerProps) {
|
|
64
|
+
const bottomRef = useRef<HTMLDivElement>(null);
|
|
65
|
+
const theme = getTheme(themeMode);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
69
|
+
}, [messages]);
|
|
70
|
+
|
|
71
|
+
const bubbleTheme = {
|
|
72
|
+
userBubble: theme.userBubble,
|
|
73
|
+
assistantBubble: theme.assistantBubble,
|
|
74
|
+
text: theme.text,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const inputTheme = {
|
|
78
|
+
surface: theme.surface,
|
|
79
|
+
text: theme.text,
|
|
80
|
+
border: theme.border,
|
|
81
|
+
primary: theme.primary,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const toolCardTheme = {
|
|
85
|
+
surface: theme.surface,
|
|
86
|
+
text: theme.text,
|
|
87
|
+
textSecondary: theme.textSecondary,
|
|
88
|
+
border: theme.border,
|
|
89
|
+
success: theme.success,
|
|
90
|
+
error: theme.error,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div style={containerStyle(theme)}>
|
|
95
|
+
{sessions && onSelectSession && onCreateSession && (
|
|
96
|
+
<SessionList
|
|
97
|
+
sessions={sessions}
|
|
98
|
+
activeId={activeSessionId}
|
|
99
|
+
onSelect={onSelectSession}
|
|
100
|
+
onCreate={onCreateSession}
|
|
101
|
+
theme={{
|
|
102
|
+
surface: theme.surface,
|
|
103
|
+
text: theme.text,
|
|
104
|
+
textSecondary: theme.textSecondary,
|
|
105
|
+
border: theme.border,
|
|
106
|
+
primary: theme.primary,
|
|
107
|
+
}}
|
|
108
|
+
/>
|
|
109
|
+
)}
|
|
110
|
+
<div style={mainStyle}>
|
|
111
|
+
<div style={messagesStyle}>
|
|
112
|
+
{messages.length === 0 && (
|
|
113
|
+
<div style={emptyStyle}>Start a conversation</div>
|
|
114
|
+
)}
|
|
115
|
+
{messages.map((msg) => (
|
|
116
|
+
<div key={msg.id}>
|
|
117
|
+
<MessageBubble message={msg} theme={bubbleTheme} />
|
|
118
|
+
{msg.toolCalls?.map((tc) => (
|
|
119
|
+
<div key={tc.id} style={{ marginTop: "8px" }}>
|
|
120
|
+
<ToolCallCard toolCall={tc} theme={toolCardTheme} />
|
|
121
|
+
</div>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
))}
|
|
125
|
+
<div ref={bottomRef} />
|
|
126
|
+
</div>
|
|
127
|
+
<ChatInput
|
|
128
|
+
onSend={onSend}
|
|
129
|
+
disabled={disabled}
|
|
130
|
+
theme={inputTheme}
|
|
131
|
+
/>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default ChatContainer;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { useState, type KeyboardEvent } from "react";
|
|
2
|
+
|
|
3
|
+
export interface ChatInputProps {
|
|
4
|
+
onSend: (text: string) => void;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
theme: {
|
|
8
|
+
surface: string;
|
|
9
|
+
text: string;
|
|
10
|
+
border: string;
|
|
11
|
+
primary: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const containerStyle: React.CSSProperties = {
|
|
16
|
+
display: "flex",
|
|
17
|
+
gap: "8px",
|
|
18
|
+
padding: "12px",
|
|
19
|
+
borderTop: "1px solid var(--border, #e2e8f0)",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const textareaStyle = (
|
|
23
|
+
theme: ChatInputProps["theme"],
|
|
24
|
+
): React.CSSProperties => ({
|
|
25
|
+
flex: 1,
|
|
26
|
+
backgroundColor: theme.surface,
|
|
27
|
+
color: theme.text,
|
|
28
|
+
border: `1px solid ${theme.border}`,
|
|
29
|
+
borderRadius: "8px",
|
|
30
|
+
padding: "10px 14px",
|
|
31
|
+
fontSize: "14px",
|
|
32
|
+
fontFamily: "inherit",
|
|
33
|
+
resize: "none",
|
|
34
|
+
outline: "none",
|
|
35
|
+
minHeight: "42px",
|
|
36
|
+
maxHeight: "200px",
|
|
37
|
+
lineHeight: "1.4",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const buttonStyle = (theme: ChatInputProps["theme"]): React.CSSProperties => ({
|
|
41
|
+
backgroundColor: theme.primary,
|
|
42
|
+
color: "#fff",
|
|
43
|
+
border: "none",
|
|
44
|
+
borderRadius: "8px",
|
|
45
|
+
padding: "8px 16px",
|
|
46
|
+
fontSize: "14px",
|
|
47
|
+
cursor: "pointer",
|
|
48
|
+
alignSelf: "flex-end",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export function ChatInput({ onSend, disabled, placeholder, theme }: ChatInputProps) {
|
|
52
|
+
const [text, setText] = useState("");
|
|
53
|
+
|
|
54
|
+
const handleSend = () => {
|
|
55
|
+
const trimmed = text.trim();
|
|
56
|
+
if (!trimmed || disabled) return;
|
|
57
|
+
onSend(trimmed);
|
|
58
|
+
setText("");
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
62
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
handleSend();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div style={containerStyle}>
|
|
70
|
+
<textarea
|
|
71
|
+
value={text}
|
|
72
|
+
onChange={(e) => setText(e.target.value)}
|
|
73
|
+
onKeyDown={handleKeyDown}
|
|
74
|
+
style={textareaStyle(theme)}
|
|
75
|
+
placeholder={placeholder ?? "Type a message..."}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
rows={1}
|
|
78
|
+
/>
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
onClick={handleSend}
|
|
82
|
+
disabled={disabled || !text.trim()}
|
|
83
|
+
style={buttonStyle(theme)}
|
|
84
|
+
>
|
|
85
|
+
Send
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default ChatInput;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ChatMessage } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export interface MessageBubbleProps {
|
|
4
|
+
message: ChatMessage;
|
|
5
|
+
theme: {
|
|
6
|
+
userBubble: string;
|
|
7
|
+
assistantBubble: string;
|
|
8
|
+
text: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const bubbleStyle = (role: ChatMessage["role"], theme: MessageBubbleProps["theme"]): React.CSSProperties => ({
|
|
13
|
+
alignSelf: role === "user" ? "flex-end" : "flex-start",
|
|
14
|
+
backgroundColor: role === "user" ? theme.userBubble : theme.assistantBubble,
|
|
15
|
+
color: theme.text,
|
|
16
|
+
borderRadius: "12px",
|
|
17
|
+
padding: "8px 14px",
|
|
18
|
+
maxWidth: "80%",
|
|
19
|
+
wordBreak: "break-word",
|
|
20
|
+
whiteSpace: "pre-wrap",
|
|
21
|
+
fontSize: "14px",
|
|
22
|
+
lineHeight: "1.5",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const streamingStyle: React.CSSProperties = {
|
|
26
|
+
display: "inline-block",
|
|
27
|
+
width: "8px",
|
|
28
|
+
height: "16px",
|
|
29
|
+
backgroundColor: "currentColor",
|
|
30
|
+
animation: "wo-blink 1s step-end infinite",
|
|
31
|
+
marginLeft: "2px",
|
|
32
|
+
opacity: 0.7,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function MessageBubble({ message, theme }: MessageBubbleProps) {
|
|
36
|
+
return (
|
|
37
|
+
<div key={message.id} style={bubbleStyle(message.role, theme)}>
|
|
38
|
+
{message.content}
|
|
39
|
+
{message.streaming && <span style={streamingStyle} />}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default MessageBubble;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { SessionInfo } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export interface SessionListProps {
|
|
4
|
+
sessions: SessionInfo[];
|
|
5
|
+
activeId?: string;
|
|
6
|
+
onSelect: (id: string) => void;
|
|
7
|
+
onCreate: () => void;
|
|
8
|
+
theme: {
|
|
9
|
+
surface: string;
|
|
10
|
+
text: string;
|
|
11
|
+
textSecondary: string;
|
|
12
|
+
border: string;
|
|
13
|
+
primary: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const sidebarStyle = (theme: SessionListProps["theme"]): React.CSSProperties => ({
|
|
18
|
+
width: "260px",
|
|
19
|
+
backgroundColor: theme.surface,
|
|
20
|
+
borderRight: `1px solid ${theme.border}`,
|
|
21
|
+
display: "flex",
|
|
22
|
+
flexDirection: "column",
|
|
23
|
+
height: "100%",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const headerStyle: React.CSSProperties = {
|
|
27
|
+
padding: "16px",
|
|
28
|
+
fontSize: "16px",
|
|
29
|
+
fontWeight: 600,
|
|
30
|
+
display: "flex",
|
|
31
|
+
justifyContent: "space-between",
|
|
32
|
+
alignItems: "center",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const createButtonStyle = (theme: SessionListProps["theme"]): React.CSSProperties => ({
|
|
36
|
+
backgroundColor: theme.primary,
|
|
37
|
+
color: "#fff",
|
|
38
|
+
border: "none",
|
|
39
|
+
borderRadius: "6px",
|
|
40
|
+
padding: "4px 10px",
|
|
41
|
+
fontSize: "12px",
|
|
42
|
+
cursor: "pointer",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const listStyle: React.CSSProperties = {
|
|
46
|
+
flex: 1,
|
|
47
|
+
overflowY: "auto",
|
|
48
|
+
padding: "4px 8px",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function itemStyle(isActive: boolean, theme: SessionListProps["theme"]): React.CSSProperties {
|
|
52
|
+
return {
|
|
53
|
+
padding: "10px 12px",
|
|
54
|
+
borderRadius: "6px",
|
|
55
|
+
cursor: "pointer",
|
|
56
|
+
backgroundColor: isActive ? theme.primary + "20" : "transparent",
|
|
57
|
+
marginBottom: "2px",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const labelStyle: React.CSSProperties = {
|
|
62
|
+
fontSize: "14px",
|
|
63
|
+
fontWeight: 500,
|
|
64
|
+
overflow: "hidden",
|
|
65
|
+
textOverflow: "ellipsis",
|
|
66
|
+
whiteSpace: "nowrap",
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const metaStyle = (theme: SessionListProps["theme"]): React.CSSProperties => ({
|
|
70
|
+
fontSize: "11px",
|
|
71
|
+
color: theme.textSecondary,
|
|
72
|
+
marginTop: "2px",
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function formatTime(ts: number): string {
|
|
76
|
+
const d = new Date(ts);
|
|
77
|
+
const now = new Date();
|
|
78
|
+
const diff = now.getTime() - d.getTime();
|
|
79
|
+
if (diff < 60000) return "just now";
|
|
80
|
+
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
|
|
81
|
+
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
|
|
82
|
+
return d.toLocaleDateString();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function SessionList({ sessions, activeId, onSelect, onCreate, theme }: SessionListProps) {
|
|
86
|
+
return (
|
|
87
|
+
<div style={sidebarStyle(theme)}>
|
|
88
|
+
<div style={headerStyle}>
|
|
89
|
+
<span>Sessions</span>
|
|
90
|
+
<button type="button" style={createButtonStyle(theme)} onClick={onCreate}>
|
|
91
|
+
+ New
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
<div style={listStyle}>
|
|
95
|
+
{sessions.map((s) => (
|
|
96
|
+
<div
|
|
97
|
+
key={s.id}
|
|
98
|
+
style={itemStyle(s.id === activeId, theme)}
|
|
99
|
+
onClick={() => onSelect(s.id)}
|
|
100
|
+
>
|
|
101
|
+
<div style={labelStyle}>{s.label}</div>
|
|
102
|
+
<div style={metaStyle(theme)}>
|
|
103
|
+
{formatTime(s.updatedAt)} · {s.messageCount} msgs
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default SessionList;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { ToolCall } from "../types.js";
|
|
3
|
+
|
|
4
|
+
export interface ToolCallCardProps {
|
|
5
|
+
toolCall: ToolCall;
|
|
6
|
+
theme: {
|
|
7
|
+
surface: string;
|
|
8
|
+
text: string;
|
|
9
|
+
textSecondary: string;
|
|
10
|
+
border: string;
|
|
11
|
+
success: string;
|
|
12
|
+
error: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const cardStyle = (theme: ToolCallCardProps["theme"]): React.CSSProperties => ({
|
|
17
|
+
backgroundColor: theme.surface,
|
|
18
|
+
border: `1px solid ${theme.border}`,
|
|
19
|
+
borderRadius: "8px",
|
|
20
|
+
overflow: "hidden",
|
|
21
|
+
fontSize: "13px",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const headerStyle: React.CSSProperties = {
|
|
25
|
+
display: "flex",
|
|
26
|
+
alignItems: "center",
|
|
27
|
+
gap: "8px",
|
|
28
|
+
padding: "8px 12px",
|
|
29
|
+
cursor: "pointer",
|
|
30
|
+
userSelect: "none",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const dotStyle = (color: string): React.CSSProperties => ({
|
|
34
|
+
width: "8px",
|
|
35
|
+
height: "8px",
|
|
36
|
+
borderRadius: "50%",
|
|
37
|
+
backgroundColor: color,
|
|
38
|
+
flexShrink: 0,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const nameStyle: React.CSSProperties = {
|
|
42
|
+
fontWeight: 600,
|
|
43
|
+
flex: 1,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const contentStyle = (theme: ToolCallCardProps["theme"]): React.CSSProperties => ({
|
|
47
|
+
padding: "8px 12px",
|
|
48
|
+
borderTop: `1px solid ${theme.border}`,
|
|
49
|
+
fontFamily: "monospace",
|
|
50
|
+
fontSize: "12px",
|
|
51
|
+
whiteSpace: "pre-wrap",
|
|
52
|
+
wordBreak: "break-word",
|
|
53
|
+
maxHeight: "200px",
|
|
54
|
+
overflowY: "auto",
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export function ToolCallCard({ toolCall, theme }: ToolCallCardProps) {
|
|
58
|
+
const [expanded, setExpanded] = useState(false);
|
|
59
|
+
const statusColor = toolCall.result?.isError ? theme.error : theme.success;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div style={cardStyle(theme)}>
|
|
63
|
+
<div style={headerStyle} onClick={() => setExpanded(!expanded)}>
|
|
64
|
+
<span style={dotStyle(statusColor)} />
|
|
65
|
+
<span style={nameStyle}>{toolCall.name}</span>
|
|
66
|
+
<span style={{ color: theme.textSecondary, fontSize: "11px" }}>
|
|
67
|
+
{expanded ? "▲" : "▼"}
|
|
68
|
+
</span>
|
|
69
|
+
</div>
|
|
70
|
+
{expanded && (
|
|
71
|
+
<div style={contentStyle(theme)}>
|
|
72
|
+
<div style={{ color: theme.textSecondary, marginBottom: "4px" }}>Arguments:</div>
|
|
73
|
+
{toolCall.args}
|
|
74
|
+
{toolCall.result && (
|
|
75
|
+
<>
|
|
76
|
+
<div
|
|
77
|
+
style={{
|
|
78
|
+
color: theme.textSecondary,
|
|
79
|
+
marginTop: "8px",
|
|
80
|
+
marginBottom: "4px",
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
Result:
|
|
84
|
+
</div>
|
|
85
|
+
{toolCall.result.content}
|
|
86
|
+
</>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default ToolCallCard;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { ChatContainer } from "./components/chat-container.js";
|
|
2
|
+
export { MessageBubble } from "./components/message-bubble.js";
|
|
3
|
+
export { ChatInput } from "./components/chat-input.js";
|
|
4
|
+
export { SessionList } from "./components/session-list.js";
|
|
5
|
+
export { ToolCallCard } from "./components/tool-call-card.js";
|
|
6
|
+
|
|
7
|
+
export type { ChatMessage, ToolCall, ToolResult } from "./types.js";
|
|
8
|
+
export type { SessionInfo, DiagnosticInfo } from "./types.js";
|
|
9
|
+
export type { ThemeConfig, WebSocketMessage } from "./types.js";
|
|
10
|
+
export { darkTheme, lightTheme, getTheme } from "./theme.js";
|
package/src/theme.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ThemeConfig } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export const darkTheme: ThemeConfig = {
|
|
4
|
+
mode: "dark",
|
|
5
|
+
primary: "#6366f1",
|
|
6
|
+
background: "#0f172a",
|
|
7
|
+
surface: "#1e293b",
|
|
8
|
+
text: "#f1f5f9",
|
|
9
|
+
textSecondary: "#94a3b8",
|
|
10
|
+
border: "#334155",
|
|
11
|
+
userBubble: "#6366f1",
|
|
12
|
+
assistantBubble: "#334155",
|
|
13
|
+
error: "#ef4444",
|
|
14
|
+
success: "#22c55e",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const lightTheme: ThemeConfig = {
|
|
18
|
+
mode: "light",
|
|
19
|
+
primary: "#6366f1",
|
|
20
|
+
background: "#ffffff",
|
|
21
|
+
surface: "#f8fafc",
|
|
22
|
+
text: "#0f172a",
|
|
23
|
+
textSecondary: "#64748b",
|
|
24
|
+
border: "#e2e8f0",
|
|
25
|
+
userBubble: "#6366f1",
|
|
26
|
+
assistantBubble: "#f1f5f9",
|
|
27
|
+
error: "#ef4444",
|
|
28
|
+
success: "#22c55e",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function getTheme(mode: "dark" | "light"): ThemeConfig {
|
|
32
|
+
return mode === "dark" ? darkTheme : lightTheme;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type { ThemeConfig };
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface ChatMessage {
|
|
2
|
+
id: string;
|
|
3
|
+
role: "user" | "assistant" | "system" | "tool";
|
|
4
|
+
content: string;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
toolCalls?: ToolCall[];
|
|
7
|
+
toolResult?: ToolResult;
|
|
8
|
+
streaming?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ToolCall {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
args: string;
|
|
15
|
+
result?: ToolResult;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ToolResult {
|
|
19
|
+
content: string;
|
|
20
|
+
isError?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SessionInfo {
|
|
24
|
+
id: string;
|
|
25
|
+
label: string;
|
|
26
|
+
createdAt: number;
|
|
27
|
+
updatedAt: number;
|
|
28
|
+
messageCount: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DiagnosticInfo {
|
|
32
|
+
message: string;
|
|
33
|
+
severity: "error" | "warning" | "info" | "hint";
|
|
34
|
+
line?: number;
|
|
35
|
+
column?: number;
|
|
36
|
+
tool?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ThemeConfig {
|
|
40
|
+
mode: "dark" | "light";
|
|
41
|
+
primary: string;
|
|
42
|
+
background: string;
|
|
43
|
+
surface: string;
|
|
44
|
+
text: string;
|
|
45
|
+
textSecondary: string;
|
|
46
|
+
border: string;
|
|
47
|
+
userBubble: string;
|
|
48
|
+
assistantBubble: string;
|
|
49
|
+
error: string;
|
|
50
|
+
success: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface WebSocketMessage {
|
|
54
|
+
type: "message" | "tool_call" | "tool_result" | "error" | "status" | "diagnostic";
|
|
55
|
+
payload: unknown;
|
|
56
|
+
}
|