chore: stop chat & ui style (#25)
@@ -15,6 +15,7 @@
|
|||||||
"@tauri-apps/plugin-http": "~2.0.1",
|
"@tauri-apps/plugin-http": "~2.0.1",
|
||||||
"@tauri-apps/plugin-shell": ">=2.0.0",
|
"@tauri-apps/plugin-shell": ">=2.0.0",
|
||||||
"@tauri-apps/plugin-websocket": "~2",
|
"@tauri-apps/plugin-websocket": "~2",
|
||||||
|
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
|
||||||
"@traptitech/markdown-it-katex": "^3.6.0",
|
"@traptitech/markdown-it-katex": "^3.6.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|||||||
16
pnpm-lock.yaml
generated
@@ -23,6 +23,9 @@ importers:
|
|||||||
'@tauri-apps/plugin-websocket':
|
'@tauri-apps/plugin-websocket':
|
||||||
specifier: ~2
|
specifier: ~2
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
|
'@tauri-apps/plugin-window':
|
||||||
|
specifier: 2.0.0-alpha.1
|
||||||
|
version: 2.0.0-alpha.1
|
||||||
'@traptitech/markdown-it-katex':
|
'@traptitech/markdown-it-katex':
|
||||||
specifier: ^3.6.0
|
specifier: ^3.6.0
|
||||||
version: 3.6.0
|
version: 3.6.0
|
||||||
@@ -607,6 +610,10 @@ packages:
|
|||||||
'@tanstack/virtual-core@3.10.8':
|
'@tanstack/virtual-core@3.10.8':
|
||||||
resolution: {integrity: sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA==}
|
resolution: {integrity: sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA==}
|
||||||
|
|
||||||
|
'@tauri-apps/api@2.0.0-alpha.6':
|
||||||
|
resolution: {integrity: sha512-ZMOc3eu9amwvkC6M69h3hWt4/EsFaAXmtkiw4xd2LN59/lTb4ZQiVfq2QKlRcu1rj3n/Tcr7U30ZopvHwXBGIg==}
|
||||||
|
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
|
||||||
|
|
||||||
'@tauri-apps/api@2.0.2':
|
'@tauri-apps/api@2.0.2':
|
||||||
resolution: {integrity: sha512-3wSwmG+1kr6WrgAFKK5ijkNFPp8TT3FLj3YHUb5EwMO+3FxX4uWlfSWkeeBy+Kc1RsKzugtYLuuya+98Flj+3w==}
|
resolution: {integrity: sha512-3wSwmG+1kr6WrgAFKK5ijkNFPp8TT3FLj3YHUb5EwMO+3FxX4uWlfSWkeeBy+Kc1RsKzugtYLuuya+98Flj+3w==}
|
||||||
|
|
||||||
@@ -688,6 +695,9 @@ packages:
|
|||||||
'@tauri-apps/plugin-websocket@2.0.0':
|
'@tauri-apps/plugin-websocket@2.0.0':
|
||||||
resolution: {integrity: sha512-O2qRxZCljd4g+ceJhW7LfgQr+fg0fBBiAaLiMopoKL6TXKMnhBHOenp4nZ5/MoVTr77OQIDNO6Jp/c1YwiRVtQ==}
|
resolution: {integrity: sha512-O2qRxZCljd4g+ceJhW7LfgQr+fg0fBBiAaLiMopoKL6TXKMnhBHOenp4nZ5/MoVTr77OQIDNO6Jp/c1YwiRVtQ==}
|
||||||
|
|
||||||
|
'@tauri-apps/plugin-window@2.0.0-alpha.1':
|
||||||
|
resolution: {integrity: sha512-dFOAgal/3Txz3SQ+LNQq0AK1EPC+acdaFlwPVB/6KXUZYmaFleIlzgxDVoJCQ+/xOhxvYrdQaFLefh0I/Kldbg==}
|
||||||
|
|
||||||
'@traptitech/markdown-it-katex@3.6.0':
|
'@traptitech/markdown-it-katex@3.6.0':
|
||||||
resolution: {integrity: sha512-CnJzTWxsgLGXFdSrWRaGz7GZ1kUUi8g3E9HzJmeveX1YwVJavrKYqysktfHZQsujdnRqV5O7g8FPKEA/aeTkOQ==}
|
resolution: {integrity: sha512-CnJzTWxsgLGXFdSrWRaGz7GZ1kUUi8g3E9HzJmeveX1YwVJavrKYqysktfHZQsujdnRqV5O7g8FPKEA/aeTkOQ==}
|
||||||
|
|
||||||
@@ -2667,6 +2677,8 @@ snapshots:
|
|||||||
|
|
||||||
'@tanstack/virtual-core@3.10.8': {}
|
'@tanstack/virtual-core@3.10.8': {}
|
||||||
|
|
||||||
|
'@tauri-apps/api@2.0.0-alpha.6': {}
|
||||||
|
|
||||||
'@tauri-apps/api@2.0.2': {}
|
'@tauri-apps/api@2.0.2': {}
|
||||||
|
|
||||||
'@tauri-apps/cli-darwin-arm64@2.0.3':
|
'@tauri-apps/cli-darwin-arm64@2.0.3':
|
||||||
@@ -2724,6 +2736,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 2.0.2
|
'@tauri-apps/api': 2.0.2
|
||||||
|
|
||||||
|
'@tauri-apps/plugin-window@2.0.0-alpha.1':
|
||||||
|
dependencies:
|
||||||
|
'@tauri-apps/api': 2.0.0-alpha.6
|
||||||
|
|
||||||
'@traptitech/markdown-it-katex@3.6.0':
|
'@traptitech/markdown-it-katex@3.6.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
katex: 0.16.11
|
katex: 0.16.11
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
BIN
src/assets/chat_bg_dark.png
Normal file
|
After Width: | Height: | Size: 244 KiB |
BIN
src/assets/chat_bg_light.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
src/assets/inputbox_bg_dark.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
src/assets/inputbox_bg_light.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
src/assets/search_bg_dark.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
src/assets/search_bg_light.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
@@ -22,15 +22,17 @@ interface ChatAIProps {
|
|||||||
|
|
||||||
export interface ChatAIRef {
|
export interface ChatAIRef {
|
||||||
init: () => void;
|
init: () => void;
|
||||||
|
cancelChat: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatAI = forwardRef<ChatAIRef, ChatAIProps>(
|
const ChatAI = forwardRef<ChatAIRef, ChatAIProps>(
|
||||||
({ inputValue, isTransitioned, changeInput }, ref) => {
|
({ inputValue, isTransitioned, changeInput }, ref) => {
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
init: init,
|
init: init,
|
||||||
|
cancelChat: cancelChat,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { curChatEnd, setCurChatEnd, stopChat } = useChatStore();
|
const { curChatEnd, setCurChatEnd } = useChatStore();
|
||||||
|
|
||||||
const [activeChat, setActiveChat] = useState<Chat>();
|
const [activeChat, setActiveChat] = useState<Chat>();
|
||||||
const [isTyping, setIsTyping] = useState(false);
|
const [isTyping, setIsTyping] = useState(false);
|
||||||
@@ -39,6 +41,13 @@ const ChatAI = forwardRef<ChatAIRef, ChatAIProps>(
|
|||||||
|
|
||||||
const [websocketId, setWebsocketId] = useState("");
|
const [websocketId, setWebsocketId] = useState("");
|
||||||
const [curMessage, setCurMessage] = useState("");
|
const [curMessage, setCurMessage] = useState("");
|
||||||
|
const [curId, setCurId] = useState("");
|
||||||
|
|
||||||
|
const curChatEndRef = useRef(curChatEnd);
|
||||||
|
curChatEndRef.current = curChatEnd;
|
||||||
|
|
||||||
|
const curIdRef = useRef(curId);
|
||||||
|
curIdRef.current = curId;
|
||||||
const { messages, setMessages } = useWebSocket(
|
const { messages, setMessages } = useWebSocket(
|
||||||
"ws://localhost:2900/ws",
|
"ws://localhost:2900/ws",
|
||||||
(msg) => {
|
(msg) => {
|
||||||
@@ -48,28 +57,30 @@ const ChatAI = forwardRef<ChatAIRef, ChatAIProps>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (msg.includes("PRIVATE")) {
|
if (msg.includes("PRIVATE")) {
|
||||||
if (msg.includes("assistant finished output")) {
|
if (
|
||||||
|
msg.includes("assistant finished output") ||
|
||||||
|
curChatEndRef.current
|
||||||
|
) {
|
||||||
setCurChatEnd(true);
|
setCurChatEnd(true);
|
||||||
} else {
|
} else {
|
||||||
const cleanedData = msg.replace(/^PRIVATE /, "");
|
const cleanedData = msg.replace(/^PRIVATE /, "");
|
||||||
try {
|
try {
|
||||||
const chunkData = JSON.parse(cleanedData);
|
const chunkData = JSON.parse(cleanedData);
|
||||||
setCurMessage((prev) => prev + chunkData.message_chunk);
|
if (chunkData.reply_to_message === curIdRef.current) {
|
||||||
return chunkData.message_chunk;
|
setCurMessage((prev) => prev + chunkData.message_chunk);
|
||||||
|
return chunkData.message_chunk;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("JSON Parse error:", error);
|
console.error("JSON Parse error:", error);
|
||||||
}
|
}
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// websocket
|
// websocket
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (messages.length === 0 || !activeChat?._id || stopChat) return;
|
if (messages.length === 0 || !activeChat?._id) return;
|
||||||
|
|
||||||
const simulateAssistantResponse = () => {
|
const simulateAssistantResponse = () => {
|
||||||
console.log("messages", messages);
|
console.log("messages", messages);
|
||||||
@@ -94,7 +105,7 @@ const ChatAI = forwardRef<ChatAIRef, ChatAIProps>(
|
|||||||
if (curChatEnd) {
|
if (curChatEnd) {
|
||||||
simulateAssistantResponse();
|
simulateAssistantResponse();
|
||||||
}
|
}
|
||||||
}, [messages, curChatEnd, stopChat]);
|
}, [messages, curChatEnd]);
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
messagesEndRef.current?.scrollIntoView({
|
messagesEndRef.current?.scrollIntoView({
|
||||||
@@ -125,6 +136,7 @@ const ChatAI = forwardRef<ChatAIRef, ChatAIProps>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
|
if (!curChatEnd) return;
|
||||||
if (!activeChat?._id) {
|
if (!activeChat?._id) {
|
||||||
createNewChat();
|
createNewChat();
|
||||||
} else {
|
} else {
|
||||||
@@ -145,6 +157,7 @@ const ChatAI = forwardRef<ChatAIRef, ChatAIProps>(
|
|||||||
body: JSON.stringify({ message: content }),
|
body: JSON.stringify({ message: content }),
|
||||||
});
|
});
|
||||||
console.log("_send", response, websocketId);
|
console.log("_send", response, websocketId);
|
||||||
|
setCurId(response.data[0]?._id);
|
||||||
const updatedChat: Chat = {
|
const updatedChat: Chat = {
|
||||||
...newChat,
|
...newChat,
|
||||||
messages: [...(newChat?.messages || []), ...(response.data || [])],
|
messages: [...(newChat?.messages || []), ...(response.data || [])],
|
||||||
@@ -172,24 +185,21 @@ const ChatAI = forwardRef<ChatAIRef, ChatAIProps>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const cancelChat = async () => {
|
const cancelChat = async () => {
|
||||||
|
setCurChatEnd(true);
|
||||||
|
setIsTyping(false);
|
||||||
if (!activeChat?._id) return;
|
if (!activeChat?._id) return;
|
||||||
try {
|
try {
|
||||||
const response = await tauriFetch({
|
const response = await tauriFetch({
|
||||||
url: `/chat/${activeChat._id}/_cancel`,
|
url: `/chat/${activeChat._id}/_cancel`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("_cancel", response);
|
console.log("_cancel", response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch user data:", error);
|
console.error("Failed to fetch user data:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (curChatEnd) {
|
|
||||||
cancelChat();
|
|
||||||
}
|
|
||||||
}, [curChatEnd]);
|
|
||||||
|
|
||||||
async function openChatAI() {
|
async function openChatAI() {
|
||||||
createWin({
|
createWin({
|
||||||
label: "chat",
|
label: "chat",
|
||||||
|
|||||||
@@ -6,20 +6,21 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useEffect,
|
useEffect,
|
||||||
} from "react";
|
} from "react";
|
||||||
import ChatSwitch from "../SearchChat/ChatSwitch";
|
|
||||||
import AutoResizeTextarea from "./AutoResizeTextarea";
|
import AutoResizeTextarea from "./AutoResizeTextarea";
|
||||||
|
import StopIcon from "../../icons/Stop";
|
||||||
|
|
||||||
interface ChatInputProps {
|
interface ChatInputProps {
|
||||||
onSend: (message: string) => void;
|
onSend: (message: string) => void;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
disabledChange: (disabled: boolean) => void;
|
curChatEnd: boolean;
|
||||||
changeMode: (isChatMode: boolean) => void;
|
disabledChange: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatInput({
|
export function ChatInput({
|
||||||
onSend,
|
onSend,
|
||||||
disabled,
|
disabled,
|
||||||
changeMode,
|
curChatEnd,
|
||||||
|
disabledChange,
|
||||||
}: ChatInputProps) {
|
}: ChatInputProps) {
|
||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
@@ -58,7 +59,7 @@ export function ChatInput({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className="w-full rounded-xl overflow-hidden">
|
<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">
|
<div className="bg-inputbox_bg_light dark:bg-inputbox_bg_dark bg-cover rounded-xl border border-[#E6E6E6] dark:border-[#272626]">
|
||||||
{/* Search Bar */}
|
{/* Search Bar */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="p-[13px] flex items-center bg-white dark:bg-[#202126] rounded-xl transition-all">
|
<div className="p-[13px] flex items-center bg-white dark:bg-[#202126] rounded-xl transition-all">
|
||||||
@@ -72,14 +73,29 @@ export function ChatInput({
|
|||||||
<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]" />
|
<Mic className="w-4 h-4 text-[#999] dark:text-[#999]" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
{curChatEnd ? (
|
||||||
className={`ml-1 p-1 ${
|
<button
|
||||||
input ? "bg-[#0072FF]" : "bg-[#E4E5F0]"
|
className={`ml-1 p-1 ${
|
||||||
} rounded-full transition-colors`}
|
input ? "bg-[#0072FF]" : "bg-[#E4E5F0]"
|
||||||
onClick={(e) => handleSubmit(e as unknown as FormEvent)}
|
} rounded-full transition-colors`}
|
||||||
>
|
onClick={(e) => handleSubmit(e as unknown as FormEvent)}
|
||||||
<Send className="w-4 h-4 text-white hover:text-[#999]" />
|
>
|
||||||
</button>
|
<Send className="w-4 h-4 text-white hover:text-[#999]" />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
{!curChatEnd ? (
|
||||||
|
<button
|
||||||
|
className={`ml-1 px-1 bg-[#0072FF] rounded-full transition-colors`}
|
||||||
|
type="submit"
|
||||||
|
onClick={() => disabledChange()}
|
||||||
|
>
|
||||||
|
<StopIcon
|
||||||
|
size={16}
|
||||||
|
className="w-4 h-4 text-white"
|
||||||
|
aria-label="Stop message"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -98,15 +114,6 @@ export function ChatInput({
|
|||||||
Upload
|
Upload
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Switch */}
|
|
||||||
<ChatSwitch
|
|
||||||
isChatMode={true}
|
|
||||||
onChange={(value) => {
|
|
||||||
changeMode(value);
|
|
||||||
setInput("");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { MessageSquare, Plus } from "lucide-react";
|
import { MessageSquare, Plus } from "lucide-react";
|
||||||
import type { Chat } from "./types";
|
import type { Chat } from "./types";
|
||||||
import { useTheme } from "../ThemeProvider";
|
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
chats: Chat[];
|
chats: Chat[];
|
||||||
activeChat: Chat;
|
activeChat: Chat;
|
||||||
@@ -18,25 +16,14 @@ export function Sidebar({
|
|||||||
onSelectChat,
|
onSelectChat,
|
||||||
className = "",
|
className = "",
|
||||||
}: SidebarProps) {
|
}: SidebarProps) {
|
||||||
const { theme } = useTheme();
|
|
||||||
const isDark = theme === "dark";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`h-full flex flex-col ${className}`}>
|
<div className={`h-full flex flex-col ${className}`}>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<button
|
<button
|
||||||
onClick={onNewChat}
|
onClick={onNewChat}
|
||||||
className={`w-full flex items-center gap-3 px-4 py-3 text-sm rounded-lg transition-all ${
|
className={`w-full flex items-center gap-3 px-4 py-3 text-sm rounded-lg transition-all border border-[#E6E6E6] dark:border-[#272626] text-gray-700 hover:bg-gray-50 active:bg-gray-100 shadow-sm dark:text-white dark:hover:bg-gray-600 dark:active:bg-gray-500`}
|
||||||
isDark
|
|
||||||
? " text-white hover:bg-gray-600 active:bg-gray-500"
|
|
||||||
: " text-gray-700 hover:bg-gray-50 active:bg-gray-100 shadow-sm"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<Plus
|
<Plus className={`h-4 w-4 text-indigo-600 dark:text-indigo-400`} />
|
||||||
className={`h-4 w-4 ${
|
|
||||||
isDark ? "text-indigo-400" : "text-indigo-600"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
New Chat
|
New Chat
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,30 +31,14 @@ export function Sidebar({
|
|||||||
{chats.map((chat) => (
|
{chats.map((chat) => (
|
||||||
<div
|
<div
|
||||||
key={chat._id}
|
key={chat._id}
|
||||||
className={`group relative rounded-lg transition-all ${
|
className={`group relative rounded-lg transition-all hover:border border-[#E6E6E6] dark:border-[#272626] text-gray-900 shadow-sm dark:text-white`}
|
||||||
activeChat._id === chat._id
|
|
||||||
? isDark
|
|
||||||
? " text-white"
|
|
||||||
: " text-gray-900 shadow-sm"
|
|
||||||
: isDark
|
|
||||||
? "text-gray-300 hover:bg-gray-700/30"
|
|
||||||
: "text-gray-600 hover:bg-white/10"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="w-full flex items-center gap-3 px-4 py-3 text-sm text-left"
|
className="w-full flex items-center gap-3 px-4 py-3 text-sm text-left"
|
||||||
onClick={() => onSelectChat(chat)}
|
onClick={() => onSelectChat(chat)}
|
||||||
>
|
>
|
||||||
<MessageSquare
|
<MessageSquare
|
||||||
className={`h-4 w-4 flex-shrink-0 ${
|
className={`h-4 w-4 flex-shrink-0 text-indigo-600 dark:text-indigo-400`}
|
||||||
activeChat._id === chat._id
|
|
||||||
? isDark
|
|
||||||
? "text-indigo-400"
|
|
||||||
: "text-indigo-600"
|
|
||||||
: isDark
|
|
||||||
? "text-gray-400"
|
|
||||||
: "text-gray-500"
|
|
||||||
}`}
|
|
||||||
/>
|
/>
|
||||||
<span className="truncate">{chat.title || chat._id}</span>
|
<span className="truncate">{chat.title || chat._id}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -85,9 +56,7 @@ export function Sidebar({
|
|||||||
)} */}
|
)} */}
|
||||||
{activeChat._id === chat._id && (
|
{activeChat._id === chat._id && (
|
||||||
<div
|
<div
|
||||||
className={`absolute left-0 top-1/2 -translate-y-1/2 w-1 h-6 rounded-full ${
|
className={`absolute left-0 top-1/2 -translate-y-1/2 w-1 h-6 rounded-full bg-indigo-600 dark:bg-indigo-400`}
|
||||||
isDark ? "bg-indigo-400" : "bg-indigo-600"
|
|
||||||
}`}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ export default function ChatAI({}: ChatAIProps) {
|
|||||||
const [websocketId, setWebsocketId] = useState("");
|
const [websocketId, setWebsocketId] = useState("");
|
||||||
const [curMessage, setCurMessage] = useState("");
|
const [curMessage, setCurMessage] = useState("");
|
||||||
const [curChatEnd, setCurChatEnd] = useState(true);
|
const [curChatEnd, setCurChatEnd] = useState(true);
|
||||||
|
|
||||||
|
const [curId, setCurId] = useState("");
|
||||||
|
|
||||||
|
const curChatEndRef = useRef(curChatEnd);
|
||||||
|
curChatEndRef.current = curChatEnd;
|
||||||
|
|
||||||
|
const curIdRef = useRef(curId);
|
||||||
|
curIdRef.current = curId;
|
||||||
const { messages, setMessages } = useWebSocket(
|
const { messages, setMessages } = useWebSocket(
|
||||||
"ws://localhost:2900/ws",
|
"ws://localhost:2900/ws",
|
||||||
(msg) => {
|
(msg) => {
|
||||||
@@ -31,22 +39,24 @@ export default function ChatAI({}: ChatAIProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (msg.includes("PRIVATE")) {
|
if (msg.includes("PRIVATE")) {
|
||||||
if (msg.includes("assistant finished output")) {
|
if (
|
||||||
|
msg.includes("assistant finished output") ||
|
||||||
|
curChatEndRef.current
|
||||||
|
) {
|
||||||
setCurChatEnd(true);
|
setCurChatEnd(true);
|
||||||
} else {
|
} else {
|
||||||
const cleanedData = msg.replace(/^PRIVATE /, "");
|
const cleanedData = msg.replace(/^PRIVATE /, "");
|
||||||
try {
|
try {
|
||||||
const chunkData = JSON.parse(cleanedData);
|
const chunkData = JSON.parse(cleanedData);
|
||||||
setCurMessage((prev) => prev + chunkData.message_chunk);
|
if (chunkData.reply_to_message === curIdRef.current) {
|
||||||
return chunkData.message_chunk;
|
setCurMessage((prev) => prev + chunkData.message_chunk);
|
||||||
|
return chunkData.message_chunk;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("JSON Parse error:", error);
|
console.error("JSON Parse error:", error);
|
||||||
}
|
}
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -153,6 +163,7 @@ export default function ChatAI({}: ChatAIProps) {
|
|||||||
body: JSON.stringify({ message: content }),
|
body: JSON.stringify({ message: content }),
|
||||||
});
|
});
|
||||||
console.log("_send", response, websocketId);
|
console.log("_send", response, websocketId);
|
||||||
|
setCurId(response.data[0]?._id);
|
||||||
const updatedChat: Chat = {
|
const updatedChat: Chat = {
|
||||||
...activeChat,
|
...activeChat,
|
||||||
messages: [...(activeChat?.messages || []), ...(response.data || [])],
|
messages: [...(activeChat?.messages || []), ...(response.data || [])],
|
||||||
@@ -229,14 +240,14 @@ export default function ChatAI({}: ChatAIProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen bg-chat_bg_light dark:bg-chat_bg_dark bg-cover">
|
<div className="h-screen">
|
||||||
<div className="h-[100%] flex">
|
<div className="h-[100%] flex">
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
{isSidebarOpen ? (
|
{isSidebarOpen ? (
|
||||||
<div
|
<div
|
||||||
className={`fixed inset-y-0 left-0 z-50 w-64 transform ${
|
className={`fixed inset-y-0 left-0 z-50 w-64 transform ${
|
||||||
isSidebarOpen ? "translate-x-0" : "-translate-x-full"
|
isSidebarOpen ? "translate-x-0" : "-translate-x-full"
|
||||||
} transition-transform duration-300 ease-in-out md:translate-x-0 md:static md:block`}
|
} transition-transform duration-300 ease-in-out md:translate-x-0 md:static md:block bg-gray-100 dark:bg-gray-800`}
|
||||||
>
|
>
|
||||||
{activeChat ? (
|
{activeChat ? (
|
||||||
<Sidebar
|
<Sidebar
|
||||||
@@ -251,7 +262,7 @@ export default function ChatAI({}: ChatAIProps) {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* Main content */}
|
{/* Main content */}
|
||||||
<div className={`flex-1 flex flex-col`}>
|
<div className={`flex-1 flex flex-col bg-white dark:bg-gray-900`}>
|
||||||
<header
|
<header
|
||||||
className={`flex items-center justify-between p-2 border-b border-gray-200 dark:border-gray-800`}
|
className={`flex items-center justify-between p-2 border-b border-gray-200 dark:border-gray-800`}
|
||||||
>
|
>
|
||||||
@@ -295,13 +306,6 @@ export default function ChatAI({}: ChatAIProps) {
|
|||||||
isTyping={!curChatEnd}
|
isTyping={!curChatEnd}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{isTyping && (
|
|
||||||
<div className="flex pt-0 pb-4 pl-20 gap-2 items-center text-gray-500 dark:text-gray-400">
|
|
||||||
<div className="w-2 h-2 rounded-full bg-current animate-bounce" />
|
|
||||||
<div className="w-2 h-2 rounded-full bg-current animate-bounce [animation-delay:0.2s]" />
|
|
||||||
<div className="w-2 h-2 rounded-full bg-current animate-bounce [animation-delay:0.4s]" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div ref={messagesEndRef} />
|
<div ref={messagesEndRef} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -310,11 +314,12 @@ export default function ChatAI({}: ChatAIProps) {
|
|||||||
<ChatInput
|
<ChatInput
|
||||||
onSend={handleSendMessage}
|
onSend={handleSendMessage}
|
||||||
disabled={isTyping}
|
disabled={isTyping}
|
||||||
disabledChange={(value) => {
|
curChatEnd={curChatEnd}
|
||||||
|
disabledChange={() => {
|
||||||
cancelChat();
|
cancelChat();
|
||||||
setIsTyping(value);
|
setCurChatEnd(true);
|
||||||
|
setIsTyping(false);
|
||||||
}}
|
}}
|
||||||
changeMode={() => {}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ function DropdownList({ selected, suggests }: DropdownListProps) {
|
|||||||
const [selectedItem, setSelectedItem] = useState<number | null>(null);
|
const [selectedItem, setSelectedItem] = useState<number | null>(null);
|
||||||
const [showIndex, setShowIndex] = useState<boolean>(false);
|
const [showIndex, setShowIndex] = useState<boolean>(false);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||||
|
|
||||||
const handleOpenURL = async (url: string) => {
|
const handleOpenURL = async (url: string) => {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
@@ -103,13 +103,16 @@ function DropdownList({ selected, suggests }: DropdownListProps) {
|
|||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className="h-[500px] w-full p-2 pb-10 flex flex-col rounded-xl overflow-y-auto overflow-hidden focus:outline-none"
|
className="max-h-[458px] w-full p-2 flex flex-col rounded-xl overflow-y-auto overflow-hidden custom-scrollbar focus:outline-none"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
|
<div className="p-2 text-xs text-[#999] dark:text-[#666]">
|
||||||
|
Results
|
||||||
|
</div>
|
||||||
{suggests?.map((item, index) => {
|
{suggests?.map((item, index) => {
|
||||||
const isSelected = selectedItem === index;
|
const isSelected = selectedItem === index;
|
||||||
return (
|
return (
|
||||||
<button
|
<div
|
||||||
key={item._id}
|
key={item._id}
|
||||||
ref={(el) => (itemRefs.current[index] = el)}
|
ref={(el) => (itemRefs.current[index] = el)}
|
||||||
onMouseEnter={() => setSelectedItem(index)}
|
onMouseEnter={() => setSelectedItem(index)}
|
||||||
@@ -120,7 +123,7 @@ function DropdownList({ selected, suggests }: DropdownListProps) {
|
|||||||
selected(item);
|
selected(item);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`w-full h-10 px-2 text-sm flex items-center justify-between rounded-lg transition-colors ${
|
className={`w-full px-2 py-2.5 text-sm flex items-center justify-between rounded-lg transition-colors ${
|
||||||
isSelected
|
isSelected
|
||||||
? "bg-[rgba(0,0,0,0.1)] dark:bg-[rgba(255,255,255,0.1)]"
|
? "bg-[rgba(0,0,0,0.1)] dark:bg-[rgba(255,255,255,0.1)]"
|
||||||
: "hover:bg-[rgba(0,0,0,0.1)] dark:hover:bg-[rgba(255,255,255,0.1)]"
|
: "hover:bg-[rgba(0,0,0,0.1)] dark:hover:bg-[rgba(255,255,255,0.1)]"
|
||||||
@@ -144,7 +147,7 @@ function DropdownList({ selected, suggests }: DropdownListProps) {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
import {
|
import { Library, Mic, Send, Plus, AudioLines, Image } from "lucide-react";
|
||||||
Library,
|
|
||||||
Mic,
|
|
||||||
Send,
|
|
||||||
Plus,
|
|
||||||
AudioLines,
|
|
||||||
Image,
|
|
||||||
CircleStop,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useRef, type KeyboardEvent } from "react";
|
import { useRef, type KeyboardEvent } from "react";
|
||||||
|
|
||||||
import ChatSwitch from "../SearchChat/ChatSwitch";
|
import ChatSwitch from "../SearchChat/ChatSwitch";
|
||||||
import AutoResizeTextarea from "./AutoResizeTextarea";
|
import AutoResizeTextarea from "./AutoResizeTextarea";
|
||||||
import { useChatStore } from "../../stores/chatStore";
|
import { useChatStore } from "../../stores/chatStore";
|
||||||
|
import StopIcon from "../../icons/Stop";
|
||||||
|
|
||||||
interface ChatInputProps {
|
interface ChatInputProps {
|
||||||
onSend: (message: string) => void;
|
onSend: (message: string) => void;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
disabledChange: (disabled: boolean) => void;
|
disabledChange: () => void;
|
||||||
changeMode: (isChatMode: boolean) => void;
|
changeMode: (isChatMode: boolean) => void;
|
||||||
isChatMode: boolean;
|
isChatMode: boolean;
|
||||||
inputValue: string;
|
inputValue: string;
|
||||||
@@ -30,14 +23,14 @@ export default function ChatInput({
|
|||||||
isChatMode,
|
isChatMode,
|
||||||
inputValue,
|
inputValue,
|
||||||
changeInput,
|
changeInput,
|
||||||
|
disabledChange,
|
||||||
}: ChatInputProps) {
|
}: ChatInputProps) {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const { stopChat, setStopChat } = useChatStore();
|
const { curChatEnd } = useChatStore();
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (inputValue.trim() && !disabled) {
|
if (inputValue.trim() && !disabled) {
|
||||||
setStopChat(false);
|
|
||||||
onSend(inputValue.trim());
|
onSend(inputValue.trim());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -90,24 +83,28 @@ export default function ChatInput({
|
|||||||
<Mic className="w-4 h-4 text-[#999] dark:text-[#999]" />
|
<Mic className="w-4 h-4 text-[#999] dark:text-[#999]" />
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
{isChatMode && stopChat ? (
|
{isChatMode && curChatEnd ? (
|
||||||
<button
|
<button
|
||||||
className={`ml-1 p-1 ${
|
className={`ml-1 p-1 ${
|
||||||
inputValue ? "bg-[#0072FF]" : "bg-[#E4E5F0]"
|
inputValue ? "bg-[#0072FF]" : "bg-[#E4E5F0] dark:bg-[#545454]"
|
||||||
} rounded-full transition-colors`}
|
} rounded-full transition-colors`}
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => onSend(inputValue.trim())}
|
onClick={() => onSend(inputValue.trim())}
|
||||||
>
|
>
|
||||||
<Send className="w-4 h-4 text-white hover:text-[#999]" />
|
<Send className="w-4 h-4 text-white" />
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
{isChatMode && !stopChat ? (
|
{isChatMode && !curChatEnd ? (
|
||||||
<button
|
<button
|
||||||
className={`ml-1 p-1 bg-[#0072FF] rounded-full transition-colors`}
|
className={`ml-1 px-1 bg-[#0072FF] rounded-full transition-colors`}
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => setStopChat(true)}
|
onClick={() => disabledChange()}
|
||||||
>
|
>
|
||||||
<CircleStop className="w-4 h-4 text-white hover:text-[#999]" />
|
<StopIcon
|
||||||
|
size={16}
|
||||||
|
className="w-4 h-4 text-white"
|
||||||
|
aria-label="Stop message"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@@ -147,7 +144,7 @@ export default function ChatInput({
|
|||||||
<ChatSwitch
|
<ChatSwitch
|
||||||
isChatMode={isChatMode}
|
isChatMode={isChatMode}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setStopChat(!value);
|
value && disabledChange();
|
||||||
changeMode(value);
|
changeMode(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function Search({ isTransitioned, isChatMode, input }: SearchProps) {
|
|||||||
// } else {
|
// } else {
|
||||||
// await getCurrentWebviewWindow().setSize(new LogicalSize(680, 90));
|
// await getCurrentWebviewWindow().setSize(new LogicalSize(680, 90));
|
||||||
// }
|
// }
|
||||||
setSuggests(data);
|
setSuggests([...data, ...data, ...data, ...data]);
|
||||||
setIsSearchComplete(true);
|
setIsSearchComplete(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch user data:", error);
|
console.error("Failed to fetch user data:", error);
|
||||||
@@ -56,23 +56,20 @@ function Search({ isTransitioned, isChatMode, input }: SearchProps) {
|
|||||||
!isChatMode && debouncedSearch();
|
!isChatMode && debouncedSearch();
|
||||||
}, [input]);
|
}, [input]);
|
||||||
|
|
||||||
console.log(11111, isChatMode);
|
|
||||||
if (isChatMode || suggests.length === 0) return null;
|
if (isChatMode || suggests.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`rounded-xl overflow-hidden bg-search_bg_light dark:bg-search_bg_dark bg-cover border border-[#E6E6E6] dark:border-[#272626] absolute w-full transition-opacity duration-500 ${
|
className={`shadow-window-custom rounded-xl overflow-hidden bg-search_bg_light dark:bg-search_bg_dark bg-cover border border-[#E6E6E6] dark:border-[#272626] absolute w-full transition-opacity duration-500 ${
|
||||||
isTransitioned ? "opacity-0 pointer-events-none" : "opacity-100"
|
isTransitioned ? "opacity-0 pointer-events-none" : "opacity-100"
|
||||||
} bottom-0 h-[500px]`}
|
} top-[96px]`}
|
||||||
style={{
|
style={{
|
||||||
backgroundPosition: "-1px 0",
|
backgroundPosition: "-1px 0",
|
||||||
backgroundSize: "101% 100%",
|
backgroundSize: "101% 100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isChatMode ? null : (
|
{isChatMode ? null : (
|
||||||
<div
|
<div className={`max-h-[498px] pb-10 w-full relative`}>
|
||||||
className={`min-h-full w-full flex items-start justify-center overflow-hidden relative`}
|
|
||||||
>
|
|
||||||
{/* Search Results Panel */}
|
{/* Search Results Panel */}
|
||||||
{suggests.length > 0 ? (
|
{suggests.length > 0 ? (
|
||||||
<DropdownList
|
<DropdownList
|
||||||
|
|||||||
@@ -1,11 +1,45 @@
|
|||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
// import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
// import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||||
// import { LogicalSize } from "@tauri-apps/api/dpi";
|
// import { LogicalSize } from "@tauri-apps/api/dpi";
|
||||||
|
// import { Window, LogicalPosition } from "@tauri-apps/api/window";
|
||||||
|
// import { currentMonitor } from "@tauri-apps/plugin-window";
|
||||||
|
|
||||||
import InputBox from "./InputBox";
|
import InputBox from "./InputBox";
|
||||||
import Search from "./Search";
|
import Search from "./Search";
|
||||||
import ChatAI, { ChatAIRef } from "../ChatAI/Chat";
|
import ChatAI, { ChatAIRef } from "../ChatAI/Chat";
|
||||||
|
|
||||||
|
// const appWindow = new Window("main");
|
||||||
|
|
||||||
|
// async function preventOutOfBounds() {
|
||||||
|
// const monitor = await currentMonitor();
|
||||||
|
|
||||||
|
// if (monitor) {
|
||||||
|
// const screenBounds = {
|
||||||
|
// x: monitor.position.x,
|
||||||
|
// y: monitor.position.y,
|
||||||
|
// width: monitor.size.width,
|
||||||
|
// height: monitor.size.height,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const windowPosition = await appWindow.outerPosition();
|
||||||
|
// const windowSize = await appWindow.outerSize();
|
||||||
|
|
||||||
|
// let newX = windowPosition.x;
|
||||||
|
// let newY = windowPosition.y;
|
||||||
|
|
||||||
|
// if (newX < screenBounds.x) newX = screenBounds.x;
|
||||||
|
// if (newY < screenBounds.y) newY = screenBounds.y;
|
||||||
|
// if (newX + windowSize.width > screenBounds.x + screenBounds.width)
|
||||||
|
// newX = screenBounds.x + screenBounds.width - windowSize.width;
|
||||||
|
// if (newY + windowSize.height > screenBounds.y + screenBounds.height)
|
||||||
|
// newY = screenBounds.y + screenBounds.height - windowSize.height;
|
||||||
|
|
||||||
|
// if (newX !== windowPosition.x || newY !== windowPosition.y) {
|
||||||
|
// await appWindow.setPosition(new LogicalPosition(newX, newY));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
export default function SearchChat() {
|
export default function SearchChat() {
|
||||||
const chatAIRef = useRef<ChatAIRef>(null);
|
const chatAIRef = useRef<ChatAIRef>(null);
|
||||||
|
|
||||||
@@ -13,6 +47,16 @@ export default function SearchChat() {
|
|||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
const [isTransitioned, setIsTransitioned] = useState(false);
|
const [isTransitioned, setIsTransitioned] = useState(false);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const unlisten = appWindow.listen("tauri://move", () => {
|
||||||
|
// preventOutOfBounds();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return () => {
|
||||||
|
// unlisten.then((off: any) => off());
|
||||||
|
// };
|
||||||
|
// }, []);
|
||||||
|
|
||||||
async function setWindowSize() {
|
async function setWindowSize() {
|
||||||
if (isTransitioned) {
|
if (isTransitioned) {
|
||||||
// await getCurrentWebviewWindow()?.setSize(new LogicalSize(680, 600));
|
// await getCurrentWebviewWindow()?.setSize(new LogicalSize(680, 600));
|
||||||
@@ -42,9 +86,8 @@ export default function SearchChat() {
|
|||||||
chatAIRef.current?.init();
|
chatAIRef.current?.init();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const cancelChat = () => {};
|
const cancelChat = () => {
|
||||||
const setIsTyping = (value: any) => {
|
chatAIRef.current?.cancelChat();
|
||||||
console.log(value);
|
|
||||||
};
|
};
|
||||||
const isTyping = false;
|
const isTyping = false;
|
||||||
|
|
||||||
@@ -54,7 +97,7 @@ export default function SearchChat() {
|
|||||||
className={`w-full h-full min-h-screen mx-auto overflow-hidden relative`}
|
className={`w-full h-full min-h-screen mx-auto overflow-hidden relative`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`rounded-xl overflow-hidden bg-inputbox_bg_light dark:bg-inputbox_bg_dark bg-cover border border-[#E6E6E6] dark:border-[#272626] absolute z-100 w-full flex items-center justify-center duration-500 ${
|
className={`shadow-window-custom rounded-xl overflow-hidden bg-inputbox_bg_light dark:bg-inputbox_bg_dark bg-cover border border-[#E6E6E6] dark:border-[#272626] absolute z-100 w-full flex items-center justify-center duration-500 ${
|
||||||
isTransitioned ? "top-[506px] h-[90px]" : "top-0 h-[90px]"
|
isTransitioned ? "top-[506px] h-[90px]" : "top-0 h-[90px]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -63,9 +106,8 @@ export default function SearchChat() {
|
|||||||
inputValue={input}
|
inputValue={input}
|
||||||
onSend={handleSendMessage}
|
onSend={handleSendMessage}
|
||||||
disabled={isTyping}
|
disabled={isTyping}
|
||||||
disabledChange={(value) => {
|
disabledChange={() => {
|
||||||
cancelChat();
|
cancelChat();
|
||||||
setIsTyping(value);
|
|
||||||
}}
|
}}
|
||||||
changeMode={changeMode}
|
changeMode={changeMode}
|
||||||
changeInput={changeInput}
|
changeInput={changeInput}
|
||||||
@@ -73,7 +115,7 @@ export default function SearchChat() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`rounded-xl overflow-hidden bg-chat_bg_light dark:bg-chat_bg_dark bg-cover border border-[#E6E6E6] dark:border-[#272626] absolute w-full transition-all duration-500 ${
|
className={`shadow-window-custom rounded-xl overflow-hidden bg-chat_bg_light dark:bg-chat_bg_dark bg-cover border border-[#E6E6E6] dark:border-[#272626] absolute w-full transition-all duration-500 ${
|
||||||
isTransitioned
|
isTransitioned
|
||||||
? "top-0 opacity-100 pointer-events-auto"
|
? "top-0 opacity-100 pointer-events-auto"
|
||||||
: "-top-[506px] opacity-0 pointer-events-none"
|
: "-top-[506px] opacity-0 pointer-events-none"
|
||||||
|
|||||||
@@ -24,16 +24,16 @@ export default function SVGWrap({
|
|||||||
title={title}
|
title={title}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"inline-flex items-center justify-center rounded-sm p-[2px] text-slate-500 dark:text-slate-500 transition-all",
|
"inline-flex items-center justify-center rounded-sm p-[2px] transition-all",
|
||||||
{
|
{
|
||||||
"cursor-pointer hover:bg-slate-300/50 hover:dark:bg-white/10": action,
|
"cursor-pointer": action,
|
||||||
},
|
},
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
style={{ widows: "100%", height: "100%" }}
|
style={{ width: "100%", height: "100%" }}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
28
src/icons/Stop.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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>
|
||||||
|
</SVGWrap>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -11,12 +11,12 @@ export default {
|
|||||||
separator: "rgb(var(--color-separator) / <alpha-value>)",
|
separator: "rgb(var(--color-separator) / <alpha-value>)",
|
||||||
},
|
},
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
chat_bg_light: "url('/public/chat_bg_light.png')",
|
chat_bg_light: "url('./assets/chat_bg_light.png')",
|
||||||
chat_bg_dark: "url('/public/chat_bg_dark.png')",
|
chat_bg_dark: "url('./assets/chat_bg_dark.png')",
|
||||||
search_bg_light: "url('/public/search_bg_light.png')",
|
search_bg_light: "url('./assets/search_bg_light.png')",
|
||||||
search_bg_dark: "url('/public/search_bg_dark.png')",
|
search_bg_dark: "url('./assets/search_bg_dark.png')",
|
||||||
inputbox_bg_light: "url('/public/inputbox_bg_light.png')",
|
inputbox_bg_light: "url('./assets/inputbox_bg_light.png')",
|
||||||
inputbox_bg_dark: "url('/public/inputbox_bg_dark.png')",
|
inputbox_bg_dark: "url('./assets/inputbox_bg_dark.png')",
|
||||||
},
|
},
|
||||||
textColor: {
|
textColor: {
|
||||||
primary: "rgb(var(--color-foreground) / <alpha-value>)",
|
primary: "rgb(var(--color-foreground) / <alpha-value>)",
|
||||||
@@ -31,8 +31,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
boxShadow: {
|
boxShadow: {
|
||||||
"window-custom":
|
"window-custom": "0px 16px 32px 0px rgba(0,0,0,0.3)",
|
||||||
"0 1px 5px 0 rgba(0, 0, 0, 0.15), 0 1px 5px -1px rgba(0, 0, 0, 0.1), 0 2px 5px rgba(0, 0, 0, 0.1)",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||