mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +01:00
chore: add deep think & stop icon & chat page (#141)
* fix: stop icon & useCallback * fix: add deep think & stop icon * chore: chat app should not always on top * refactor: ui styles * build: build error * refactor: ui styles --------- Co-authored-by: medcl <m@medcl.net>
This commit is contained in:
@@ -1,302 +1,318 @@
|
||||
import {
|
||||
useState,
|
||||
useRef,
|
||||
useEffect,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
} from "react";
|
||||
import { MessageSquarePlus, PanelLeft } from "lucide-react";
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
import {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef, useState,} from "react";
|
||||
import {MessageSquarePlus, PanelLeft} from "lucide-react";
|
||||
import {isTauri} from "@tauri-apps/api/core";
|
||||
|
||||
import {ChatMessage} from "./ChatMessage";
|
||||
import type {Chat, Message} from "./types";
|
||||
import {tauriFetch} from "../../api/tauriFetchClient";
|
||||
import {useWebSocket} from "../../hooks/useWebSocket";
|
||||
import {useChatStore} from "../../stores/chatStore";
|
||||
import {useWindows} from "../../hooks/useWindows";
|
||||
import {clientEnv} from "@/utils/env";
|
||||
|
||||
import { ChatMessage } from "./ChatMessage";
|
||||
import type { Chat, Message } from "./types";
|
||||
import { tauriFetch } from "../../api/tauriFetchClient";
|
||||
import { useWebSocket } from "../../hooks/useWebSocket";
|
||||
import { useChatStore } from "../../stores/chatStore";
|
||||
import { useWindows } from "../../hooks/useWindows";
|
||||
import { clientEnv } from "@/utils/env";
|
||||
// import { useAppStore } from '@/stores/appStore';
|
||||
|
||||
interface ChatAIProps {
|
||||
inputValue: string;
|
||||
isTransitioned: boolean;
|
||||
changeInput: (val: string) => void;
|
||||
isSearchActive?: boolean;
|
||||
isTransitioned: boolean;
|
||||
changeInput: (val: string) => void;
|
||||
isSearchActive?: boolean;
|
||||
isDeepThinkActive?: boolean;
|
||||
}
|
||||
|
||||
export interface ChatAIRef {
|
||||
init: () => void;
|
||||
cancelChat: () => void;
|
||||
connected: boolean;
|
||||
reconnect: () => void;
|
||||
init: (value: string) => void;
|
||||
cancelChat: () => void;
|
||||
connected: boolean;
|
||||
reconnect: () => void;
|
||||
}
|
||||
|
||||
const ChatAI = forwardRef<ChatAIRef, ChatAIProps>(
|
||||
({ inputValue, isTransitioned, changeInput, isSearchActive }, ref) => {
|
||||
useImperativeHandle(ref, () => ({
|
||||
init: init,
|
||||
cancelChat: cancelChat,
|
||||
connected: connected,
|
||||
reconnect: reconnect
|
||||
}));
|
||||
const ChatAI = memo(forwardRef<ChatAIRef, ChatAIProps>(
|
||||
({isTransitioned, changeInput, isSearchActive, isDeepThinkActive}, ref) => {
|
||||
useImperativeHandle(ref, () => ({
|
||||
init: init,
|
||||
cancelChat: cancelChat,
|
||||
connected: connected,
|
||||
reconnect: reconnect
|
||||
}));
|
||||
|
||||
// const appStore = useAppStore();
|
||||
// const appStore = useAppStore();
|
||||
|
||||
const { createWin } = useWindows();
|
||||
const {createWin} = useWindows();
|
||||
|
||||
const { curChatEnd, setCurChatEnd, setConnected } = useChatStore();
|
||||
const {curChatEnd, setCurChatEnd, setConnected} = useChatStore();
|
||||
|
||||
const [activeChat, setActiveChat] = useState<Chat>();
|
||||
const [isTyping, setIsTyping] = useState(false);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const [activeChat, setActiveChat] = useState<Chat>();
|
||||
const [isTyping, setIsTyping] = useState(false);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [websocketId, setWebsocketId] = useState("");
|
||||
const [curMessage, setCurMessage] = useState("");
|
||||
const [curId, setCurId] = useState("");
|
||||
const [websocketId, setWebsocketId] = useState("");
|
||||
const [curMessage, setCurMessage] = useState("");
|
||||
const [curId, setCurId] = useState("");
|
||||
|
||||
const curChatEndRef = useRef(curChatEnd);
|
||||
curChatEndRef.current = curChatEnd;
|
||||
const curChatEndRef = useRef(curChatEnd);
|
||||
curChatEndRef.current = curChatEnd;
|
||||
|
||||
const curIdRef = useRef(curId);
|
||||
curIdRef.current = curId;
|
||||
const curIdRef = useRef(curId);
|
||||
curIdRef.current = curId;
|
||||
|
||||
// console.log("chat useWebSocket", clientEnv.COCO_WEBSOCKET_URL)
|
||||
const { messages, setMessages, connected, reconnect } = useWebSocket(
|
||||
clientEnv.COCO_WEBSOCKET_URL,
|
||||
(msg) => {
|
||||
// console.log("msg", msg);
|
||||
const handleMessageChunk = useCallback((chunk: string) => {
|
||||
setCurMessage(prev => prev + chunk);
|
||||
}, []);
|
||||
|
||||
if (msg.includes("websocket-session-id")) {
|
||||
const array = msg.split(" ");
|
||||
setWebsocketId(array[2]);
|
||||
}
|
||||
// console.log("chat useWebSocket", clientEnv.COCO_WEBSOCKET_URL)
|
||||
const {messages, setMessages, connected, reconnect} = useWebSocket(
|
||||
clientEnv.COCO_WEBSOCKET_URL,
|
||||
(msg) => {
|
||||
// console.log("msg", msg);
|
||||
|
||||
if (msg.includes("PRIVATE")) {
|
||||
if (
|
||||
msg.includes("assistant finished output") ||
|
||||
curChatEndRef.current
|
||||
) {
|
||||
setCurChatEnd(true);
|
||||
} else {
|
||||
const cleanedData = msg.replace(/^PRIVATE /, "");
|
||||
try {
|
||||
const chunkData = JSON.parse(cleanedData);
|
||||
if (chunkData.reply_to_message === curIdRef.current) {
|
||||
setCurMessage((prev) => prev + chunkData.message_chunk);
|
||||
return chunkData.message_chunk;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("JSON Parse error:", error);
|
||||
if (msg.includes("websocket-session-id")) {
|
||||
const array = msg.split(" ");
|
||||
setWebsocketId(array[2]);
|
||||
}
|
||||
|
||||
if (msg.includes("PRIVATE")) {
|
||||
if (
|
||||
msg.includes("assistant finished output") ||
|
||||
curChatEndRef.current
|
||||
) {
|
||||
setCurChatEnd(true);
|
||||
} else {
|
||||
const cleanedData = msg.replace(/^PRIVATE /, "");
|
||||
try {
|
||||
const chunkData = JSON.parse(cleanedData);
|
||||
if (chunkData.reply_to_message === curIdRef.current) {
|
||||
handleMessageChunk(chunkData.message_chunk)
|
||||
return chunkData.message_chunk;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("JSON Parse error:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
useEffect(()=>{
|
||||
setConnected(connected)
|
||||
}, [connected])
|
||||
useEffect(() => {
|
||||
setConnected(connected)
|
||||
}, [connected])
|
||||
|
||||
// websocket
|
||||
useEffect(() => {
|
||||
if (messages.length === 0 || !activeChat?._id) return;
|
||||
const simulateAssistantResponse = useCallback(() => {
|
||||
if (messages.length === 0 || !activeChat?._id) return;
|
||||
|
||||
const simulateAssistantResponse = () => {
|
||||
console.log("messages", messages);
|
||||
console.log("messages", messages);
|
||||
|
||||
const assistantMessage: Message = {
|
||||
_id: activeChat._id,
|
||||
_source: {
|
||||
type: "assistant",
|
||||
message: messages,
|
||||
},
|
||||
};
|
||||
|
||||
const updatedChat = {
|
||||
...activeChat,
|
||||
messages: [...(activeChat.messages || []), assistantMessage],
|
||||
};
|
||||
setMessages("");
|
||||
setCurMessage("");
|
||||
setActiveChat(updatedChat);
|
||||
setTimeout(() => setIsTyping(false), 1000);
|
||||
};
|
||||
if (curChatEnd) {
|
||||
simulateAssistantResponse();
|
||||
}
|
||||
}, [messages, curChatEnd]);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "end",
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [activeChat?.messages, isTyping, curMessage]);
|
||||
|
||||
const createNewChat = async () => {
|
||||
chatClose();
|
||||
try {
|
||||
const response = await tauriFetch({
|
||||
url: "/chat/_new",
|
||||
method: "POST",
|
||||
});
|
||||
console.log("_new", response);
|
||||
const newChat: Chat = response.data;
|
||||
|
||||
setActiveChat(newChat);
|
||||
handleSendMessage(inputValue, newChat);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
if (!curChatEnd) return;
|
||||
if (!activeChat?._id) {
|
||||
createNewChat();
|
||||
} else {
|
||||
handleSendMessage(inputValue);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendMessage = async (content: string, newChat?: Chat) => {
|
||||
newChat = newChat || activeChat;
|
||||
if (!newChat?._id || !content) return;
|
||||
try {
|
||||
const response = await tauriFetch({
|
||||
url: `/chat/${newChat?._id}/_send?search=${isSearchActive}`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"WEBSOCKET-SESSION-ID": websocketId,
|
||||
},
|
||||
body: JSON.stringify({ message: content }),
|
||||
});
|
||||
console.log("_send", response, websocketId);
|
||||
setCurId(response.data[0]?._id);
|
||||
const updatedChat: Chat = {
|
||||
...newChat,
|
||||
messages: [...(newChat?.messages || []), ...(response.data || [])],
|
||||
};
|
||||
changeInput("");
|
||||
setActiveChat(updatedChat);
|
||||
setIsTyping(true);
|
||||
setCurChatEnd(false);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const chatClose = async () => {
|
||||
if (!activeChat?._id) return;
|
||||
try {
|
||||
const response = await tauriFetch({
|
||||
url: `/chat/${activeChat._id}/_close`,
|
||||
method: "POST",
|
||||
});
|
||||
console.log("_close", response);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelChat = async () => {
|
||||
setCurChatEnd(true);
|
||||
setIsTyping(false);
|
||||
if (!activeChat?._id) return;
|
||||
try {
|
||||
const response = await tauriFetch({
|
||||
url: `/chat/${activeChat._id}/_cancel`,
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
console.log("_cancel", response);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
async function openChatAI() {
|
||||
if (isTauri()) {
|
||||
createWin && createWin({
|
||||
label: "chat",
|
||||
title: "Coco Chat",
|
||||
dragDropEnabled: true,
|
||||
center: true,
|
||||
width: 900,
|
||||
height: 800,
|
||||
alwaysOnTop: true,
|
||||
skipTaskbar: true,
|
||||
decorations: true,
|
||||
closable: true,
|
||||
url: "/ui/app/chat",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!isTransitioned) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={`h-[500px] flex flex-col rounded-xl overflow-hidden`}
|
||||
>
|
||||
<header
|
||||
data-tauri-drag-region
|
||||
className={`flex items-center justify-between py-2 px-1`}
|
||||
>
|
||||
<button
|
||||
onClick={() => openChatAI()}
|
||||
className={`p-2 rounded-lg transition-colors text-[#333] dark:text-[#d8d8d8]`}
|
||||
>
|
||||
<PanelLeft className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
createNewChat();
|
||||
}}
|
||||
className={`p-2 rounded-lg transition-colors text-[#333] dark:text-[#d8d8d8]`}
|
||||
>
|
||||
<MessageSquarePlus className="h-4 w-4" />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{/* Chat messages */}
|
||||
<div className="overflow-y-auto border-t border-[rgba(0,0,0,0.1)] dark:border-[rgba(255,255,255,0.15)] custom-scrollbar">
|
||||
{activeChat?.messages?.map((message, index) => (
|
||||
<ChatMessage
|
||||
key={message._id + index}
|
||||
message={message}
|
||||
isTyping={
|
||||
isTyping &&
|
||||
index === (activeChat.messages?.length || 0) - 1 &&
|
||||
message._source?.type === "assistant"
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{!curChatEnd && activeChat?._id ? (
|
||||
<ChatMessage
|
||||
key={"last"}
|
||||
message={{
|
||||
_id: activeChat?._id,
|
||||
const assistantMessage: Message = {
|
||||
_id: activeChat._id,
|
||||
_source: {
|
||||
type: "assistant",
|
||||
message: curMessage,
|
||||
type: "assistant",
|
||||
message: messages,
|
||||
},
|
||||
}}
|
||||
isTyping={!curChatEnd}
|
||||
/>
|
||||
) : null}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const updatedChat = {
|
||||
...activeChat,
|
||||
messages: [...(activeChat.messages || []), assistantMessage],
|
||||
};
|
||||
setMessages("");
|
||||
setCurMessage("");
|
||||
console.log("updatedChat", updatedChat);
|
||||
setActiveChat(updatedChat);
|
||||
|
||||
const timer = setTimeout(() => setIsTyping(false), 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}, [activeChat?._id]);
|
||||
|
||||
// websocket
|
||||
useEffect(() => {
|
||||
if (curChatEnd) {
|
||||
simulateAssistantResponse();
|
||||
}
|
||||
}, [messages, curChatEnd]);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "end",
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [activeChat?.messages, isTyping, curMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
chatClose();
|
||||
setMessages("");
|
||||
setCurMessage("");
|
||||
setActiveChat(undefined);
|
||||
setIsTyping(false);
|
||||
setCurChatEnd(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const createNewChat = useCallback(async (value: string = "") => {
|
||||
chatClose();
|
||||
try {
|
||||
const response = await tauriFetch({
|
||||
url: "/chat/_new",
|
||||
method: "POST",
|
||||
});
|
||||
console.log("_new", response);
|
||||
const newChat: Chat = response.data;
|
||||
|
||||
setActiveChat(newChat);
|
||||
handleSendMessage(value, newChat);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user data:", error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const init = (value: string) => {
|
||||
if (!curChatEnd) return;
|
||||
if (!activeChat?._id) {
|
||||
createNewChat(value);
|
||||
} else {
|
||||
handleSendMessage(value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendMessage = async (content: string, newChat?: Chat) => {
|
||||
newChat = newChat || activeChat;
|
||||
if (!newChat?._id || !content) return;
|
||||
try {
|
||||
const response = await tauriFetch({
|
||||
url: `/chat/${newChat?._id}/_send?search=${isSearchActive}&deep_thinking=${isDeepThinkActive}`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"WEBSOCKET-SESSION-ID": websocketId,
|
||||
},
|
||||
body: JSON.stringify({message: content}),
|
||||
});
|
||||
console.log("_send", response, websocketId);
|
||||
setCurId(response.data[0]?._id);
|
||||
const updatedChat: Chat = {
|
||||
...newChat,
|
||||
messages: [...(newChat?.messages || []), ...(response.data || [])],
|
||||
};
|
||||
changeInput("");
|
||||
console.log("updatedChat2", updatedChat);
|
||||
setActiveChat(updatedChat);
|
||||
setIsTyping(true);
|
||||
setCurChatEnd(false);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const chatClose = async () => {
|
||||
if (!activeChat?._id) return;
|
||||
try {
|
||||
const response = await tauriFetch({
|
||||
url: `/chat/${activeChat._id}/_close`,
|
||||
method: "POST",
|
||||
});
|
||||
console.log("_close", response);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelChat = async () => {
|
||||
setCurChatEnd(true);
|
||||
setIsTyping(false);
|
||||
if (!activeChat?._id) return;
|
||||
try {
|
||||
const response = await tauriFetch({
|
||||
url: `/chat/${activeChat._id}/_cancel`,
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
console.log("_cancel", response);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
async function openChatAI() {
|
||||
if (isTauri()) {
|
||||
createWin && createWin({
|
||||
label: "chat",
|
||||
title: "Coco Chat",
|
||||
dragDropEnabled: true,
|
||||
center: true,
|
||||
width: 1000,
|
||||
height: 800,
|
||||
alwaysOnTop: false,
|
||||
skipTaskbar: false,
|
||||
decorations: true,
|
||||
closable: true,
|
||||
url: "/ui/chat",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!isTransitioned) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={`h-[500px] flex flex-col rounded-xl overflow-hidden`}
|
||||
>
|
||||
<header
|
||||
data-tauri-drag-region
|
||||
className={`flex items-center justify-between py-2 px-1`}
|
||||
>
|
||||
<button
|
||||
onClick={() => openChatAI()}
|
||||
className={`p-2 rounded-lg transition-colors text-[#333] dark:text-[#d8d8d8]`}
|
||||
>
|
||||
<PanelLeft className="h-4 w-4"/>
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
createNewChat();
|
||||
}}
|
||||
className={`p-2 rounded-lg transition-colors text-[#333] dark:text-[#d8d8d8]`}
|
||||
>
|
||||
<MessageSquarePlus className="h-4 w-4"/>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{/* Chat messages */}
|
||||
<div
|
||||
className="w-full overflow-x-hidden overflow-y-auto border-t border-[rgba(0,0,0,0.1)] dark:border-[rgba(255,255,255,0.15)] custom-scrollbar">
|
||||
{activeChat?.messages?.map((message, index) => (
|
||||
<ChatMessage
|
||||
key={message._id + index}
|
||||
message={message}
|
||||
isTyping={
|
||||
isTyping &&
|
||||
index === (activeChat.messages?.length || 0) - 1 &&
|
||||
message._source?.type === "assistant"
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{!curChatEnd && activeChat?._id ? (
|
||||
<ChatMessage
|
||||
key={"last"}
|
||||
message={{
|
||||
_id: activeChat?._id,
|
||||
_source: {
|
||||
type: "assistant",
|
||||
message: curMessage,
|
||||
},
|
||||
}}
|
||||
isTyping={!curChatEnd}
|
||||
/>
|
||||
) : null}
|
||||
<div ref={messagesEndRef}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
));
|
||||
|
||||
export default ChatAI;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Mic, Send, Globe } from "lucide-react";
|
||||
import { Send, Globe, Brain } from "lucide-react";
|
||||
import {
|
||||
useState,
|
||||
type FormEvent,
|
||||
@@ -6,8 +6,9 @@ import {
|
||||
useRef,
|
||||
useEffect,
|
||||
} from "react";
|
||||
|
||||
import AutoResizeTextarea from "./AutoResizeTextarea";
|
||||
import StopIcon from "../../icons/Stop";
|
||||
import StopIcon from "@/icons/Stop";
|
||||
|
||||
interface ChatInputProps {
|
||||
onSend: (message: string) => void;
|
||||
@@ -16,6 +17,8 @@ interface ChatInputProps {
|
||||
disabledChange: () => void;
|
||||
isSearchActive: boolean;
|
||||
setIsSearchActive: () => void;
|
||||
isDeepThinkActive: boolean;
|
||||
setIsDeepThinkActive: () => void;
|
||||
}
|
||||
|
||||
export function ChatInput({
|
||||
@@ -25,8 +28,9 @@ export function ChatInput({
|
||||
disabledChange,
|
||||
isSearchActive,
|
||||
setIsSearchActive,
|
||||
isDeepThinkActive,
|
||||
setIsDeepThinkActive,
|
||||
}: ChatInputProps) {
|
||||
|
||||
const [input, setInput] = useState("");
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
@@ -64,6 +68,10 @@ export function ChatInput({
|
||||
setIsSearchActive();
|
||||
};
|
||||
|
||||
const DeepThinkClick = () => {
|
||||
setIsDeepThinkActive();
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="w-full rounded-xl overflow-hidden">
|
||||
<div className="bg-inputbox_bg_light dark:bg-inputbox_bg_dark bg-cover rounded-xl border border-[#E6E6E6] dark:border-[#272626]">
|
||||
@@ -77,9 +85,9 @@ export function ChatInput({
|
||||
handleKeyDown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<button className="p-1 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-full transition-colors">
|
||||
{/* <button className="p-1 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-full transition-colors">
|
||||
<Mic className="w-4 h-4 text-[#999] dark:text-[#999]" />
|
||||
</button>
|
||||
</button> */}
|
||||
{curChatEnd ? (
|
||||
<button
|
||||
className={`ml-1 p-1 ${
|
||||
@@ -108,20 +116,40 @@ export function ChatInput({
|
||||
|
||||
{/* Controls */}
|
||||
<div className="flex justify-between items-center p-2 rounded-xl overflow-hidden">
|
||||
<div className="flex gap-1 text-xs text-[#333] dark:text-[#d8d8d8]">
|
||||
<div className="flex gap-1 px-[5px] text-xs text-[#333] dark:text-[#fff]">
|
||||
<button
|
||||
type="button"
|
||||
className={`inline-flex items-center rounded-lg transition-colors relative py-1 px-[5px]`}
|
||||
className={`h-5 px-2 inline-flex items-center border rounded-[10px] transition-colors relative ${
|
||||
isDeepThinkActive ? "bg-[rgba(0,114,255,0.3)] border-[rgba(0,114,255,0.3)]" : "border-[#262727]"
|
||||
}`}
|
||||
onClick={DeepThinkClick}
|
||||
>
|
||||
<Brain
|
||||
className={`w-3 h-3 mr-1 ${
|
||||
isDeepThinkActive
|
||||
? "text-[#0072FF] dark:text-[#0072FF]"
|
||||
: "text-[#333] dark:text-white"
|
||||
}`}
|
||||
/>
|
||||
<span className={isDeepThinkActive ? "text-[#0072FF]" : "dark:text-white"}>
|
||||
Deep Think
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`h-5 px-2 inline-flex items-center border rounded-[10px] transition-colors relative ${
|
||||
isSearchActive ? "bg-[rgba(0,114,255,0.3)] border-[rgba(0,114,255,0.3)]" : "border-[#262727]"
|
||||
}`}
|
||||
onClick={SearchClick}
|
||||
>
|
||||
<Globe
|
||||
className={`w-4 h-4 mr-1 ${
|
||||
className={`w-3 h-3 mr-1 ${
|
||||
isSearchActive
|
||||
? "text-[#0072FF] dark:text-[#0072FF]"
|
||||
: "text-[#000] dark:text-[#d8d8d8]"
|
||||
: "text-[#333] dark:text-white"
|
||||
}`}
|
||||
/>
|
||||
<span className={isSearchActive ? "text-[#0072FF]" : ""}>
|
||||
<span className={isSearchActive ? "text-[#0072FF]" : "dark:text-white"}>
|
||||
Search
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Brain, ChevronDown, ChevronUp, Search, SquareArrowOutUpRight } from "lucide-react";
|
||||
import { Brain, ChevronDown, ChevronUp } from "lucide-react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
import type { Message } from "./types";
|
||||
import Markdown from "./Markdown";
|
||||
import { formatThinkingMessage, OpenURLWithBrowser } from "@/utils/index";
|
||||
import { formatThinkingMessage } from "@/utils/index";
|
||||
import logoImg from "@/assets/icon.svg";
|
||||
import { SourceResult } from "./SourceResult";
|
||||
|
||||
interface ChatMessageProps {
|
||||
message: Message;
|
||||
@@ -13,7 +14,6 @@ interface ChatMessageProps {
|
||||
|
||||
export function ChatMessage({ message, isTyping }: ChatMessageProps) {
|
||||
const [isThinkingExpanded, setIsThinkingExpanded] = useState(true);
|
||||
const [isSourceExpanded, setIsSourceExpanded] = useState(false);
|
||||
const [responseTime, setResponseTime] = useState(0);
|
||||
const startTimeRef = useRef<number | null>(null);
|
||||
const hasStartedRef = useRef(false);
|
||||
@@ -58,92 +58,25 @@ export function ChatMessage({ message, isTyping }: ChatMessageProps) {
|
||||
{segment.isThinking || segment.thinkContent ? (
|
||||
<div className="space-y-2 mb-3">
|
||||
{segment.text?.includes("<Source") && (
|
||||
<div>
|
||||
<button
|
||||
onClick={() =>
|
||||
setIsSourceExpanded((prev) => !prev)
|
||||
}
|
||||
className="inline-flex items-center gap-2 px-2 py-1 bg-gray-100/50 dark:bg-gray-800/50 rounded hover:bg-gray-200/50 dark:hover:bg-gray-700/50 transition-colors"
|
||||
>
|
||||
<Search className="w-4 h-4 text-gray-500" />
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Found{" "}
|
||||
{segment.text.match(
|
||||
/total=["']?(\d+)["']?/
|
||||
)?.[1] || "0"}{" "}
|
||||
results
|
||||
</span>
|
||||
{isSourceExpanded ? (
|
||||
<ChevronUp className="w-4 h-4" />
|
||||
) : (
|
||||
<ChevronDown className="w-4 h-4" />
|
||||
)}
|
||||
</button>
|
||||
{isSourceExpanded && (
|
||||
<div className="mt-2 bg-white dark:bg-[#202126] rounded-lg overflow-hidden border border-gray-100 dark:border-gray-800 shadow-sm">
|
||||
{(() => {
|
||||
try {
|
||||
const sourceMatch = segment.text.match(
|
||||
/<Source[^>]*>(.*?)<\/Source>/s
|
||||
);
|
||||
if (!sourceMatch) return null;
|
||||
const sourceData = JSON.parse(
|
||||
sourceMatch[1]
|
||||
);
|
||||
return sourceData.map(
|
||||
(item: any, idx: number) => (
|
||||
<div
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
if (item.url) {
|
||||
OpenURLWithBrowser(item.url);
|
||||
}
|
||||
}}
|
||||
className="flex items-center px-3 py-2.5 hover:bg-gray-50 dark:hover:bg-black/10 border-b border-gray-100 dark:border-gray-800 last:border-b-0 cursor-pointer transition-colors"
|
||||
>
|
||||
<div className="flex-1 min-w-0 flex items-center gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm text-gray-900 dark:text-[#D8D8D8] truncate font-medium group-hover:text-blue-500 dark:group-hover:text-blue-400">
|
||||
{item.title || item.category}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-shrink-0 text-xs text-gray-500 dark:text-[#8B8B8B]">
|
||||
<span>{item.source?.name}</span>
|
||||
<SquareArrowOutUpRight className="w-3 h-3"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Failed to parse source data:",
|
||||
error
|
||||
);
|
||||
return null;
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<SourceResult text={segment.text} />
|
||||
)}
|
||||
<button
|
||||
onClick={() =>
|
||||
setIsThinkingExpanded((prev) => !prev)
|
||||
}
|
||||
className="inline-flex items-center gap-2 px-2 py-1 bg-gray-100/50 dark:bg-gray-800/50 rounded hover:bg-gray-200/50 dark:hover:bg-gray-700/50 transition-colors"
|
||||
className="inline-flex items-center gap-2 px-2 py-1 rounded-xl transition-colors border border-[#E6E6E6] dark:border-[#272626]"
|
||||
>
|
||||
{isTyping ? (
|
||||
<>
|
||||
<Brain className="w-4 h-4 animate-pulse text-gray-500" />
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 italic">
|
||||
<Brain className="w-4 h-4 animate-pulse text-[#999999]" />
|
||||
<span className="text-xs text-[#999999] italic">
|
||||
AI is thinking...
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Brain className="w-4 h-4 text-gray-500" />
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||
<Brain className="w-4 h-4 text-[#999999]" />
|
||||
<span className="text-xs text-[#999999]">
|
||||
Thought for {responseTime.toFixed(1)} seconds
|
||||
</span>
|
||||
</>
|
||||
|
||||
98
src/components/Assistant/SourceResult.tsx
Normal file
98
src/components/Assistant/SourceResult.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
Search,
|
||||
ChevronUp,
|
||||
ChevronDown,
|
||||
SquareArrowOutUpRight,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { OpenURLWithBrowser } from "@/utils/index";
|
||||
|
||||
interface SourceResultProps {
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface SourceItem {
|
||||
url?: string;
|
||||
title?: string;
|
||||
category?: string;
|
||||
source?: {
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function SourceResult({ text }: SourceResultProps) {
|
||||
const [isSourceExpanded, setIsSourceExpanded] = useState(false);
|
||||
|
||||
if (!text?.includes("<Source")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getSourceData = (): SourceItem[] => {
|
||||
try {
|
||||
const sourceMatch = text.match(/<Source[^>]*>(.*?)<\/Source>/s);
|
||||
if (!sourceMatch) return [];
|
||||
return JSON.parse(sourceMatch[1]);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse source data:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const totalResults = text.match(/total=["']?(\d+)["']?/)?.[1] || "0";
|
||||
const sourceData = getSourceData();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`mt-2 ${
|
||||
isSourceExpanded
|
||||
? "rounded-lg overflow-hidden border border-[#E6E6E6] dark:border-[#272626]"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
onClick={() => setIsSourceExpanded((prev) => !prev)}
|
||||
className={`inline-flex justify-between items-center gap-2 px-2 py-1 rounded-xl transition-colors ${
|
||||
isSourceExpanded ? "w-full" : "border border-[#E6E6E6] dark:border-[#272626]"
|
||||
}`}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<Search className="w-4 h-4 text-[#999999] dark:text-[#999999]" />
|
||||
<span className="text-xs text-[#999999] dark:text-[#999999]">
|
||||
Found {totalResults} results
|
||||
</span>
|
||||
</div>
|
||||
{isSourceExpanded ? (
|
||||
<ChevronUp className="w-4 h-4 text-[#999999] dark:text-[#999999]" />
|
||||
) : (
|
||||
<ChevronDown className="w-4 h-4 text-[#999999] dark:text-[#999999]" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{isSourceExpanded && (
|
||||
<div className="">
|
||||
{sourceData.map((item, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
onClick={() => item.url && OpenURLWithBrowser(item.url)}
|
||||
className="group flex items-center px-2 py-1 hover:bg-[#F7F7F7] dark:hover:bg-[#2C2C2C] border-b border-[#E6E6E6] dark:border-[#272626] last:border-b-0 cursor-pointer transition-colors"
|
||||
>
|
||||
<div className="flex-1 min-w-0 flex items-center gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-xs text-[#333333] dark:text-[#D8D8D8] truncate font-normal group-hover:text-[#0072FF] dark:group-hover:text-[#0072FF]">
|
||||
{item.title || item.category}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
<span className="text-xs text-[#999999] dark:text-[#999999]">
|
||||
{item.source?.name}
|
||||
</span>
|
||||
<SquareArrowOutUpRight className="w-3 h-3 text-[#999999] dark:text-[#999999]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Library, Mic, Send, Plus, AudioLines, Image } from "lucide-react";
|
||||
import { Library, Send, Plus, AudioLines, Image } from "lucide-react";
|
||||
import { useRef, useState, useEffect, useCallback } from "react";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
@@ -176,14 +176,14 @@ export default function ChatInput({
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{isChatMode ? (
|
||||
{/* {isChatMode ? (
|
||||
<button
|
||||
className="p-1 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-full transition-colors"
|
||||
type="button"
|
||||
>
|
||||
<Mic className="w-4 h-4 text-[#999] dark:text-[#999]" />
|
||||
</button>
|
||||
) : null}
|
||||
) : null} */}
|
||||
|
||||
{isChatMode && curChatEnd ? (
|
||||
<button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ArrowBigLeft, Mic, Search, Send, Globe } from "lucide-react";
|
||||
import { ArrowBigLeft, Search, Send, Globe, Brain } from "lucide-react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { invoke, isTauri } from "@tauri-apps/api/core";
|
||||
@@ -22,6 +22,8 @@ interface ChatInputProps {
|
||||
reconnect: () => void;
|
||||
isSearchActive: boolean;
|
||||
setIsSearchActive: () => void;
|
||||
isDeepThinkActive: boolean;
|
||||
setIsDeepThinkActive: () => void;
|
||||
}
|
||||
|
||||
export default function ChatInput({
|
||||
@@ -35,6 +37,8 @@ export default function ChatInput({
|
||||
reconnect,
|
||||
isSearchActive,
|
||||
setIsSearchActive,
|
||||
isDeepThinkActive,
|
||||
setIsDeepThinkActive,
|
||||
}: ChatInputProps) {
|
||||
const showTooltip = useAppStore(
|
||||
(state: { showTooltip: boolean }) => state.showTooltip
|
||||
@@ -216,6 +220,10 @@ export default function ChatInput({
|
||||
setIsSearchActive();
|
||||
};
|
||||
|
||||
const DeepThinkClick = () => {
|
||||
setIsDeepThinkActive();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full relative">
|
||||
<div className="p-2 flex items-center dark:text-[#D8D8D8] bg-[#ededed] dark:bg-[#202126] rounded transition-all relative">
|
||||
@@ -276,14 +284,23 @@ export default function ChatInput({
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{isChatMode ? (
|
||||
{/* {isChatMode ? (
|
||||
<button
|
||||
className="p-1 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-full transition-colors"
|
||||
className={`p-1 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-full transition-colors ${
|
||||
isListening ? "bg-blue-100 dark:bg-blue-900" : ""
|
||||
}`}
|
||||
type="button"
|
||||
onClick={() => {}}
|
||||
>
|
||||
<Mic className="w-4 h-4 text-[#999] dark:text-[#999]" />
|
||||
<Mic
|
||||
className={`w-4 h-4 ${
|
||||
isListening
|
||||
? "text-blue-500 animate-pulse"
|
||||
: "text-[#999] dark:text-[#999]"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
) : null}
|
||||
) : null} */}
|
||||
|
||||
{isChatMode && curChatEnd ? (
|
||||
<button
|
||||
@@ -347,6 +364,21 @@ export default function ChatInput({
|
||||
>
|
||||
{isChatMode ? (
|
||||
<div className="flex gap-2 text-xs text-[#333] dark:text-[#d8d8d8]">
|
||||
<button
|
||||
className={`inline-flex items-center rounded-lg transition-colors relative py-1`}
|
||||
onClick={DeepThinkClick}
|
||||
>
|
||||
<Brain
|
||||
className={`w-4 h-4 mr-1 ${
|
||||
isDeepThinkActive
|
||||
? "text-[#0072FF] dark:text-[#0072FF]"
|
||||
: "text-[#000] dark:text-[#d8d8d8]"
|
||||
}`}
|
||||
/>
|
||||
<span className={isDeepThinkActive ? "text-[#0072FF]" : ""}>
|
||||
Deep Think
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className={`inline-flex items-center rounded-lg transition-colors relative py-1`}
|
||||
onClick={SearchClick}
|
||||
|
||||
@@ -3,26 +3,14 @@ import SVGWrap from "./SVGWrap";
|
||||
export default function Stop(props: I.SVG) {
|
||||
return (
|
||||
<SVGWrap viewBox="0 0 16 16" {...props}>
|
||||
<g stroke="currentColor" strokeWidth="1" fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M8,0.333333333 C12.2341831,0.333333333 15.6666667,3.76581692 15.6666667,8 C15.6666667,12.2341831 12.2341831,15.6666667 8,15.6666667 C3.76581692,15.6666667 0.333333333,12.2341831 0.333333333,8 C0.333333333,3.76581692 3.76581692,0.333333333 8,0.333333333 Z M8,1.66666667 C4.50219658,1.66666667 1.66666667,4.50219658 1.66666667,8 C1.66666667,11.4978034 4.50219658,14.3333333 8,14.3333333 C11.4978034,14.3333333 14.3333333,11.4978034 14.3333333,8 C14.3333333,4.50219658 11.4978034,1.66666667 8,1.66666667 Z"
|
||||
fill="currentColor"
|
||||
fillRule="nonzero"
|
||||
></path>
|
||||
<rect
|
||||
fill="currentColor"
|
||||
x="6"
|
||||
y="6"
|
||||
width="4"
|
||||
height="4"
|
||||
rx="0.666666687"
|
||||
></rect>
|
||||
<path
|
||||
d="M9.33333333,5.33333333 C10.069713,5.33333333 10.6666667,5.93028701 10.6666667,6.66666669 L10.6666667,9.33333333 C10.6666667,10.069713 10.069713,10.6666667 9.33333333,10.6666667 L6.66666669,10.6666667 C5.93028701,10.6666667 5.33333333,10.069713 5.33333333,9.33333333 L5.33333333,6.66666669 C5.33333333,5.93028701 5.93028701,5.33333333 6.66666669,5.33333333 L9.33333333,5.33333333 Z M9.33333333,6.66666669 L6.66666667,6.66666669 L6.66666667,9.33333333 L9.33333333,9.33333333 L9.33333333,6.66666669 Z"
|
||||
fill="currentColor"
|
||||
fillRule="nonzero"
|
||||
></path>
|
||||
</g>
|
||||
<g id="输入区域融合" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
|
||||
<g id="搜索结果" transform="translate(-1324, -770)" fill="#FFFFFF" fillRule="nonzero">
|
||||
<g id="停止" transform="translate(1324, 770)">
|
||||
<rect id="矩形" opacity="0" x="0" y="0" width="16" height="16"></rect>
|
||||
<path d="M4.64003125,12.7998906 L11.360125,12.7998906 C12.1554063,12.7998906 12.8,12.1552969 12.8,11.3600156 L12.8,4.64 C12.8,3.84475 12.1554375,3.2 11.360125,3.2 L4.64003125,3.2 C3.84476562,3.2 3.20003125,3.84475 3.20003125,4.64 L3.20003125,11.36 C3.20003125,12.15525 3.84473437,12.7998906 4.64003125,12.7998906 L4.64003125,12.7998906 Z" id="路径"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</SVGWrap>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ import { useState, useRef, useEffect } from "react";
|
||||
import { PanelRightClose, PanelRightOpen, X } from "lucide-react";
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
|
||||
import { ChatMessage } from "./ChatMessage";
|
||||
import { ChatInput } from "./ChatInput";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
import type { Chat, Message } from "./types";
|
||||
import { tauriFetch } from "../../api/tauriFetchClient";
|
||||
import { useWebSocket } from "../../hooks/useWebSocket";
|
||||
import { useWindows } from "../../hooks/useWindows";
|
||||
import { ChatMessage } from "@/components/Assistant/ChatMessage";
|
||||
import { ChatInput } from "@/components/Assistant/ChatInput";
|
||||
import { Sidebar } from "@/components/Assistant/Sidebar";
|
||||
import type { Chat, Message } from "@/components/Assistant/types";
|
||||
import { tauriFetch } from "@/api/tauriFetchClient";
|
||||
import { useWebSocket } from "@/hooks/useWebSocket";
|
||||
import { useWindows } from "@/hooks/useWindows";
|
||||
import { clientEnv } from "@/utils/env";
|
||||
// import { useAppStore } from '@/stores/appStore';
|
||||
import ApiDetails from "@/components/Common/ApiDetails";
|
||||
@@ -33,6 +33,7 @@ export default function ChatAI({}: ChatAIProps) {
|
||||
const [curId, setCurId] = useState("");
|
||||
|
||||
const [isSearchActive, setIsSearchActive] = useState(false);
|
||||
const [isDeepThinkActive, setIsDeepThinkActive] = useState(false);
|
||||
|
||||
const curChatEndRef = useRef(curChatEnd);
|
||||
curChatEndRef.current = curChatEnd;
|
||||
@@ -168,7 +169,7 @@ export default function ChatAI({}: ChatAIProps) {
|
||||
if (!activeChat?._id) return;
|
||||
try {
|
||||
const response = await tauriFetch({
|
||||
url: `/chat/${activeChat?._id}/_send?search=${isSearchActive}`,
|
||||
url: `/chat/${activeChat?._id}/_send?search=${isSearchActive}&deep_thinking=${isDeepThinkActive}`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"WEBSOCKET-SESSION-ID": websocketId,
|
||||
@@ -286,9 +287,9 @@ export default function ChatAI({}: ChatAIProps) {
|
||||
className={`rounded-lg transition-colors hover:bg-gray-100 text-gray-600 dark:hover:bg-gray-800 dark:text-gray-300`}
|
||||
>
|
||||
{isSidebarOpen ? (
|
||||
<PanelRightClose className="h-6 w-6" />
|
||||
) : (
|
||||
<PanelRightOpen className="h-6 w-6" />
|
||||
) : (
|
||||
<PanelRightClose className="h-6 w-6" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -337,6 +338,8 @@ export default function ChatAI({}: ChatAIProps) {
|
||||
}}
|
||||
isSearchActive={isSearchActive}
|
||||
setIsSearchActive={() => setIsSearchActive((prev) => !prev)}
|
||||
isDeepThinkActive={isDeepThinkActive}
|
||||
setIsDeepThinkActive={() => setIsDeepThinkActive((prev) => !prev)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,6 +53,7 @@ export default function DesktopApp() {
|
||||
const [isTransitioned, setIsTransitioned] = useState(false);
|
||||
|
||||
const [isSearchActive, setIsSearchActive] = useState(false);
|
||||
const [isDeepThinkActive, setIsDeepThinkActive] = useState(false);
|
||||
|
||||
async function changeMode(value: boolean) {
|
||||
setIsChatMode(value);
|
||||
@@ -69,7 +70,7 @@ export default function DesktopApp() {
|
||||
if (isTauri()) {
|
||||
await getCurrentWebviewWindow()?.setSize(new LogicalSize(680, 596));
|
||||
}
|
||||
chatAIRef.current?.init();
|
||||
chatAIRef.current?.init(value);
|
||||
}
|
||||
};
|
||||
const cancelChat = () => {
|
||||
@@ -110,7 +111,9 @@ export default function DesktopApp() {
|
||||
changeInput={changeInput}
|
||||
reconnect={reconnect}
|
||||
isSearchActive={isSearchActive}
|
||||
setIsSearchActive={() => setIsSearchActive((prev) => !prev)}
|
||||
setIsSearchActive={() => setIsSearchActive((prev) => !prev)}
|
||||
isDeepThinkActive={isDeepThinkActive}
|
||||
setIsDeepThinkActive={() => setIsDeepThinkActive((prev) => !prev)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -140,10 +143,10 @@ export default function DesktopApp() {
|
||||
<ChatAI
|
||||
ref={chatAIRef}
|
||||
key="ChatAI"
|
||||
inputValue={input}
|
||||
isTransitioned={isTransitioned}
|
||||
changeInput={changeInput}
|
||||
isSearchActive={isSearchActive}
|
||||
isDeepThinkActive={isDeepThinkActive}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { createBrowserRouter } from "react-router-dom";
|
||||
|
||||
import Layout from "./layout.tsx";
|
||||
import Layout from "./layout";
|
||||
import ErrorPage from "@/error-page";
|
||||
import DesktopApp from "@/pages/main/index.tsx";
|
||||
import SettingsPage from "@/pages/settings/index.tsx";
|
||||
import ChatAI from "@/components/Assistant";
|
||||
import DesktopApp from "@/pages/main/index";
|
||||
import SettingsPage from "@/pages/settings/index";
|
||||
import ChatAI from "@/pages/chat/index";
|
||||
|
||||
export const router = createBrowserRouter([
|
||||
{
|
||||
@@ -14,7 +14,7 @@ export const router = createBrowserRouter([
|
||||
children: [
|
||||
{ path: "/ui", element: <DesktopApp /> },
|
||||
{ path: "/ui/settings", element: <SettingsPage /> },
|
||||
{ path: "/ui/app/chat", element: <ChatAI /> },
|
||||
{ path: "/ui/chat", element: <ChatAI /> },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const clientEnv = {
|
||||
COCO_SERVER_URL: "http://localhost:9000", //"https://coco.infini.cloud", // http://localhost:9000
|
||||
COCO_WEBSOCKET_URL: "ws://localhost:9000/ws", // "wss://coco.infini.cloud/ws", // ws://localhost:9000/ws
|
||||
COCO_SERVER_URL: "https://coco.infini.cloud", // http://localhost:9000
|
||||
COCO_WEBSOCKET_URL: "wss://coco.infini.cloud/ws", // ws://localhost:9000/ws
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user