chore: Coco chat window input and message (#237)

This commit is contained in:
BiggerRain
2025-03-04 15:49:04 +08:00
committed by GitHub
parent 7fc1b0e31f
commit 0bc61881c4
9 changed files with 126 additions and 84 deletions

View File

@@ -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}

View File

@@ -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>
);

View 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"),
},
}}
/>
);
};

View File

@@ -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)
}

View File

@@ -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>
);

View File

@@ -138,7 +138,7 @@
"fetch_source": "检索 {{count}} 份文档",
"pick_source": "智能预选 {{count}} 个结果",
"pick_source_start": "正在智能预选",
"deep_read": "深度阅读",
"deep_read": "深度阅读",
"think": "AI 正在思考...",
"thoughtTime": "思考了数秒",
"keywords": "关键词",

View File

@@ -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>

View File

@@ -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}

View File

@@ -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,
}),
}
)
);