mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +01:00
feat: history added search and action menus (#322)
* feat: history added search and action menus * refactor: refinement of the dark theme * feat: add renamed input box style * feat: internalization * refactor: optimize the bright theme style * refactor: change dark theme style * feat: added api for deleting and modifying conversations * feat: supported search * feat: support for modifying the title * feat: support for deleting sessions * refactor: remove popup internationalization
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
"@wavesurfer/react": "^1.0.9",
|
||||
"ahooks": "^3.8.4",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.4.7",
|
||||
"filesize": "^10.1.6",
|
||||
"i18next": "^23.16.8",
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -59,6 +59,9 @@ importers:
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
dotenv:
|
||||
specifier: ^16.4.7
|
||||
version: 16.4.7
|
||||
|
||||
@@ -12,6 +12,7 @@ pub async fn chat_history<R: Runtime>(
|
||||
server_id: String,
|
||||
from: u32,
|
||||
size: u32,
|
||||
query: Option<String>,
|
||||
) -> Result<String, String> {
|
||||
let mut query_params: HashMap<String, Value> = HashMap::new();
|
||||
if from > 0 {
|
||||
@@ -21,6 +22,10 @@ pub async fn chat_history<R: Runtime>(
|
||||
query_params.insert("size".to_string(), size.into());
|
||||
}
|
||||
|
||||
if let Some(query) = query {
|
||||
query_params.insert("query".to_string(), query.into());
|
||||
}
|
||||
|
||||
let response = HttpClient::get(&server_id, "/chat/_history", Some(query_params))
|
||||
.await
|
||||
.map_err(|e| format!("Error get sessions: {}", e))?;
|
||||
@@ -135,9 +140,10 @@ pub async fn new_chat<R: Runtime>(
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("WEBSOCKET-SESSION-ID".to_string(), websocket_id.into());
|
||||
|
||||
let response = HttpClient::advanced_post(&server_id, "/chat/_new", Some(headers), query_params, body)
|
||||
.await
|
||||
.map_err(|e| format!("Error sending message: {}", e))?;
|
||||
let response =
|
||||
HttpClient::advanced_post(&server_id, "/chat/_new", Some(headers), query_params, body)
|
||||
.await
|
||||
.map_err(|e| format!("Error sending message: {}", e))?;
|
||||
|
||||
if response.status().as_u16() < 200 || response.status().as_u16() >= 400 {
|
||||
return Err("Failed to send message".to_string());
|
||||
@@ -174,10 +180,58 @@ pub async fn send_message<R: Runtime>(
|
||||
headers.insert("WEBSOCKET-SESSION-ID".to_string(), websocket_id.into());
|
||||
|
||||
let body = reqwest::Body::from(serde_json::to_string(&msg).unwrap());
|
||||
let response =
|
||||
HttpClient::advanced_post(&server_id, path.as_str(), Some(headers), query_params, Some(body))
|
||||
.await
|
||||
.map_err(|e| format!("Error cancel session: {}", e))?;
|
||||
let response = HttpClient::advanced_post(
|
||||
&server_id,
|
||||
path.as_str(),
|
||||
Some(headers),
|
||||
query_params,
|
||||
Some(body),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Error cancel session: {}", e))?;
|
||||
|
||||
handle_raw_response(response).await?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_session_chat(server_id: String, session_id: String) -> Result<bool, String> {
|
||||
let response =
|
||||
HttpClient::delete(&server_id, &format!("/chat/{}", session_id), None, None).await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(format!("Delete failed with status: {}", response.status()))
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_session_chat(
|
||||
server_id: String,
|
||||
session_id: String,
|
||||
title: Option<String>,
|
||||
context: Option<HashMap<String, Value>>,
|
||||
) -> Result<bool, String> {
|
||||
let mut body = HashMap::new();
|
||||
if let Some(title) = title {
|
||||
body.insert("title".to_string(), Value::String(title));
|
||||
}
|
||||
if let Some(context) = context {
|
||||
body.insert(
|
||||
"context".to_string(),
|
||||
Value::Object(context.into_iter().collect()),
|
||||
);
|
||||
}
|
||||
|
||||
let response = HttpClient::put(
|
||||
&server_id,
|
||||
&format!("/chat/{}", session_id),
|
||||
None,
|
||||
None,
|
||||
Some(reqwest::Body::from(serde_json::to_string(&body).unwrap())),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Error updating session: {}", e))?;
|
||||
|
||||
Ok(response.status().is_success())
|
||||
}
|
||||
|
||||
@@ -120,6 +120,8 @@ pub fn run() {
|
||||
assistant::open_session_chat,
|
||||
assistant::close_session_chat,
|
||||
assistant::cancel_session_chat,
|
||||
assistant::delete_session_chat,
|
||||
assistant::update_session_chat,
|
||||
// server::get_coco_server_datasources,
|
||||
// server::get_coco_server_connectors,
|
||||
server::websocket::connect_to_server,
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
import { ServerTokenResponse, Server, Connector, DataSource, GetResponse } from "@/types/commands"
|
||||
import {
|
||||
ServerTokenResponse,
|
||||
Server,
|
||||
Connector,
|
||||
DataSource,
|
||||
GetResponse,
|
||||
} from "@/types/commands";
|
||||
|
||||
export function get_server_token(id: string): Promise<ServerTokenResponse> {
|
||||
return invoke(`get_server_token`, { id });
|
||||
@@ -70,15 +76,18 @@ export function chat_history({
|
||||
serverId,
|
||||
from = 0,
|
||||
size = 20,
|
||||
query = "",
|
||||
}: {
|
||||
serverId: string;
|
||||
from?: number;
|
||||
size?: number;
|
||||
query?: string;
|
||||
}): Promise<string> {
|
||||
return invoke(`chat_history`, {
|
||||
serverId,
|
||||
from,
|
||||
size,
|
||||
query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -179,4 +188,19 @@ export function send_message({
|
||||
message,
|
||||
queryParams,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const delete_session_chat = (serverId: string, sessionId: string) => {
|
||||
return invoke<boolean>(`delete_session_chat`, { serverId, sessionId });
|
||||
};
|
||||
|
||||
export const update_session_chat = (payload: {
|
||||
serverId: string;
|
||||
sessionId: string;
|
||||
title?: string;
|
||||
context?: {
|
||||
attachments?: string[];
|
||||
};
|
||||
}): Promise<boolean> => {
|
||||
return invoke<boolean>("update_session_chat", payload);
|
||||
};
|
||||
|
||||
@@ -41,4 +41,4 @@ export const ChatSidebar: React.FC<ChatSidebarProps> = ({
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
274
src/components/Common/HistoryList/index.tsx
Normal file
274
src/components/Common/HistoryList/index.tsx
Normal file
@@ -0,0 +1,274 @@
|
||||
import { Chat } from "@/components/Assistant/types";
|
||||
import {
|
||||
Description,
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
DialogTitle,
|
||||
Input,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuItems,
|
||||
} from "@headlessui/react";
|
||||
import { debounce, groupBy, isNil } from "lodash-es";
|
||||
import { FC, useMemo, useState } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
|
||||
import clsx from "clsx";
|
||||
import { Ellipsis, Pencil, RefreshCcw, Search, Trash2 } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
dayjs.extend(isSameOrAfter);
|
||||
|
||||
interface HistoryListProps {
|
||||
list: Chat[];
|
||||
active?: Chat;
|
||||
onSearch: (keyword: string) => void;
|
||||
onRefresh: () => void;
|
||||
onSelect: (chat: Chat) => void;
|
||||
onRename: (chat: Chat, title: string) => void;
|
||||
onRemove: (chatId: string) => void;
|
||||
}
|
||||
|
||||
const HistoryList: FC<HistoryListProps> = (props) => {
|
||||
const { list, active, onSearch, onRefresh, onSelect, onRename, onRemove } =
|
||||
props;
|
||||
const { t } = useTranslation();
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const sortedList = useMemo(() => {
|
||||
if (isNil(list)) return {};
|
||||
|
||||
const now = dayjs();
|
||||
|
||||
return groupBy(list, (chat) => {
|
||||
const date = dayjs(chat._source?.updated);
|
||||
|
||||
if (date.isSame(now, "day")) {
|
||||
return "history_list.date.today";
|
||||
}
|
||||
|
||||
if (date.isSame(now.subtract(1, "day"), "day")) {
|
||||
return "history_list.date.yesterday";
|
||||
}
|
||||
|
||||
if (date.isSameOrAfter(now.subtract(7, "day"), "day")) {
|
||||
return "history_list.date.last7Days";
|
||||
}
|
||||
|
||||
if (date.isSameOrAfter(now.subtract(30, "day"), "day")) {
|
||||
return "history_list.date.last30Days";
|
||||
}
|
||||
|
||||
return date.format("YYYY-MM");
|
||||
});
|
||||
}, [list]);
|
||||
|
||||
const menuItems = [
|
||||
// {
|
||||
// label: "history_list.menu.share",
|
||||
// icon: Share2,
|
||||
// onClick: () => {},
|
||||
// },
|
||||
{
|
||||
label: "history_list.menu.rename",
|
||||
icon: Pencil,
|
||||
onClick: () => {
|
||||
setIsEdit(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "history_list.menu.delete",
|
||||
icon: Trash2,
|
||||
iconColor: "#FF2018",
|
||||
onClick: () => {
|
||||
setIsOpen(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const debouncedSearch = useMemo(() => {
|
||||
return debounce((value: string) => onSearch(value), 500);
|
||||
}, [onSearch]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"h-full overflow-auto px-3 py-2 text-sm bg-[#F3F4F6] dark:bg-[#1F2937]"
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-1 children:h-8">
|
||||
<div className="flex-1 flex items-center gap-2 px-2 rounded-lg border transition border-[#E6E6E6] bg-[#F8F9FA] dark:bg-[#2B3444] dark:border-[#343D4D] focus-within:border-[#0061FF]">
|
||||
<Search className="size-4 text-[#6B7280]" />
|
||||
|
||||
<Input
|
||||
className="w-full bg-transparent outline-none"
|
||||
placeholder={t("history_list.search.placeholder")}
|
||||
onChange={(event) => {
|
||||
debouncedSearch(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="size-8 flex items-center justify-center rounded-lg border text-[#0072FF] border-[#E6E6E6] bg-[#F3F4F6] dark:border-[#343D4D] dark:bg-[#1F2937] hover:bg-[#F8F9FA] dark:hover:bg-[#353F4D] cursor-pointer transition"
|
||||
onClick={onRefresh}
|
||||
>
|
||||
<RefreshCcw className="size-4" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
{Object.entries(sortedList).map(([label, list]) => {
|
||||
return (
|
||||
<div key={label}>
|
||||
<span className="text-xs text-[#999] px-3">{t(label)}</span>
|
||||
|
||||
<ul>
|
||||
{list.map((item) => {
|
||||
const { _id, _source } = item;
|
||||
|
||||
const isActive = _id === active?._id;
|
||||
const title = _source?.title ?? _id;
|
||||
|
||||
return (
|
||||
<li
|
||||
key={_id}
|
||||
className={clsx(
|
||||
"flex items-center mt-1 h-10 rounded-lg cursor-pointer hover:bg-[#F8F9FA] dark:hover:bg-[#353F4D] transition",
|
||||
{
|
||||
"!bg-[#E5E7EB] dark:!bg-[#2B3444]": isActive,
|
||||
}
|
||||
)}
|
||||
onClick={() => {
|
||||
if (!isActive) {
|
||||
setIsEdit(false);
|
||||
}
|
||||
|
||||
onSelect(item);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={clsx("w-1 h-6 rounded-sm bg-[#0072FF]", {
|
||||
"opacity-0": _id !== active?._id,
|
||||
})}
|
||||
/>
|
||||
|
||||
<div className="flex-1 flex items-center justify-between gap-2 px-2 overflow-hidden">
|
||||
{isEdit && isActive ? (
|
||||
<Input
|
||||
defaultValue={title}
|
||||
className="flex-1 -mx-px outline-none bg-transparent border border-[#0061FF] rounded-[4px]"
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== "Enter") return;
|
||||
|
||||
onRename(item, event.currentTarget.value);
|
||||
|
||||
setIsEdit(false);
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
onRename(item, event.target.value);
|
||||
|
||||
setIsEdit(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<span className="truncate">{title}</span>
|
||||
)}
|
||||
|
||||
<Menu>
|
||||
{isActive && !isEdit && (
|
||||
<MenuButton>
|
||||
<Ellipsis className="size-4 text-[#979797]" />
|
||||
</MenuButton>
|
||||
)}
|
||||
|
||||
<MenuItems
|
||||
anchor="bottom"
|
||||
className="flex flex-col rounded-lg shadow-md z-100 bg-white dark:bg-[#202126] p-1 border border-black/2 dark:border-white/10"
|
||||
>
|
||||
{menuItems.map((menuItem) => {
|
||||
const {
|
||||
label,
|
||||
icon: Icon,
|
||||
iconColor,
|
||||
onClick,
|
||||
} = menuItem;
|
||||
|
||||
return (
|
||||
<MenuItem key={label}>
|
||||
<button
|
||||
className="flex items-center gap-2 px-3 py-2 text-sm rounded-md hover:bg-[#EDEDED] dark:hover:bg-[#2B2C31] transition"
|
||||
onClick={() => onClick()}
|
||||
>
|
||||
<Icon
|
||||
className="size-4"
|
||||
style={{
|
||||
color: iconColor,
|
||||
}}
|
||||
/>
|
||||
|
||||
<span>{t(label)}</span>
|
||||
</button>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
className="relative z-1000"
|
||||
>
|
||||
<div className="fixed inset-0 flex items-center justify-center w-screen">
|
||||
<DialogPanel className="flex flex-col justify-between w-[360px] h-[160px] p-3 border border-[#e6e6e6] bg-white dark:bg-[#202126] dark:border-white/10 shadow-xl rounded-lg">
|
||||
<div className="flex flex-col gap-3">
|
||||
<DialogTitle className="text-base font-bold text-[#333]">
|
||||
{t("history_list.delete_modal.title")}
|
||||
</DialogTitle>
|
||||
<Description className="text-sm text-[#333]">
|
||||
{t("history_list.delete_modal.description", {
|
||||
replace: [active?._source?.title || active?._id],
|
||||
})}
|
||||
</Description>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 self-end">
|
||||
<button
|
||||
className="h-8 px-4 text-sm text-[#666666] bg-[#F8F9FA] dark:text-white dark:bg-[#202126] border border-[#E6E6E6] dark:border-white/10 rounded-lg"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
{t("history_list.delete_modal.button.cancel")}
|
||||
</button>
|
||||
<button
|
||||
className="h-8 px-4 text-sm text-white bg-[#EF4444] rounded-lg"
|
||||
onClick={() => {
|
||||
if (!active?._id) return;
|
||||
|
||||
onRemove(active._id);
|
||||
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
{t("history_list.delete_modal.button.delete")}
|
||||
</button>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HistoryList;
|
||||
@@ -294,5 +294,29 @@
|
||||
},
|
||||
"error": {
|
||||
"message": "Sorry, there is an error in your Coco App. Please contact the administrator."
|
||||
},
|
||||
"history_list": {
|
||||
"search": {
|
||||
"placeholder": "Search"
|
||||
},
|
||||
"date": {
|
||||
"today": "Today",
|
||||
"yesterday": "Yesterday",
|
||||
"last7Days": "Last 7 Days",
|
||||
"last30Days": "Last 30 Days"
|
||||
},
|
||||
"menu": {
|
||||
"share": "Share",
|
||||
"rename": "Rename",
|
||||
"delete": "Delete"
|
||||
},
|
||||
"delete_modal": {
|
||||
"title": "Delete chat?",
|
||||
"description": "This will delete \"{{0}}\"",
|
||||
"button": {
|
||||
"delete": "Delete",
|
||||
"cancel": "Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,5 +293,29 @@
|
||||
},
|
||||
"error": {
|
||||
"message": "抱歉,Coco 应用出现了错误。请联系管理员。"
|
||||
},
|
||||
"history_list": {
|
||||
"search": {
|
||||
"placeholder": "搜索"
|
||||
},
|
||||
"date": {
|
||||
"today": "今天",
|
||||
"yesterday": "昨天",
|
||||
"last7Days": "最近 7 天",
|
||||
"last30Days": "最近 30 天"
|
||||
},
|
||||
"menu": {
|
||||
"share": "分享",
|
||||
"rename": "重命名",
|
||||
"delete": "删除"
|
||||
},
|
||||
"delete_modal": {
|
||||
"title": "删除聊天?",
|
||||
"description": "这将删除“{{0}}”",
|
||||
"button": {
|
||||
"delete": "删除",
|
||||
"cancel": "取消"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { metadata, icon } from "tauri-plugin-fs-pro-api";
|
||||
|
||||
import ChatAI, { ChatAIRef } from "@/components/Assistant/Chat";
|
||||
import { Sidebar } from "@/components/Assistant/Sidebar";
|
||||
import type { Chat } from "@/components/Assistant/types";
|
||||
import type { Chat as typeChat } from "@/components/Assistant/types";
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
import InputBox from "@/components/Search/InputBox";
|
||||
import {
|
||||
@@ -25,8 +24,11 @@ import {
|
||||
close_session_chat,
|
||||
open_session_chat,
|
||||
get_datasources_by_server,
|
||||
delete_session_chat,
|
||||
update_session_chat,
|
||||
} from "@/commands";
|
||||
import { DataSource } from "@/types/commands"
|
||||
import { DataSource } from "@/types/commands";
|
||||
import HistoryList from "@/components/Common/HistoryList";
|
||||
|
||||
interface ChatProps {}
|
||||
|
||||
@@ -35,8 +37,8 @@ export default function Chat({}: ChatProps) {
|
||||
|
||||
const chatAIRef = useRef<ChatAIRef>(null);
|
||||
|
||||
const [chats, setChats] = useState<Chat[]>([]);
|
||||
const [activeChat, setActiveChat] = useState<Chat>();
|
||||
const [chats, setChats] = useState<typeChat[]>([]);
|
||||
const [activeChat, setActiveChat] = useState<typeChat>();
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
||||
const isTyping = false;
|
||||
|
||||
@@ -44,12 +46,13 @@ export default function Chat({}: ChatProps) {
|
||||
|
||||
const [isSearchActive, setIsSearchActive] = useState(false);
|
||||
const [isDeepThinkActive, setIsDeepThinkActive] = useState(false);
|
||||
const [keyword, setKeyword] = useState("");
|
||||
|
||||
const isChatPage = true;
|
||||
|
||||
useEffect(() => {
|
||||
getChatHistory();
|
||||
}, []);
|
||||
}, [keyword]);
|
||||
|
||||
const getChatHistory = async () => {
|
||||
try {
|
||||
@@ -57,6 +60,7 @@ export default function Chat({}: ChatProps) {
|
||||
serverId: currentService?.id,
|
||||
from: 0,
|
||||
size: 20,
|
||||
query: keyword,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
console.log("_history", response);
|
||||
@@ -72,24 +76,24 @@ export default function Chat({}: ChatProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const deleteChat = (chatId: string) => {
|
||||
setChats((prev) => prev.filter((chat) => chat._id !== chatId));
|
||||
if (activeChat?._id === chatId) {
|
||||
const remainingChats = chats.filter((chat) => chat._id !== chatId);
|
||||
if (remainingChats.length > 0) {
|
||||
setActiveChat(remainingChats[0]);
|
||||
} else {
|
||||
chatAIRef.current?.init("");
|
||||
}
|
||||
}
|
||||
};
|
||||
// const deleteChat = (chatId: string) => {
|
||||
// setChats((prev) => prev.filter((chat) => chat._id !== chatId));
|
||||
// if (activeChat?._id === chatId) {
|
||||
// const remainingChats = chats.filter((chat) => chat._id !== chatId);
|
||||
// if (remainingChats.length > 0) {
|
||||
// setActiveChat(remainingChats[0]);
|
||||
// } else {
|
||||
// chatAIRef.current?.init("");
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
const handleSendMessage = async (content: string) => {
|
||||
setInput(content);
|
||||
chatAIRef.current?.init(content);
|
||||
};
|
||||
|
||||
const chatHistory = async (chat: Chat) => {
|
||||
const chatHistory = async (chat: typeChat) => {
|
||||
try {
|
||||
let response: any = await session_chat_history({
|
||||
serverId: currentService?.id,
|
||||
@@ -100,7 +104,7 @@ export default function Chat({}: ChatProps) {
|
||||
response = JSON.parse(response || "");
|
||||
console.log("id_history", response);
|
||||
const hits = response?.hits?.hits || [];
|
||||
const updatedChat: Chat = {
|
||||
const updatedChat: typeChat = {
|
||||
...chat,
|
||||
messages: hits,
|
||||
};
|
||||
@@ -203,6 +207,33 @@ export default function Chat({}: ChatProps) {
|
||||
return icon(path, size);
|
||||
}, []);
|
||||
|
||||
const handleSearch = (keyword: string) => {
|
||||
setKeyword(keyword);
|
||||
};
|
||||
|
||||
const handleRename = async (chat: typeChat, title: string) => {
|
||||
if (!currentService?.id) return;
|
||||
|
||||
console.log("chat", chat);
|
||||
console.log("title", title);
|
||||
|
||||
await update_session_chat({
|
||||
serverId: currentService.id,
|
||||
sessionId: chat?._id,
|
||||
title,
|
||||
});
|
||||
|
||||
getChatHistory();
|
||||
};
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (!currentService?.id) return;
|
||||
|
||||
await delete_session_chat(currentService.id, id);
|
||||
|
||||
getChatHistory();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-screen">
|
||||
<div className="h-[100%] flex">
|
||||
@@ -213,15 +244,14 @@ export default function Chat({}: ChatProps) {
|
||||
isSidebarOpen ? "translate-x-0" : "-translate-x-full"
|
||||
} transition-transform duration-300 ease-in-out md:translate-x-0 md:static md:block bg-gray-100 dark:bg-gray-800`}
|
||||
>
|
||||
<Sidebar
|
||||
chats={chats}
|
||||
activeChat={activeChat}
|
||||
onNewChat={() => {
|
||||
chatAIRef.current?.clearChat();
|
||||
}}
|
||||
onSelectChat={onSelectChat}
|
||||
onDeleteChat={deleteChat}
|
||||
fetchChatHistory={getChatHistory}
|
||||
<HistoryList
|
||||
list={chats}
|
||||
active={activeChat}
|
||||
onSearch={handleSearch}
|
||||
onRefresh={getChatHistory}
|
||||
onSelect={onSelectChat}
|
||||
onRename={handleRename}
|
||||
onRemove={handleDelete}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -23,8 +23,8 @@ export default {
|
||||
},
|
||||
animation: {
|
||||
"fade-in": "fade-in 0.2s ease-in-out",
|
||||
'typing': 'typing 1.5s ease-in-out infinite',
|
||||
'shake': 'shake 0.5s ease-in-out',
|
||||
typing: "typing 1.5s ease-in-out infinite",
|
||||
shake: "shake 0.5s ease-in-out",
|
||||
},
|
||||
keyframes: {
|
||||
"fade-in": {
|
||||
@@ -32,15 +32,15 @@ export default {
|
||||
"100%": { opacity: "1" },
|
||||
},
|
||||
typing: {
|
||||
'0%': { opacity: '0.3' },
|
||||
'50%': { opacity: '1' },
|
||||
'100%': { opacity: '0.3' },
|
||||
"0%": { opacity: "0.3" },
|
||||
"50%": { opacity: "1" },
|
||||
"100%": { opacity: "0.3" },
|
||||
},
|
||||
shake: {
|
||||
'0%, 100%': { transform: 'rotate(0deg)' },
|
||||
'25%': { transform: 'rotate(-20deg)' },
|
||||
'75%': { transform: 'rotate(20deg)' }
|
||||
}
|
||||
"0%, 100%": { transform: "rotate(0deg)" },
|
||||
"25%": { transform: "rotate(-20deg)" },
|
||||
"75%": { transform: "rotate(20deg)" },
|
||||
},
|
||||
},
|
||||
boxShadow: {
|
||||
"window-custom": "0px 16px 32px 0px rgba(0,0,0,0.3)",
|
||||
|
||||
Reference in New Issue
Block a user