Skip to content

Commit bde658b

Browse files
authored
feat: add chat mode launch page (#424)
1 parent 4380b56 commit bde658b

13 files changed

Lines changed: 271 additions & 51 deletions

File tree

src-tauri/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ pub fn run() {
135135
server::transcription::transcription,
136136
local::application::get_default_search_paths,
137137
local::application::list_app_with_metadata_in,
138-
util::open
138+
util::open,
139+
server::system_settings::get_system_settings
139140
])
140141
.setup(|app| {
141142
let registry = SearchSourceRegistry::default();

src-tauri/src/server/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ pub mod http_client;
88
pub mod profile;
99
pub mod search;
1010
pub mod servers;
11+
pub mod system_settings;
1112
pub mod transcription;
1213
pub mod websocket;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use crate::server::http_client::HttpClient;
2+
use serde_json::Value;
3+
use tauri::command;
4+
5+
#[command]
6+
pub async fn get_system_settings(server_id: String) -> Result<Value, String> {
7+
let response = HttpClient::get(&server_id, "/settings", None)
8+
.await
9+
.map_err(|err| err.to_string())?;
10+
11+
response
12+
.json::<Value>()
13+
.await
14+
.map_err(|err| err.to_string())
15+
}

src/api/axiosRequest.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import axios from "axios";
22

3-
import { useAppStore } from '@/stores/appStore';
3+
import { useAppStore } from "@/stores/appStore";
44

55
import {
66
handleChangeRequestHeader,
@@ -44,21 +44,22 @@ axios.interceptors.response.use(
4444

4545
export const handleApiError = (error: any) => {
4646
const addError = useAppStore.getState().addError;
47-
48-
let message = 'Request failed';
49-
47+
48+
let message = "Request failed";
49+
5050
if (error.response) {
5151
// Server error response
52-
message = error.response.data?.message || `Error (${error.response.status})`;
52+
message =
53+
error.response.data?.message || `Error (${error.response.status})`;
5354
} else if (error.request) {
5455
// Request failed to send
55-
message = 'Network connection failed';
56+
message = "Network connection failed";
5657
} else {
5758
// Other errors
5859
message = error.message;
5960
}
60-
61-
addError(message, 'error');
61+
62+
addError(message, "error");
6263
return error;
6364
};
6465

src/components/Assistant/AssistantList.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) {
2222
const { t } = useTranslation();
2323
const { connected } = useChatStore();
2424
const isTauri = useAppStore((state) => state.isTauri);
25+
const assistantList = useConnectStore((state) => state.assistantList);
26+
const setAssistantList = useConnectStore((state) => state.setAssistantList);
2527
const currentService = useConnectStore((state) => state.currentService);
2628
const currentAssistant = useConnectStore((state) => state.currentAssistant);
2729
const setCurrentAssistant = useConnectStore(
@@ -34,7 +36,6 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) {
3436
const menuRef = useRef<HTMLDivElement>(null);
3537

3638
useClickAway(menuRef, () => setIsOpen(false));
37-
const [assistants, setAssistants] = useState<any[]>([]);
3839

3940
const fetchAssistant = useCallback(async (serverId?: string) => {
4041
let response: any;
@@ -46,14 +47,14 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) {
4647
});
4748
response = response ? JSON.parse(response) : null;
4849
} catch (err) {
49-
setAssistants([]);
50+
setAssistantList([]);
5051
setCurrentAssistant(null);
5152
console.error("assistant_search", err);
5253
}
5354
} else {
5455
const [error, res] = await Get(`/assistant/_search`);
5556
if (error) {
56-
setAssistants([]);
57+
setAssistantList([]);
5758
setCurrentAssistant(null);
5859
console.error("assistant_search", error);
5960
return;
@@ -64,11 +65,12 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) {
6465
console.log("assistant_search", response);
6566
let assistantList = response?.hits?.hits || [];
6667

67-
assistantList = assistantIDs.length > 0
68-
? assistantList.filter((item: any) => assistantIDs.includes(item._id))
69-
: assistantList;
68+
assistantList =
69+
assistantIDs.length > 0
70+
? assistantList.filter((item: any) => assistantIDs.includes(item._id))
71+
: assistantList;
7072

71-
setAssistants(assistantList);
73+
setAssistantList(assistantList);
7274
if (assistantList.length > 0) {
7375
const assistant = assistantList.find(
7476
(item: any) => item._id === currentAssistant?._id
@@ -150,7 +152,7 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) {
150152
</VisibleKey>
151153
</button>
152154
</div>
153-
{assistants.map((assistant) => (
155+
{assistantList.map((assistant) => (
154156
<button
155157
key={assistant._id}
156158
onClick={() => {

src/components/Assistant/Chat.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ const ChatAI = memo(
7474
useChatStore();
7575

7676
const currentService = useConnectStore((state) => state.currentService);
77+
const visibleStartPage = useConnectStore((state) => {
78+
return state.visibleStartPage;
79+
});
7780

7881
const addError = useAppStore.getState().addError;
7982

@@ -402,7 +405,9 @@ const ChatAI = memo(
402405
<ConnectPrompt />
403406
)}
404407

405-
{showPrevSuggestion ? <PrevSuggestion sendMessage={init} /> : null}
408+
{showPrevSuggestion && !visibleStartPage && (
409+
<PrevSuggestion sendMessage={init} />
410+
)}
406411
</div>
407412
);
408413
}

src/components/Assistant/ChatContent.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { Chat, IChunkData } from "./types";
1010
// import SessionFile from "./SessionFile";
1111
import { useConnectStore } from "@/stores/connectStore";
1212
import SessionFile from "./SessionFile";
13+
import Splash from "./Splash";
1314

1415
interface ChatContentProps {
1516
activeChat?: Chat;
@@ -145,6 +146,8 @@ export const ChatContent = ({
145146
)}
146147

147148
{sessionId && <SessionFile sessionId={sessionId} />}
149+
150+
<Splash />
148151
</div>
149152
);
150153
};
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { CircleX, MoveRight } from "lucide-react";
2+
import { useMount } from "ahooks";
3+
import { useAppStore } from "@/stores/appStore";
4+
import { useMemo, useState } from "react";
5+
import platformAdapter from "@/utils/platformAdapter";
6+
import { useConnectStore } from "@/stores/connectStore";
7+
import { useThemeStore } from "@/stores/themeStore";
8+
import FontIcon from "../Common/Icons/FontIcon";
9+
import logoImg from "@/assets/icon.svg";
10+
import { Get } from "@/api/axiosRequest";
11+
12+
interface StartPage {
13+
enabled?: boolean;
14+
logo?: {
15+
light?: string;
16+
dark?: string;
17+
};
18+
introduction?: string;
19+
display_assistants?: string[];
20+
}
21+
22+
export interface Response {
23+
app_settings?: {
24+
chat?: {
25+
start_page?: StartPage;
26+
};
27+
};
28+
}
29+
30+
const Splash = () => {
31+
const isTauri = useAppStore((state) => state.isTauri);
32+
const currentService = useConnectStore((state) => state.currentService);
33+
const [settings, setSettings] = useState<StartPage>();
34+
const visibleStartPage = useConnectStore((state) => state.visibleStartPage);
35+
const setVisibleStartPage = useConnectStore((state) => {
36+
return state.setVisibleStartPage;
37+
});
38+
const addError = useAppStore((state) => state.addError);
39+
const isDark = useThemeStore((state) => state.isDark);
40+
const assistantList = useConnectStore((state) => state.assistantList);
41+
const setCurrentAssistant = useConnectStore((state) => {
42+
return state.setCurrentAssistant;
43+
});
44+
45+
useMount(async () => {
46+
try {
47+
const serverId = currentService.id;
48+
49+
let response: Response = {};
50+
51+
if (isTauri) {
52+
response = await platformAdapter.invokeBackend<Response>(
53+
"get_system_settings",
54+
{
55+
serverId,
56+
}
57+
);
58+
} else {
59+
const [err, result] = await Get("/settings");
60+
61+
if (err) {
62+
throw new Error(err);
63+
}
64+
65+
response = result as Response;
66+
}
67+
68+
const settings = response?.app_settings?.chat?.start_page;
69+
70+
setVisibleStartPage(Boolean(settings?.enabled));
71+
72+
setSettings(settings);
73+
} catch (error) {
74+
addError(String(error), "error");
75+
}
76+
});
77+
78+
const settingsAssistantList = useMemo(() => {
79+
console.log("assistantList", assistantList);
80+
81+
return assistantList.filter((item) => {
82+
return settings?.display_assistants?.includes(item?._source?.id);
83+
});
84+
}, [settings, assistantList]);
85+
86+
const logo = useMemo(() => {
87+
const { light, dark } = settings?.logo || {};
88+
89+
if (isDark) {
90+
return dark || light;
91+
}
92+
93+
return light || dark;
94+
}, [settings, isDark]);
95+
96+
return (
97+
visibleStartPage && (
98+
<div className="absolute inset-0 flex flex-col items-center px-6 pt-6 text-[#333] dark:text-white">
99+
<CircleX
100+
className="absolute top-3 right-3 size-4 text-[#999] cursor-pointer"
101+
onClick={() => {
102+
setVisibleStartPage(false);
103+
}}
104+
/>
105+
106+
<img src={logo} className="h-8" />
107+
108+
<div className="mt-3 mb-6 text-lg font-medium">
109+
{settings?.introduction}
110+
</div>
111+
112+
<ul className="flex flex-wrap -m-1">
113+
{settingsAssistantList?.map((item) => {
114+
const { id, name, description, icon } = item._source;
115+
116+
return (
117+
<li key={id} className="w-1/2 p-1">
118+
<div
119+
className="group h-[74px] px-3 py-2 text-sm rounded-xl border dark:border-[#262626] bg-white dark:bg-black cursor-pointer transition hover:!border-[#0087FF]"
120+
onClick={() => {
121+
setCurrentAssistant(item);
122+
123+
setVisibleStartPage(false);
124+
}}
125+
>
126+
<div className="flex items-center justify-between">
127+
<div className="flex items-center gap-1">
128+
{icon?.startsWith("font_") ? (
129+
<div className="size-4 flex items-center justify-center rounded-full bg-white">
130+
<FontIcon name={icon} className="w-5 h-5" />
131+
</div>
132+
) : (
133+
<img
134+
src={logoImg}
135+
className="size-4 rounded-full"
136+
alt={name}
137+
/>
138+
)}
139+
140+
<span>{name}</span>
141+
</div>
142+
143+
<MoveRight className="size-4 transition group-hover:text-[#0087FF]" />
144+
</div>
145+
146+
<div className="mt-1 text-xs text-[#999] line-clamp-2">
147+
{description}
148+
</div>
149+
</div>
150+
</li>
151+
);
152+
})}
153+
</ul>
154+
</div>
155+
)
156+
);
157+
};
158+
159+
export default Splash;

src/components/ChatMessage/index.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { SuggestionList } from "./SuggestionList";
1414
import { UserMessage } from "./UserMessage";
1515
import { useConnectStore } from "@/stores/connectStore";
1616
import FontIcon from "@/components/Common/Icons/FontIcon";
17+
import clsx from "clsx";
1718

1819
interface ChatMessageProps {
1920
message: Message;
@@ -53,6 +54,7 @@ export const ChatMessage = memo(function ChatMessage({
5354
isTyping === false && (messageContent || response?.message_chunk);
5455

5556
const [suggestion, setSuggestion] = useState<string[]>([]);
57+
const visibleStartPage = useConnectStore((state) => state.visibleStartPage);
5658

5759
const getSuggestion = (suggestion: string[]) => {
5860
setSuggestion(suggestion);
@@ -121,15 +123,23 @@ export const ChatMessage = memo(function ChatMessage({
121123

122124
return (
123125
<div
124-
className={`py-8 flex ${isAssistant ? "justify-start" : "justify-end"}`}
126+
className={clsx(
127+
"py-8 flex",
128+
[isAssistant ? "justify-start" : "justify-end"],
129+
{
130+
hidden: visibleStartPage,
131+
}
132+
)}
125133
>
126134
<div
127135
className={`px-4 flex gap-4 ${
128136
isAssistant ? "w-full" : "flex-row-reverse"
129137
}`}
130138
>
131139
<div
132-
className={`w-full space-y-2 ${isAssistant ? "text-left" : "text-right"}`}
140+
className={`w-full space-y-2 ${
141+
isAssistant ? "text-left" : "text-right"
142+
}`}
133143
>
134144
<div className="w-full flex items-center gap-1 font-semibold text-sm text-[#333] dark:text-[#d8d8d8]">
135145
{isAssistant ? (

src/components/Search/InputBox.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ export default function ChatInput({
131131
const setModifierKeyPressed = useShortcutsStore((state) => {
132132
return state.setModifierKeyPressed;
133133
});
134+
const setVisibleStartPage = useConnectStore((state) => {
135+
return state.setVisibleStartPage;
136+
});
134137

135138
useEffect(() => {
136139
const handleFocus = () => {
@@ -154,6 +157,8 @@ export default function ChatInput({
154157
}, [isChatMode, textareaRef, inputRef]);
155158

156159
const handleSubmit = useCallback(() => {
160+
setVisibleStartPage(false);
161+
157162
const trimmedValue = inputValue.trim();
158163
console.log("handleSubmit", trimmedValue, disabled);
159164
if (trimmedValue && !disabled) {
@@ -477,7 +482,8 @@ export default function ChatInput({
477482
/>
478483
)}
479484

480-
{!currentAssistant?._source?.datasource?.visible && !currentAssistant?._source?.config?.visible ? (
485+
{!currentAssistant?._source?.datasource?.visible &&
486+
!currentAssistant?._source?.config?.visible ? (
481487
<div className="px-[9px]">
482488
<Copyright />
483489
</div>

0 commit comments

Comments
 (0)