@sciol/xyzen 0.1.4 → 0.1.6
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 +3 -3
- package/src/app/App.tsx +0 -221
- package/src/assets/react.svg +0 -1
- package/src/components/layouts/XyzenChat.tsx +0 -212
- package/src/components/layouts/XyzenHistory.tsx +0 -117
- package/src/components/layouts/XyzenNodes.tsx +0 -7
- package/src/components/layouts/components/ChatBubble.tsx +0 -140
- package/src/components/layouts/components/ChatInput.tsx +0 -173
- package/src/components/layouts/components/EmptyChat.tsx +0 -117
- package/src/components/layouts/components/WelcomeMessage.tsx +0 -104
- package/src/configs/index.ts +0 -1
- package/src/context/XyzenProvider.tsx +0 -0
- package/src/hooks/useTheme.ts +0 -79
- package/src/index.ts +0 -6
- package/src/lib/Markdown.tsx +0 -98
- package/src/lib/formatDate.ts +0 -32
- package/src/main.tsx +0 -9
- package/src/service/xyzenService.ts +0 -90
- package/src/store/xyzenStore.ts +0 -326
- package/src/types/xyzen.d.ts +0 -0
- package/src/vite-env.d.ts +0 -1
package/src/lib/Markdown.tsx
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import ReactMarkdown from "react-markdown";
|
|
3
|
-
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
|
4
|
-
import rehypeHighlight from "rehype-highlight";
|
|
5
|
-
import rehypeKatex from "rehype-katex";
|
|
6
|
-
import rehypeRaw from "rehype-raw";
|
|
7
|
-
import remarkGfm from "remark-gfm";
|
|
8
|
-
import remarkMath from "remark-math";
|
|
9
|
-
|
|
10
|
-
import "katex/dist/katex.css";
|
|
11
|
-
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
|
12
|
-
|
|
13
|
-
interface MarkdownProps {
|
|
14
|
-
content: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const Markdown: React.FC<MarkdownProps> = function Markdown(props) {
|
|
18
|
-
const { content = "" } = props;
|
|
19
|
-
const [copiedCode, setCopiedCode] = useState<string | null>(null);
|
|
20
|
-
|
|
21
|
-
const copyToClipboard = (code: string) => {
|
|
22
|
-
navigator.clipboard.writeText(code).then(() => {
|
|
23
|
-
setCopiedCode(code);
|
|
24
|
-
setTimeout(() => {
|
|
25
|
-
setCopiedCode(null);
|
|
26
|
-
}, 2000);
|
|
27
|
-
});
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const MarkdownComponents = {
|
|
31
|
-
code({
|
|
32
|
-
inline,
|
|
33
|
-
className,
|
|
34
|
-
children,
|
|
35
|
-
...props
|
|
36
|
-
}: React.ComponentPropsWithoutRef<"code"> & { inline?: boolean }) {
|
|
37
|
-
const match = /language-(\w+)/.exec(className || "");
|
|
38
|
-
const code = String(children).replace(/\n$/, "");
|
|
39
|
-
|
|
40
|
-
return !inline && match ? (
|
|
41
|
-
<div style={{ position: "relative" }}>
|
|
42
|
-
<button
|
|
43
|
-
onClick={() => copyToClipboard(code)}
|
|
44
|
-
style={{
|
|
45
|
-
position: "absolute",
|
|
46
|
-
top: "5px",
|
|
47
|
-
right: "5px",
|
|
48
|
-
padding: "4px 8px",
|
|
49
|
-
backgroundColor: "#282c34",
|
|
50
|
-
color: "white",
|
|
51
|
-
border: "1px solid #444",
|
|
52
|
-
borderRadius: "4px",
|
|
53
|
-
cursor: "pointer",
|
|
54
|
-
fontSize: "12px",
|
|
55
|
-
zIndex: 2,
|
|
56
|
-
opacity: 0.8,
|
|
57
|
-
transition: "opacity 0.2s",
|
|
58
|
-
}}
|
|
59
|
-
onMouseEnter={(e) => {
|
|
60
|
-
e.currentTarget.style.opacity = "1";
|
|
61
|
-
}}
|
|
62
|
-
onMouseLeave={(e) => {
|
|
63
|
-
e.currentTarget.style.opacity = "0.8";
|
|
64
|
-
}}
|
|
65
|
-
>
|
|
66
|
-
{copiedCode === code ? (
|
|
67
|
-
<span className=" text-green-400">Copied!</span>
|
|
68
|
-
) : (
|
|
69
|
-
"Copy"
|
|
70
|
-
)}
|
|
71
|
-
</button>
|
|
72
|
-
<SyntaxHighlighter style={oneDark} language={match[1]} PreTag="div">
|
|
73
|
-
{code}
|
|
74
|
-
</SyntaxHighlighter>
|
|
75
|
-
</div>
|
|
76
|
-
) : (
|
|
77
|
-
<code className={className} {...props}>
|
|
78
|
-
{children}
|
|
79
|
-
</code>
|
|
80
|
-
);
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
return (
|
|
84
|
-
<ReactMarkdown
|
|
85
|
-
components={MarkdownComponents}
|
|
86
|
-
remarkPlugins={[remarkMath, remarkGfm]}
|
|
87
|
-
rehypePlugins={[
|
|
88
|
-
rehypeKatex,
|
|
89
|
-
rehypeRaw,
|
|
90
|
-
[rehypeHighlight, { ignoreMissing: true }],
|
|
91
|
-
]}
|
|
92
|
-
>
|
|
93
|
-
{content}
|
|
94
|
-
</ReactMarkdown>
|
|
95
|
-
);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
export default Markdown;
|
package/src/lib/formatDate.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { format, formatDistanceToNow, isToday, isYesterday } from "date-fns";
|
|
2
|
-
import { zhCN } from "date-fns/locale";
|
|
3
|
-
|
|
4
|
-
export function formatTime(dateString: string): string {
|
|
5
|
-
const date = new Date(dateString);
|
|
6
|
-
const now = new Date();
|
|
7
|
-
|
|
8
|
-
// 使用 formatDistanceToNow 显示相对时间,并添加中文支持
|
|
9
|
-
// addSuffix: true 会添加 "前" 或 "后"
|
|
10
|
-
const relativeTime = formatDistanceToNow(date, {
|
|
11
|
-
addSuffix: true,
|
|
12
|
-
locale: zhCN,
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
// 如果是一天内,直接返回相对时间,例如 "约5小时前"
|
|
16
|
-
if (now.getTime() - date.getTime() < 24 * 60 * 60 * 1000) {
|
|
17
|
-
return relativeTime;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// 如果是昨天
|
|
21
|
-
if (isYesterday(date)) {
|
|
22
|
-
return `昨天 ${format(date, "HH:mm")}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// 如果是今天(理论上被前一个if覆盖,但作为保险)
|
|
26
|
-
if (isToday(date)) {
|
|
27
|
-
return format(date, "HH:mm");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// 如果是更早的时间,显示具体日期
|
|
31
|
-
return format(date, "yyyy-MM-dd");
|
|
32
|
-
}
|
package/src/main.tsx
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { type Message } from "@/store/xyzenStore";
|
|
2
|
-
|
|
3
|
-
interface StatusChangePayload {
|
|
4
|
-
connected: boolean;
|
|
5
|
-
error: string | null;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
type ServiceCallback<T> = (payload: T) => void;
|
|
9
|
-
|
|
10
|
-
class XyzenService {
|
|
11
|
-
private ws: WebSocket | null = null;
|
|
12
|
-
private onMessageCallback: ServiceCallback<Message> | null = null;
|
|
13
|
-
private onStatusChangeCallback: ServiceCallback<StatusChangePayload> | null =
|
|
14
|
-
null;
|
|
15
|
-
private backendUrl = "";
|
|
16
|
-
|
|
17
|
-
public setBackendUrl(url: string) {
|
|
18
|
-
this.backendUrl = url;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
public connect(
|
|
22
|
-
sessionId: string,
|
|
23
|
-
topicId: string,
|
|
24
|
-
onMessage: ServiceCallback<Message>,
|
|
25
|
-
onStatusChange: ServiceCallback<StatusChangePayload>,
|
|
26
|
-
) {
|
|
27
|
-
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
28
|
-
console.log("WebSocket is already connected.");
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
this.onMessageCallback = onMessage;
|
|
33
|
-
this.onStatusChangeCallback = onStatusChange;
|
|
34
|
-
|
|
35
|
-
const wsUrl = `${this.backendUrl.replace(
|
|
36
|
-
/^http(s?):\/\//,
|
|
37
|
-
"ws$1://",
|
|
38
|
-
)}/ws/v1/chat/sessions/${sessionId}/topics/${topicId}`;
|
|
39
|
-
this.ws = new WebSocket(wsUrl);
|
|
40
|
-
|
|
41
|
-
this.ws.onopen = () => {
|
|
42
|
-
console.log("XyzenService: WebSocket connected");
|
|
43
|
-
this.onStatusChangeCallback?.({ connected: true, error: null });
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
this.ws.onmessage = (event) => {
|
|
47
|
-
try {
|
|
48
|
-
const messageData = JSON.parse(event.data);
|
|
49
|
-
this.onMessageCallback?.(messageData);
|
|
50
|
-
} catch (error) {
|
|
51
|
-
console.error("XyzenService: Failed to parse message data:", error);
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
this.ws.onclose = () => {
|
|
56
|
-
console.log("XyzenService: WebSocket disconnected");
|
|
57
|
-
this.onStatusChangeCallback?.({
|
|
58
|
-
connected: false,
|
|
59
|
-
error: "Connection closed.",
|
|
60
|
-
});
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
this.ws.onerror = (error) => {
|
|
64
|
-
console.error("XyzenService: WebSocket error:", error);
|
|
65
|
-
this.onStatusChangeCallback?.({
|
|
66
|
-
connected: false,
|
|
67
|
-
error: "A connection error occurred.",
|
|
68
|
-
});
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
public sendMessage(message: string) {
|
|
73
|
-
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
74
|
-
this.ws.send(JSON.stringify({ message }));
|
|
75
|
-
} else {
|
|
76
|
-
console.error("XyzenService: WebSocket is not connected.");
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
public disconnect() {
|
|
81
|
-
if (this.ws) {
|
|
82
|
-
this.ws.close();
|
|
83
|
-
this.ws = null;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Export a singleton instance of the service
|
|
89
|
-
const xyzenService = new XyzenService();
|
|
90
|
-
export default xyzenService;
|
package/src/store/xyzenStore.ts
DELETED
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
import xyzenService from "@/service/xyzenService";
|
|
2
|
-
import { create } from "zustand";
|
|
3
|
-
import { persist } from "zustand/middleware";
|
|
4
|
-
import { immer } from "zustand/middleware/immer";
|
|
5
|
-
|
|
6
|
-
// 定义应用中的核心类型
|
|
7
|
-
export interface Message {
|
|
8
|
-
id: string;
|
|
9
|
-
content: string;
|
|
10
|
-
sender: "user" | "assistant" | "system";
|
|
11
|
-
timestamp: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface ChatChannel {
|
|
15
|
-
id: string; // This will now be the Topic ID
|
|
16
|
-
sessionId: string; // The session this topic belongs to
|
|
17
|
-
title: string;
|
|
18
|
-
messages: Message[];
|
|
19
|
-
assistantId?: string;
|
|
20
|
-
connected: boolean;
|
|
21
|
-
error: string | null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface ChatHistoryItem {
|
|
25
|
-
id: string;
|
|
26
|
-
title: string;
|
|
27
|
-
updatedAt: string;
|
|
28
|
-
assistantTitle: string;
|
|
29
|
-
lastMessage?: string;
|
|
30
|
-
isPinned: boolean;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface Assistant {
|
|
34
|
-
id: string;
|
|
35
|
-
title: string;
|
|
36
|
-
description: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface User {
|
|
40
|
-
username: string;
|
|
41
|
-
avatar: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export type Theme = "light" | "dark" | "system";
|
|
45
|
-
|
|
46
|
-
// Add types for API response
|
|
47
|
-
interface TopicResponse {
|
|
48
|
-
id: string;
|
|
49
|
-
name: string;
|
|
50
|
-
updated_at: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface SessionResponse {
|
|
54
|
-
id: string;
|
|
55
|
-
name: string;
|
|
56
|
-
username: string;
|
|
57
|
-
topics: TopicResponse[];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface XyzenState {
|
|
61
|
-
backendUrl: string;
|
|
62
|
-
isXyzenOpen: boolean;
|
|
63
|
-
panelWidth: number;
|
|
64
|
-
activeChatChannel: string | null;
|
|
65
|
-
user: User | null;
|
|
66
|
-
activeTabIndex: number;
|
|
67
|
-
theme: Theme;
|
|
68
|
-
|
|
69
|
-
chatHistory: ChatHistoryItem[];
|
|
70
|
-
chatHistoryLoading: boolean;
|
|
71
|
-
channels: Record<string, ChatChannel>;
|
|
72
|
-
assistants: Assistant[];
|
|
73
|
-
|
|
74
|
-
toggleXyzen: () => void;
|
|
75
|
-
openXyzen: () => void;
|
|
76
|
-
closeXyzen: () => void;
|
|
77
|
-
setPanelWidth: (width: number) => void;
|
|
78
|
-
setActiveChatChannel: (channelUUID: string | null) => void;
|
|
79
|
-
setTabIndex: (index: number) => void;
|
|
80
|
-
setTheme: (theme: Theme) => void;
|
|
81
|
-
setBackendUrl: (url: string) => void;
|
|
82
|
-
|
|
83
|
-
fetchChatHistory: () => Promise<void>;
|
|
84
|
-
togglePinChat: (chatId: string) => void;
|
|
85
|
-
connectToChannel: (sessionId: string, topicId: string) => void;
|
|
86
|
-
disconnectFromChannel: () => void;
|
|
87
|
-
sendMessage: (message: string) => void;
|
|
88
|
-
createDefaultChannel: () => Promise<void>;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// --- Mock Data ---
|
|
92
|
-
const mockUser: User = {
|
|
93
|
-
username: "Harvey",
|
|
94
|
-
avatar: `https://i.pravatar.cc/40?u=harvey`,
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const mockAssistants: Assistant[] = [
|
|
98
|
-
{
|
|
99
|
-
id: "asst_1",
|
|
100
|
-
title: "通用助理",
|
|
101
|
-
description: "我可以回答各种问题。",
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
id: "asst_2",
|
|
105
|
-
title: "代码助手",
|
|
106
|
-
description: "我可以帮助你处理代码相关的任务。",
|
|
107
|
-
},
|
|
108
|
-
];
|
|
109
|
-
|
|
110
|
-
// --- End Mock Data ---
|
|
111
|
-
|
|
112
|
-
export const useXyzen = create<XyzenState>()(
|
|
113
|
-
persist(
|
|
114
|
-
immer((set, get) => ({
|
|
115
|
-
// --- State ---
|
|
116
|
-
backendUrl: "",
|
|
117
|
-
isXyzenOpen: false,
|
|
118
|
-
panelWidth: 380,
|
|
119
|
-
activeChatChannel: null,
|
|
120
|
-
user: mockUser,
|
|
121
|
-
activeTabIndex: 0,
|
|
122
|
-
theme: "system",
|
|
123
|
-
chatHistory: [],
|
|
124
|
-
chatHistoryLoading: true,
|
|
125
|
-
channels: {},
|
|
126
|
-
assistants: mockAssistants,
|
|
127
|
-
|
|
128
|
-
// --- Actions ---
|
|
129
|
-
toggleXyzen: () => set((state) => ({ isXyzenOpen: !state.isXyzenOpen })),
|
|
130
|
-
openXyzen: () => set({ isXyzenOpen: true }),
|
|
131
|
-
closeXyzen: () => set({ isXyzenOpen: false }),
|
|
132
|
-
setPanelWidth: (width) => set({ panelWidth: width }),
|
|
133
|
-
setActiveChatChannel: (channelId) =>
|
|
134
|
-
set({ activeChatChannel: channelId }),
|
|
135
|
-
setTabIndex: (index) => set({ activeTabIndex: index }),
|
|
136
|
-
setTheme: (theme) => set({ theme }),
|
|
137
|
-
setBackendUrl: (url) => {
|
|
138
|
-
set({ backendUrl: url });
|
|
139
|
-
xyzenService.setBackendUrl(url);
|
|
140
|
-
},
|
|
141
|
-
|
|
142
|
-
// --- Async Actions ---
|
|
143
|
-
fetchChatHistory: async () => {
|
|
144
|
-
set({ chatHistoryLoading: true });
|
|
145
|
-
try {
|
|
146
|
-
const response = await fetch(`${get().backendUrl}/api/v1/sessions/`);
|
|
147
|
-
if (!response.ok) {
|
|
148
|
-
throw new Error("Failed to fetch chat history");
|
|
149
|
-
}
|
|
150
|
-
const history: SessionResponse[] = await response.json();
|
|
151
|
-
|
|
152
|
-
// Transform the fetched data into the format expected by the store
|
|
153
|
-
const channels: Record<string, ChatChannel> = {};
|
|
154
|
-
const chatHistory: ChatHistoryItem[] = history.flatMap(
|
|
155
|
-
(session: SessionResponse) =>
|
|
156
|
-
session.topics.map((topic: TopicResponse) => {
|
|
157
|
-
channels[topic.id] = {
|
|
158
|
-
id: topic.id,
|
|
159
|
-
sessionId: session.id,
|
|
160
|
-
title: topic.name,
|
|
161
|
-
messages: [], // Messages will be fetched on demand or via WebSocket
|
|
162
|
-
connected: false,
|
|
163
|
-
error: null,
|
|
164
|
-
};
|
|
165
|
-
return {
|
|
166
|
-
id: topic.id,
|
|
167
|
-
title: topic.name,
|
|
168
|
-
updatedAt: topic.updated_at,
|
|
169
|
-
assistantTitle: "通用助理", // Placeholder
|
|
170
|
-
lastMessage: "", // Placeholder
|
|
171
|
-
isPinned: false, // Placeholder
|
|
172
|
-
};
|
|
173
|
-
}),
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
set({
|
|
177
|
-
chatHistory,
|
|
178
|
-
channels,
|
|
179
|
-
chatHistoryLoading: false,
|
|
180
|
-
activeChatChannel:
|
|
181
|
-
chatHistory.length > 0 ? chatHistory[0].id : null,
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
if (chatHistory.length > 0) {
|
|
185
|
-
const activeChannel = channels[chatHistory[0].id];
|
|
186
|
-
get().connectToChannel(activeChannel.sessionId, activeChannel.id);
|
|
187
|
-
}
|
|
188
|
-
} catch (error) {
|
|
189
|
-
console.error("Failed to fetch chat history:", error);
|
|
190
|
-
set({ chatHistoryLoading: false });
|
|
191
|
-
}
|
|
192
|
-
},
|
|
193
|
-
|
|
194
|
-
togglePinChat: (chatId: string) => {
|
|
195
|
-
set((state) => {
|
|
196
|
-
const chat = state.chatHistory.find(
|
|
197
|
-
(c: ChatHistoryItem) => c.id === chatId,
|
|
198
|
-
);
|
|
199
|
-
if (chat) {
|
|
200
|
-
chat.isPinned = !chat.isPinned;
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
},
|
|
204
|
-
|
|
205
|
-
connectToChannel: (sessionId, topicId) => {
|
|
206
|
-
xyzenService.connect(
|
|
207
|
-
sessionId,
|
|
208
|
-
topicId,
|
|
209
|
-
(incomingMessage) => {
|
|
210
|
-
// onMessage callback
|
|
211
|
-
set((state) => {
|
|
212
|
-
const channel = state.channels[topicId];
|
|
213
|
-
if (channel) {
|
|
214
|
-
const newMsg: Message = {
|
|
215
|
-
id: `msg-${Date.now()}`, // Or use an ID from the server
|
|
216
|
-
sender: incomingMessage.sender,
|
|
217
|
-
content: incomingMessage.content,
|
|
218
|
-
timestamp: new Date().toISOString(),
|
|
219
|
-
};
|
|
220
|
-
channel.messages.push(newMsg);
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
},
|
|
224
|
-
(status) => {
|
|
225
|
-
// onStatusChange callback
|
|
226
|
-
set((state) => {
|
|
227
|
-
const channel = state.channels[topicId];
|
|
228
|
-
if (channel) {
|
|
229
|
-
channel.connected = status.connected;
|
|
230
|
-
channel.error = status.error;
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
},
|
|
234
|
-
);
|
|
235
|
-
},
|
|
236
|
-
|
|
237
|
-
disconnectFromChannel: () => {
|
|
238
|
-
xyzenService.disconnect();
|
|
239
|
-
},
|
|
240
|
-
|
|
241
|
-
sendMessage: (message) => {
|
|
242
|
-
const channelId = get().activeChatChannel;
|
|
243
|
-
if (!channelId) return;
|
|
244
|
-
|
|
245
|
-
const userMessage: Message = {
|
|
246
|
-
id: `msg-${Date.now()}`,
|
|
247
|
-
sender: "user",
|
|
248
|
-
content: message,
|
|
249
|
-
timestamp: new Date().toISOString(),
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
set((state) => {
|
|
253
|
-
state.channels[channelId]?.messages.push(userMessage);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
xyzenService.sendMessage(message);
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
createDefaultChannel: async () => {
|
|
260
|
-
try {
|
|
261
|
-
// Step 1: Call the backend to create a new session and a default topic
|
|
262
|
-
const response = await fetch(`${get().backendUrl}/api/v1/sessions/`, {
|
|
263
|
-
method: "POST",
|
|
264
|
-
headers: {
|
|
265
|
-
"Content-Type": "application/json",
|
|
266
|
-
},
|
|
267
|
-
body: JSON.stringify({
|
|
268
|
-
name: "New Session",
|
|
269
|
-
username: get().user?.username || "default_user", // Get username from state
|
|
270
|
-
}),
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
if (!response.ok) {
|
|
274
|
-
throw new Error("Failed to create a new session on the backend.");
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const newSession = await response.json();
|
|
278
|
-
const newTopic = newSession.topics[0]; // Assuming the first topic is the default one
|
|
279
|
-
|
|
280
|
-
if (!newTopic) {
|
|
281
|
-
throw new Error("Backend did not return a default topic.");
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const newChannel: ChatChannel = {
|
|
285
|
-
id: newTopic.id,
|
|
286
|
-
sessionId: newSession.id,
|
|
287
|
-
title: newTopic.name,
|
|
288
|
-
messages: [],
|
|
289
|
-
connected: false,
|
|
290
|
-
error: null,
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
const newHistoryItem: ChatHistoryItem = {
|
|
294
|
-
id: newTopic.id,
|
|
295
|
-
title: newTopic.name,
|
|
296
|
-
updatedAt: new Date().toISOString(),
|
|
297
|
-
assistantTitle: "通用助理", // Or derive from session/topic
|
|
298
|
-
lastMessage: "",
|
|
299
|
-
isPinned: false,
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
set((state) => {
|
|
303
|
-
state.channels[newTopic.id] = newChannel;
|
|
304
|
-
state.chatHistory.unshift(newHistoryItem);
|
|
305
|
-
state.activeChatChannel = newTopic.id;
|
|
306
|
-
state.activeTabIndex = 0; // Switch to the chat tab
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
// Step 2: Connect to the WebSocket with the real IDs
|
|
310
|
-
get().connectToChannel(newSession.id, newTopic.id);
|
|
311
|
-
} catch (error) {
|
|
312
|
-
console.error("Error creating default channel:", error);
|
|
313
|
-
// Optionally, update the state to show an error to the user
|
|
314
|
-
}
|
|
315
|
-
},
|
|
316
|
-
})),
|
|
317
|
-
{
|
|
318
|
-
name: "xyzen-storage", // local storage key
|
|
319
|
-
partialize: (state) => ({
|
|
320
|
-
panelWidth: state.panelWidth,
|
|
321
|
-
isXyzenOpen: state.isXyzenOpen,
|
|
322
|
-
theme: state.theme,
|
|
323
|
-
}), // only persist panelWidth and isXyzenOpen
|
|
324
|
-
},
|
|
325
|
-
),
|
|
326
|
-
);
|
package/src/types/xyzen.d.ts
DELETED
|
File without changes
|
package/src/vite-env.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|