mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 03:27:43 +01:00
fix: duplicate chat content (#916)
* style: add dark drop shadow to images * docs: add release note * style: add dark * fix: duplicate chat content * docs: add release note * chore: update history list
This commit is contained in:
@@ -19,6 +19,7 @@ feat: support opening logs from about page #915
|
||||
### 🐛 Bug fix
|
||||
|
||||
fix: automatic update of service list #913
|
||||
fix: duplicate chat content #916
|
||||
|
||||
### ✈️ Improvements
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ interface ChatAIProps {
|
||||
startPage?: StartPage;
|
||||
formatUrl?: (data: any) => string;
|
||||
instanceId?: string;
|
||||
getChatHistoryChatPage?: () => void;
|
||||
}
|
||||
|
||||
export interface SendMessageParams {
|
||||
@@ -52,6 +53,7 @@ export interface ChatAIRef {
|
||||
init: (params: SendMessageParams) => void;
|
||||
cancelChat: () => void;
|
||||
clearChat: () => void;
|
||||
onSelectChat: (chat: Chat) => void;
|
||||
}
|
||||
|
||||
const ChatAI = memo(
|
||||
@@ -73,6 +75,7 @@ const ChatAI = memo(
|
||||
startPage,
|
||||
formatUrl,
|
||||
instanceId,
|
||||
getChatHistoryChatPage,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
@@ -80,6 +83,7 @@ const ChatAI = memo(
|
||||
init: init,
|
||||
cancelChat: () => cancelChat(activeChat),
|
||||
clearChat: clearChat,
|
||||
onSelectChat: onSelectChat,
|
||||
}));
|
||||
|
||||
const curChatEnd = useChatStore((state) => state.curChatEnd);
|
||||
@@ -193,7 +197,8 @@ const ChatAI = memo(
|
||||
isDeepThinkActive,
|
||||
isMCPActive,
|
||||
changeInput,
|
||||
showChatHistory
|
||||
showChatHistory,
|
||||
getChatHistoryChatPage,
|
||||
);
|
||||
|
||||
const { dealMsg } = useMessageHandler(
|
||||
|
||||
@@ -5,7 +5,6 @@ import { ChatMessage } from "@/components/ChatMessage";
|
||||
import { Greetings } from "./Greetings";
|
||||
import AttachmentList from "@/components/Assistant/AttachmentList";
|
||||
import { useChatScroll } from "@/hooks/useChatScroll";
|
||||
|
||||
import type { Chat, IChunkData } from "@/types/chat";
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
// import SessionFile from "./SessionFile";
|
||||
@@ -45,20 +44,23 @@ export const ChatContent = ({
|
||||
handleSendMessage,
|
||||
formatUrl,
|
||||
}: ChatContentProps) => {
|
||||
const { currentSessionId, setCurrentSessionId } = useConnectStore();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { uploadAttachments } = useChatStore();
|
||||
const currentSessionId = useConnectStore((state) => state.currentSessionId);
|
||||
const setCurrentSessionId = useConnectStore(
|
||||
(state) => state.setCurrentSessionId
|
||||
);
|
||||
const visibleStartPage = useConnectStore((state) => state.visibleStartPage);
|
||||
|
||||
const uploadAttachments = useChatStore((state) => state.uploadAttachments);
|
||||
const curChatEnd = useChatStore((state) => state.curChatEnd);
|
||||
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { scrollToBottom } = useChatScroll(messagesEndRef);
|
||||
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const [isAtBottom, setIsAtBottom] = useState(true);
|
||||
const visibleStartPage = useConnectStore((state) => state.visibleStartPage);
|
||||
|
||||
const curChatEnd = useChatStore((state) => state.curChatEnd);
|
||||
|
||||
useEffect(() => {
|
||||
setIsAtBottom(true);
|
||||
|
||||
@@ -139,7 +139,7 @@ const ExtensionStore = ({ extensionId }: { extensionId?: string }) => {
|
||||
}
|
||||
);
|
||||
|
||||
console.log("search_extension", result);
|
||||
// console.log("search_extension", result);
|
||||
|
||||
setList(result ?? []);
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ export default function ChatInput({
|
||||
const handleSubmit = useCallback(() => {
|
||||
const trimmedValue = inputValue.trim();
|
||||
|
||||
console.log("handleSubmit", trimmedValue, disabled);
|
||||
// console.log("handleSubmit", trimmedValue, disabled);
|
||||
|
||||
if ((trimmedValue || !isEmpty(uploadAttachments)) && !disabled) {
|
||||
changeInput("");
|
||||
|
||||
@@ -29,7 +29,8 @@ export function useChatActions(
|
||||
isDeepThinkActive?: boolean,
|
||||
isMCPActive?: boolean,
|
||||
changeInput?: (val: string) => void,
|
||||
showChatHistory?: boolean
|
||||
showChatHistory?: boolean,
|
||||
getChatHistoryChatPage?: () => void,
|
||||
) {
|
||||
const isCurrentLogin = useAuthStore((state) => state.isCurrentLogin);
|
||||
|
||||
@@ -197,17 +198,17 @@ export function useChatActions(
|
||||
) {
|
||||
try {
|
||||
const response = JSON.parse(msg);
|
||||
console.log("first", response);
|
||||
// console.log("first", response);
|
||||
|
||||
let updatedChat: Chat;
|
||||
if (Array.isArray(response)) {
|
||||
curIdRef.current = response[0]?._id;
|
||||
curSessionIdRef.current = response[0]?._source?.session_id;
|
||||
console.log(
|
||||
"curIdRef-curSessionIdRef-Array",
|
||||
curIdRef.current,
|
||||
curSessionIdRef.current
|
||||
);
|
||||
// console.log(
|
||||
// "curIdRef-curSessionIdRef-Array",
|
||||
// curIdRef.current,
|
||||
// curSessionIdRef.current
|
||||
// );
|
||||
updatedChat = {
|
||||
...updatedChatRef.current,
|
||||
messages: [
|
||||
@@ -215,16 +216,16 @@ export function useChatActions(
|
||||
...(response || []),
|
||||
],
|
||||
};
|
||||
console.log("array", updatedChat, updatedChatRef.current?.messages);
|
||||
// console.log("array", updatedChat, updatedChatRef.current?.messages);
|
||||
} else {
|
||||
const newChat: Chat = response;
|
||||
curIdRef.current = response?.payload?.id;
|
||||
curSessionIdRef.current = response?.payload?.session_id;
|
||||
console.log(
|
||||
"curIdRef-curSessionIdRef",
|
||||
curIdRef.current,
|
||||
curSessionIdRef.current
|
||||
);
|
||||
// console.log(
|
||||
// "curIdRef-curSessionIdRef",
|
||||
// curIdRef.current,
|
||||
// curSessionIdRef.current
|
||||
// );
|
||||
|
||||
newChat._source = {
|
||||
...response?.payload,
|
||||
@@ -252,7 +253,7 @@ export function useChatActions(
|
||||
async (timestamp: number) => {
|
||||
cleanupListeners();
|
||||
|
||||
console.log("setupListeners", clientId, timestamp);
|
||||
// console.log("setupListeners", clientId, timestamp);
|
||||
const unlisten_chat_message = await platformAdapter.listenEvent(
|
||||
`chat-stream-${clientId}-${timestamp}`,
|
||||
(event) => {
|
||||
@@ -300,12 +301,45 @@ export function useChatActions(
|
||||
[setupListeners]
|
||||
);
|
||||
|
||||
const getChatHistory = useCallback(async () => {
|
||||
let response: any;
|
||||
if (isTauri) {
|
||||
if (await unrequitable()) {
|
||||
return setChats([]);
|
||||
}
|
||||
|
||||
response = await platformAdapter.commands("chat_history", {
|
||||
serverId: currentService?.id,
|
||||
from: 0,
|
||||
size: 100,
|
||||
query: keyword,
|
||||
});
|
||||
|
||||
response = response ? JSON.parse(response) : null;
|
||||
} else {
|
||||
const [_error, res] = await Get(`/chat/_history`, {
|
||||
from: 0,
|
||||
size: 100,
|
||||
});
|
||||
response = res;
|
||||
}
|
||||
|
||||
const hits = response?.hits?.hits || [];
|
||||
setChats(hits);
|
||||
}, [
|
||||
currentService?.id,
|
||||
keyword,
|
||||
isTauri,
|
||||
currentService?.enabled,
|
||||
isCurrentLogin,
|
||||
]);
|
||||
|
||||
const createNewChat = useCallback(
|
||||
async (params?: SendMessageParams) => {
|
||||
const { message, attachments } = params || {};
|
||||
|
||||
console.log("message", message);
|
||||
console.log("attachments", attachments);
|
||||
// console.log("message", message);
|
||||
// console.log("attachments", attachments);
|
||||
|
||||
if (!message && isEmpty(attachments)) return;
|
||||
|
||||
@@ -325,7 +359,7 @@ export function useChatActions(
|
||||
if (isTauri) {
|
||||
if (!currentService?.id) return;
|
||||
|
||||
console.log("chat_create", clientId, timestamp);
|
||||
// console.log("chat_create", clientId, timestamp);
|
||||
|
||||
await platformAdapter.commands("chat_create", {
|
||||
serverId: currentService?.id,
|
||||
@@ -334,7 +368,7 @@ export function useChatActions(
|
||||
queryParams,
|
||||
clientId: `chat-stream-${clientId}-${timestamp}`,
|
||||
});
|
||||
console.log("_create end", message);
|
||||
// console.log("_create end", message);
|
||||
resetChatState();
|
||||
} else {
|
||||
await streamPost({
|
||||
@@ -342,12 +376,17 @@ export function useChatActions(
|
||||
body: { message },
|
||||
queryParams,
|
||||
onMessage: (line) => {
|
||||
console.log("⏳", line);
|
||||
// console.log("⏳", line);
|
||||
handleChatCreateStreamMessage(line);
|
||||
// append to chat box
|
||||
},
|
||||
});
|
||||
}
|
||||
// console.log("showChatHistory", showChatHistory);
|
||||
|
||||
if (showChatHistory) {
|
||||
getChatHistoryChatPage ? getChatHistoryChatPage() : getChatHistory();
|
||||
}
|
||||
},
|
||||
[
|
||||
isTauri,
|
||||
@@ -360,6 +399,8 @@ export function useChatActions(
|
||||
currentAssistant,
|
||||
chatClose,
|
||||
clientId,
|
||||
showChatHistory,
|
||||
getChatHistory,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -386,7 +427,7 @@ export function useChatActions(
|
||||
|
||||
if (isTauri) {
|
||||
if (!currentService?.id) return;
|
||||
console.log("chat_chat", clientId, timestamp);
|
||||
// console.log("chat_chat", clientId, timestamp);
|
||||
await platformAdapter.commands("chat_chat", {
|
||||
serverId: currentService?.id,
|
||||
sessionId: newChat?._id,
|
||||
@@ -395,7 +436,7 @@ export function useChatActions(
|
||||
attachments,
|
||||
clientId: `chat-stream-${clientId}-${timestamp}`,
|
||||
});
|
||||
console.log("chat_chat end", message, clientId);
|
||||
// console.log("chat_chat end", message, clientId);
|
||||
resetChatState();
|
||||
} else {
|
||||
await streamPost({
|
||||
@@ -403,7 +444,7 @@ export function useChatActions(
|
||||
body: { message },
|
||||
queryParams,
|
||||
onMessage: (line) => {
|
||||
console.log("line", line);
|
||||
// console.log("line", line);
|
||||
handleChatCreateStreamMessage(line);
|
||||
// append to chat box
|
||||
},
|
||||
@@ -468,39 +509,6 @@ export function useChatActions(
|
||||
[currentService?.id, isTauri]
|
||||
);
|
||||
|
||||
const getChatHistory = useCallback(async () => {
|
||||
let response: any;
|
||||
if (isTauri) {
|
||||
if (await unrequitable()) {
|
||||
return setChats([]);
|
||||
}
|
||||
|
||||
response = await platformAdapter.commands("chat_history", {
|
||||
serverId: currentService?.id,
|
||||
from: 0,
|
||||
size: 100,
|
||||
query: keyword,
|
||||
});
|
||||
|
||||
response = response ? JSON.parse(response) : null;
|
||||
} else {
|
||||
const [_error, res] = await Get(`/chat/_history`, {
|
||||
from: 0,
|
||||
size: 100,
|
||||
});
|
||||
response = res;
|
||||
}
|
||||
|
||||
const hits = response?.hits?.hits || [];
|
||||
setChats(hits);
|
||||
}, [
|
||||
currentService?.id,
|
||||
keyword,
|
||||
isTauri,
|
||||
currentService?.enabled,
|
||||
isCurrentLogin,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showChatHistory) {
|
||||
getChatHistory();
|
||||
|
||||
160
src/hooks/useChatPanel.ts
Normal file
160
src/hooks/useChatPanel.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import type { Chat } from "@/types/chat";
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
import platformAdapter from "@/utils/platformAdapter";
|
||||
import { unrequitable } from "@/utils";
|
||||
|
||||
export function useChatPanel() {
|
||||
const {
|
||||
assistantList,
|
||||
setCurrentAssistant,
|
||||
setVisibleStartPage,
|
||||
currentService,
|
||||
} = useConnectStore();
|
||||
|
||||
const [chats, setChats] = useState<Chat[]>([]);
|
||||
const [activeChat, setActiveChat] = useState<Chat | undefined>();
|
||||
const [keyword, setKeyword] = useState("");
|
||||
|
||||
const getChatHistory = useCallback(async () => {
|
||||
try {
|
||||
if (await unrequitable()) {
|
||||
return setChats([]);
|
||||
}
|
||||
let response: any = await platformAdapter.commands("chat_history", {
|
||||
serverId: currentService?.id,
|
||||
from: 0,
|
||||
size: 100,
|
||||
query: keyword,
|
||||
});
|
||||
response = response ? JSON.parse(response) : null;
|
||||
const hits = response?.hits?.hits || [];
|
||||
setChats(hits);
|
||||
} catch (error) {
|
||||
console.error("chat_history:", error);
|
||||
}
|
||||
}, [keyword, currentService?.id]);
|
||||
|
||||
const chatHistory = useCallback(
|
||||
async (chat: Chat) => {
|
||||
try {
|
||||
let response: any = await platformAdapter.commands(
|
||||
"session_chat_history",
|
||||
{
|
||||
serverId: currentService?.id,
|
||||
sessionId: chat?._id || "",
|
||||
from: 0,
|
||||
size: 500,
|
||||
}
|
||||
);
|
||||
response = response ? JSON.parse(response) : null;
|
||||
const hits = response?.hits?.hits || [];
|
||||
|
||||
// set current assistant based on last message
|
||||
const lastAssistantId = hits[hits.length - 1]?._source?.assistant_id;
|
||||
const matchedAssistant = assistantList?.find(
|
||||
(assistant) => assistant._id === lastAssistantId
|
||||
);
|
||||
if (matchedAssistant) {
|
||||
setCurrentAssistant(matchedAssistant);
|
||||
}
|
||||
|
||||
const updatedChat: Chat = {
|
||||
...chat,
|
||||
messages: hits,
|
||||
};
|
||||
setActiveChat(updatedChat);
|
||||
} catch (error) {
|
||||
console.error("session_chat_history:", error);
|
||||
}
|
||||
},
|
||||
[assistantList, currentService?.id, setCurrentAssistant]
|
||||
);
|
||||
|
||||
const onSelectChat = useCallback(
|
||||
async (chat: any) => {
|
||||
try {
|
||||
let response: any = await platformAdapter.commands(
|
||||
"open_session_chat",
|
||||
{
|
||||
serverId: currentService?.id,
|
||||
sessionId: chat?._id,
|
||||
}
|
||||
);
|
||||
response = response ? JSON.parse(response) : null;
|
||||
chatHistory(response);
|
||||
setVisibleStartPage(false);
|
||||
} catch (error) {
|
||||
console.error("open_session_chat:", error);
|
||||
}
|
||||
},
|
||||
[currentService?.id, chatHistory, setVisibleStartPage]
|
||||
);
|
||||
|
||||
const deleteChat = useCallback(
|
||||
async (chatId: string) => {
|
||||
if (!currentService?.id) return;
|
||||
|
||||
await platformAdapter.commands(
|
||||
"delete_session_chat",
|
||||
currentService.id,
|
||||
chatId
|
||||
);
|
||||
|
||||
setChats((prev) => prev.filter((chat) => chat._id !== chatId));
|
||||
if (activeChat?._id === chatId) {
|
||||
const remainingChats = chats.filter((chat) => chat._id !== chatId);
|
||||
setActiveChat(remainingChats[0]);
|
||||
}
|
||||
},
|
||||
[currentService?.id, activeChat?._id, chats]
|
||||
);
|
||||
|
||||
const handleSearch = useCallback((kw: string) => {
|
||||
setKeyword(kw);
|
||||
}, []);
|
||||
|
||||
const handleRename = useCallback(
|
||||
(chatId: string, title: string) => {
|
||||
if (!currentService?.id) return;
|
||||
|
||||
setChats((prev) => {
|
||||
const updatedChats = prev.map((item) => {
|
||||
if (item._id !== chatId) return item;
|
||||
return { ...item, _source: { ...item._source, title } };
|
||||
});
|
||||
return updatedChats;
|
||||
});
|
||||
|
||||
if (activeChat?._id === chatId) {
|
||||
setActiveChat((prev) => {
|
||||
if (!prev) return prev;
|
||||
return { ...prev, _source: { ...prev._source, title } };
|
||||
});
|
||||
}
|
||||
|
||||
platformAdapter.commands("update_session_chat", {
|
||||
serverId: currentService.id,
|
||||
sessionId: chatId,
|
||||
title,
|
||||
});
|
||||
},
|
||||
[currentService?.id, activeChat?._id]
|
||||
);
|
||||
|
||||
return {
|
||||
chats,
|
||||
setChats,
|
||||
activeChat,
|
||||
setActiveChat,
|
||||
keyword,
|
||||
setKeyword,
|
||||
getChatHistory,
|
||||
chatHistory,
|
||||
onSelectChat,
|
||||
deleteChat,
|
||||
handleSearch,
|
||||
handleRename,
|
||||
};
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export function useMessageHandler(
|
||||
}
|
||||
|
||||
messageTimeoutRef.current = setTimeout(() => {
|
||||
console.log("AI response timeout");
|
||||
// console.log("AI response timeout");
|
||||
setTimedoutShow(true);
|
||||
onCancel();
|
||||
}, (connectionTimeout ?? 120) * 1000);
|
||||
@@ -108,7 +108,7 @@ export function useMessageHandler(
|
||||
clearTimeout(messageTimeoutRef.current);
|
||||
}
|
||||
setCurChatEnd(true);
|
||||
console.log("AI finished output");
|
||||
// console.log("AI finished output");
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -198,7 +198,7 @@ export function useSearch() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log("_suggest", searchInput, response);
|
||||
//console.log("_suggest", searchInput, response);
|
||||
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
|
||||
@@ -68,7 +68,7 @@ export default function useSettingsWindow() {
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = platformAdapter.listenEvent("open_settings", async (event) => {
|
||||
console.log("open_settings event received:", event);
|
||||
//console.log("open_settings event received:", event);
|
||||
const tab = event.payload as string | "";
|
||||
|
||||
platformAdapter.emitEvent("tab_index", tab);
|
||||
|
||||
@@ -52,7 +52,7 @@ export const useStreamChat = (options: Options) => {
|
||||
unlistenRef.current = await platformAdapter.listenEvent(
|
||||
clientId,
|
||||
({ payload }) => {
|
||||
console.log(clientId, JSON.parse(payload));
|
||||
//console.log(clientId, JSON.parse(payload));
|
||||
|
||||
const chunkData = JSON.parse(payload);
|
||||
|
||||
|
||||
@@ -35,12 +35,7 @@ export default function StandaloneChat({}: StandaloneChatProps) {
|
||||
setIsTauri(true);
|
||||
}, []);
|
||||
|
||||
const {
|
||||
setCurrentAssistant,
|
||||
assistantList,
|
||||
setVisibleStartPage,
|
||||
currentService,
|
||||
} = useConnectStore();
|
||||
const currentService = useConnectStore((state) => state.currentService);
|
||||
|
||||
const chatAIRef = useRef<ChatAIRef>(null);
|
||||
|
||||
@@ -77,7 +72,7 @@ export default function StandaloneChat({}: StandaloneChatProps) {
|
||||
query: keyword,
|
||||
});
|
||||
response = response ? JSON.parse(response) : null;
|
||||
console.log("_history", response);
|
||||
// console.log("_history", response);
|
||||
const hits = response?.hits?.hits || [];
|
||||
setChats(hits);
|
||||
} catch (error) {
|
||||
@@ -112,39 +107,6 @@ export default function StandaloneChat({}: StandaloneChatProps) {
|
||||
chatAIRef.current?.init(params);
|
||||
};
|
||||
|
||||
const chatHistory = async (chat: typeChat) => {
|
||||
try {
|
||||
let response: any = await platformAdapter.commands(
|
||||
"session_chat_history",
|
||||
{
|
||||
serverId: currentService?.id,
|
||||
sessionId: chat?._id || "",
|
||||
from: 0,
|
||||
size: 500,
|
||||
}
|
||||
);
|
||||
response = response ? JSON.parse(response) : null;
|
||||
console.log("id_history", response);
|
||||
const hits = response?.hits?.hits || [];
|
||||
// set current assistant
|
||||
const lastAssistantId = hits[hits.length - 1]?._source?.assistant_id;
|
||||
const matchedAssistant = assistantList?.find(
|
||||
(assistant) => assistant._id === lastAssistantId
|
||||
);
|
||||
if (matchedAssistant) {
|
||||
setCurrentAssistant(matchedAssistant);
|
||||
}
|
||||
//
|
||||
const updatedChat: typeChat = {
|
||||
...chat,
|
||||
messages: hits,
|
||||
};
|
||||
setActiveChat(updatedChat);
|
||||
} catch (error) {
|
||||
console.error("session_chat_history:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const chatClose = async () => {
|
||||
if (!activeChat?._id) return;
|
||||
try {
|
||||
@@ -153,26 +115,14 @@ export default function StandaloneChat({}: StandaloneChatProps) {
|
||||
sessionId: activeChat?._id,
|
||||
});
|
||||
response = response ? JSON.parse(response) : null;
|
||||
console.log("_close", response);
|
||||
// console.log("_close", response);
|
||||
} catch (error) {
|
||||
console.error("close_session_chat:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const onSelectChat = async (chat: any) => {
|
||||
chatClose();
|
||||
try {
|
||||
let response: any = await platformAdapter.commands("open_session_chat", {
|
||||
serverId: currentService?.id,
|
||||
sessionId: chat?._id,
|
||||
});
|
||||
response = response ? JSON.parse(response) : null;
|
||||
console.log("_open", response);
|
||||
chatHistory(response);
|
||||
setVisibleStartPage(false);
|
||||
} catch (error) {
|
||||
console.error("open_session_chat:", error);
|
||||
}
|
||||
chatAIRef.current?.onSelectChat(chat);
|
||||
};
|
||||
|
||||
const cancelChat = async () => {
|
||||
@@ -317,6 +267,8 @@ export default function StandaloneChat({}: StandaloneChatProps) {
|
||||
isChatPage={isChatPage}
|
||||
getFileUrl={getFileUrl}
|
||||
changeInput={setInput}
|
||||
showChatHistory={true}
|
||||
getChatHistoryChatPage={getChatHistory}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user