mirror of
https://github.com/infinilabs/coco-app.git
synced 2026-08-29 10:09:30 +02:00
chore: Coco chat window input and message (#237)
This commit is contained in:
@@ -22,7 +22,8 @@ import { Sidebar } from "@/components/Assistant/Sidebar";
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
import { useSearchStore } from "@/stores/searchStore";
|
||||
import { IServer } from "@/stores/appStore";
|
||||
import FileList from "../Search/FileList";
|
||||
import FileList from "@/components/Search/FileList";
|
||||
import { Greetings } from "./Greetings";
|
||||
|
||||
interface ChatAIProps {
|
||||
isTransitioned: boolean;
|
||||
@@ -33,6 +34,7 @@ interface ChatAIProps {
|
||||
setIsSidebarOpen?: (value: boolean) => void;
|
||||
isSidebarOpen?: boolean;
|
||||
clearChatPage?: () => void;
|
||||
isChatPage?: boolean;
|
||||
}
|
||||
|
||||
export interface ChatAIRef {
|
||||
@@ -56,6 +58,7 @@ const ChatAI = memo(
|
||||
setIsSidebarOpen,
|
||||
isSidebarOpen = false,
|
||||
clearChatPage,
|
||||
isChatPage = false,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
@@ -174,6 +177,7 @@ const ChatAI = memo(
|
||||
});
|
||||
}, []);
|
||||
|
||||
// send、new、history
|
||||
const clearCurrentChat = useCallback(() => {
|
||||
setQuery_intent(undefined);
|
||||
setFetch_source(undefined);
|
||||
@@ -258,11 +262,12 @@ const ChatAI = memo(
|
||||
|
||||
useEffect(() => {
|
||||
let unlisten_message = null;
|
||||
console.log("connected:", connected);
|
||||
if (connected) {
|
||||
setErrorShow(false);
|
||||
unlisten_message = listen("ws-message", (event) => {
|
||||
dealMsg(String(event.payload));
|
||||
console.log("message:", event.payload);
|
||||
// console.log("message:", event.payload);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -415,7 +420,6 @@ const ChatAI = memo(
|
||||
);
|
||||
|
||||
const init = (value: string) => {
|
||||
console.log("init", value, curChatEnd, activeChat);
|
||||
if (!curChatEnd) return;
|
||||
if (!activeChat?._id) {
|
||||
createNewChat(value);
|
||||
@@ -424,16 +428,10 @@ const ChatAI = memo(
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendMessage = useCallback(
|
||||
async (content: string, newChat?: Chat) => {
|
||||
newChat = newChat || activeChat;
|
||||
const sendMessage = useCallback(
|
||||
async (content: string, newChat: Chat) => {
|
||||
if (!newChat?._id || !content) return;
|
||||
setQuestion(content);
|
||||
await chatHistory(newChat);
|
||||
|
||||
setTimedoutShow(false);
|
||||
setErrorShow(false);
|
||||
clearCurrentChat();
|
||||
try {
|
||||
// console.log("sourceDataIds", sourceDataIds);
|
||||
let response: any = await invoke("send_message", {
|
||||
@@ -464,7 +462,21 @@ const ChatAI = memo(
|
||||
console.error("Failed to fetch user data:", error);
|
||||
}
|
||||
},
|
||||
[activeChat, isSearchActive, isDeepThinkActive]
|
||||
[JSON.stringify(activeChat?.messages), isSearchActive, isDeepThinkActive]
|
||||
);
|
||||
|
||||
const handleSendMessage = useCallback(
|
||||
async (content: string, newChat?: Chat) => {
|
||||
newChat = newChat || activeChat;
|
||||
if (!newChat?._id || !content) return;
|
||||
setQuestion(content);
|
||||
await chatHistory(newChat, (chat) => sendMessage(content, chat));
|
||||
|
||||
setTimedoutShow(false);
|
||||
setErrorShow(false);
|
||||
clearCurrentChat();
|
||||
},
|
||||
[activeChat, sendMessage]
|
||||
);
|
||||
|
||||
const chatClose = async () => {
|
||||
@@ -527,7 +539,7 @@ const ChatAI = memo(
|
||||
};
|
||||
}, []);
|
||||
|
||||
const chatHistory = async (chat: Chat) => {
|
||||
const chatHistory = async (chat: Chat, callback?: (chat: Chat) => void) => {
|
||||
try {
|
||||
let response: any = await invoke("session_chat_history", {
|
||||
serverId: currentService?.id,
|
||||
@@ -543,6 +555,7 @@ const ChatAI = memo(
|
||||
messages: hits,
|
||||
};
|
||||
setActiveChat(updatedChat);
|
||||
callback && callback(updatedChat)
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user data:", error);
|
||||
}
|
||||
@@ -550,6 +563,7 @@ const ChatAI = memo(
|
||||
|
||||
const onSelectChat = async (chat: any) => {
|
||||
chatClose();
|
||||
clearCurrentChat();
|
||||
try {
|
||||
let response: any = await invoke("open_session_chat", {
|
||||
serverId: currentService?.id,
|
||||
@@ -655,20 +669,12 @@ const ChatAI = memo(
|
||||
isSidebarOpen={isSidebarOpenChat}
|
||||
activeChat={activeChat}
|
||||
reconnect={reconnect}
|
||||
isChatPage={isChatPage}
|
||||
/>
|
||||
{/* Chat messages */}
|
||||
<div className="flex flex-col h-full justify-between overflow-hidden">
|
||||
<div className="flex-1 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 relative">
|
||||
<ChatMessage
|
||||
key={"greetings"}
|
||||
message={{
|
||||
_id: "greetings",
|
||||
_source: {
|
||||
type: "assistant",
|
||||
message: t("assistant.chat.greetings"),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Greetings />
|
||||
{activeChat?.messages?.map((message, index) => (
|
||||
<ChatMessage
|
||||
key={message._id + index}
|
||||
|
||||
@@ -39,6 +39,7 @@ interface ChatHeaderProps {
|
||||
isSidebarOpen: boolean;
|
||||
activeChat: Chat | undefined;
|
||||
reconnect: (server?: IServer) => void;
|
||||
isChatPage?: boolean;
|
||||
}
|
||||
|
||||
export function ChatHeader({
|
||||
@@ -47,6 +48,7 @@ export function ChatHeader({
|
||||
setIsSidebarOpen,
|
||||
activeChat,
|
||||
reconnect,
|
||||
isChatPage = false,
|
||||
}: ChatHeaderProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -77,7 +79,7 @@ export function ChatHeader({
|
||||
);
|
||||
|
||||
if (currentServiceExists) {
|
||||
switchServer(currentService);
|
||||
switchServer(currentService, true);
|
||||
} else {
|
||||
switchServer(enabledServers[enabledServers.length - 1]);
|
||||
}
|
||||
@@ -108,7 +110,7 @@ export function ChatHeader({
|
||||
}
|
||||
};
|
||||
|
||||
const switchServer = async (server: IServer) => {
|
||||
const switchServer = async (server: IServer, isCurrent: boolean = false) => {
|
||||
if (!server) return;
|
||||
try {
|
||||
// Switch UI first, then switch server connection
|
||||
@@ -117,8 +119,10 @@ export function ChatHeader({
|
||||
setMessages(""); // Clear previous messages
|
||||
onCreateNewChat();
|
||||
//
|
||||
await disconnect();
|
||||
reconnect && reconnect(server);
|
||||
if (!(isCurrent && connected)) {
|
||||
await disconnect();
|
||||
reconnect && reconnect(server);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("switchServer:", error);
|
||||
}
|
||||
@@ -306,9 +310,11 @@ export function ChatHeader({
|
||||
</PopoverPanel>
|
||||
</Popover>
|
||||
|
||||
<button onClick={onOpenChatAI}>
|
||||
<WindowsFullIcon className="rotate-30 scale-x-[-1]" />
|
||||
</button>
|
||||
{isChatPage ? null : (
|
||||
<button onClick={onOpenChatAI}>
|
||||
<WindowsFullIcon className="rotate-30 scale-x-[-1]" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
20
src/components/Assistant/Greetings.tsx
Normal file
20
src/components/Assistant/Greetings.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ChatMessage } from "@/components/ChatMessage";
|
||||
|
||||
export const Greetings = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ChatMessage
|
||||
key={"greetings"}
|
||||
message={{
|
||||
_id: "greetings",
|
||||
_source: {
|
||||
type: "assistant",
|
||||
message: t("assistant.chat.greetings"),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -37,7 +37,7 @@ export const QueryIntent = ({ ChunkData, getSuggestion }: QueryIntentProps) => {
|
||||
const lastMatch = allMatches[allMatches.length - 1];
|
||||
const jsonString = lastMatch.replace(/<JSON>|<\/JSON>/g, "");
|
||||
const data = JSON.parse(jsonString);
|
||||
console.log("QueryIntent", data);
|
||||
// console.log("QueryIntent", data);
|
||||
if (data?.suggestion && getSuggestion) {
|
||||
getSuggestion(data?.suggestion)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
import { useReactive, useRequest } from "ahooks";
|
||||
import { isArray } from "lodash-es";
|
||||
|
||||
import ChatSwitch from "@/components/Common/ChatSwitch";
|
||||
import AutoResizeTextarea from "./AutoResizeTextarea";
|
||||
@@ -31,14 +32,13 @@ import { useSearchStore } from "@/stores/searchStore";
|
||||
import { metaOrCtrlKey } from "@/utils/keyboardUtils";
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
import TypeIcon from "@/components/Common/Icons/TypeIcon";
|
||||
import { isArray } from "lodash-es";
|
||||
import InputExtra from "./InputExtra";
|
||||
|
||||
interface ChatInputProps {
|
||||
onSend: (message: string) => void;
|
||||
disabled: boolean;
|
||||
disabledChange: () => void;
|
||||
changeMode: (isChatMode: boolean) => void;
|
||||
changeMode?: (isChatMode: boolean) => void;
|
||||
isChatMode: boolean;
|
||||
inputValue: string;
|
||||
changeInput: (val: string) => void;
|
||||
@@ -47,6 +47,7 @@ interface ChatInputProps {
|
||||
setIsSearchActive: () => void;
|
||||
isDeepThinkActive: boolean;
|
||||
setIsDeepThinkActive: () => void;
|
||||
isChatPage?: boolean;
|
||||
}
|
||||
|
||||
export default function ChatInput({
|
||||
@@ -62,6 +63,7 @@ export default function ChatInput({
|
||||
setIsSearchActive,
|
||||
isDeepThinkActive,
|
||||
setIsDeepThinkActive,
|
||||
isChatPage = false,
|
||||
}: ChatInputProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -266,7 +268,7 @@ export default function ChatInput({
|
||||
if (!isChatMode) return;
|
||||
if (connected) return;
|
||||
if (countdown <= 0) {
|
||||
console.log("ReconnectClick", 111111);
|
||||
console.log("ReconnectClick");
|
||||
ReconnectClick();
|
||||
return;
|
||||
}
|
||||
@@ -310,8 +312,16 @@ export default function ChatInput({
|
||||
};
|
||||
|
||||
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">
|
||||
<div
|
||||
className={`w-full relative ${
|
||||
isChatPage
|
||||
? "bg-inputbox_bg_light dark:bg-inputbox_bg_dark bg-cover rounded-xl border border-[#E6E6E6] dark:border-[#272626]"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`p-2 flex items-center dark:text-[#D8D8D8] bg-[#ededed] dark:bg-[#202126] rounded transition-all relative `}
|
||||
>
|
||||
<div className="flex flex-wrap gap-2 flex-1 items-center relative">
|
||||
{!isChatMode && !sourceData ? (
|
||||
<Search className="w-4 h-4 text-[#ccc] dark:text-[#d8d8d8]" />
|
||||
@@ -326,7 +336,9 @@ export default function ChatInput({
|
||||
<AutoResizeTextarea
|
||||
ref={textareaRef}
|
||||
input={inputValue}
|
||||
setInput={changeInput}
|
||||
setInput={(value: string) => {
|
||||
changeInput(value);
|
||||
}}
|
||||
connected={connected}
|
||||
handleKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
@@ -657,23 +669,25 @@ export default function ChatInput({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="relative w-16 flex justify-end items-center">
|
||||
{showTooltip && isCommandPressed ? (
|
||||
<div
|
||||
className={`absolute left-1 z-10 w-4 h-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000]`}
|
||||
>
|
||||
T
|
||||
</div>
|
||||
) : null}
|
||||
<ChatSwitch
|
||||
isChatMode={isChatMode}
|
||||
onChange={(value: boolean) => {
|
||||
value && disabledChange();
|
||||
changeMode(value);
|
||||
setSourceData(undefined);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{isChatPage ? null : (
|
||||
<div className="relative w-16 flex justify-end items-center">
|
||||
{showTooltip && isCommandPressed ? (
|
||||
<div
|
||||
className={`absolute left-1 z-10 w-4 h-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000]`}
|
||||
>
|
||||
T
|
||||
</div>
|
||||
) : null}
|
||||
<ChatSwitch
|
||||
isChatMode={isChatMode}
|
||||
onChange={(value: boolean) => {
|
||||
value && disabledChange();
|
||||
changeMode && changeMode(value);
|
||||
setSourceData(undefined);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
"fetch_source": "检索 {{count}} 份文档",
|
||||
"pick_source": "智能预选 {{count}} 个结果",
|
||||
"pick_source_start": "正在智能预选",
|
||||
"deep_read": "深度阅读,",
|
||||
"deep_read": "深度阅读",
|
||||
"think": "AI 正在思考...",
|
||||
"thoughtTime": "思考了数秒",
|
||||
"keywords": "关键词",
|
||||
|
||||
@@ -2,10 +2,10 @@ import { useState, useRef, useEffect } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
import ChatAI, { ChatAIRef } from "@/components/Assistant/Chat";
|
||||
import { ChatInput } from "@/components/Assistant/ChatInput";
|
||||
import { Sidebar } from "@/components/Assistant/Sidebar";
|
||||
import type { Chat } from "@/components/Assistant/types";
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
import InputBox from "@/components/Search/InputBox";
|
||||
|
||||
interface ChatProps {}
|
||||
|
||||
@@ -17,13 +17,15 @@ export default function Chat({}: ChatProps) {
|
||||
const [chats, setChats] = useState<Chat[]>([]);
|
||||
const [activeChat, setActiveChat] = useState<Chat>();
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
||||
const [isTyping, setIsTyping] = useState(false);
|
||||
const isTyping = false;
|
||||
|
||||
const [curChatEnd, setCurChatEnd] = useState(true);
|
||||
const [input, setInput] = useState("");
|
||||
|
||||
const [isSearchActive, setIsSearchActive] = useState(false);
|
||||
const [isDeepThinkActive, setIsDeepThinkActive] = useState(false);
|
||||
|
||||
const isChatPage = true
|
||||
|
||||
useEffect(() => {
|
||||
getChatHistory();
|
||||
}, []);
|
||||
@@ -62,6 +64,7 @@ export default function Chat({}: ChatProps) {
|
||||
};
|
||||
|
||||
const handleSendMessage = async (content: string) => {
|
||||
setInput(content);
|
||||
chatAIRef.current?.init(content);
|
||||
};
|
||||
|
||||
@@ -116,17 +119,7 @@ export default function Chat({}: ChatProps) {
|
||||
};
|
||||
|
||||
const cancelChat = async () => {
|
||||
if (!activeChat?._id) return;
|
||||
try {
|
||||
let response: any = await invoke("cancel_session_chat", {
|
||||
serverId: currentService?.id,
|
||||
sessionId: activeChat?._id,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
console.log("_cancel", response);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user data:", error);
|
||||
}
|
||||
chatAIRef.current?.cancelChat();
|
||||
};
|
||||
|
||||
const clearChat = () => {
|
||||
@@ -134,6 +127,10 @@ export default function Chat({}: ChatProps) {
|
||||
setActiveChat(undefined);
|
||||
};
|
||||
|
||||
const reconnect = () => {
|
||||
chatAIRef.current?.reconnect();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-screen">
|
||||
<div className="h-[100%] flex">
|
||||
@@ -169,23 +166,24 @@ export default function Chat({}: ChatProps) {
|
||||
setIsSidebarOpen={setIsSidebarOpen}
|
||||
isSidebarOpen={isSidebarOpen}
|
||||
clearChatPage={clearChat}
|
||||
isChatPage={isChatPage}
|
||||
/>
|
||||
|
||||
{/* Input area */}
|
||||
<div className={`border-t p-4 border-gray-200 dark:border-gray-800`}>
|
||||
<ChatInput
|
||||
<InputBox
|
||||
isChatMode={true}
|
||||
inputValue={input}
|
||||
onSend={handleSendMessage}
|
||||
changeInput={setInput}
|
||||
disabled={isTyping}
|
||||
curChatEnd={curChatEnd}
|
||||
disabledChange={() => {
|
||||
cancelChat();
|
||||
setCurChatEnd(true);
|
||||
setIsTyping(false);
|
||||
}}
|
||||
disabledChange={cancelChat}
|
||||
reconnect={reconnect}
|
||||
isSearchActive={isSearchActive}
|
||||
setIsSearchActive={() => setIsSearchActive((prev) => !prev)}
|
||||
isDeepThinkActive={isDeepThinkActive}
|
||||
setIsDeepThinkActive={() => setIsDeepThinkActive((prev) => !prev)}
|
||||
isChatPage={isChatPage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function DesktopApp() {
|
||||
setIsTransitioned(value);
|
||||
}
|
||||
|
||||
async function changeInput(value: string) {
|
||||
function changeInput(value: string) {
|
||||
setInput(value);
|
||||
}
|
||||
|
||||
@@ -114,9 +114,7 @@ export default function DesktopApp() {
|
||||
inputValue={input}
|
||||
onSend={handleSendMessage}
|
||||
disabled={isTyping}
|
||||
disabledChange={() => {
|
||||
cancelChat();
|
||||
}}
|
||||
disabledChange={cancelChat}
|
||||
changeMode={changeMode}
|
||||
changeInput={changeInput}
|
||||
reconnect={reconnect}
|
||||
|
||||
@@ -46,10 +46,10 @@ export const useChatStore = create<IChatStore>()(
|
||||
{
|
||||
name: "chat-state",
|
||||
// storage: createJSONStorage(() => sessionStorage),
|
||||
partialize: (state) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(state).filter(([key]) => key === "curChatEnd")
|
||||
),
|
||||
partialize: (state) => ({
|
||||
curChatEnd: state.curChatEnd,
|
||||
connected: state.connected,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user