546 lines
26 KiB
JavaScript
546 lines
26 KiB
JavaScript
// Filename: frontend/js/pages/chat/index.js
|
|
|
|
import { nanoid } from '../../vendor/nanoid.js';
|
|
import { uiPatterns } from '../../components/ui.js';
|
|
import { apiFetch } from '../../services/api.js';
|
|
import { marked } from '../../vendor/marked.min.js';
|
|
import { SessionManager } from './SessionManager.js';
|
|
import { ChatSettings } from './chatSettings.js';
|
|
import CustomSelectV2 from '../../components/customSelectV2.js';
|
|
|
|
marked.use({ breaks: true, gfm: true });
|
|
|
|
function getCookie(name) {
|
|
let cookieValue = null;
|
|
if (document.cookie && document.cookie !== '') {
|
|
const cookies = document.cookie.split(';');
|
|
for (let i = 0; i < cookies.length; i++) {
|
|
const cookie = cookies[i].trim();
|
|
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return cookieValue;
|
|
}
|
|
|
|
class ChatPage {
|
|
constructor() {
|
|
this.sessionManager = new SessionManager();
|
|
this.isStreaming = false;
|
|
this.elements = {};
|
|
this.initialized = false;
|
|
this.searchTerm = '';
|
|
this.settingsManager = null;
|
|
}
|
|
|
|
init() {
|
|
if (!document.querySelector('[data-page-id="chat"]')) { return; }
|
|
|
|
this.sessionManager.init();
|
|
|
|
this.initialized = true;
|
|
this._initDOMElements();
|
|
this._initComponents();
|
|
this._initEventListeners();
|
|
this._render();
|
|
console.log("ChatPage initialized. Session management is delegated.", this.sessionManager.state);
|
|
}
|
|
|
|
_initDOMElements() {
|
|
this.elements.chatScrollContainer = document.getElementById('chat-scroll-container');
|
|
this.elements.chatMessagesContainer = document.getElementById('chat-messages-container');
|
|
this.elements.messageForm = document.getElementById('message-form');
|
|
this.elements.messageInput = document.getElementById('message-input');
|
|
this.elements.sendBtn = document.getElementById('send-btn');
|
|
this.elements.newSessionBtn = document.getElementById('new-session-btn');
|
|
this.elements.sessionListContainer = document.getElementById('session-list-container');
|
|
this.elements.chatHeaderTitle = document.querySelector('.chat-header-title');
|
|
this.elements.clearSessionBtn = document.getElementById('clear-session-btn');
|
|
this.elements.sessionSearchInput = document.getElementById('session-search-input');
|
|
this.elements.toggleQuickSettingsBtn = document.getElementById('toggle-quick-settings');
|
|
this.elements.toggleSessionParamsBtn = document.getElementById('toggle-session-params');
|
|
this.elements.quickSettingsPanel = document.getElementById('quick-settings-panel');
|
|
this.elements.sessionParamsPanel = document.getElementById('session-params-panel');
|
|
this.elements.directRoutingOptions = document.getElementById('direct-routing-options');
|
|
this.elements.btnGroups = document.querySelectorAll('.btn-group');
|
|
this.elements.temperatureSlider = document.getElementById('temperature-slider');
|
|
this.elements.temperatureValue = document.getElementById('temperature-value');
|
|
this.elements.contextSlider = document.getElementById('context-slider');
|
|
this.elements.contextValue = document.getElementById('context-value');
|
|
this.elements.groupSelectContainer = document.getElementById('group-select-container');
|
|
}
|
|
// [NEW] A dedicated method for initializing complex UI components
|
|
_initComponents() {
|
|
if (this.elements.groupSelectContainer) {
|
|
new CustomSelectV2(this.elements.groupSelectContainer);
|
|
}
|
|
// In the future, we will initialize the model select component here as well
|
|
}
|
|
|
|
_initEventListeners() {
|
|
// --- Initialize Settings Manager First ---
|
|
this.settingsManager = new ChatSettings(this.elements);
|
|
this.settingsManager.init();
|
|
// --- Core Chat Event Listeners ---
|
|
this.elements.messageForm.addEventListener('submit', (e) => { e.preventDefault(); this._handleSendMessage(); });
|
|
this.elements.messageInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this._handleSendMessage(); } });
|
|
this.elements.messageInput.addEventListener('input', () => this._autoResizeTextarea());
|
|
this.elements.newSessionBtn.addEventListener('click', () => {
|
|
this.sessionManager.createSession();
|
|
this._render();
|
|
this.elements.messageInput.focus();
|
|
});
|
|
this.elements.sessionListContainer.addEventListener('click', (e) => {
|
|
const sessionItem = e.target.closest('[data-session-id]');
|
|
const deleteBtn = e.target.closest('.delete-session-btn');
|
|
|
|
if (deleteBtn) {
|
|
e.preventDefault();
|
|
const sessionId = deleteBtn.closest('[data-session-id]').dataset.sessionId;
|
|
this._handleDeleteSession(sessionId);
|
|
} else if (sessionItem) {
|
|
e.preventDefault();
|
|
const sessionId = sessionItem.dataset.sessionId;
|
|
this.sessionManager.switchSession(sessionId);
|
|
this._render();
|
|
this.elements.messageInput.focus();
|
|
}
|
|
});
|
|
this.elements.clearSessionBtn.addEventListener('click', () => this._handleClearSession());
|
|
this.elements.sessionSearchInput.addEventListener('input', (e) => {
|
|
this.searchTerm = e.target.value.trim();
|
|
this._renderSessionList();
|
|
});
|
|
this.elements.chatMessagesContainer.addEventListener('click', (e) => {
|
|
const messageElement = e.target.closest('[data-message-id]');
|
|
if (!messageElement) return;
|
|
const messageId = messageElement.dataset.messageId;
|
|
const copyBtn = e.target.closest('.action-copy');
|
|
const deleteBtn = e.target.closest('.action-delete');
|
|
const retryBtn = e.target.closest('.action-retry');
|
|
if (copyBtn) {
|
|
this._handleCopyMessage(messageId);
|
|
} else if (deleteBtn) {
|
|
this._handleDeleteMessage(messageId, e.target);
|
|
} else if (retryBtn) {
|
|
this._handleRetryMessage(messageId);
|
|
}
|
|
});
|
|
}
|
|
|
|
_handleCopyMessage(messageId) {
|
|
const currentSession = this.sessionManager.getCurrentSession();
|
|
if (!currentSession) return;
|
|
const message = currentSession.messages.find(m => m.id === messageId);
|
|
if (!message || !message.content) {
|
|
console.error("Message content not found for copying.");
|
|
return;
|
|
}
|
|
// Handle cases where content might be HTML (like error messages)
|
|
// by stripping tags to get plain text.
|
|
let textToCopy = message.content;
|
|
if (textToCopy.includes('<') && textToCopy.includes('>')) {
|
|
const tempDiv = document.createElement('div');
|
|
tempDiv.innerHTML = textToCopy;
|
|
textToCopy = tempDiv.textContent || tempDiv.innerText || '';
|
|
}
|
|
navigator.clipboard.writeText(textToCopy)
|
|
.then(() => {
|
|
Swal.fire({
|
|
toast: true,
|
|
position: 'top-end',
|
|
icon: 'success',
|
|
title: '已复制',
|
|
showConfirmButton: false,
|
|
timer: 1500,
|
|
customClass: {
|
|
popup: `${document.documentElement.classList.contains('dark') ? 'swal2-dark' : ''}`
|
|
}
|
|
});
|
|
})
|
|
.catch(err => {
|
|
console.error('Failed to copy text: ', err);
|
|
Swal.fire({
|
|
toast: true,
|
|
position: 'top-end',
|
|
icon: 'error',
|
|
title: '复制失败',
|
|
showConfirmButton: false,
|
|
timer: 1500,
|
|
customClass: {
|
|
popup: `${document.documentElement.classList.contains('dark') ? 'swal2-dark' : ''}`
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
_handleDeleteMessage(messageId, targetElement) {
|
|
// Remove any existing popover first to prevent duplicates
|
|
const existingPopover = document.getElementById('delete-confirmation-popover');
|
|
if (existingPopover) {
|
|
existingPopover.remove();
|
|
}
|
|
// Create the popover element with your specified dimensions.
|
|
const popover = document.createElement('div');
|
|
popover.id = 'delete-confirmation-popover';
|
|
// [MODIFIED] - Using your w-36, and adding flexbox classes for centering.
|
|
popover.className = 'absolute z-50 p-3 w-45 border border-border rounded-md shadow-lg bg-background text-popover-foreground flex flex-col items-center';
|
|
|
|
// [MODIFIED] - Added an icon and classes for horizontal centering.
|
|
popover.innerHTML = `
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<i class="fas fa-exclamation-circle text-yellow-500"></i>
|
|
<p class="text-sm">确认删除此消息吗?</p>
|
|
</div>
|
|
<div class="flex translate-x-12 gap-2 w-full">
|
|
<button class="btn btn-secondary rounded-xs w-12 btn-xs popover-cancel">取消</button>
|
|
<button class="btn btn-destructive rounded-xs w-12 btn-xs popover-confirm">确认</button>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(popover);
|
|
// Position the popover above the clicked icon
|
|
const iconRect = targetElement.closest('button').getBoundingClientRect();
|
|
const popoverRect = popover.getBoundingClientRect();
|
|
popover.style.top = `${window.scrollY + iconRect.top - popoverRect.height - 8}px`;
|
|
popover.style.left = `${window.scrollX + iconRect.left + (iconRect.width / 2) - (popoverRect.width / 2)}px`;
|
|
// Event listener to close the popover if clicked outside
|
|
const outsideClickListener = (event) => {
|
|
if (!popover.contains(event.target) && event.target !== targetElement) {
|
|
popover.remove();
|
|
document.removeEventListener('click', outsideClickListener);
|
|
}
|
|
};
|
|
setTimeout(() => document.addEventListener('click', outsideClickListener), 0);
|
|
// Event listeners for the buttons inside the popover
|
|
popover.querySelector('.popover-confirm').addEventListener('click', () => {
|
|
this.sessionManager.deleteMessage(messageId);
|
|
this._renderChatMessages();
|
|
this._renderSessionList();
|
|
popover.remove();
|
|
document.removeEventListener('click', outsideClickListener);
|
|
});
|
|
popover.querySelector('.popover-cancel').addEventListener('click', () => {
|
|
popover.remove();
|
|
document.removeEventListener('click', outsideClickListener);
|
|
});
|
|
}
|
|
|
|
_handleRetryMessage(messageId) {
|
|
if (this.isStreaming) return; // Prevent retrying while a response is already generating
|
|
const currentSession = this.sessionManager.getCurrentSession();
|
|
if (!currentSession) return;
|
|
|
|
const message = currentSession.messages.find(m => m.id === messageId);
|
|
if (!message) return;
|
|
if (message.role === 'user') {
|
|
// Logic for retrying from a user's prompt
|
|
this.sessionManager.truncateMessagesAfter(messageId);
|
|
} else if (message.role === 'assistant') {
|
|
// Logic for regenerating an assistant's response (must be the last one)
|
|
this.sessionManager.deleteMessage(messageId);
|
|
}
|
|
// After data manipulation, update the UI and trigger a new response
|
|
this._renderChatMessages();
|
|
this._renderSessionList();
|
|
this._getAssistantResponse();
|
|
}
|
|
_autoResizeTextarea() {
|
|
const el = this.elements.messageInput;
|
|
el.style.height = 'auto';
|
|
el.style.height = (el.scrollHeight) + 'px';
|
|
}
|
|
|
|
_handleSendMessage() {
|
|
if (this.isStreaming) return;
|
|
const content = this.elements.messageInput.value.trim();
|
|
if (!content) return;
|
|
|
|
const userMessage = { id: nanoid(), role: 'user', content: content };
|
|
this.sessionManager.addMessage(userMessage);
|
|
|
|
this._renderChatMessages();
|
|
this._renderSessionList();
|
|
|
|
this.elements.messageInput.value = '';
|
|
this._autoResizeTextarea();
|
|
this.elements.messageInput.focus();
|
|
this._getAssistantResponse();
|
|
}
|
|
|
|
async _getAssistantResponse() {
|
|
this.isStreaming = true;
|
|
this._setLoadingState(true);
|
|
const currentSession = this.sessionManager.getCurrentSession();
|
|
const assistantMessageId = nanoid();
|
|
let finalAssistantMessage = { id: assistantMessageId, role: 'assistant', content: '' };
|
|
// Step 1: Create and render a temporary UI placeholder for streaming.
|
|
// [MODIFIED] The placeholder now uses the three-dot animation.
|
|
const placeholderHtml = `
|
|
<div class="flex items-start gap-4" data-message-id="${assistantMessageId}">
|
|
<span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
|
<i class="fas fa-robot"></i>
|
|
</span>
|
|
<div class="flex-1 space-y-2">
|
|
<div class="relative group rounded-lg p-5 bg-primary/10 border/20">
|
|
<div class="prose prose-sm max-w-none text-foreground message-content">
|
|
<div class="flex items-center gap-1">
|
|
<span class="h-2 w-2 bg-foreground/50 rounded-full animate-bounce" style="animation-delay: 0s;"></span>
|
|
<span class="h-2 w-2 bg-foreground/50 rounded-full animate-bounce" style="animation-delay: 0.1s;"></span>
|
|
<span class="h-2 w-2 bg-foreground/50 rounded-full animate-bounce" style="animation-delay: 0.2s;"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
this.elements.chatMessagesContainer.insertAdjacentHTML('beforeend', placeholderHtml);
|
|
this._scrollToBottom();
|
|
const assistantMessageContentEl = this.elements.chatMessagesContainer.querySelector(`[data-message-id="${assistantMessageId}"] .message-content`);
|
|
try {
|
|
const token = getCookie('gemini_admin_session');
|
|
const headers = { 'Authorization': `Bearer ${token}` };
|
|
const response = await apiFetch('/v1/chat/completions', {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({
|
|
model: currentSession.modelConfig.model,
|
|
messages: currentSession.messages.filter(m => m.content).map(({ role, content }) => ({ role, content })),
|
|
stream: true,
|
|
})
|
|
});
|
|
if (!response.body) throw new Error("Response body is null.");
|
|
|
|
const reader = response.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
while (true) {
|
|
const { value, done } = await reader.read();
|
|
if (done) break;
|
|
|
|
const chunk = decoder.decode(value);
|
|
const lines = chunk.split('\n').filter(line => line.trim().startsWith('data:'));
|
|
|
|
for (const line of lines) {
|
|
const dataStr = line.replace(/^data: /, '').trim();
|
|
if (dataStr !== '[DONE]') {
|
|
try {
|
|
const data = JSON.parse(dataStr);
|
|
const deltaContent = data.choices[0]?.delta?.content;
|
|
if (deltaContent) {
|
|
finalAssistantMessage.content += deltaContent;
|
|
assistantMessageContentEl.innerHTML = marked.parse(finalAssistantMessage.content);
|
|
this._scrollToBottom();
|
|
}
|
|
} catch (e) { /* ignore malformed JSON */ }
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Fetch stream error:', error);
|
|
const errorMessage = error.rawMessageFromServer || error.message;
|
|
finalAssistantMessage.content = `<span class="text-red-500">请求失败: ${errorMessage}</span>`;
|
|
} finally {
|
|
this.sessionManager.addMessage(finalAssistantMessage);
|
|
this._renderChatMessages();
|
|
this._renderSessionList();
|
|
this.isStreaming = false;
|
|
this._setLoadingState(false);
|
|
this.elements.messageInput.focus();
|
|
}
|
|
}
|
|
|
|
_renderMessage(message, replace = false, isLastMessage = false) {
|
|
let contentHtml;
|
|
if (message.role === 'user') {
|
|
const escapedContent = message.content.replace(/</g, "<").replace(/>/g, ">");
|
|
contentHtml = `<p class="text-sm text-foreground message-content">${escapedContent.replace(/\n/g, '<br>')}</p>`;
|
|
} else {
|
|
// [FIXED] Simplified logic: if it's an assistant message, it either has real content or error HTML.
|
|
// The isStreamingPlaceholder case is now handled differently and removed from here.
|
|
const isErrorHtml = message.content && message.content.includes('<span class="text-red-500">');
|
|
contentHtml = isErrorHtml ?
|
|
`<div class="message-content">${message.content}</div>` :
|
|
`<div class="prose prose-sm max-w-none text-foreground message-content">${marked.parse(message.content || '')}</div>`;
|
|
}
|
|
|
|
// [FIXED] No special handling for streaming placeholders needed anymore.
|
|
// If a message has content, it gets actions. An error message has content, so it will get actions.
|
|
let actionsHtml = '';
|
|
let retryButton = '';
|
|
if (message.role === 'user') {
|
|
retryButton = `
|
|
<button class="btn btn-ghost btn-icon w-6 h-6 hover:text-sky-500 action-retry rounded-full" title="从此消息重新生成">
|
|
<i class="fas fa-redo text-xs"></i>
|
|
</button>`;
|
|
} else if (message.role === 'assistant' && isLastMessage) {
|
|
// This now correctly applies to final error messages too.
|
|
retryButton = `
|
|
<button class="btn btn-ghost btn-icon w-6 h-6 hover:text-sky-500 action-retry rounded-full" title="重新生成回答">
|
|
<i class="fas fa-redo text-xs"></i>
|
|
</button>`;
|
|
}
|
|
const toolbarBaseClasses = "message-actions flex items-center gap-1 transition-opacity duration-200";
|
|
const toolbarPositionClass = isLastMessage ? "mt-2" : "absolute bottom-2.5 right-2.5";
|
|
const visibilityClass = isLastMessage ? "" : "opacity-0 group-hover:opacity-100";
|
|
|
|
actionsHtml = `
|
|
<div class="${toolbarBaseClasses} ${toolbarPositionClass} ${visibilityClass}">
|
|
${retryButton}
|
|
<button class="btn btn-ghost btn-icon w-6 h-6 hover:text-sky-500 action-copy rounded-full" title="复制">
|
|
<i class="far fa-copy text-xs"></i>
|
|
</button>
|
|
<button class="btn btn-ghost btn-icon w-6 h-6 hover:text-sky-500 action-delete rounded-full" title="删除">
|
|
<i class="far fa-trash-alt text-xs"></i>
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
const messageBubbleClasses = `relative group rounded-lg p-5 ${message.role === 'user' ? 'bg-muted' : 'bg-primary/10 border/20'}`;
|
|
const messageHtml = `
|
|
<div class="flex items-start gap-4" data-message-id="${message.id}">
|
|
<span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full ${message.role === 'user' ? 'bg-secondary text-secondary-foreground' : 'bg-primary text-primary-foreground'}">
|
|
<i class="fas ${message.role === 'user' ? 'fa-user' : 'fa-robot'}"></i>
|
|
</span>
|
|
<div class="flex-1 space-y-2">
|
|
<div class="${messageBubbleClasses}">
|
|
${contentHtml}
|
|
${actionsHtml}
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
|
|
const existingElement = this.elements.chatMessagesContainer.querySelector(`[data-message-id="${message.id}"]`);
|
|
if (replace && existingElement) {
|
|
existingElement.outerHTML = messageHtml;
|
|
} else if (!existingElement) {
|
|
this.elements.chatMessagesContainer.insertAdjacentHTML('beforeend', messageHtml);
|
|
}
|
|
|
|
if (!replace) { this._scrollToBottom(); }
|
|
}
|
|
|
|
_scrollToBottom() {
|
|
this.elements.chatScrollContainer.scrollTop = this.elements.chatScrollContainer.scrollHeight;
|
|
}
|
|
|
|
_render() {
|
|
this._renderSessionList();
|
|
this._renderChatMessages();
|
|
this._renderChatHeader();
|
|
}
|
|
|
|
_renderSessionList() {
|
|
let sessions = this.sessionManager.getSessions();
|
|
const currentSessionId = this.sessionManager.getCurrentSessionId();
|
|
if (this.searchTerm) {
|
|
const lowerCaseSearchTerm = this.searchTerm.toLowerCase();
|
|
sessions = sessions.filter(session => {
|
|
const titleMatch = session.name.toLowerCase().includes(lowerCaseSearchTerm);
|
|
const messageMatch = session.messages.some(message =>
|
|
message.content && message.content.toLowerCase().includes(lowerCaseSearchTerm)
|
|
);
|
|
return titleMatch || messageMatch;
|
|
});
|
|
}
|
|
|
|
this.elements.sessionListContainer.innerHTML = sessions.map(session => {
|
|
const isActive = session.id === currentSessionId;
|
|
const lastMessage = session.messages.length > 0 ? session.messages[session.messages.length - 1].content : '新会话';
|
|
|
|
return `
|
|
<div class="relative group flex items-center" data-session-id="${session.id}">
|
|
<a href="#" class="grow flex flex-col items-start gap-2 rounded-lg p-3 text-left text-sm transition-all hover:bg-accent ${isActive ? 'bg-accent' : ''} min-w-0">
|
|
<div class="w-full font-semibold truncate pr-2">${session.name}</div>
|
|
<div class="w-full text-xs text-muted-foreground line-clamp-2">${session.messages.length > 0 ? (lastMessage.includes('<span class="text-red-500">') ? '[请求失败]' : lastMessage) : '新会话'}</div>
|
|
</a>
|
|
<button class="delete-session-btn absolute top-1/2 -translate-y-1/2 right-2 w-6 h-6 flex items-center justify-center rounded-full bg-muted text-muted-foreground opacity-0 group-hover:opacity-100 hover:bg-destructive/80 hover:text-destructive-foreground transition-all" aria-label="删除会话">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
_renderChatMessages() {
|
|
this.elements.chatMessagesContainer.innerHTML = '';
|
|
const currentSession = this.sessionManager.getCurrentSession();
|
|
if (currentSession) {
|
|
const messages = currentSession.messages;
|
|
const lastMessageIndex = messages.length > 0 ? messages.length - 1 : -1;
|
|
|
|
messages.forEach((message, index) => {
|
|
const isLastMessage = (index === lastMessageIndex);
|
|
this._renderMessage(message, false, isLastMessage);
|
|
});
|
|
}
|
|
}
|
|
|
|
_renderChatHeader() {
|
|
const currentSession = this.sessionManager.getCurrentSession();
|
|
if (currentSession && this.elements.chatHeaderTitle) {
|
|
this.elements.chatHeaderTitle.textContent = currentSession.name;
|
|
}
|
|
}
|
|
|
|
_setLoadingState(isLoading) {
|
|
this.elements.messageInput.disabled = isLoading;
|
|
this.elements.sendBtn.disabled = isLoading;
|
|
if (isLoading) {
|
|
uiPatterns.setButtonLoading(this.elements.sendBtn);
|
|
} else {
|
|
uiPatterns.clearButtonLoading(this.elements.sendBtn);
|
|
}
|
|
}
|
|
_handleClearSession() {
|
|
Swal.fire({
|
|
width: '22rem',
|
|
backdrop: `rgba(0,0,0,0.5)`,
|
|
heightAuto: false,
|
|
customClass: { popup: `swal2-custom-style ${document.documentElement.classList.contains('dark') ? 'swal2-dark' : ''}` },
|
|
title: '确定要清空会话吗?',
|
|
text: '当前会话的所有聊天记录将被删除,但会话本身会保留。',
|
|
showCancelButton: true,
|
|
confirmButtonText: '确认清空',
|
|
cancelButtonText: '取消',
|
|
reverseButtons: false,
|
|
confirmButtonColor: '#ef4444',
|
|
cancelButtonColor: '#6b7280',
|
|
focusConfirm: false,
|
|
focusCancel: true,
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
this.sessionManager.clearCurrentSession(); // This method needs to be added to SessionManager
|
|
this._render();
|
|
}
|
|
});
|
|
}
|
|
_handleDeleteSession(sessionId) {
|
|
Swal.fire({
|
|
width: '22rem',
|
|
backdrop: `rgba(0,0,0,0.5)`,
|
|
heightAuto: false,
|
|
customClass: { popup: `swal2-custom-style ${document.documentElement.classList.contains('dark') ? 'swal2-dark' : ''}` },
|
|
title: '确定要删除吗?',
|
|
text: '此会话的所有记录将被永久删除,无法撤销。',
|
|
showCancelButton: true,
|
|
confirmButtonText: '确认删除',
|
|
cancelButtonText: '取消',
|
|
reverseButtons: false,
|
|
confirmButtonColor: '#ef4444',
|
|
cancelButtonColor: '#6b7280',
|
|
focusConfirm: false,
|
|
focusCancel: true,
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
this.sessionManager.deleteSession(sessionId);
|
|
this._render();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
export default function() {
|
|
const page = new ChatPage();
|
|
page.init();
|
|
}
|