Update: Basic Functions of chat.html 75% maybe
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
--secondary: theme(colors.zinc.200);
|
||||
--secondary-foreground: theme(colors.zinc.900);
|
||||
|
||||
--destructive: theme(colors.red.600);
|
||||
--destructive: theme(colors.red.500);
|
||||
--destructive-foreground: theme(colors.white);
|
||||
--accent: theme(colors.zinc.100);
|
||||
--accent-foreground: theme(colors.zinc.900);
|
||||
@@ -69,10 +69,10 @@
|
||||
@apply bg-primary text-primary-foreground hover:bg-primary/90;
|
||||
}
|
||||
.btn-secondary {
|
||||
@apply bg-secondary text-secondary-foreground hover:bg-secondary/80;
|
||||
@apply bg-secondary text-secondary-foreground border border-zinc-500/30 hover:bg-secondary/80;
|
||||
}
|
||||
.btn-destructive {
|
||||
@apply bg-destructive text-destructive-foreground hover:bg-destructive/90;
|
||||
@apply bg-destructive text-destructive-foreground border border-zinc-500/30 hover:bg-destructive/90;
|
||||
}
|
||||
.btn-outline {
|
||||
@apply border border-input bg-background hover:bg-accent hover:text-accent-foreground;
|
||||
@@ -83,7 +83,9 @@
|
||||
.btn-link {
|
||||
@apply text-primary underline-offset-4 hover:underline;
|
||||
}
|
||||
|
||||
.btn-group-item.active {
|
||||
@apply bg-primary text-primary-foreground;
|
||||
}
|
||||
/* 按钮尺寸变体 */
|
||||
.btn-lg { @apply h-11 rounded-md px-8; }
|
||||
.btn-md { @apply h-10 px-4 py-2; }
|
||||
|
||||
@@ -326,6 +326,41 @@ class UIPatterns {
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets a button to a loading state by disabling it and showing a spinner.
|
||||
* It stores the button's original content to be restored later.
|
||||
* @param {HTMLButtonElement} button - The button element to modify.
|
||||
*/
|
||||
setButtonLoading(button) {
|
||||
if (!button) return;
|
||||
// Store original content if it hasn't been stored already
|
||||
if (!button.dataset.originalContent) {
|
||||
button.dataset.originalContent = button.innerHTML;
|
||||
}
|
||||
button.disabled = true;
|
||||
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||
}
|
||||
/**
|
||||
* Restores a button from its loading state to its original content and enables it.
|
||||
* @param {HTMLButtonElement} button - The button element to restore.
|
||||
*/
|
||||
clearButtonLoading(button) {
|
||||
if (!button) return;
|
||||
if (button.dataset.originalContent) {
|
||||
button.innerHTML = button.dataset.originalContent;
|
||||
// Clean up the data attribute
|
||||
delete button.dataset.originalContent;
|
||||
}
|
||||
button.disabled = false;
|
||||
}
|
||||
/**
|
||||
* Returns the HTML for a streaming text cursor animation.
|
||||
* This is used as a placeholder in the chat UI while waiting for an assistant's response.
|
||||
* @returns {string} The HTML string for the loader.
|
||||
*/
|
||||
renderStreamingLoader() {
|
||||
return '<span class="streaming-cursor animate-pulse">▋</span>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@ const pageModules = {
|
||||
'dashboard': () => import('./pages/dashboard.js'),
|
||||
'keys': () => import('./pages/keys/index.js'),
|
||||
'logs': () => import('./pages/logs/index.js'),
|
||||
'chat': () => import('./pages/chat/index.js'),
|
||||
// 'settings': () => import('./pages/settings.js'), // 未来启用 settings 页面
|
||||
// 未来新增的页面,只需在这里添加一行映射,esbuild会自动处理
|
||||
};
|
||||
|
||||
178
frontend/js/pages/chat/SessionManager.js
Normal file
178
frontend/js/pages/chat/SessionManager.js
Normal file
@@ -0,0 +1,178 @@
|
||||
// Filename: frontend/js/pages/chat/SessionManager.js
|
||||
|
||||
import { nanoid } from 'https://cdn.jsdelivr.net/npm/nanoid/nanoid.js';
|
||||
|
||||
const LOCAL_STORAGE_KEY = 'gemini_chat_state';
|
||||
|
||||
/**
|
||||
* Manages the state and persistence of chat sessions.
|
||||
* This class handles loading from/saving to localStorage,
|
||||
* and all operations like creating, switching, and deleting sessions.
|
||||
*/
|
||||
export class SessionManager {
|
||||
constructor() {
|
||||
this.state = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the manager by loading state from localStorage or creating a default state.
|
||||
*/
|
||||
init() {
|
||||
this._loadState();
|
||||
}
|
||||
|
||||
// --- Public API for state access ---
|
||||
|
||||
getSessions() {
|
||||
return this.state.sessions;
|
||||
}
|
||||
|
||||
getCurrentSessionId() {
|
||||
return this.state.currentSessionId;
|
||||
}
|
||||
|
||||
getCurrentSession() {
|
||||
return this.state.sessions.find(s => s.id === this.state.currentSessionId);
|
||||
}
|
||||
|
||||
// --- Public API for state mutation ---
|
||||
|
||||
/**
|
||||
* Creates a new, empty session and sets it as the current one.
|
||||
*/
|
||||
createSession() {
|
||||
const newSessionId = nanoid();
|
||||
const newSession = {
|
||||
id: newSessionId,
|
||||
name: '新会话',
|
||||
systemPrompt: '',
|
||||
messages: [],
|
||||
modelConfig: { model: 'gemini-2.0-flash-lite' },
|
||||
params: { temperature: 0.7 }
|
||||
};
|
||||
this.state.sessions.unshift(newSession);
|
||||
this.state.currentSessionId = newSessionId;
|
||||
this._saveState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the current session to the one with the given ID.
|
||||
* @param {string} sessionId The ID of the session to switch to.
|
||||
*/
|
||||
switchSession(sessionId) {
|
||||
if (this.state.currentSessionId === sessionId) return;
|
||||
this.state.currentSessionId = sessionId;
|
||||
this._saveState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a session by its ID.
|
||||
* @param {string} sessionId The ID of the session to delete.
|
||||
*/
|
||||
deleteSession(sessionId) {
|
||||
this.state.sessions = this.state.sessions.filter(s => s.id !== sessionId);
|
||||
|
||||
if (this.state.currentSessionId === sessionId) {
|
||||
this.state.currentSessionId = this.state.sessions[0]?.id || null;
|
||||
if (!this.state.currentSessionId) {
|
||||
this._createInitialState(); // Create a new one if all are deleted
|
||||
}
|
||||
}
|
||||
|
||||
this._saveState();
|
||||
}
|
||||
|
||||
/**
|
||||
* [NEW] Clears all messages from the currently active session.
|
||||
*/
|
||||
clearCurrentSession() {
|
||||
const currentSession = this.getCurrentSession();
|
||||
if (currentSession) {
|
||||
currentSession.messages = [];
|
||||
this._saveState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a message to the current session and updates the session name if it's the first message.
|
||||
* @param {object} message The message object to add.
|
||||
* @returns {object} The session that was updated.
|
||||
*/
|
||||
addMessage(message) {
|
||||
const currentSession = this.getCurrentSession();
|
||||
if (currentSession) {
|
||||
if (currentSession.messages.length === 0 && message.role === 'user') {
|
||||
currentSession.name = message.content.substring(0, 30);
|
||||
}
|
||||
currentSession.messages.push(message);
|
||||
this._saveState();
|
||||
return currentSession;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
deleteMessage(messageId) {
|
||||
const currentSession = this.getCurrentSession();
|
||||
if (currentSession) {
|
||||
const messageIndex = currentSession.messages.findIndex(m => m.id === messageId);
|
||||
if (messageIndex > -1) {
|
||||
currentSession.messages.splice(messageIndex, 1);
|
||||
this._saveState();
|
||||
console.log(`Message ${messageId} deleted.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
truncateMessagesAfter(messageId) {
|
||||
const currentSession = this.getCurrentSession();
|
||||
if (currentSession) {
|
||||
const messageIndex = currentSession.messages.findIndex(m => m.id === messageId);
|
||||
// Ensure the message exists and it's not already the last one
|
||||
if (messageIndex > -1 && messageIndex < currentSession.messages.length - 1) {
|
||||
currentSession.messages.splice(messageIndex + 1);
|
||||
this._saveState();
|
||||
console.log(`Truncated messages after ${messageId}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// --- Private persistence methods ---
|
||||
|
||||
_saveState() {
|
||||
try {
|
||||
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(this.state));
|
||||
} catch (error) {
|
||||
console.error("Failed to save session state:", error);
|
||||
}
|
||||
}
|
||||
|
||||
_loadState() {
|
||||
try {
|
||||
const stateString = localStorage.getItem(LOCAL_STORAGE_KEY);
|
||||
if (stateString) {
|
||||
this.state = JSON.parse(stateString);
|
||||
} else {
|
||||
this._createInitialState();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load session state, creating initial state:", error);
|
||||
this._createInitialState();
|
||||
}
|
||||
}
|
||||
|
||||
_createInitialState() {
|
||||
const initialSessionId = nanoid();
|
||||
this.state = {
|
||||
sessions: [{
|
||||
id: initialSessionId,
|
||||
name: '新会话',
|
||||
systemPrompt: '',
|
||||
messages: [],
|
||||
modelConfig: { model: 'gemini-2.0-flash-lite' },
|
||||
params: { temperature: 0.7 }
|
||||
}],
|
||||
currentSessionId: initialSessionId,
|
||||
settings: {}
|
||||
};
|
||||
this._saveState();
|
||||
}
|
||||
}
|
||||
113
frontend/js/pages/chat/chatSettings.js
Normal file
113
frontend/js/pages/chat/chatSettings.js
Normal file
@@ -0,0 +1,113 @@
|
||||
// Filename: frontend/js/pages/chat/chatSettings.js
|
||||
|
||||
export class ChatSettings {
|
||||
constructor(elements) {
|
||||
// [MODIFIED] Store the root elements passed from ChatPage
|
||||
this.elements = {};
|
||||
this.elements.root = elements; // Keep a reference to all elements
|
||||
|
||||
// [MODIFIED] Query for specific elements this class controls, relative to their panels
|
||||
this._initScopedDOMElements();
|
||||
|
||||
// Initialize panel states to ensure they are collapsed on load
|
||||
this.elements.quickSettingsPanel.style.gridTemplateRows = '0fr';
|
||||
this.elements.sessionParamsPanel.style.gridTemplateRows = '0fr';
|
||||
}
|
||||
|
||||
// [NEW] A dedicated method to find elements within their specific panels
|
||||
_initScopedDOMElements() {
|
||||
this.elements.quickSettingsPanel = this.elements.root.quickSettingsPanel;
|
||||
this.elements.sessionParamsPanel = this.elements.root.sessionParamsPanel;
|
||||
this.elements.toggleQuickSettingsBtn = this.elements.root.toggleQuickSettingsBtn;
|
||||
this.elements.toggleSessionParamsBtn = this.elements.root.toggleSessionParamsBtn;
|
||||
|
||||
// Query elements within the quick settings panel
|
||||
this.elements.btnGroups = this.elements.quickSettingsPanel.querySelectorAll('.btn-group');
|
||||
this.elements.directRoutingOptions = this.elements.quickSettingsPanel.querySelector('#direct-routing-options');
|
||||
|
||||
// Query elements within the session params panel
|
||||
this.elements.temperatureSlider = this.elements.sessionParamsPanel.querySelector('#temperature-slider');
|
||||
this.elements.temperatureValue = this.elements.sessionParamsPanel.querySelector('#temperature-value');
|
||||
this.elements.contextSlider = this.elements.sessionParamsPanel.querySelector('#context-slider');
|
||||
this.elements.contextValue = this.elements.sessionParamsPanel.querySelector('#context-value');
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.elements.toggleQuickSettingsBtn) {
|
||||
console.warn("ChatSettings: Aborting initialization, required elements not found.");
|
||||
return;
|
||||
}
|
||||
this._initPanelToggleListeners();
|
||||
this._initButtonGroupListeners();
|
||||
this._initSliderListeners();
|
||||
}
|
||||
|
||||
_initPanelToggleListeners() {
|
||||
this.elements.toggleQuickSettingsBtn.addEventListener('click', () =>
|
||||
this._togglePanel(this.elements.quickSettingsPanel, this.elements.toggleQuickSettingsBtn)
|
||||
);
|
||||
this.elements.toggleSessionParamsBtn.addEventListener('click', () =>
|
||||
this._togglePanel(this.elements.sessionParamsPanel, this.elements.toggleSessionParamsBtn)
|
||||
);
|
||||
}
|
||||
|
||||
_initButtonGroupListeners() {
|
||||
// [FIXED] This logic is now guaranteed to work with the correctly scoped elements.
|
||||
this.elements.btnGroups.forEach(group => {
|
||||
group.addEventListener('click', (e) => {
|
||||
const button = e.target.closest('.btn-group-item');
|
||||
if (!button) return;
|
||||
|
||||
group.querySelectorAll('.btn-group-item').forEach(btn => btn.removeAttribute('data-active'));
|
||||
button.setAttribute('data-active', 'true');
|
||||
|
||||
if (button.dataset.group === 'routing-mode') {
|
||||
this._handleRoutingModeChange(button.dataset.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_initSliderListeners() {
|
||||
// [FIXED] Add null checks for robustness, now that elements are queried scoped.
|
||||
if (this.elements.temperatureSlider) {
|
||||
this.elements.temperatureSlider.addEventListener('input', () => {
|
||||
this.elements.temperatureValue.textContent = parseFloat(this.elements.temperatureSlider.value).toFixed(1);
|
||||
});
|
||||
}
|
||||
if (this.elements.contextSlider) {
|
||||
this.elements.contextSlider.addEventListener('input', () => {
|
||||
this.elements.contextValue.textContent = `${this.elements.contextSlider.value}k`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_handleRoutingModeChange(selectedValue) {
|
||||
// [FIXED] This logic now correctly targets the scoped element.
|
||||
if (this.elements.directRoutingOptions) {
|
||||
if (selectedValue === 'direct') {
|
||||
this.elements.directRoutingOptions.classList.remove('hidden');
|
||||
} else {
|
||||
this.elements.directRoutingOptions.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_togglePanel(panel, button) {
|
||||
const isExpanded = panel.hasAttribute('data-expanded');
|
||||
|
||||
this.elements.quickSettingsPanel.removeAttribute('data-expanded');
|
||||
this.elements.sessionParamsPanel.removeAttribute('data-expanded');
|
||||
this.elements.toggleQuickSettingsBtn.removeAttribute('data-active');
|
||||
this.elements.toggleSessionParamsBtn.removeAttribute('data-active');
|
||||
|
||||
this.elements.quickSettingsPanel.style.gridTemplateRows = '0fr';
|
||||
this.elements.sessionParamsPanel.style.gridTemplateRows = '0fr';
|
||||
|
||||
if (!isExpanded) {
|
||||
panel.setAttribute('data-expanded', 'true');
|
||||
button.setAttribute('data-active', 'true');
|
||||
panel.style.gridTemplateRows = '1fr';
|
||||
}
|
||||
}
|
||||
}
|
||||
545
frontend/js/pages/chat/index.js
Normal file
545
frontend/js/pages/chat/index.js
Normal file
@@ -0,0 +1,545 @@
|
||||
// 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();
|
||||
}
|
||||
72
frontend/js/vendor/marked.min.js
vendored
Normal file
72
frontend/js/vendor/marked.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/js/vendor/nanoid.js
vendored
Normal file
1
frontend/js/vendor/nanoid.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
let a="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";export let nanoid=(e=21)=>{let t="",r=crypto.getRandomValues(new Uint8Array(e));for(let n=0;n<e;n++)t+=a[63&r[n]];return t};
|
||||
@@ -8,6 +8,12 @@ module.exports = {
|
||||
'./web/templates/**/*.html',
|
||||
'./web/static/js/**/*.js',
|
||||
],
|
||||
safelist: [
|
||||
'grid-rows-[1]',
|
||||
{
|
||||
pattern: /data-\[(expanded|active)\]/,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
// 定义语义化颜色
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
--color-cyan-900: oklch(39.8% 0.07 227.392);
|
||||
--color-sky-100: oklch(95.1% 0.026 236.824);
|
||||
--color-sky-300: oklch(82.8% 0.111 230.318);
|
||||
--color-sky-500: oklch(68.5% 0.169 237.323);
|
||||
--color-sky-800: oklch(44.3% 0.11 240.79);
|
||||
--color-sky-900: oklch(39.1% 0.09 240.876);
|
||||
--color-blue-50: oklch(97% 0.014 254.604);
|
||||
@@ -144,6 +145,7 @@
|
||||
--font-weight-extrabold: 800;
|
||||
--tracking-tight: -0.025em;
|
||||
--tracking-wider: 0.05em;
|
||||
--radius-xs: 0.125rem;
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: 0.75rem;
|
||||
@@ -154,6 +156,7 @@
|
||||
--animate-spin: spin 1s linear infinite;
|
||||
--animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
--animate-bounce: bounce 1s infinite;
|
||||
--blur-sm: 8px;
|
||||
--blur-md: 12px;
|
||||
--blur-lg: 16px;
|
||||
@@ -327,6 +330,9 @@
|
||||
}
|
||||
}
|
||||
@layer utilities {
|
||||
.\@container {
|
||||
container-type: inline-size;
|
||||
}
|
||||
.pointer-events-none {
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -398,8 +404,8 @@
|
||||
.right-2 {
|
||||
right: calc(var(--spacing) * 2);
|
||||
}
|
||||
.right-3 {
|
||||
right: calc(var(--spacing) * 3);
|
||||
.right-2\.5 {
|
||||
right: calc(var(--spacing) * 2.5);
|
||||
}
|
||||
.right-4 {
|
||||
right: calc(var(--spacing) * 4);
|
||||
@@ -416,18 +422,30 @@
|
||||
.bottom-0 {
|
||||
bottom: calc(var(--spacing) * 0);
|
||||
}
|
||||
.bottom-2 {
|
||||
bottom: calc(var(--spacing) * 2);
|
||||
}
|
||||
.bottom-2\.5 {
|
||||
bottom: calc(var(--spacing) * 2.5);
|
||||
}
|
||||
.bottom-4 {
|
||||
bottom: calc(var(--spacing) * 4);
|
||||
}
|
||||
.bottom-6 {
|
||||
bottom: calc(var(--spacing) * 6);
|
||||
}
|
||||
.bottom-full {
|
||||
bottom: 100%;
|
||||
}
|
||||
.left-0 {
|
||||
left: calc(var(--spacing) * 0);
|
||||
}
|
||||
.left-1 {
|
||||
left: calc(var(--spacing) * 1);
|
||||
}
|
||||
.left-2 {
|
||||
left: calc(var(--spacing) * 2);
|
||||
}
|
||||
.left-3 {
|
||||
left: calc(var(--spacing) * 3);
|
||||
}
|
||||
@@ -711,6 +729,9 @@
|
||||
.min-h-0 {
|
||||
min-height: calc(var(--spacing) * 0);
|
||||
}
|
||||
.min-h-14 {
|
||||
min-height: calc(var(--spacing) * 14);
|
||||
}
|
||||
.min-h-\[56px\] {
|
||||
min-height: 56px;
|
||||
}
|
||||
@@ -747,9 +768,18 @@
|
||||
.w-8 {
|
||||
width: calc(var(--spacing) * 8);
|
||||
}
|
||||
.w-9 {
|
||||
width: calc(var(--spacing) * 9);
|
||||
}
|
||||
.w-10 {
|
||||
width: calc(var(--spacing) * 10);
|
||||
}
|
||||
.w-11 {
|
||||
width: calc(var(--spacing) * 11);
|
||||
}
|
||||
.w-12 {
|
||||
width: calc(var(--spacing) * 12);
|
||||
}
|
||||
.w-14 {
|
||||
width: calc(var(--spacing) * 14);
|
||||
}
|
||||
@@ -774,6 +804,9 @@
|
||||
.w-40 {
|
||||
width: calc(var(--spacing) * 40);
|
||||
}
|
||||
.w-45 {
|
||||
width: calc(var(--spacing) * 45);
|
||||
}
|
||||
.w-48 {
|
||||
width: calc(var(--spacing) * 48);
|
||||
}
|
||||
@@ -792,6 +825,9 @@
|
||||
.w-\[280px\] {
|
||||
width: 280px;
|
||||
}
|
||||
.w-\[var\(--radix-popper-anchor-width\)\] {
|
||||
width: var(--radix-popper-anchor-width);
|
||||
}
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -816,6 +852,9 @@
|
||||
.max-w-md {
|
||||
max-width: var(--container-md);
|
||||
}
|
||||
.max-w-none {
|
||||
max-width: none;
|
||||
}
|
||||
.max-w-sm {
|
||||
max-width: var(--container-sm);
|
||||
}
|
||||
@@ -874,6 +913,10 @@
|
||||
--tw-translate-x: calc(var(--spacing) * 1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.translate-x-12 {
|
||||
--tw-translate-x: calc(var(--spacing) * 12);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.translate-x-full {
|
||||
--tw-translate-x: 100%;
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
@@ -916,6 +959,9 @@
|
||||
.transform {
|
||||
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
||||
}
|
||||
.animate-bounce {
|
||||
animation: var(--animate-bounce);
|
||||
}
|
||||
.animate-ping {
|
||||
animation: var(--animate-ping);
|
||||
}
|
||||
@@ -955,6 +1001,12 @@
|
||||
.grid-cols-3 {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
.grid-rows-\[0\] {
|
||||
grid-template-rows: 0;
|
||||
}
|
||||
.grid-rows-\[1\] {
|
||||
grid-template-rows: 1;
|
||||
}
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -994,6 +1046,9 @@
|
||||
.gap-6 {
|
||||
gap: calc(var(--spacing) * 6);
|
||||
}
|
||||
.gap-px {
|
||||
gap: 1px;
|
||||
}
|
||||
.space-y-0 {
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-space-y-reverse: 0;
|
||||
@@ -1136,6 +1191,9 @@
|
||||
.overflow-y-auto {
|
||||
overflow-y: auto;
|
||||
}
|
||||
.overflow-y-hidden {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
@@ -1154,6 +1212,9 @@
|
||||
.rounded-xl {
|
||||
border-radius: var(--radius-xl);
|
||||
}
|
||||
.rounded-xs {
|
||||
border-radius: var(--radius-xs);
|
||||
}
|
||||
.rounded-l-md {
|
||||
border-top-left-radius: var(--radius-md);
|
||||
border-bottom-left-radius: var(--radius-md);
|
||||
@@ -1235,15 +1296,6 @@
|
||||
.border-green-200 {
|
||||
border-color: var(--color-green-200);
|
||||
}
|
||||
.border-primary {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
.border-primary\/20 {
|
||||
border-color: var(--color-primary);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-primary) 20%, transparent);
|
||||
}
|
||||
}
|
||||
.border-red-100 {
|
||||
border-color: var(--color-red-100);
|
||||
}
|
||||
@@ -1343,6 +1395,15 @@
|
||||
background-color: color-mix(in oklab, var(--color-destructive) 10%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-foreground {
|
||||
background-color: var(--color-foreground);
|
||||
}
|
||||
.bg-foreground\/50 {
|
||||
background-color: var(--color-foreground);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-foreground) 50%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-gray-50 {
|
||||
background-color: var(--color-gray-50);
|
||||
}
|
||||
@@ -1622,6 +1683,9 @@
|
||||
.p-4 {
|
||||
padding: calc(var(--spacing) * 4);
|
||||
}
|
||||
.p-5 {
|
||||
padding: calc(var(--spacing) * 5);
|
||||
}
|
||||
.p-6 {
|
||||
padding: calc(var(--spacing) * 6);
|
||||
}
|
||||
@@ -1679,6 +1743,9 @@
|
||||
.pt-2 {
|
||||
padding-top: calc(var(--spacing) * 2);
|
||||
}
|
||||
.pt-3 {
|
||||
padding-top: calc(var(--spacing) * 3);
|
||||
}
|
||||
.pt-4 {
|
||||
padding-top: calc(var(--spacing) * 4);
|
||||
}
|
||||
@@ -1703,6 +1770,9 @@
|
||||
.pr-20 {
|
||||
padding-right: calc(var(--spacing) * 20);
|
||||
}
|
||||
.pr-24 {
|
||||
padding-right: calc(var(--spacing) * 24);
|
||||
}
|
||||
.pb-1 {
|
||||
padding-bottom: calc(var(--spacing) * 1);
|
||||
}
|
||||
@@ -1911,6 +1981,12 @@
|
||||
.text-muted-foreground {
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
.text-muted-foreground\/60 {
|
||||
color: var(--color-muted-foreground);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
color: color-mix(in oklab, var(--color-muted-foreground) 60%, transparent);
|
||||
}
|
||||
}
|
||||
.text-orange-500 {
|
||||
color: var(--color-orange-500);
|
||||
}
|
||||
@@ -2137,6 +2213,11 @@
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
}
|
||||
.transition-\[grid-template-rows\] {
|
||||
transition-property: grid-template-rows;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
}
|
||||
.transition-all {
|
||||
transition-property: all;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
@@ -2226,19 +2307,108 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.even\:bg-zinc-50\/50 {
|
||||
&:nth-child(even) {
|
||||
background-color: color-mix(in srgb, oklch(98.5% 0 0) 50%, transparent);
|
||||
.peer-checked\:bg-primary {
|
||||
&:is(:where(.peer):checked ~ *) {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
.peer-focus\:ring-2 {
|
||||
&:is(:where(.peer):focus ~ *) {
|
||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
}
|
||||
.peer-focus\:ring-primary\/50 {
|
||||
&:is(:where(.peer):focus ~ *) {
|
||||
--tw-ring-color: var(--color-primary);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-zinc-50) 50%, transparent);
|
||||
--tw-ring-color: color-mix(in oklab, var(--color-primary) 50%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
.even\:bg-zinc-100\/50 {
|
||||
&:nth-child(even) {
|
||||
background-color: color-mix(in srgb, oklch(96.7% 0.001 286.375) 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-zinc-100) 50%, transparent);
|
||||
.after\:absolute {
|
||||
&::after {
|
||||
content: var(--tw-content);
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
.after\:top-0\.5 {
|
||||
&::after {
|
||||
content: var(--tw-content);
|
||||
top: calc(var(--spacing) * 0.5);
|
||||
}
|
||||
}
|
||||
.after\:left-\[2px\] {
|
||||
&::after {
|
||||
content: var(--tw-content);
|
||||
left: 2px;
|
||||
}
|
||||
}
|
||||
.after\:h-5 {
|
||||
&::after {
|
||||
content: var(--tw-content);
|
||||
height: calc(var(--spacing) * 5);
|
||||
}
|
||||
}
|
||||
.after\:w-5 {
|
||||
&::after {
|
||||
content: var(--tw-content);
|
||||
width: calc(var(--spacing) * 5);
|
||||
}
|
||||
}
|
||||
.after\:rounded-full {
|
||||
&::after {
|
||||
content: var(--tw-content);
|
||||
border-radius: calc(infinity * 1px);
|
||||
}
|
||||
}
|
||||
.after\:border {
|
||||
&::after {
|
||||
content: var(--tw-content);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
}
|
||||
}
|
||||
.after\:border-border {
|
||||
&::after {
|
||||
content: var(--tw-content);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
}
|
||||
.after\:bg-white {
|
||||
&::after {
|
||||
content: var(--tw-content);
|
||||
background-color: var(--color-white);
|
||||
}
|
||||
}
|
||||
.after\:transition-all {
|
||||
&::after {
|
||||
content: var(--tw-content);
|
||||
transition-property: all;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
}
|
||||
}
|
||||
.after\:content-\[\'\'\] {
|
||||
&::after {
|
||||
--tw-content: '';
|
||||
content: var(--tw-content);
|
||||
}
|
||||
}
|
||||
.peer-checked\:after\:translate-x-full {
|
||||
&:is(:where(.peer):checked ~ *) {
|
||||
&::after {
|
||||
content: var(--tw-content);
|
||||
--tw-translate-x: 100%;
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
}
|
||||
}
|
||||
.peer-checked\:after\:border-white {
|
||||
&:is(:where(.peer):checked ~ *) {
|
||||
&::after {
|
||||
content: var(--tw-content);
|
||||
border-color: var(--color-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2250,14 +2420,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.even\:bg-zinc-200\/50 {
|
||||
&:nth-child(even) {
|
||||
background-color: color-mix(in srgb, oklch(92% 0.004 286.32) 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-zinc-200) 50%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
.focus-within\:border-blue-500 {
|
||||
&:focus-within {
|
||||
border-color: var(--color-blue-500);
|
||||
@@ -2365,6 +2527,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-destructive\/80 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-destructive);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-destructive) 80%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-gray-50 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -2407,6 +2579,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-green-600 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-green-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-green-700 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -2522,6 +2701,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-destructive-foreground {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-destructive-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-gray-800 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -2550,6 +2736,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-sky-500 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-sky-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-yellow-500 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -2580,6 +2773,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.focus\:relative {
|
||||
&:focus {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
.focus\:border-blue-500 {
|
||||
&:focus {
|
||||
border-color: var(--color-blue-500);
|
||||
@@ -2664,11 +2862,49 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
.disabled\:bg-green-500\/50 {
|
||||
&:disabled {
|
||||
background-color: color-mix(in srgb, oklch(72.3% 0.219 149.579) 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-green-500) 50%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled\:opacity-50 {
|
||||
&:disabled {
|
||||
opacity: 50%;
|
||||
}
|
||||
}
|
||||
.data-\[active\=true\]\:bg-muted {
|
||||
&[data-active="true"] {
|
||||
background-color: var(--color-muted);
|
||||
}
|
||||
}
|
||||
.data-\[active\=true\]\:bg-primary {
|
||||
&[data-active="true"] {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
.data-\[active\=true\]\:text-accent-foreground {
|
||||
&[data-active="true"] {
|
||||
color: var(--color-accent-foreground);
|
||||
}
|
||||
}
|
||||
.data-\[active\=true\]\:text-primary-foreground {
|
||||
&[data-active="true"] {
|
||||
color: var(--color-primary-foreground);
|
||||
}
|
||||
}
|
||||
.data-\[expanded\=true\]\:visible {
|
||||
&[data-expanded="true"] {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.data-\[expanded\=true\]\:grid-rows-\[1\] {
|
||||
&[data-expanded="true"] {
|
||||
grid-template-rows: 1;
|
||||
}
|
||||
}
|
||||
.sm\:w-48 {
|
||||
@media (width >= 40rem) {
|
||||
width: calc(var(--spacing) * 48);
|
||||
@@ -2957,6 +3193,11 @@
|
||||
background-color: var(--color-gray-800);
|
||||
}
|
||||
}
|
||||
.dark\:bg-green-600 {
|
||||
&:where(.dark, .dark *) {
|
||||
background-color: var(--color-green-600);
|
||||
}
|
||||
}
|
||||
.dark\:bg-green-900 {
|
||||
&:where(.dark, .dark *) {
|
||||
background-color: var(--color-green-900);
|
||||
@@ -3107,6 +3348,11 @@
|
||||
background-color: var(--color-zinc-950);
|
||||
}
|
||||
}
|
||||
.dark\:text-black {
|
||||
&:where(.dark, .dark *) {
|
||||
color: var(--color-black);
|
||||
}
|
||||
}
|
||||
.dark\:text-blue-300 {
|
||||
&:where(.dark, .dark *) {
|
||||
color: var(--color-blue-300);
|
||||
@@ -3235,16 +3481,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:even\:bg-black\/5 {
|
||||
&:where(.dark, .dark *) {
|
||||
&:nth-child(even) {
|
||||
background-color: color-mix(in srgb, #000 5%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-black) 5%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:even\:bg-black\/10 {
|
||||
&:where(.dark, .dark *) {
|
||||
&:nth-child(even) {
|
||||
@@ -3255,86 +3491,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:even\:bg-black\/20 {
|
||||
&:where(.dark, .dark *) {
|
||||
&:nth-child(even) {
|
||||
background-color: color-mix(in srgb, #000 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-black) 20%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:even\:bg-black\/30 {
|
||||
&:where(.dark, .dark *) {
|
||||
&:nth-child(even) {
|
||||
background-color: color-mix(in srgb, #000 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-black) 30%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:even\:bg-black\/50 {
|
||||
&:where(.dark, .dark *) {
|
||||
&:nth-child(even) {
|
||||
background-color: color-mix(in srgb, #000 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-black) 50%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:even\:bg-black\/60 {
|
||||
&:where(.dark, .dark *) {
|
||||
&:nth-child(even) {
|
||||
background-color: color-mix(in srgb, #000 60%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-black) 60%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:even\:bg-black\/90 {
|
||||
&:where(.dark, .dark *) {
|
||||
&:nth-child(even) {
|
||||
background-color: color-mix(in srgb, #000 90%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-black) 90%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:even\:bg-white\/5 {
|
||||
&:where(.dark, .dark *) {
|
||||
&:nth-child(even) {
|
||||
background-color: color-mix(in srgb, #fff 5%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-white) 5%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:even\:bg-white\/15 {
|
||||
&:where(.dark, .dark *) {
|
||||
&:nth-child(even) {
|
||||
background-color: color-mix(in srgb, #fff 15%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-white) 15%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:even\:bg-white\/50 {
|
||||
&:where(.dark, .dark *) {
|
||||
&:nth-child(even) {
|
||||
background-color: color-mix(in srgb, #fff 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-white) 50%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:hover\:border-blue-400 {
|
||||
&:where(.dark, .dark *) {
|
||||
&:hover {
|
||||
@@ -3356,6 +3512,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:hover\:bg-green-700 {
|
||||
&:where(.dark, .dark *) {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-green-700);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:hover\:bg-muted\/80 {
|
||||
&:where(.dark, .dark *) {
|
||||
&:hover {
|
||||
@@ -3443,7 +3608,7 @@
|
||||
--primary-foreground: #fff;
|
||||
--secondary: oklch(92% 0.004 286.32);
|
||||
--secondary-foreground: oklch(21% 0.006 285.885);
|
||||
--destructive: oklch(57.7% 0.245 27.325);
|
||||
--destructive: oklch(63.7% 0.237 25.331);
|
||||
--destructive-foreground: #fff;
|
||||
--accent: oklch(96.7% 0.001 286.375);
|
||||
--accent-foreground: oklch(21% 0.006 285.885);
|
||||
@@ -3518,6 +3683,12 @@
|
||||
}
|
||||
}
|
||||
.btn-secondary {
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
border-color: color-mix(in srgb, oklch(55.2% 0.016 285.938) 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-zinc-500) 30%, transparent);
|
||||
}
|
||||
background-color: var(--color-secondary);
|
||||
color: var(--color-secondary-foreground);
|
||||
&:hover {
|
||||
@@ -3530,6 +3701,12 @@
|
||||
}
|
||||
}
|
||||
.btn-destructive {
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
border-color: color-mix(in srgb, oklch(55.2% 0.016 285.938) 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-zinc-500) 30%, transparent);
|
||||
}
|
||||
background-color: var(--color-destructive);
|
||||
color: var(--color-destructive-foreground);
|
||||
&:hover {
|
||||
@@ -3578,6 +3755,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-group-item.active {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-primary-foreground);
|
||||
}
|
||||
.btn-lg {
|
||||
height: calc(var(--spacing) * 11);
|
||||
border-radius: var(--radius-md);
|
||||
@@ -5888,6 +6069,11 @@
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-content {
|
||||
syntax: "*";
|
||||
initial-value: "";
|
||||
inherits: false;
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
@@ -5904,6 +6090,16 @@
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
@keyframes bounce {
|
||||
0%, 100% {
|
||||
transform: translateY(-25%);
|
||||
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
||||
}
|
||||
50% {
|
||||
transform: none;
|
||||
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
@layer properties {
|
||||
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
||||
*, ::before, ::after, ::backdrop {
|
||||
@@ -5978,6 +6174,7 @@
|
||||
--tw-backdrop-sepia: initial;
|
||||
--tw-duration: initial;
|
||||
--tw-ease: initial;
|
||||
--tw-content: "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1926
web/static/js/chat-2W4NJWMO.js
Normal file
1926
web/static/js/chat-2W4NJWMO.js
Normal file
File diff suppressed because it is too large
Load Diff
606
web/static/js/chunk-T5V6LQ42.js
Normal file
606
web/static/js/chunk-T5V6LQ42.js
Normal file
@@ -0,0 +1,606 @@
|
||||
import {
|
||||
__commonJS,
|
||||
__toESM
|
||||
} from "./chunk-JSBRDJBE.js";
|
||||
|
||||
// frontend/js/vendor/popper.esm.min.js
|
||||
var require_popper_esm_min = __commonJS({
|
||||
"frontend/js/vendor/popper.esm.min.js"(exports, module) {
|
||||
!(function(e, t) {
|
||||
"object" == typeof exports && "undefined" != typeof module ? t(exports) : "function" == typeof define && define.amd ? define(["exports"], t) : t((e = "undefined" != typeof globalThis ? globalThis : e || self).Popper = {});
|
||||
})(exports, (function(e) {
|
||||
"use strict";
|
||||
function t(e2) {
|
||||
if (null == e2) return window;
|
||||
if ("[object Window]" !== e2.toString()) {
|
||||
var t2 = e2.ownerDocument;
|
||||
return t2 && t2.defaultView || window;
|
||||
}
|
||||
return e2;
|
||||
}
|
||||
function n(e2) {
|
||||
return e2 instanceof t(e2).Element || e2 instanceof Element;
|
||||
}
|
||||
function r(e2) {
|
||||
return e2 instanceof t(e2).HTMLElement || e2 instanceof HTMLElement;
|
||||
}
|
||||
function o(e2) {
|
||||
return "undefined" != typeof ShadowRoot && (e2 instanceof t(e2).ShadowRoot || e2 instanceof ShadowRoot);
|
||||
}
|
||||
var i = Math.max, a = Math.min, s = Math.round;
|
||||
function f() {
|
||||
var e2 = navigator.userAgentData;
|
||||
return null != e2 && e2.brands && Array.isArray(e2.brands) ? e2.brands.map((function(e3) {
|
||||
return e3.brand + "/" + e3.version;
|
||||
})).join(" ") : navigator.userAgent;
|
||||
}
|
||||
function c() {
|
||||
return !/^((?!chrome|android).)*safari/i.test(f());
|
||||
}
|
||||
function p(e2, o2, i2) {
|
||||
void 0 === o2 && (o2 = false), void 0 === i2 && (i2 = false);
|
||||
var a2 = e2.getBoundingClientRect(), f2 = 1, p2 = 1;
|
||||
o2 && r(e2) && (f2 = e2.offsetWidth > 0 && s(a2.width) / e2.offsetWidth || 1, p2 = e2.offsetHeight > 0 && s(a2.height) / e2.offsetHeight || 1);
|
||||
var u2 = (n(e2) ? t(e2) : window).visualViewport, l2 = !c() && i2, d2 = (a2.left + (l2 && u2 ? u2.offsetLeft : 0)) / f2, h2 = (a2.top + (l2 && u2 ? u2.offsetTop : 0)) / p2, m2 = a2.width / f2, v2 = a2.height / p2;
|
||||
return { width: m2, height: v2, top: h2, right: d2 + m2, bottom: h2 + v2, left: d2, x: d2, y: h2 };
|
||||
}
|
||||
function u(e2) {
|
||||
var n2 = t(e2);
|
||||
return { scrollLeft: n2.pageXOffset, scrollTop: n2.pageYOffset };
|
||||
}
|
||||
function l(e2) {
|
||||
return e2 ? (e2.nodeName || "").toLowerCase() : null;
|
||||
}
|
||||
function d(e2) {
|
||||
return ((n(e2) ? e2.ownerDocument : e2.document) || window.document).documentElement;
|
||||
}
|
||||
function h(e2) {
|
||||
return p(d(e2)).left + u(e2).scrollLeft;
|
||||
}
|
||||
function m(e2) {
|
||||
return t(e2).getComputedStyle(e2);
|
||||
}
|
||||
function v(e2) {
|
||||
var t2 = m(e2), n2 = t2.overflow, r2 = t2.overflowX, o2 = t2.overflowY;
|
||||
return /auto|scroll|overlay|hidden/.test(n2 + o2 + r2);
|
||||
}
|
||||
function y(e2, n2, o2) {
|
||||
void 0 === o2 && (o2 = false);
|
||||
var i2, a2, f2 = r(n2), c2 = r(n2) && (function(e3) {
|
||||
var t2 = e3.getBoundingClientRect(), n3 = s(t2.width) / e3.offsetWidth || 1, r2 = s(t2.height) / e3.offsetHeight || 1;
|
||||
return 1 !== n3 || 1 !== r2;
|
||||
})(n2), m2 = d(n2), y2 = p(e2, c2, o2), g2 = { scrollLeft: 0, scrollTop: 0 }, b2 = { x: 0, y: 0 };
|
||||
return (f2 || !f2 && !o2) && (("body" !== l(n2) || v(m2)) && (g2 = (i2 = n2) !== t(i2) && r(i2) ? { scrollLeft: (a2 = i2).scrollLeft, scrollTop: a2.scrollTop } : u(i2)), r(n2) ? ((b2 = p(n2, true)).x += n2.clientLeft, b2.y += n2.clientTop) : m2 && (b2.x = h(m2))), { x: y2.left + g2.scrollLeft - b2.x, y: y2.top + g2.scrollTop - b2.y, width: y2.width, height: y2.height };
|
||||
}
|
||||
function g(e2) {
|
||||
var t2 = p(e2), n2 = e2.offsetWidth, r2 = e2.offsetHeight;
|
||||
return Math.abs(t2.width - n2) <= 1 && (n2 = t2.width), Math.abs(t2.height - r2) <= 1 && (r2 = t2.height), { x: e2.offsetLeft, y: e2.offsetTop, width: n2, height: r2 };
|
||||
}
|
||||
function b(e2) {
|
||||
return "html" === l(e2) ? e2 : e2.assignedSlot || e2.parentNode || (o(e2) ? e2.host : null) || d(e2);
|
||||
}
|
||||
function x(e2) {
|
||||
return ["html", "body", "#document"].indexOf(l(e2)) >= 0 ? e2.ownerDocument.body : r(e2) && v(e2) ? e2 : x(b(e2));
|
||||
}
|
||||
function w(e2, n2) {
|
||||
var r2;
|
||||
void 0 === n2 && (n2 = []);
|
||||
var o2 = x(e2), i2 = o2 === (null == (r2 = e2.ownerDocument) ? void 0 : r2.body), a2 = t(o2), s2 = i2 ? [a2].concat(a2.visualViewport || [], v(o2) ? o2 : []) : o2, f2 = n2.concat(s2);
|
||||
return i2 ? f2 : f2.concat(w(b(s2)));
|
||||
}
|
||||
function O(e2) {
|
||||
return ["table", "td", "th"].indexOf(l(e2)) >= 0;
|
||||
}
|
||||
function j(e2) {
|
||||
return r(e2) && "fixed" !== m(e2).position ? e2.offsetParent : null;
|
||||
}
|
||||
function E(e2) {
|
||||
for (var n2 = t(e2), i2 = j(e2); i2 && O(i2) && "static" === m(i2).position; ) i2 = j(i2);
|
||||
return i2 && ("html" === l(i2) || "body" === l(i2) && "static" === m(i2).position) ? n2 : i2 || (function(e3) {
|
||||
var t2 = /firefox/i.test(f());
|
||||
if (/Trident/i.test(f()) && r(e3) && "fixed" === m(e3).position) return null;
|
||||
var n3 = b(e3);
|
||||
for (o(n3) && (n3 = n3.host); r(n3) && ["html", "body"].indexOf(l(n3)) < 0; ) {
|
||||
var i3 = m(n3);
|
||||
if ("none" !== i3.transform || "none" !== i3.perspective || "paint" === i3.contain || -1 !== ["transform", "perspective"].indexOf(i3.willChange) || t2 && "filter" === i3.willChange || t2 && i3.filter && "none" !== i3.filter) return n3;
|
||||
n3 = n3.parentNode;
|
||||
}
|
||||
return null;
|
||||
})(e2) || n2;
|
||||
}
|
||||
var D = "top", A = "bottom", L = "right", P = "left", M = "auto", k = [D, A, L, P], W = "start", B = "end", H = "viewport", T = "popper", R = k.reduce((function(e2, t2) {
|
||||
return e2.concat([t2 + "-" + W, t2 + "-" + B]);
|
||||
}), []), S = [].concat(k, [M]).reduce((function(e2, t2) {
|
||||
return e2.concat([t2, t2 + "-" + W, t2 + "-" + B]);
|
||||
}), []), V = ["beforeRead", "read", "afterRead", "beforeMain", "main", "afterMain", "beforeWrite", "write", "afterWrite"];
|
||||
function q(e2) {
|
||||
var t2 = /* @__PURE__ */ new Map(), n2 = /* @__PURE__ */ new Set(), r2 = [];
|
||||
function o2(e3) {
|
||||
n2.add(e3.name), [].concat(e3.requires || [], e3.requiresIfExists || []).forEach((function(e4) {
|
||||
if (!n2.has(e4)) {
|
||||
var r3 = t2.get(e4);
|
||||
r3 && o2(r3);
|
||||
}
|
||||
})), r2.push(e3);
|
||||
}
|
||||
return e2.forEach((function(e3) {
|
||||
t2.set(e3.name, e3);
|
||||
})), e2.forEach((function(e3) {
|
||||
n2.has(e3.name) || o2(e3);
|
||||
})), r2;
|
||||
}
|
||||
function C(e2, t2) {
|
||||
var n2 = t2.getRootNode && t2.getRootNode();
|
||||
if (e2.contains(t2)) return true;
|
||||
if (n2 && o(n2)) {
|
||||
var r2 = t2;
|
||||
do {
|
||||
if (r2 && e2.isSameNode(r2)) return true;
|
||||
r2 = r2.parentNode || r2.host;
|
||||
} while (r2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function N(e2) {
|
||||
return Object.assign({}, e2, { left: e2.x, top: e2.y, right: e2.x + e2.width, bottom: e2.y + e2.height });
|
||||
}
|
||||
function I(e2, r2, o2) {
|
||||
return r2 === H ? N((function(e3, n2) {
|
||||
var r3 = t(e3), o3 = d(e3), i2 = r3.visualViewport, a2 = o3.clientWidth, s2 = o3.clientHeight, f2 = 0, p2 = 0;
|
||||
if (i2) {
|
||||
a2 = i2.width, s2 = i2.height;
|
||||
var u2 = c();
|
||||
(u2 || !u2 && "fixed" === n2) && (f2 = i2.offsetLeft, p2 = i2.offsetTop);
|
||||
}
|
||||
return { width: a2, height: s2, x: f2 + h(e3), y: p2 };
|
||||
})(e2, o2)) : n(r2) ? (function(e3, t2) {
|
||||
var n2 = p(e3, false, "fixed" === t2);
|
||||
return n2.top = n2.top + e3.clientTop, n2.left = n2.left + e3.clientLeft, n2.bottom = n2.top + e3.clientHeight, n2.right = n2.left + e3.clientWidth, n2.width = e3.clientWidth, n2.height = e3.clientHeight, n2.x = n2.left, n2.y = n2.top, n2;
|
||||
})(r2, o2) : N((function(e3) {
|
||||
var t2, n2 = d(e3), r3 = u(e3), o3 = null == (t2 = e3.ownerDocument) ? void 0 : t2.body, a2 = i(n2.scrollWidth, n2.clientWidth, o3 ? o3.scrollWidth : 0, o3 ? o3.clientWidth : 0), s2 = i(n2.scrollHeight, n2.clientHeight, o3 ? o3.scrollHeight : 0, o3 ? o3.clientHeight : 0), f2 = -r3.scrollLeft + h(e3), c2 = -r3.scrollTop;
|
||||
return "rtl" === m(o3 || n2).direction && (f2 += i(n2.clientWidth, o3 ? o3.clientWidth : 0) - a2), { width: a2, height: s2, x: f2, y: c2 };
|
||||
})(d(e2)));
|
||||
}
|
||||
function _(e2, t2, o2, s2) {
|
||||
var f2 = "clippingParents" === t2 ? (function(e3) {
|
||||
var t3 = w(b(e3)), o3 = ["absolute", "fixed"].indexOf(m(e3).position) >= 0 && r(e3) ? E(e3) : e3;
|
||||
return n(o3) ? t3.filter((function(e4) {
|
||||
return n(e4) && C(e4, o3) && "body" !== l(e4);
|
||||
})) : [];
|
||||
})(e2) : [].concat(t2), c2 = [].concat(f2, [o2]), p2 = c2[0], u2 = c2.reduce((function(t3, n2) {
|
||||
var r2 = I(e2, n2, s2);
|
||||
return t3.top = i(r2.top, t3.top), t3.right = a(r2.right, t3.right), t3.bottom = a(r2.bottom, t3.bottom), t3.left = i(r2.left, t3.left), t3;
|
||||
}), I(e2, p2, s2));
|
||||
return u2.width = u2.right - u2.left, u2.height = u2.bottom - u2.top, u2.x = u2.left, u2.y = u2.top, u2;
|
||||
}
|
||||
function F(e2) {
|
||||
return e2.split("-")[0];
|
||||
}
|
||||
function U(e2) {
|
||||
return e2.split("-")[1];
|
||||
}
|
||||
function z(e2) {
|
||||
return ["top", "bottom"].indexOf(e2) >= 0 ? "x" : "y";
|
||||
}
|
||||
function X(e2) {
|
||||
var t2, n2 = e2.reference, r2 = e2.element, o2 = e2.placement, i2 = o2 ? F(o2) : null, a2 = o2 ? U(o2) : null, s2 = n2.x + n2.width / 2 - r2.width / 2, f2 = n2.y + n2.height / 2 - r2.height / 2;
|
||||
switch (i2) {
|
||||
case D:
|
||||
t2 = { x: s2, y: n2.y - r2.height };
|
||||
break;
|
||||
case A:
|
||||
t2 = { x: s2, y: n2.y + n2.height };
|
||||
break;
|
||||
case L:
|
||||
t2 = { x: n2.x + n2.width, y: f2 };
|
||||
break;
|
||||
case P:
|
||||
t2 = { x: n2.x - r2.width, y: f2 };
|
||||
break;
|
||||
default:
|
||||
t2 = { x: n2.x, y: n2.y };
|
||||
}
|
||||
var c2 = i2 ? z(i2) : null;
|
||||
if (null != c2) {
|
||||
var p2 = "y" === c2 ? "height" : "width";
|
||||
switch (a2) {
|
||||
case W:
|
||||
t2[c2] = t2[c2] - (n2[p2] / 2 - r2[p2] / 2);
|
||||
break;
|
||||
case B:
|
||||
t2[c2] = t2[c2] + (n2[p2] / 2 - r2[p2] / 2);
|
||||
}
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
function Y(e2) {
|
||||
return Object.assign({}, { top: 0, right: 0, bottom: 0, left: 0 }, e2);
|
||||
}
|
||||
function G(e2, t2) {
|
||||
return t2.reduce((function(t3, n2) {
|
||||
return t3[n2] = e2, t3;
|
||||
}), {});
|
||||
}
|
||||
function J(e2, t2) {
|
||||
void 0 === t2 && (t2 = {});
|
||||
var r2 = t2, o2 = r2.placement, i2 = void 0 === o2 ? e2.placement : o2, a2 = r2.strategy, s2 = void 0 === a2 ? e2.strategy : a2, f2 = r2.boundary, c2 = void 0 === f2 ? "clippingParents" : f2, u2 = r2.rootBoundary, l2 = void 0 === u2 ? H : u2, h2 = r2.elementContext, m2 = void 0 === h2 ? T : h2, v2 = r2.altBoundary, y2 = void 0 !== v2 && v2, g2 = r2.padding, b2 = void 0 === g2 ? 0 : g2, x2 = Y("number" != typeof b2 ? b2 : G(b2, k)), w2 = m2 === T ? "reference" : T, O2 = e2.rects.popper, j2 = e2.elements[y2 ? w2 : m2], E2 = _(n(j2) ? j2 : j2.contextElement || d(e2.elements.popper), c2, l2, s2), P2 = p(e2.elements.reference), M2 = X({ reference: P2, element: O2, strategy: "absolute", placement: i2 }), W2 = N(Object.assign({}, O2, M2)), B2 = m2 === T ? W2 : P2, R2 = { top: E2.top - B2.top + x2.top, bottom: B2.bottom - E2.bottom + x2.bottom, left: E2.left - B2.left + x2.left, right: B2.right - E2.right + x2.right }, S2 = e2.modifiersData.offset;
|
||||
if (m2 === T && S2) {
|
||||
var V2 = S2[i2];
|
||||
Object.keys(R2).forEach((function(e3) {
|
||||
var t3 = [L, A].indexOf(e3) >= 0 ? 1 : -1, n2 = [D, A].indexOf(e3) >= 0 ? "y" : "x";
|
||||
R2[e3] += V2[n2] * t3;
|
||||
}));
|
||||
}
|
||||
return R2;
|
||||
}
|
||||
var K = { placement: "bottom", modifiers: [], strategy: "absolute" };
|
||||
function Q() {
|
||||
for (var e2 = arguments.length, t2 = new Array(e2), n2 = 0; n2 < e2; n2++) t2[n2] = arguments[n2];
|
||||
return !t2.some((function(e3) {
|
||||
return !(e3 && "function" == typeof e3.getBoundingClientRect);
|
||||
}));
|
||||
}
|
||||
function Z(e2) {
|
||||
void 0 === e2 && (e2 = {});
|
||||
var t2 = e2, r2 = t2.defaultModifiers, o2 = void 0 === r2 ? [] : r2, i2 = t2.defaultOptions, a2 = void 0 === i2 ? K : i2;
|
||||
return function(e3, t3, r3) {
|
||||
void 0 === r3 && (r3 = a2);
|
||||
var i3, s2, f2 = { placement: "bottom", orderedModifiers: [], options: Object.assign({}, K, a2), modifiersData: {}, elements: { reference: e3, popper: t3 }, attributes: {}, styles: {} }, c2 = [], p2 = false, u2 = { state: f2, setOptions: function(r4) {
|
||||
var i4 = "function" == typeof r4 ? r4(f2.options) : r4;
|
||||
l2(), f2.options = Object.assign({}, a2, f2.options, i4), f2.scrollParents = { reference: n(e3) ? w(e3) : e3.contextElement ? w(e3.contextElement) : [], popper: w(t3) };
|
||||
var s3, p3, d2 = (function(e4) {
|
||||
var t4 = q(e4);
|
||||
return V.reduce((function(e5, n2) {
|
||||
return e5.concat(t4.filter((function(e6) {
|
||||
return e6.phase === n2;
|
||||
})));
|
||||
}), []);
|
||||
})((s3 = [].concat(o2, f2.options.modifiers), p3 = s3.reduce((function(e4, t4) {
|
||||
var n2 = e4[t4.name];
|
||||
return e4[t4.name] = n2 ? Object.assign({}, n2, t4, { options: Object.assign({}, n2.options, t4.options), data: Object.assign({}, n2.data, t4.data) }) : t4, e4;
|
||||
}), {}), Object.keys(p3).map((function(e4) {
|
||||
return p3[e4];
|
||||
}))));
|
||||
return f2.orderedModifiers = d2.filter((function(e4) {
|
||||
return e4.enabled;
|
||||
})), f2.orderedModifiers.forEach((function(e4) {
|
||||
var t4 = e4.name, n2 = e4.options, r5 = void 0 === n2 ? {} : n2, o3 = e4.effect;
|
||||
if ("function" == typeof o3) {
|
||||
var i5 = o3({ state: f2, name: t4, instance: u2, options: r5 }), a3 = function() {
|
||||
};
|
||||
c2.push(i5 || a3);
|
||||
}
|
||||
})), u2.update();
|
||||
}, forceUpdate: function() {
|
||||
if (!p2) {
|
||||
var e4 = f2.elements, t4 = e4.reference, n2 = e4.popper;
|
||||
if (Q(t4, n2)) {
|
||||
f2.rects = { reference: y(t4, E(n2), "fixed" === f2.options.strategy), popper: g(n2) }, f2.reset = false, f2.placement = f2.options.placement, f2.orderedModifiers.forEach((function(e5) {
|
||||
return f2.modifiersData[e5.name] = Object.assign({}, e5.data);
|
||||
}));
|
||||
for (var r4 = 0; r4 < f2.orderedModifiers.length; r4++) if (true !== f2.reset) {
|
||||
var o3 = f2.orderedModifiers[r4], i4 = o3.fn, a3 = o3.options, s3 = void 0 === a3 ? {} : a3, c3 = o3.name;
|
||||
"function" == typeof i4 && (f2 = i4({ state: f2, options: s3, name: c3, instance: u2 }) || f2);
|
||||
} else f2.reset = false, r4 = -1;
|
||||
}
|
||||
}
|
||||
}, update: (i3 = function() {
|
||||
return new Promise((function(e4) {
|
||||
u2.forceUpdate(), e4(f2);
|
||||
}));
|
||||
}, function() {
|
||||
return s2 || (s2 = new Promise((function(e4) {
|
||||
Promise.resolve().then((function() {
|
||||
s2 = void 0, e4(i3());
|
||||
}));
|
||||
}))), s2;
|
||||
}), destroy: function() {
|
||||
l2(), p2 = true;
|
||||
} };
|
||||
if (!Q(e3, t3)) return u2;
|
||||
function l2() {
|
||||
c2.forEach((function(e4) {
|
||||
return e4();
|
||||
})), c2 = [];
|
||||
}
|
||||
return u2.setOptions(r3).then((function(e4) {
|
||||
!p2 && r3.onFirstUpdate && r3.onFirstUpdate(e4);
|
||||
})), u2;
|
||||
};
|
||||
}
|
||||
var $ = { passive: true };
|
||||
var ee = { name: "eventListeners", enabled: true, phase: "write", fn: function() {
|
||||
}, effect: function(e2) {
|
||||
var n2 = e2.state, r2 = e2.instance, o2 = e2.options, i2 = o2.scroll, a2 = void 0 === i2 || i2, s2 = o2.resize, f2 = void 0 === s2 || s2, c2 = t(n2.elements.popper), p2 = [].concat(n2.scrollParents.reference, n2.scrollParents.popper);
|
||||
return a2 && p2.forEach((function(e3) {
|
||||
e3.addEventListener("scroll", r2.update, $);
|
||||
})), f2 && c2.addEventListener("resize", r2.update, $), function() {
|
||||
a2 && p2.forEach((function(e3) {
|
||||
e3.removeEventListener("scroll", r2.update, $);
|
||||
})), f2 && c2.removeEventListener("resize", r2.update, $);
|
||||
};
|
||||
}, data: {} };
|
||||
var te = { name: "popperOffsets", enabled: true, phase: "read", fn: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.name;
|
||||
t2.modifiersData[n2] = X({ reference: t2.rects.reference, element: t2.rects.popper, strategy: "absolute", placement: t2.placement });
|
||||
}, data: {} }, ne = { top: "auto", right: "auto", bottom: "auto", left: "auto" };
|
||||
function re(e2) {
|
||||
var n2, r2 = e2.popper, o2 = e2.popperRect, i2 = e2.placement, a2 = e2.variation, f2 = e2.offsets, c2 = e2.position, p2 = e2.gpuAcceleration, u2 = e2.adaptive, l2 = e2.roundOffsets, h2 = e2.isFixed, v2 = f2.x, y2 = void 0 === v2 ? 0 : v2, g2 = f2.y, b2 = void 0 === g2 ? 0 : g2, x2 = "function" == typeof l2 ? l2({ x: y2, y: b2 }) : { x: y2, y: b2 };
|
||||
y2 = x2.x, b2 = x2.y;
|
||||
var w2 = f2.hasOwnProperty("x"), O2 = f2.hasOwnProperty("y"), j2 = P, M2 = D, k2 = window;
|
||||
if (u2) {
|
||||
var W2 = E(r2), H2 = "clientHeight", T2 = "clientWidth";
|
||||
if (W2 === t(r2) && "static" !== m(W2 = d(r2)).position && "absolute" === c2 && (H2 = "scrollHeight", T2 = "scrollWidth"), W2 = W2, i2 === D || (i2 === P || i2 === L) && a2 === B) M2 = A, b2 -= (h2 && W2 === k2 && k2.visualViewport ? k2.visualViewport.height : W2[H2]) - o2.height, b2 *= p2 ? 1 : -1;
|
||||
if (i2 === P || (i2 === D || i2 === A) && a2 === B) j2 = L, y2 -= (h2 && W2 === k2 && k2.visualViewport ? k2.visualViewport.width : W2[T2]) - o2.width, y2 *= p2 ? 1 : -1;
|
||||
}
|
||||
var R2, S2 = Object.assign({ position: c2 }, u2 && ne), V2 = true === l2 ? (function(e3, t2) {
|
||||
var n3 = e3.x, r3 = e3.y, o3 = t2.devicePixelRatio || 1;
|
||||
return { x: s(n3 * o3) / o3 || 0, y: s(r3 * o3) / o3 || 0 };
|
||||
})({ x: y2, y: b2 }, t(r2)) : { x: y2, y: b2 };
|
||||
return y2 = V2.x, b2 = V2.y, p2 ? Object.assign({}, S2, ((R2 = {})[M2] = O2 ? "0" : "", R2[j2] = w2 ? "0" : "", R2.transform = (k2.devicePixelRatio || 1) <= 1 ? "translate(" + y2 + "px, " + b2 + "px)" : "translate3d(" + y2 + "px, " + b2 + "px, 0)", R2)) : Object.assign({}, S2, ((n2 = {})[M2] = O2 ? b2 + "px" : "", n2[j2] = w2 ? y2 + "px" : "", n2.transform = "", n2));
|
||||
}
|
||||
var oe = { name: "computeStyles", enabled: true, phase: "beforeWrite", fn: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.options, r2 = n2.gpuAcceleration, o2 = void 0 === r2 || r2, i2 = n2.adaptive, a2 = void 0 === i2 || i2, s2 = n2.roundOffsets, f2 = void 0 === s2 || s2, c2 = { placement: F(t2.placement), variation: U(t2.placement), popper: t2.elements.popper, popperRect: t2.rects.popper, gpuAcceleration: o2, isFixed: "fixed" === t2.options.strategy };
|
||||
null != t2.modifiersData.popperOffsets && (t2.styles.popper = Object.assign({}, t2.styles.popper, re(Object.assign({}, c2, { offsets: t2.modifiersData.popperOffsets, position: t2.options.strategy, adaptive: a2, roundOffsets: f2 })))), null != t2.modifiersData.arrow && (t2.styles.arrow = Object.assign({}, t2.styles.arrow, re(Object.assign({}, c2, { offsets: t2.modifiersData.arrow, position: "absolute", adaptive: false, roundOffsets: f2 })))), t2.attributes.popper = Object.assign({}, t2.attributes.popper, { "data-popper-placement": t2.placement });
|
||||
}, data: {} };
|
||||
var ie = { name: "applyStyles", enabled: true, phase: "write", fn: function(e2) {
|
||||
var t2 = e2.state;
|
||||
Object.keys(t2.elements).forEach((function(e3) {
|
||||
var n2 = t2.styles[e3] || {}, o2 = t2.attributes[e3] || {}, i2 = t2.elements[e3];
|
||||
r(i2) && l(i2) && (Object.assign(i2.style, n2), Object.keys(o2).forEach((function(e4) {
|
||||
var t3 = o2[e4];
|
||||
false === t3 ? i2.removeAttribute(e4) : i2.setAttribute(e4, true === t3 ? "" : t3);
|
||||
})));
|
||||
}));
|
||||
}, effect: function(e2) {
|
||||
var t2 = e2.state, n2 = { popper: { position: t2.options.strategy, left: "0", top: "0", margin: "0" }, arrow: { position: "absolute" }, reference: {} };
|
||||
return Object.assign(t2.elements.popper.style, n2.popper), t2.styles = n2, t2.elements.arrow && Object.assign(t2.elements.arrow.style, n2.arrow), function() {
|
||||
Object.keys(t2.elements).forEach((function(e3) {
|
||||
var o2 = t2.elements[e3], i2 = t2.attributes[e3] || {}, a2 = Object.keys(t2.styles.hasOwnProperty(e3) ? t2.styles[e3] : n2[e3]).reduce((function(e4, t3) {
|
||||
return e4[t3] = "", e4;
|
||||
}), {});
|
||||
r(o2) && l(o2) && (Object.assign(o2.style, a2), Object.keys(i2).forEach((function(e4) {
|
||||
o2.removeAttribute(e4);
|
||||
})));
|
||||
}));
|
||||
};
|
||||
}, requires: ["computeStyles"] };
|
||||
var ae = { name: "offset", enabled: true, phase: "main", requires: ["popperOffsets"], fn: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.options, r2 = e2.name, o2 = n2.offset, i2 = void 0 === o2 ? [0, 0] : o2, a2 = S.reduce((function(e3, n3) {
|
||||
return e3[n3] = (function(e4, t3, n4) {
|
||||
var r3 = F(e4), o3 = [P, D].indexOf(r3) >= 0 ? -1 : 1, i3 = "function" == typeof n4 ? n4(Object.assign({}, t3, { placement: e4 })) : n4, a3 = i3[0], s3 = i3[1];
|
||||
return a3 = a3 || 0, s3 = (s3 || 0) * o3, [P, L].indexOf(r3) >= 0 ? { x: s3, y: a3 } : { x: a3, y: s3 };
|
||||
})(n3, t2.rects, i2), e3;
|
||||
}), {}), s2 = a2[t2.placement], f2 = s2.x, c2 = s2.y;
|
||||
null != t2.modifiersData.popperOffsets && (t2.modifiersData.popperOffsets.x += f2, t2.modifiersData.popperOffsets.y += c2), t2.modifiersData[r2] = a2;
|
||||
} }, se = { left: "right", right: "left", bottom: "top", top: "bottom" };
|
||||
function fe(e2) {
|
||||
return e2.replace(/left|right|bottom|top/g, (function(e3) {
|
||||
return se[e3];
|
||||
}));
|
||||
}
|
||||
var ce = { start: "end", end: "start" };
|
||||
function pe(e2) {
|
||||
return e2.replace(/start|end/g, (function(e3) {
|
||||
return ce[e3];
|
||||
}));
|
||||
}
|
||||
function ue(e2, t2) {
|
||||
void 0 === t2 && (t2 = {});
|
||||
var n2 = t2, r2 = n2.placement, o2 = n2.boundary, i2 = n2.rootBoundary, a2 = n2.padding, s2 = n2.flipVariations, f2 = n2.allowedAutoPlacements, c2 = void 0 === f2 ? S : f2, p2 = U(r2), u2 = p2 ? s2 ? R : R.filter((function(e3) {
|
||||
return U(e3) === p2;
|
||||
})) : k, l2 = u2.filter((function(e3) {
|
||||
return c2.indexOf(e3) >= 0;
|
||||
}));
|
||||
0 === l2.length && (l2 = u2);
|
||||
var d2 = l2.reduce((function(t3, n3) {
|
||||
return t3[n3] = J(e2, { placement: n3, boundary: o2, rootBoundary: i2, padding: a2 })[F(n3)], t3;
|
||||
}), {});
|
||||
return Object.keys(d2).sort((function(e3, t3) {
|
||||
return d2[e3] - d2[t3];
|
||||
}));
|
||||
}
|
||||
var le = { name: "flip", enabled: true, phase: "main", fn: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.options, r2 = e2.name;
|
||||
if (!t2.modifiersData[r2]._skip) {
|
||||
for (var o2 = n2.mainAxis, i2 = void 0 === o2 || o2, a2 = n2.altAxis, s2 = void 0 === a2 || a2, f2 = n2.fallbackPlacements, c2 = n2.padding, p2 = n2.boundary, u2 = n2.rootBoundary, l2 = n2.altBoundary, d2 = n2.flipVariations, h2 = void 0 === d2 || d2, m2 = n2.allowedAutoPlacements, v2 = t2.options.placement, y2 = F(v2), g2 = f2 || (y2 === v2 || !h2 ? [fe(v2)] : (function(e3) {
|
||||
if (F(e3) === M) return [];
|
||||
var t3 = fe(e3);
|
||||
return [pe(e3), t3, pe(t3)];
|
||||
})(v2)), b2 = [v2].concat(g2).reduce((function(e3, n3) {
|
||||
return e3.concat(F(n3) === M ? ue(t2, { placement: n3, boundary: p2, rootBoundary: u2, padding: c2, flipVariations: h2, allowedAutoPlacements: m2 }) : n3);
|
||||
}), []), x2 = t2.rects.reference, w2 = t2.rects.popper, O2 = /* @__PURE__ */ new Map(), j2 = true, E2 = b2[0], k2 = 0; k2 < b2.length; k2++) {
|
||||
var B2 = b2[k2], H2 = F(B2), T2 = U(B2) === W, R2 = [D, A].indexOf(H2) >= 0, S2 = R2 ? "width" : "height", V2 = J(t2, { placement: B2, boundary: p2, rootBoundary: u2, altBoundary: l2, padding: c2 }), q2 = R2 ? T2 ? L : P : T2 ? A : D;
|
||||
x2[S2] > w2[S2] && (q2 = fe(q2));
|
||||
var C2 = fe(q2), N2 = [];
|
||||
if (i2 && N2.push(V2[H2] <= 0), s2 && N2.push(V2[q2] <= 0, V2[C2] <= 0), N2.every((function(e3) {
|
||||
return e3;
|
||||
}))) {
|
||||
E2 = B2, j2 = false;
|
||||
break;
|
||||
}
|
||||
O2.set(B2, N2);
|
||||
}
|
||||
if (j2) for (var I2 = function(e3) {
|
||||
var t3 = b2.find((function(t4) {
|
||||
var n3 = O2.get(t4);
|
||||
if (n3) return n3.slice(0, e3).every((function(e4) {
|
||||
return e4;
|
||||
}));
|
||||
}));
|
||||
if (t3) return E2 = t3, "break";
|
||||
}, _2 = h2 ? 3 : 1; _2 > 0; _2--) {
|
||||
if ("break" === I2(_2)) break;
|
||||
}
|
||||
t2.placement !== E2 && (t2.modifiersData[r2]._skip = true, t2.placement = E2, t2.reset = true);
|
||||
}
|
||||
}, requiresIfExists: ["offset"], data: { _skip: false } };
|
||||
function de(e2, t2, n2) {
|
||||
return i(e2, a(t2, n2));
|
||||
}
|
||||
var he = { name: "preventOverflow", enabled: true, phase: "main", fn: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.options, r2 = e2.name, o2 = n2.mainAxis, s2 = void 0 === o2 || o2, f2 = n2.altAxis, c2 = void 0 !== f2 && f2, p2 = n2.boundary, u2 = n2.rootBoundary, l2 = n2.altBoundary, d2 = n2.padding, h2 = n2.tether, m2 = void 0 === h2 || h2, v2 = n2.tetherOffset, y2 = void 0 === v2 ? 0 : v2, b2 = J(t2, { boundary: p2, rootBoundary: u2, padding: d2, altBoundary: l2 }), x2 = F(t2.placement), w2 = U(t2.placement), O2 = !w2, j2 = z(x2), M2 = "x" === j2 ? "y" : "x", k2 = t2.modifiersData.popperOffsets, B2 = t2.rects.reference, H2 = t2.rects.popper, T2 = "function" == typeof y2 ? y2(Object.assign({}, t2.rects, { placement: t2.placement })) : y2, R2 = "number" == typeof T2 ? { mainAxis: T2, altAxis: T2 } : Object.assign({ mainAxis: 0, altAxis: 0 }, T2), S2 = t2.modifiersData.offset ? t2.modifiersData.offset[t2.placement] : null, V2 = { x: 0, y: 0 };
|
||||
if (k2) {
|
||||
if (s2) {
|
||||
var q2, C2 = "y" === j2 ? D : P, N2 = "y" === j2 ? A : L, I2 = "y" === j2 ? "height" : "width", _2 = k2[j2], X2 = _2 + b2[C2], Y2 = _2 - b2[N2], G2 = m2 ? -H2[I2] / 2 : 0, K2 = w2 === W ? B2[I2] : H2[I2], Q2 = w2 === W ? -H2[I2] : -B2[I2], Z2 = t2.elements.arrow, $2 = m2 && Z2 ? g(Z2) : { width: 0, height: 0 }, ee2 = t2.modifiersData["arrow#persistent"] ? t2.modifiersData["arrow#persistent"].padding : { top: 0, right: 0, bottom: 0, left: 0 }, te2 = ee2[C2], ne2 = ee2[N2], re2 = de(0, B2[I2], $2[I2]), oe2 = O2 ? B2[I2] / 2 - G2 - re2 - te2 - R2.mainAxis : K2 - re2 - te2 - R2.mainAxis, ie2 = O2 ? -B2[I2] / 2 + G2 + re2 + ne2 + R2.mainAxis : Q2 + re2 + ne2 + R2.mainAxis, ae2 = t2.elements.arrow && E(t2.elements.arrow), se2 = ae2 ? "y" === j2 ? ae2.clientTop || 0 : ae2.clientLeft || 0 : 0, fe2 = null != (q2 = null == S2 ? void 0 : S2[j2]) ? q2 : 0, ce2 = _2 + ie2 - fe2, pe2 = de(m2 ? a(X2, _2 + oe2 - fe2 - se2) : X2, _2, m2 ? i(Y2, ce2) : Y2);
|
||||
k2[j2] = pe2, V2[j2] = pe2 - _2;
|
||||
}
|
||||
if (c2) {
|
||||
var ue2, le2 = "x" === j2 ? D : P, he2 = "x" === j2 ? A : L, me2 = k2[M2], ve2 = "y" === M2 ? "height" : "width", ye2 = me2 + b2[le2], ge2 = me2 - b2[he2], be2 = -1 !== [D, P].indexOf(x2), xe2 = null != (ue2 = null == S2 ? void 0 : S2[M2]) ? ue2 : 0, we2 = be2 ? ye2 : me2 - B2[ve2] - H2[ve2] - xe2 + R2.altAxis, Oe = be2 ? me2 + B2[ve2] + H2[ve2] - xe2 - R2.altAxis : ge2, je = m2 && be2 ? (function(e3, t3, n3) {
|
||||
var r3 = de(e3, t3, n3);
|
||||
return r3 > n3 ? n3 : r3;
|
||||
})(we2, me2, Oe) : de(m2 ? we2 : ye2, me2, m2 ? Oe : ge2);
|
||||
k2[M2] = je, V2[M2] = je - me2;
|
||||
}
|
||||
t2.modifiersData[r2] = V2;
|
||||
}
|
||||
}, requiresIfExists: ["offset"] };
|
||||
var me = { name: "arrow", enabled: true, phase: "main", fn: function(e2) {
|
||||
var t2, n2 = e2.state, r2 = e2.name, o2 = e2.options, i2 = n2.elements.arrow, a2 = n2.modifiersData.popperOffsets, s2 = F(n2.placement), f2 = z(s2), c2 = [P, L].indexOf(s2) >= 0 ? "height" : "width";
|
||||
if (i2 && a2) {
|
||||
var p2 = (function(e3, t3) {
|
||||
return Y("number" != typeof (e3 = "function" == typeof e3 ? e3(Object.assign({}, t3.rects, { placement: t3.placement })) : e3) ? e3 : G(e3, k));
|
||||
})(o2.padding, n2), u2 = g(i2), l2 = "y" === f2 ? D : P, d2 = "y" === f2 ? A : L, h2 = n2.rects.reference[c2] + n2.rects.reference[f2] - a2[f2] - n2.rects.popper[c2], m2 = a2[f2] - n2.rects.reference[f2], v2 = E(i2), y2 = v2 ? "y" === f2 ? v2.clientHeight || 0 : v2.clientWidth || 0 : 0, b2 = h2 / 2 - m2 / 2, x2 = p2[l2], w2 = y2 - u2[c2] - p2[d2], O2 = y2 / 2 - u2[c2] / 2 + b2, j2 = de(x2, O2, w2), M2 = f2;
|
||||
n2.modifiersData[r2] = ((t2 = {})[M2] = j2, t2.centerOffset = j2 - O2, t2);
|
||||
}
|
||||
}, effect: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.options.element, r2 = void 0 === n2 ? "[data-popper-arrow]" : n2;
|
||||
null != r2 && ("string" != typeof r2 || (r2 = t2.elements.popper.querySelector(r2))) && C(t2.elements.popper, r2) && (t2.elements.arrow = r2);
|
||||
}, requires: ["popperOffsets"], requiresIfExists: ["preventOverflow"] };
|
||||
function ve(e2, t2, n2) {
|
||||
return void 0 === n2 && (n2 = { x: 0, y: 0 }), { top: e2.top - t2.height - n2.y, right: e2.right - t2.width + n2.x, bottom: e2.bottom - t2.height + n2.y, left: e2.left - t2.width - n2.x };
|
||||
}
|
||||
function ye(e2) {
|
||||
return [D, L, A, P].some((function(t2) {
|
||||
return e2[t2] >= 0;
|
||||
}));
|
||||
}
|
||||
var ge = { name: "hide", enabled: true, phase: "main", requiresIfExists: ["preventOverflow"], fn: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.name, r2 = t2.rects.reference, o2 = t2.rects.popper, i2 = t2.modifiersData.preventOverflow, a2 = J(t2, { elementContext: "reference" }), s2 = J(t2, { altBoundary: true }), f2 = ve(a2, r2), c2 = ve(s2, o2, i2), p2 = ye(f2), u2 = ye(c2);
|
||||
t2.modifiersData[n2] = { referenceClippingOffsets: f2, popperEscapeOffsets: c2, isReferenceHidden: p2, hasPopperEscaped: u2 }, t2.attributes.popper = Object.assign({}, t2.attributes.popper, { "data-popper-reference-hidden": p2, "data-popper-escaped": u2 });
|
||||
} }, be = Z({ defaultModifiers: [ee, te, oe, ie] }), xe = [ee, te, oe, ie, ae, le, he, me, ge], we = Z({ defaultModifiers: xe });
|
||||
e.applyStyles = ie, e.arrow = me, e.computeStyles = oe, e.createPopper = we, e.createPopperLite = be, e.defaultModifiers = xe, e.detectOverflow = J, e.eventListeners = ee, e.flip = le, e.hide = ge, e.offset = ae, e.popperGenerator = Z, e.popperOffsets = te, e.preventOverflow = he, Object.defineProperty(e, "__esModule", { value: true });
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
// frontend/js/components/customSelectV2.js
|
||||
var import_popper_esm_min = __toESM(require_popper_esm_min());
|
||||
var CustomSelectV2 = class _CustomSelectV2 {
|
||||
constructor(container) {
|
||||
this.container = container;
|
||||
this.trigger = this.container.querySelector(".custom-select-trigger");
|
||||
this.nativeSelect = this.container.querySelector("select");
|
||||
this.template = this.container.querySelector(".custom-select-panel-template");
|
||||
if (!this.trigger || !this.nativeSelect || !this.template) {
|
||||
console.warn("CustomSelectV2 cannot initialize: missing required elements.", this.container);
|
||||
return;
|
||||
}
|
||||
this.panel = null;
|
||||
this.popperInstance = null;
|
||||
this.isOpen = false;
|
||||
this.triggerText = this.trigger.querySelector("span");
|
||||
if (typeof _CustomSelectV2.openInstance === "undefined") {
|
||||
_CustomSelectV2.openInstance = null;
|
||||
_CustomSelectV2.initGlobalListener();
|
||||
}
|
||||
this.updateTriggerText();
|
||||
this.bindEvents();
|
||||
}
|
||||
static initGlobalListener() {
|
||||
document.addEventListener("click", (event) => {
|
||||
const instance = _CustomSelectV2.openInstance;
|
||||
if (instance && !instance.container.contains(event.target) && (!instance.panel || !instance.panel.contains(event.target))) {
|
||||
instance.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
createPanel() {
|
||||
const panelFragment = this.template.content.cloneNode(true);
|
||||
this.panel = panelFragment.querySelector(".custom-select-panel");
|
||||
document.body.appendChild(this.panel);
|
||||
this.panel.innerHTML = "";
|
||||
Array.from(this.nativeSelect.options).forEach((option) => {
|
||||
const item = document.createElement("a");
|
||||
item.href = "#";
|
||||
item.className = "custom-select-option block w-full text-left px-3 py-1.5 text-sm text-zinc-700 hover:bg-zinc-100 dark:text-zinc-200 dark:hover:bg-zinc-700";
|
||||
item.textContent = option.textContent;
|
||||
item.dataset.value = option.value;
|
||||
if (option.selected) {
|
||||
item.classList.add("is-selected");
|
||||
}
|
||||
this.panel.appendChild(item);
|
||||
});
|
||||
this.panel.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
const optionEl = event.target.closest(".custom-select-option");
|
||||
if (optionEl) {
|
||||
this.selectOption(optionEl);
|
||||
}
|
||||
});
|
||||
}
|
||||
bindEvents() {
|
||||
this.trigger.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
if (_CustomSelectV2.openInstance && _CustomSelectV2.openInstance !== this) {
|
||||
_CustomSelectV2.openInstance.close();
|
||||
}
|
||||
this.toggle();
|
||||
});
|
||||
}
|
||||
selectOption(optionEl) {
|
||||
const selectedValue = optionEl.dataset.value;
|
||||
if (this.nativeSelect.value !== selectedValue) {
|
||||
this.nativeSelect.value = selectedValue;
|
||||
this.nativeSelect.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
}
|
||||
this.updateTriggerText();
|
||||
this.close();
|
||||
}
|
||||
updateTriggerText() {
|
||||
const selectedOption = this.nativeSelect.options[this.nativeSelect.selectedIndex];
|
||||
if (selectedOption) {
|
||||
this.triggerText.textContent = selectedOption.textContent;
|
||||
}
|
||||
}
|
||||
toggle() {
|
||||
this.isOpen ? this.close() : this.open();
|
||||
}
|
||||
open() {
|
||||
if (this.isOpen) return;
|
||||
this.isOpen = true;
|
||||
if (!this.panel) {
|
||||
this.createPanel();
|
||||
}
|
||||
this.panel.style.display = "block";
|
||||
this.panel.offsetHeight;
|
||||
this.popperInstance = (0, import_popper_esm_min.createPopper)(this.trigger, this.panel, {
|
||||
placement: "top-start",
|
||||
modifiers: [
|
||||
{ name: "offset", options: { offset: [0, 8] } },
|
||||
{ name: "flip", options: { fallbackPlacements: ["bottom-start"] } }
|
||||
]
|
||||
});
|
||||
_CustomSelectV2.openInstance = this;
|
||||
}
|
||||
close() {
|
||||
if (!this.isOpen) return;
|
||||
this.isOpen = false;
|
||||
if (this.popperInstance) {
|
||||
this.popperInstance.destroy();
|
||||
this.popperInstance = null;
|
||||
}
|
||||
if (this.panel) {
|
||||
this.panel.remove();
|
||||
this.panel = null;
|
||||
}
|
||||
if (_CustomSelectV2.openInstance === this) {
|
||||
_CustomSelectV2.openInstance = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
require_popper_esm_min,
|
||||
CustomSelectV2
|
||||
};
|
||||
@@ -269,6 +269,39 @@ var UIPatterns = class {
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets a button to a loading state by disabling it and showing a spinner.
|
||||
* It stores the button's original content to be restored later.
|
||||
* @param {HTMLButtonElement} button - The button element to modify.
|
||||
*/
|
||||
setButtonLoading(button) {
|
||||
if (!button) return;
|
||||
if (!button.dataset.originalContent) {
|
||||
button.dataset.originalContent = button.innerHTML;
|
||||
}
|
||||
button.disabled = true;
|
||||
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||
}
|
||||
/**
|
||||
* Restores a button from its loading state to its original content and enables it.
|
||||
* @param {HTMLButtonElement} button - The button element to restore.
|
||||
*/
|
||||
clearButtonLoading(button) {
|
||||
if (!button) return;
|
||||
if (button.dataset.originalContent) {
|
||||
button.innerHTML = button.dataset.originalContent;
|
||||
delete button.dataset.originalContent;
|
||||
}
|
||||
button.disabled = false;
|
||||
}
|
||||
/**
|
||||
* Returns the HTML for a streaming text cursor animation.
|
||||
* This is used as a placeholder in the chat UI while waiting for an assistant's response.
|
||||
* @returns {string} The HTML string for the loader.
|
||||
*/
|
||||
renderStreamingLoader() {
|
||||
return '<span class="streaming-cursor animate-pulse">\u258B</span>';
|
||||
}
|
||||
};
|
||||
var modalManager = new ModalManager();
|
||||
var uiPatterns = new UIPatterns();
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
apiFetch,
|
||||
apiFetchJson,
|
||||
modalManager
|
||||
} from "./chunk-SHK62ZJN.js";
|
||||
} from "./chunk-VOGCL6QZ.js";
|
||||
import "./chunk-JSBRDJBE.js";
|
||||
|
||||
// frontend/js/components/tagInput.js
|
||||
@@ -2,496 +2,19 @@ import {
|
||||
debounce,
|
||||
escapeHTML
|
||||
} from "./chunk-A4OOMLXK.js";
|
||||
import {
|
||||
CustomSelectV2,
|
||||
require_popper_esm_min
|
||||
} from "./chunk-T5V6LQ42.js";
|
||||
import {
|
||||
apiFetchJson,
|
||||
modalManager
|
||||
} from "./chunk-SHK62ZJN.js";
|
||||
} from "./chunk-VOGCL6QZ.js";
|
||||
import {
|
||||
__commonJS,
|
||||
__toESM
|
||||
} from "./chunk-JSBRDJBE.js";
|
||||
|
||||
// frontend/js/vendor/popper.esm.min.js
|
||||
var require_popper_esm_min = __commonJS({
|
||||
"frontend/js/vendor/popper.esm.min.js"(exports, module) {
|
||||
!(function(e, t) {
|
||||
"object" == typeof exports && "undefined" != typeof module ? t(exports) : "function" == typeof define && define.amd ? define(["exports"], t) : t((e = "undefined" != typeof globalThis ? globalThis : e || self).Popper = {});
|
||||
})(exports, (function(e) {
|
||||
"use strict";
|
||||
function t(e2) {
|
||||
if (null == e2) return window;
|
||||
if ("[object Window]" !== e2.toString()) {
|
||||
var t2 = e2.ownerDocument;
|
||||
return t2 && t2.defaultView || window;
|
||||
}
|
||||
return e2;
|
||||
}
|
||||
function n(e2) {
|
||||
return e2 instanceof t(e2).Element || e2 instanceof Element;
|
||||
}
|
||||
function r(e2) {
|
||||
return e2 instanceof t(e2).HTMLElement || e2 instanceof HTMLElement;
|
||||
}
|
||||
function o(e2) {
|
||||
return "undefined" != typeof ShadowRoot && (e2 instanceof t(e2).ShadowRoot || e2 instanceof ShadowRoot);
|
||||
}
|
||||
var i = Math.max, a = Math.min, s = Math.round;
|
||||
function f() {
|
||||
var e2 = navigator.userAgentData;
|
||||
return null != e2 && e2.brands && Array.isArray(e2.brands) ? e2.brands.map((function(e3) {
|
||||
return e3.brand + "/" + e3.version;
|
||||
})).join(" ") : navigator.userAgent;
|
||||
}
|
||||
function c() {
|
||||
return !/^((?!chrome|android).)*safari/i.test(f());
|
||||
}
|
||||
function p(e2, o2, i2) {
|
||||
void 0 === o2 && (o2 = false), void 0 === i2 && (i2 = false);
|
||||
var a2 = e2.getBoundingClientRect(), f2 = 1, p2 = 1;
|
||||
o2 && r(e2) && (f2 = e2.offsetWidth > 0 && s(a2.width) / e2.offsetWidth || 1, p2 = e2.offsetHeight > 0 && s(a2.height) / e2.offsetHeight || 1);
|
||||
var u2 = (n(e2) ? t(e2) : window).visualViewport, l2 = !c() && i2, d2 = (a2.left + (l2 && u2 ? u2.offsetLeft : 0)) / f2, h2 = (a2.top + (l2 && u2 ? u2.offsetTop : 0)) / p2, m2 = a2.width / f2, v2 = a2.height / p2;
|
||||
return { width: m2, height: v2, top: h2, right: d2 + m2, bottom: h2 + v2, left: d2, x: d2, y: h2 };
|
||||
}
|
||||
function u(e2) {
|
||||
var n2 = t(e2);
|
||||
return { scrollLeft: n2.pageXOffset, scrollTop: n2.pageYOffset };
|
||||
}
|
||||
function l(e2) {
|
||||
return e2 ? (e2.nodeName || "").toLowerCase() : null;
|
||||
}
|
||||
function d(e2) {
|
||||
return ((n(e2) ? e2.ownerDocument : e2.document) || window.document).documentElement;
|
||||
}
|
||||
function h(e2) {
|
||||
return p(d(e2)).left + u(e2).scrollLeft;
|
||||
}
|
||||
function m(e2) {
|
||||
return t(e2).getComputedStyle(e2);
|
||||
}
|
||||
function v(e2) {
|
||||
var t2 = m(e2), n2 = t2.overflow, r2 = t2.overflowX, o2 = t2.overflowY;
|
||||
return /auto|scroll|overlay|hidden/.test(n2 + o2 + r2);
|
||||
}
|
||||
function y(e2, n2, o2) {
|
||||
void 0 === o2 && (o2 = false);
|
||||
var i2, a2, f2 = r(n2), c2 = r(n2) && (function(e3) {
|
||||
var t2 = e3.getBoundingClientRect(), n3 = s(t2.width) / e3.offsetWidth || 1, r2 = s(t2.height) / e3.offsetHeight || 1;
|
||||
return 1 !== n3 || 1 !== r2;
|
||||
})(n2), m2 = d(n2), y2 = p(e2, c2, o2), g2 = { scrollLeft: 0, scrollTop: 0 }, b2 = { x: 0, y: 0 };
|
||||
return (f2 || !f2 && !o2) && (("body" !== l(n2) || v(m2)) && (g2 = (i2 = n2) !== t(i2) && r(i2) ? { scrollLeft: (a2 = i2).scrollLeft, scrollTop: a2.scrollTop } : u(i2)), r(n2) ? ((b2 = p(n2, true)).x += n2.clientLeft, b2.y += n2.clientTop) : m2 && (b2.x = h(m2))), { x: y2.left + g2.scrollLeft - b2.x, y: y2.top + g2.scrollTop - b2.y, width: y2.width, height: y2.height };
|
||||
}
|
||||
function g(e2) {
|
||||
var t2 = p(e2), n2 = e2.offsetWidth, r2 = e2.offsetHeight;
|
||||
return Math.abs(t2.width - n2) <= 1 && (n2 = t2.width), Math.abs(t2.height - r2) <= 1 && (r2 = t2.height), { x: e2.offsetLeft, y: e2.offsetTop, width: n2, height: r2 };
|
||||
}
|
||||
function b(e2) {
|
||||
return "html" === l(e2) ? e2 : e2.assignedSlot || e2.parentNode || (o(e2) ? e2.host : null) || d(e2);
|
||||
}
|
||||
function x(e2) {
|
||||
return ["html", "body", "#document"].indexOf(l(e2)) >= 0 ? e2.ownerDocument.body : r(e2) && v(e2) ? e2 : x(b(e2));
|
||||
}
|
||||
function w(e2, n2) {
|
||||
var r2;
|
||||
void 0 === n2 && (n2 = []);
|
||||
var o2 = x(e2), i2 = o2 === (null == (r2 = e2.ownerDocument) ? void 0 : r2.body), a2 = t(o2), s2 = i2 ? [a2].concat(a2.visualViewport || [], v(o2) ? o2 : []) : o2, f2 = n2.concat(s2);
|
||||
return i2 ? f2 : f2.concat(w(b(s2)));
|
||||
}
|
||||
function O(e2) {
|
||||
return ["table", "td", "th"].indexOf(l(e2)) >= 0;
|
||||
}
|
||||
function j(e2) {
|
||||
return r(e2) && "fixed" !== m(e2).position ? e2.offsetParent : null;
|
||||
}
|
||||
function E(e2) {
|
||||
for (var n2 = t(e2), i2 = j(e2); i2 && O(i2) && "static" === m(i2).position; ) i2 = j(i2);
|
||||
return i2 && ("html" === l(i2) || "body" === l(i2) && "static" === m(i2).position) ? n2 : i2 || (function(e3) {
|
||||
var t2 = /firefox/i.test(f());
|
||||
if (/Trident/i.test(f()) && r(e3) && "fixed" === m(e3).position) return null;
|
||||
var n3 = b(e3);
|
||||
for (o(n3) && (n3 = n3.host); r(n3) && ["html", "body"].indexOf(l(n3)) < 0; ) {
|
||||
var i3 = m(n3);
|
||||
if ("none" !== i3.transform || "none" !== i3.perspective || "paint" === i3.contain || -1 !== ["transform", "perspective"].indexOf(i3.willChange) || t2 && "filter" === i3.willChange || t2 && i3.filter && "none" !== i3.filter) return n3;
|
||||
n3 = n3.parentNode;
|
||||
}
|
||||
return null;
|
||||
})(e2) || n2;
|
||||
}
|
||||
var D = "top", A = "bottom", L = "right", P = "left", M = "auto", k = [D, A, L, P], W = "start", B = "end", H = "viewport", T = "popper", R = k.reduce((function(e2, t2) {
|
||||
return e2.concat([t2 + "-" + W, t2 + "-" + B]);
|
||||
}), []), S = [].concat(k, [M]).reduce((function(e2, t2) {
|
||||
return e2.concat([t2, t2 + "-" + W, t2 + "-" + B]);
|
||||
}), []), V = ["beforeRead", "read", "afterRead", "beforeMain", "main", "afterMain", "beforeWrite", "write", "afterWrite"];
|
||||
function q(e2) {
|
||||
var t2 = /* @__PURE__ */ new Map(), n2 = /* @__PURE__ */ new Set(), r2 = [];
|
||||
function o2(e3) {
|
||||
n2.add(e3.name), [].concat(e3.requires || [], e3.requiresIfExists || []).forEach((function(e4) {
|
||||
if (!n2.has(e4)) {
|
||||
var r3 = t2.get(e4);
|
||||
r3 && o2(r3);
|
||||
}
|
||||
})), r2.push(e3);
|
||||
}
|
||||
return e2.forEach((function(e3) {
|
||||
t2.set(e3.name, e3);
|
||||
})), e2.forEach((function(e3) {
|
||||
n2.has(e3.name) || o2(e3);
|
||||
})), r2;
|
||||
}
|
||||
function C(e2, t2) {
|
||||
var n2 = t2.getRootNode && t2.getRootNode();
|
||||
if (e2.contains(t2)) return true;
|
||||
if (n2 && o(n2)) {
|
||||
var r2 = t2;
|
||||
do {
|
||||
if (r2 && e2.isSameNode(r2)) return true;
|
||||
r2 = r2.parentNode || r2.host;
|
||||
} while (r2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function N(e2) {
|
||||
return Object.assign({}, e2, { left: e2.x, top: e2.y, right: e2.x + e2.width, bottom: e2.y + e2.height });
|
||||
}
|
||||
function I(e2, r2, o2) {
|
||||
return r2 === H ? N((function(e3, n2) {
|
||||
var r3 = t(e3), o3 = d(e3), i2 = r3.visualViewport, a2 = o3.clientWidth, s2 = o3.clientHeight, f2 = 0, p2 = 0;
|
||||
if (i2) {
|
||||
a2 = i2.width, s2 = i2.height;
|
||||
var u2 = c();
|
||||
(u2 || !u2 && "fixed" === n2) && (f2 = i2.offsetLeft, p2 = i2.offsetTop);
|
||||
}
|
||||
return { width: a2, height: s2, x: f2 + h(e3), y: p2 };
|
||||
})(e2, o2)) : n(r2) ? (function(e3, t2) {
|
||||
var n2 = p(e3, false, "fixed" === t2);
|
||||
return n2.top = n2.top + e3.clientTop, n2.left = n2.left + e3.clientLeft, n2.bottom = n2.top + e3.clientHeight, n2.right = n2.left + e3.clientWidth, n2.width = e3.clientWidth, n2.height = e3.clientHeight, n2.x = n2.left, n2.y = n2.top, n2;
|
||||
})(r2, o2) : N((function(e3) {
|
||||
var t2, n2 = d(e3), r3 = u(e3), o3 = null == (t2 = e3.ownerDocument) ? void 0 : t2.body, a2 = i(n2.scrollWidth, n2.clientWidth, o3 ? o3.scrollWidth : 0, o3 ? o3.clientWidth : 0), s2 = i(n2.scrollHeight, n2.clientHeight, o3 ? o3.scrollHeight : 0, o3 ? o3.clientHeight : 0), f2 = -r3.scrollLeft + h(e3), c2 = -r3.scrollTop;
|
||||
return "rtl" === m(o3 || n2).direction && (f2 += i(n2.clientWidth, o3 ? o3.clientWidth : 0) - a2), { width: a2, height: s2, x: f2, y: c2 };
|
||||
})(d(e2)));
|
||||
}
|
||||
function _(e2, t2, o2, s2) {
|
||||
var f2 = "clippingParents" === t2 ? (function(e3) {
|
||||
var t3 = w(b(e3)), o3 = ["absolute", "fixed"].indexOf(m(e3).position) >= 0 && r(e3) ? E(e3) : e3;
|
||||
return n(o3) ? t3.filter((function(e4) {
|
||||
return n(e4) && C(e4, o3) && "body" !== l(e4);
|
||||
})) : [];
|
||||
})(e2) : [].concat(t2), c2 = [].concat(f2, [o2]), p2 = c2[0], u2 = c2.reduce((function(t3, n2) {
|
||||
var r2 = I(e2, n2, s2);
|
||||
return t3.top = i(r2.top, t3.top), t3.right = a(r2.right, t3.right), t3.bottom = a(r2.bottom, t3.bottom), t3.left = i(r2.left, t3.left), t3;
|
||||
}), I(e2, p2, s2));
|
||||
return u2.width = u2.right - u2.left, u2.height = u2.bottom - u2.top, u2.x = u2.left, u2.y = u2.top, u2;
|
||||
}
|
||||
function F(e2) {
|
||||
return e2.split("-")[0];
|
||||
}
|
||||
function U(e2) {
|
||||
return e2.split("-")[1];
|
||||
}
|
||||
function z(e2) {
|
||||
return ["top", "bottom"].indexOf(e2) >= 0 ? "x" : "y";
|
||||
}
|
||||
function X(e2) {
|
||||
var t2, n2 = e2.reference, r2 = e2.element, o2 = e2.placement, i2 = o2 ? F(o2) : null, a2 = o2 ? U(o2) : null, s2 = n2.x + n2.width / 2 - r2.width / 2, f2 = n2.y + n2.height / 2 - r2.height / 2;
|
||||
switch (i2) {
|
||||
case D:
|
||||
t2 = { x: s2, y: n2.y - r2.height };
|
||||
break;
|
||||
case A:
|
||||
t2 = { x: s2, y: n2.y + n2.height };
|
||||
break;
|
||||
case L:
|
||||
t2 = { x: n2.x + n2.width, y: f2 };
|
||||
break;
|
||||
case P:
|
||||
t2 = { x: n2.x - r2.width, y: f2 };
|
||||
break;
|
||||
default:
|
||||
t2 = { x: n2.x, y: n2.y };
|
||||
}
|
||||
var c2 = i2 ? z(i2) : null;
|
||||
if (null != c2) {
|
||||
var p2 = "y" === c2 ? "height" : "width";
|
||||
switch (a2) {
|
||||
case W:
|
||||
t2[c2] = t2[c2] - (n2[p2] / 2 - r2[p2] / 2);
|
||||
break;
|
||||
case B:
|
||||
t2[c2] = t2[c2] + (n2[p2] / 2 - r2[p2] / 2);
|
||||
}
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
function Y(e2) {
|
||||
return Object.assign({}, { top: 0, right: 0, bottom: 0, left: 0 }, e2);
|
||||
}
|
||||
function G(e2, t2) {
|
||||
return t2.reduce((function(t3, n2) {
|
||||
return t3[n2] = e2, t3;
|
||||
}), {});
|
||||
}
|
||||
function J(e2, t2) {
|
||||
void 0 === t2 && (t2 = {});
|
||||
var r2 = t2, o2 = r2.placement, i2 = void 0 === o2 ? e2.placement : o2, a2 = r2.strategy, s2 = void 0 === a2 ? e2.strategy : a2, f2 = r2.boundary, c2 = void 0 === f2 ? "clippingParents" : f2, u2 = r2.rootBoundary, l2 = void 0 === u2 ? H : u2, h2 = r2.elementContext, m2 = void 0 === h2 ? T : h2, v2 = r2.altBoundary, y2 = void 0 !== v2 && v2, g2 = r2.padding, b2 = void 0 === g2 ? 0 : g2, x2 = Y("number" != typeof b2 ? b2 : G(b2, k)), w2 = m2 === T ? "reference" : T, O2 = e2.rects.popper, j2 = e2.elements[y2 ? w2 : m2], E2 = _(n(j2) ? j2 : j2.contextElement || d(e2.elements.popper), c2, l2, s2), P2 = p(e2.elements.reference), M2 = X({ reference: P2, element: O2, strategy: "absolute", placement: i2 }), W2 = N(Object.assign({}, O2, M2)), B2 = m2 === T ? W2 : P2, R2 = { top: E2.top - B2.top + x2.top, bottom: B2.bottom - E2.bottom + x2.bottom, left: E2.left - B2.left + x2.left, right: B2.right - E2.right + x2.right }, S2 = e2.modifiersData.offset;
|
||||
if (m2 === T && S2) {
|
||||
var V2 = S2[i2];
|
||||
Object.keys(R2).forEach((function(e3) {
|
||||
var t3 = [L, A].indexOf(e3) >= 0 ? 1 : -1, n2 = [D, A].indexOf(e3) >= 0 ? "y" : "x";
|
||||
R2[e3] += V2[n2] * t3;
|
||||
}));
|
||||
}
|
||||
return R2;
|
||||
}
|
||||
var K = { placement: "bottom", modifiers: [], strategy: "absolute" };
|
||||
function Q() {
|
||||
for (var e2 = arguments.length, t2 = new Array(e2), n2 = 0; n2 < e2; n2++) t2[n2] = arguments[n2];
|
||||
return !t2.some((function(e3) {
|
||||
return !(e3 && "function" == typeof e3.getBoundingClientRect);
|
||||
}));
|
||||
}
|
||||
function Z(e2) {
|
||||
void 0 === e2 && (e2 = {});
|
||||
var t2 = e2, r2 = t2.defaultModifiers, o2 = void 0 === r2 ? [] : r2, i2 = t2.defaultOptions, a2 = void 0 === i2 ? K : i2;
|
||||
return function(e3, t3, r3) {
|
||||
void 0 === r3 && (r3 = a2);
|
||||
var i3, s2, f2 = { placement: "bottom", orderedModifiers: [], options: Object.assign({}, K, a2), modifiersData: {}, elements: { reference: e3, popper: t3 }, attributes: {}, styles: {} }, c2 = [], p2 = false, u2 = { state: f2, setOptions: function(r4) {
|
||||
var i4 = "function" == typeof r4 ? r4(f2.options) : r4;
|
||||
l2(), f2.options = Object.assign({}, a2, f2.options, i4), f2.scrollParents = { reference: n(e3) ? w(e3) : e3.contextElement ? w(e3.contextElement) : [], popper: w(t3) };
|
||||
var s3, p3, d2 = (function(e4) {
|
||||
var t4 = q(e4);
|
||||
return V.reduce((function(e5, n2) {
|
||||
return e5.concat(t4.filter((function(e6) {
|
||||
return e6.phase === n2;
|
||||
})));
|
||||
}), []);
|
||||
})((s3 = [].concat(o2, f2.options.modifiers), p3 = s3.reduce((function(e4, t4) {
|
||||
var n2 = e4[t4.name];
|
||||
return e4[t4.name] = n2 ? Object.assign({}, n2, t4, { options: Object.assign({}, n2.options, t4.options), data: Object.assign({}, n2.data, t4.data) }) : t4, e4;
|
||||
}), {}), Object.keys(p3).map((function(e4) {
|
||||
return p3[e4];
|
||||
}))));
|
||||
return f2.orderedModifiers = d2.filter((function(e4) {
|
||||
return e4.enabled;
|
||||
})), f2.orderedModifiers.forEach((function(e4) {
|
||||
var t4 = e4.name, n2 = e4.options, r5 = void 0 === n2 ? {} : n2, o3 = e4.effect;
|
||||
if ("function" == typeof o3) {
|
||||
var i5 = o3({ state: f2, name: t4, instance: u2, options: r5 }), a3 = function() {
|
||||
};
|
||||
c2.push(i5 || a3);
|
||||
}
|
||||
})), u2.update();
|
||||
}, forceUpdate: function() {
|
||||
if (!p2) {
|
||||
var e4 = f2.elements, t4 = e4.reference, n2 = e4.popper;
|
||||
if (Q(t4, n2)) {
|
||||
f2.rects = { reference: y(t4, E(n2), "fixed" === f2.options.strategy), popper: g(n2) }, f2.reset = false, f2.placement = f2.options.placement, f2.orderedModifiers.forEach((function(e5) {
|
||||
return f2.modifiersData[e5.name] = Object.assign({}, e5.data);
|
||||
}));
|
||||
for (var r4 = 0; r4 < f2.orderedModifiers.length; r4++) if (true !== f2.reset) {
|
||||
var o3 = f2.orderedModifiers[r4], i4 = o3.fn, a3 = o3.options, s3 = void 0 === a3 ? {} : a3, c3 = o3.name;
|
||||
"function" == typeof i4 && (f2 = i4({ state: f2, options: s3, name: c3, instance: u2 }) || f2);
|
||||
} else f2.reset = false, r4 = -1;
|
||||
}
|
||||
}
|
||||
}, update: (i3 = function() {
|
||||
return new Promise((function(e4) {
|
||||
u2.forceUpdate(), e4(f2);
|
||||
}));
|
||||
}, function() {
|
||||
return s2 || (s2 = new Promise((function(e4) {
|
||||
Promise.resolve().then((function() {
|
||||
s2 = void 0, e4(i3());
|
||||
}));
|
||||
}))), s2;
|
||||
}), destroy: function() {
|
||||
l2(), p2 = true;
|
||||
} };
|
||||
if (!Q(e3, t3)) return u2;
|
||||
function l2() {
|
||||
c2.forEach((function(e4) {
|
||||
return e4();
|
||||
})), c2 = [];
|
||||
}
|
||||
return u2.setOptions(r3).then((function(e4) {
|
||||
!p2 && r3.onFirstUpdate && r3.onFirstUpdate(e4);
|
||||
})), u2;
|
||||
};
|
||||
}
|
||||
var $ = { passive: true };
|
||||
var ee = { name: "eventListeners", enabled: true, phase: "write", fn: function() {
|
||||
}, effect: function(e2) {
|
||||
var n2 = e2.state, r2 = e2.instance, o2 = e2.options, i2 = o2.scroll, a2 = void 0 === i2 || i2, s2 = o2.resize, f2 = void 0 === s2 || s2, c2 = t(n2.elements.popper), p2 = [].concat(n2.scrollParents.reference, n2.scrollParents.popper);
|
||||
return a2 && p2.forEach((function(e3) {
|
||||
e3.addEventListener("scroll", r2.update, $);
|
||||
})), f2 && c2.addEventListener("resize", r2.update, $), function() {
|
||||
a2 && p2.forEach((function(e3) {
|
||||
e3.removeEventListener("scroll", r2.update, $);
|
||||
})), f2 && c2.removeEventListener("resize", r2.update, $);
|
||||
};
|
||||
}, data: {} };
|
||||
var te = { name: "popperOffsets", enabled: true, phase: "read", fn: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.name;
|
||||
t2.modifiersData[n2] = X({ reference: t2.rects.reference, element: t2.rects.popper, strategy: "absolute", placement: t2.placement });
|
||||
}, data: {} }, ne = { top: "auto", right: "auto", bottom: "auto", left: "auto" };
|
||||
function re(e2) {
|
||||
var n2, r2 = e2.popper, o2 = e2.popperRect, i2 = e2.placement, a2 = e2.variation, f2 = e2.offsets, c2 = e2.position, p2 = e2.gpuAcceleration, u2 = e2.adaptive, l2 = e2.roundOffsets, h2 = e2.isFixed, v2 = f2.x, y2 = void 0 === v2 ? 0 : v2, g2 = f2.y, b2 = void 0 === g2 ? 0 : g2, x2 = "function" == typeof l2 ? l2({ x: y2, y: b2 }) : { x: y2, y: b2 };
|
||||
y2 = x2.x, b2 = x2.y;
|
||||
var w2 = f2.hasOwnProperty("x"), O2 = f2.hasOwnProperty("y"), j2 = P, M2 = D, k2 = window;
|
||||
if (u2) {
|
||||
var W2 = E(r2), H2 = "clientHeight", T2 = "clientWidth";
|
||||
if (W2 === t(r2) && "static" !== m(W2 = d(r2)).position && "absolute" === c2 && (H2 = "scrollHeight", T2 = "scrollWidth"), W2 = W2, i2 === D || (i2 === P || i2 === L) && a2 === B) M2 = A, b2 -= (h2 && W2 === k2 && k2.visualViewport ? k2.visualViewport.height : W2[H2]) - o2.height, b2 *= p2 ? 1 : -1;
|
||||
if (i2 === P || (i2 === D || i2 === A) && a2 === B) j2 = L, y2 -= (h2 && W2 === k2 && k2.visualViewport ? k2.visualViewport.width : W2[T2]) - o2.width, y2 *= p2 ? 1 : -1;
|
||||
}
|
||||
var R2, S2 = Object.assign({ position: c2 }, u2 && ne), V2 = true === l2 ? (function(e3, t2) {
|
||||
var n3 = e3.x, r3 = e3.y, o3 = t2.devicePixelRatio || 1;
|
||||
return { x: s(n3 * o3) / o3 || 0, y: s(r3 * o3) / o3 || 0 };
|
||||
})({ x: y2, y: b2 }, t(r2)) : { x: y2, y: b2 };
|
||||
return y2 = V2.x, b2 = V2.y, p2 ? Object.assign({}, S2, ((R2 = {})[M2] = O2 ? "0" : "", R2[j2] = w2 ? "0" : "", R2.transform = (k2.devicePixelRatio || 1) <= 1 ? "translate(" + y2 + "px, " + b2 + "px)" : "translate3d(" + y2 + "px, " + b2 + "px, 0)", R2)) : Object.assign({}, S2, ((n2 = {})[M2] = O2 ? b2 + "px" : "", n2[j2] = w2 ? y2 + "px" : "", n2.transform = "", n2));
|
||||
}
|
||||
var oe = { name: "computeStyles", enabled: true, phase: "beforeWrite", fn: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.options, r2 = n2.gpuAcceleration, o2 = void 0 === r2 || r2, i2 = n2.adaptive, a2 = void 0 === i2 || i2, s2 = n2.roundOffsets, f2 = void 0 === s2 || s2, c2 = { placement: F(t2.placement), variation: U(t2.placement), popper: t2.elements.popper, popperRect: t2.rects.popper, gpuAcceleration: o2, isFixed: "fixed" === t2.options.strategy };
|
||||
null != t2.modifiersData.popperOffsets && (t2.styles.popper = Object.assign({}, t2.styles.popper, re(Object.assign({}, c2, { offsets: t2.modifiersData.popperOffsets, position: t2.options.strategy, adaptive: a2, roundOffsets: f2 })))), null != t2.modifiersData.arrow && (t2.styles.arrow = Object.assign({}, t2.styles.arrow, re(Object.assign({}, c2, { offsets: t2.modifiersData.arrow, position: "absolute", adaptive: false, roundOffsets: f2 })))), t2.attributes.popper = Object.assign({}, t2.attributes.popper, { "data-popper-placement": t2.placement });
|
||||
}, data: {} };
|
||||
var ie = { name: "applyStyles", enabled: true, phase: "write", fn: function(e2) {
|
||||
var t2 = e2.state;
|
||||
Object.keys(t2.elements).forEach((function(e3) {
|
||||
var n2 = t2.styles[e3] || {}, o2 = t2.attributes[e3] || {}, i2 = t2.elements[e3];
|
||||
r(i2) && l(i2) && (Object.assign(i2.style, n2), Object.keys(o2).forEach((function(e4) {
|
||||
var t3 = o2[e4];
|
||||
false === t3 ? i2.removeAttribute(e4) : i2.setAttribute(e4, true === t3 ? "" : t3);
|
||||
})));
|
||||
}));
|
||||
}, effect: function(e2) {
|
||||
var t2 = e2.state, n2 = { popper: { position: t2.options.strategy, left: "0", top: "0", margin: "0" }, arrow: { position: "absolute" }, reference: {} };
|
||||
return Object.assign(t2.elements.popper.style, n2.popper), t2.styles = n2, t2.elements.arrow && Object.assign(t2.elements.arrow.style, n2.arrow), function() {
|
||||
Object.keys(t2.elements).forEach((function(e3) {
|
||||
var o2 = t2.elements[e3], i2 = t2.attributes[e3] || {}, a2 = Object.keys(t2.styles.hasOwnProperty(e3) ? t2.styles[e3] : n2[e3]).reduce((function(e4, t3) {
|
||||
return e4[t3] = "", e4;
|
||||
}), {});
|
||||
r(o2) && l(o2) && (Object.assign(o2.style, a2), Object.keys(i2).forEach((function(e4) {
|
||||
o2.removeAttribute(e4);
|
||||
})));
|
||||
}));
|
||||
};
|
||||
}, requires: ["computeStyles"] };
|
||||
var ae = { name: "offset", enabled: true, phase: "main", requires: ["popperOffsets"], fn: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.options, r2 = e2.name, o2 = n2.offset, i2 = void 0 === o2 ? [0, 0] : o2, a2 = S.reduce((function(e3, n3) {
|
||||
return e3[n3] = (function(e4, t3, n4) {
|
||||
var r3 = F(e4), o3 = [P, D].indexOf(r3) >= 0 ? -1 : 1, i3 = "function" == typeof n4 ? n4(Object.assign({}, t3, { placement: e4 })) : n4, a3 = i3[0], s3 = i3[1];
|
||||
return a3 = a3 || 0, s3 = (s3 || 0) * o3, [P, L].indexOf(r3) >= 0 ? { x: s3, y: a3 } : { x: a3, y: s3 };
|
||||
})(n3, t2.rects, i2), e3;
|
||||
}), {}), s2 = a2[t2.placement], f2 = s2.x, c2 = s2.y;
|
||||
null != t2.modifiersData.popperOffsets && (t2.modifiersData.popperOffsets.x += f2, t2.modifiersData.popperOffsets.y += c2), t2.modifiersData[r2] = a2;
|
||||
} }, se = { left: "right", right: "left", bottom: "top", top: "bottom" };
|
||||
function fe(e2) {
|
||||
return e2.replace(/left|right|bottom|top/g, (function(e3) {
|
||||
return se[e3];
|
||||
}));
|
||||
}
|
||||
var ce = { start: "end", end: "start" };
|
||||
function pe(e2) {
|
||||
return e2.replace(/start|end/g, (function(e3) {
|
||||
return ce[e3];
|
||||
}));
|
||||
}
|
||||
function ue(e2, t2) {
|
||||
void 0 === t2 && (t2 = {});
|
||||
var n2 = t2, r2 = n2.placement, o2 = n2.boundary, i2 = n2.rootBoundary, a2 = n2.padding, s2 = n2.flipVariations, f2 = n2.allowedAutoPlacements, c2 = void 0 === f2 ? S : f2, p2 = U(r2), u2 = p2 ? s2 ? R : R.filter((function(e3) {
|
||||
return U(e3) === p2;
|
||||
})) : k, l2 = u2.filter((function(e3) {
|
||||
return c2.indexOf(e3) >= 0;
|
||||
}));
|
||||
0 === l2.length && (l2 = u2);
|
||||
var d2 = l2.reduce((function(t3, n3) {
|
||||
return t3[n3] = J(e2, { placement: n3, boundary: o2, rootBoundary: i2, padding: a2 })[F(n3)], t3;
|
||||
}), {});
|
||||
return Object.keys(d2).sort((function(e3, t3) {
|
||||
return d2[e3] - d2[t3];
|
||||
}));
|
||||
}
|
||||
var le = { name: "flip", enabled: true, phase: "main", fn: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.options, r2 = e2.name;
|
||||
if (!t2.modifiersData[r2]._skip) {
|
||||
for (var o2 = n2.mainAxis, i2 = void 0 === o2 || o2, a2 = n2.altAxis, s2 = void 0 === a2 || a2, f2 = n2.fallbackPlacements, c2 = n2.padding, p2 = n2.boundary, u2 = n2.rootBoundary, l2 = n2.altBoundary, d2 = n2.flipVariations, h2 = void 0 === d2 || d2, m2 = n2.allowedAutoPlacements, v2 = t2.options.placement, y2 = F(v2), g2 = f2 || (y2 === v2 || !h2 ? [fe(v2)] : (function(e3) {
|
||||
if (F(e3) === M) return [];
|
||||
var t3 = fe(e3);
|
||||
return [pe(e3), t3, pe(t3)];
|
||||
})(v2)), b2 = [v2].concat(g2).reduce((function(e3, n3) {
|
||||
return e3.concat(F(n3) === M ? ue(t2, { placement: n3, boundary: p2, rootBoundary: u2, padding: c2, flipVariations: h2, allowedAutoPlacements: m2 }) : n3);
|
||||
}), []), x2 = t2.rects.reference, w2 = t2.rects.popper, O2 = /* @__PURE__ */ new Map(), j2 = true, E2 = b2[0], k2 = 0; k2 < b2.length; k2++) {
|
||||
var B2 = b2[k2], H2 = F(B2), T2 = U(B2) === W, R2 = [D, A].indexOf(H2) >= 0, S2 = R2 ? "width" : "height", V2 = J(t2, { placement: B2, boundary: p2, rootBoundary: u2, altBoundary: l2, padding: c2 }), q2 = R2 ? T2 ? L : P : T2 ? A : D;
|
||||
x2[S2] > w2[S2] && (q2 = fe(q2));
|
||||
var C2 = fe(q2), N2 = [];
|
||||
if (i2 && N2.push(V2[H2] <= 0), s2 && N2.push(V2[q2] <= 0, V2[C2] <= 0), N2.every((function(e3) {
|
||||
return e3;
|
||||
}))) {
|
||||
E2 = B2, j2 = false;
|
||||
break;
|
||||
}
|
||||
O2.set(B2, N2);
|
||||
}
|
||||
if (j2) for (var I2 = function(e3) {
|
||||
var t3 = b2.find((function(t4) {
|
||||
var n3 = O2.get(t4);
|
||||
if (n3) return n3.slice(0, e3).every((function(e4) {
|
||||
return e4;
|
||||
}));
|
||||
}));
|
||||
if (t3) return E2 = t3, "break";
|
||||
}, _2 = h2 ? 3 : 1; _2 > 0; _2--) {
|
||||
if ("break" === I2(_2)) break;
|
||||
}
|
||||
t2.placement !== E2 && (t2.modifiersData[r2]._skip = true, t2.placement = E2, t2.reset = true);
|
||||
}
|
||||
}, requiresIfExists: ["offset"], data: { _skip: false } };
|
||||
function de(e2, t2, n2) {
|
||||
return i(e2, a(t2, n2));
|
||||
}
|
||||
var he = { name: "preventOverflow", enabled: true, phase: "main", fn: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.options, r2 = e2.name, o2 = n2.mainAxis, s2 = void 0 === o2 || o2, f2 = n2.altAxis, c2 = void 0 !== f2 && f2, p2 = n2.boundary, u2 = n2.rootBoundary, l2 = n2.altBoundary, d2 = n2.padding, h2 = n2.tether, m2 = void 0 === h2 || h2, v2 = n2.tetherOffset, y2 = void 0 === v2 ? 0 : v2, b2 = J(t2, { boundary: p2, rootBoundary: u2, padding: d2, altBoundary: l2 }), x2 = F(t2.placement), w2 = U(t2.placement), O2 = !w2, j2 = z(x2), M2 = "x" === j2 ? "y" : "x", k2 = t2.modifiersData.popperOffsets, B2 = t2.rects.reference, H2 = t2.rects.popper, T2 = "function" == typeof y2 ? y2(Object.assign({}, t2.rects, { placement: t2.placement })) : y2, R2 = "number" == typeof T2 ? { mainAxis: T2, altAxis: T2 } : Object.assign({ mainAxis: 0, altAxis: 0 }, T2), S2 = t2.modifiersData.offset ? t2.modifiersData.offset[t2.placement] : null, V2 = { x: 0, y: 0 };
|
||||
if (k2) {
|
||||
if (s2) {
|
||||
var q2, C2 = "y" === j2 ? D : P, N2 = "y" === j2 ? A : L, I2 = "y" === j2 ? "height" : "width", _2 = k2[j2], X2 = _2 + b2[C2], Y2 = _2 - b2[N2], G2 = m2 ? -H2[I2] / 2 : 0, K2 = w2 === W ? B2[I2] : H2[I2], Q2 = w2 === W ? -H2[I2] : -B2[I2], Z2 = t2.elements.arrow, $2 = m2 && Z2 ? g(Z2) : { width: 0, height: 0 }, ee2 = t2.modifiersData["arrow#persistent"] ? t2.modifiersData["arrow#persistent"].padding : { top: 0, right: 0, bottom: 0, left: 0 }, te2 = ee2[C2], ne2 = ee2[N2], re2 = de(0, B2[I2], $2[I2]), oe2 = O2 ? B2[I2] / 2 - G2 - re2 - te2 - R2.mainAxis : K2 - re2 - te2 - R2.mainAxis, ie2 = O2 ? -B2[I2] / 2 + G2 + re2 + ne2 + R2.mainAxis : Q2 + re2 + ne2 + R2.mainAxis, ae2 = t2.elements.arrow && E(t2.elements.arrow), se2 = ae2 ? "y" === j2 ? ae2.clientTop || 0 : ae2.clientLeft || 0 : 0, fe2 = null != (q2 = null == S2 ? void 0 : S2[j2]) ? q2 : 0, ce2 = _2 + ie2 - fe2, pe2 = de(m2 ? a(X2, _2 + oe2 - fe2 - se2) : X2, _2, m2 ? i(Y2, ce2) : Y2);
|
||||
k2[j2] = pe2, V2[j2] = pe2 - _2;
|
||||
}
|
||||
if (c2) {
|
||||
var ue2, le2 = "x" === j2 ? D : P, he2 = "x" === j2 ? A : L, me2 = k2[M2], ve2 = "y" === M2 ? "height" : "width", ye2 = me2 + b2[le2], ge2 = me2 - b2[he2], be2 = -1 !== [D, P].indexOf(x2), xe2 = null != (ue2 = null == S2 ? void 0 : S2[M2]) ? ue2 : 0, we2 = be2 ? ye2 : me2 - B2[ve2] - H2[ve2] - xe2 + R2.altAxis, Oe = be2 ? me2 + B2[ve2] + H2[ve2] - xe2 - R2.altAxis : ge2, je = m2 && be2 ? (function(e3, t3, n3) {
|
||||
var r3 = de(e3, t3, n3);
|
||||
return r3 > n3 ? n3 : r3;
|
||||
})(we2, me2, Oe) : de(m2 ? we2 : ye2, me2, m2 ? Oe : ge2);
|
||||
k2[M2] = je, V2[M2] = je - me2;
|
||||
}
|
||||
t2.modifiersData[r2] = V2;
|
||||
}
|
||||
}, requiresIfExists: ["offset"] };
|
||||
var me = { name: "arrow", enabled: true, phase: "main", fn: function(e2) {
|
||||
var t2, n2 = e2.state, r2 = e2.name, o2 = e2.options, i2 = n2.elements.arrow, a2 = n2.modifiersData.popperOffsets, s2 = F(n2.placement), f2 = z(s2), c2 = [P, L].indexOf(s2) >= 0 ? "height" : "width";
|
||||
if (i2 && a2) {
|
||||
var p2 = (function(e3, t3) {
|
||||
return Y("number" != typeof (e3 = "function" == typeof e3 ? e3(Object.assign({}, t3.rects, { placement: t3.placement })) : e3) ? e3 : G(e3, k));
|
||||
})(o2.padding, n2), u2 = g(i2), l2 = "y" === f2 ? D : P, d2 = "y" === f2 ? A : L, h2 = n2.rects.reference[c2] + n2.rects.reference[f2] - a2[f2] - n2.rects.popper[c2], m2 = a2[f2] - n2.rects.reference[f2], v2 = E(i2), y2 = v2 ? "y" === f2 ? v2.clientHeight || 0 : v2.clientWidth || 0 : 0, b2 = h2 / 2 - m2 / 2, x2 = p2[l2], w2 = y2 - u2[c2] - p2[d2], O2 = y2 / 2 - u2[c2] / 2 + b2, j2 = de(x2, O2, w2), M2 = f2;
|
||||
n2.modifiersData[r2] = ((t2 = {})[M2] = j2, t2.centerOffset = j2 - O2, t2);
|
||||
}
|
||||
}, effect: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.options.element, r2 = void 0 === n2 ? "[data-popper-arrow]" : n2;
|
||||
null != r2 && ("string" != typeof r2 || (r2 = t2.elements.popper.querySelector(r2))) && C(t2.elements.popper, r2) && (t2.elements.arrow = r2);
|
||||
}, requires: ["popperOffsets"], requiresIfExists: ["preventOverflow"] };
|
||||
function ve(e2, t2, n2) {
|
||||
return void 0 === n2 && (n2 = { x: 0, y: 0 }), { top: e2.top - t2.height - n2.y, right: e2.right - t2.width + n2.x, bottom: e2.bottom - t2.height + n2.y, left: e2.left - t2.width - n2.x };
|
||||
}
|
||||
function ye(e2) {
|
||||
return [D, L, A, P].some((function(t2) {
|
||||
return e2[t2] >= 0;
|
||||
}));
|
||||
}
|
||||
var ge = { name: "hide", enabled: true, phase: "main", requiresIfExists: ["preventOverflow"], fn: function(e2) {
|
||||
var t2 = e2.state, n2 = e2.name, r2 = t2.rects.reference, o2 = t2.rects.popper, i2 = t2.modifiersData.preventOverflow, a2 = J(t2, { elementContext: "reference" }), s2 = J(t2, { altBoundary: true }), f2 = ve(a2, r2), c2 = ve(s2, o2, i2), p2 = ye(f2), u2 = ye(c2);
|
||||
t2.modifiersData[n2] = { referenceClippingOffsets: f2, popperEscapeOffsets: c2, isReferenceHidden: p2, hasPopperEscaped: u2 }, t2.attributes.popper = Object.assign({}, t2.attributes.popper, { "data-popper-reference-hidden": p2, "data-popper-escaped": u2 });
|
||||
} }, be = Z({ defaultModifiers: [ee, te, oe, ie] }), xe = [ee, te, oe, ie, ae, le, he, me, ge], we = Z({ defaultModifiers: xe });
|
||||
e.applyStyles = ie, e.arrow = me, e.computeStyles = oe, e.createPopper = we, e.createPopperLite = be, e.defaultModifiers = xe, e.detectOverflow = J, e.eventListeners = ee, e.flip = le, e.hide = ge, e.offset = ae, e.popperGenerator = Z, e.popperOffsets = te, e.preventOverflow = he, Object.defineProperty(e, "__esModule", { value: true });
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
// frontend/js/vendor/flatpickr.js
|
||||
var require_flatpickr = __commonJS({
|
||||
"frontend/js/vendor/flatpickr.js"(exports, module) {
|
||||
@@ -1647,127 +1170,11 @@ var LogList = class {
|
||||
};
|
||||
var logList_default = LogList;
|
||||
|
||||
// frontend/js/components/customSelectV2.js
|
||||
var import_popper_esm_min = __toESM(require_popper_esm_min());
|
||||
var CustomSelectV2 = class _CustomSelectV2 {
|
||||
constructor(container) {
|
||||
this.container = container;
|
||||
this.trigger = this.container.querySelector(".custom-select-trigger");
|
||||
this.nativeSelect = this.container.querySelector("select");
|
||||
this.template = this.container.querySelector(".custom-select-panel-template");
|
||||
if (!this.trigger || !this.nativeSelect || !this.template) {
|
||||
console.warn("CustomSelectV2 cannot initialize: missing required elements.", this.container);
|
||||
return;
|
||||
}
|
||||
this.panel = null;
|
||||
this.popperInstance = null;
|
||||
this.isOpen = false;
|
||||
this.triggerText = this.trigger.querySelector("span");
|
||||
if (typeof _CustomSelectV2.openInstance === "undefined") {
|
||||
_CustomSelectV2.openInstance = null;
|
||||
_CustomSelectV2.initGlobalListener();
|
||||
}
|
||||
this.updateTriggerText();
|
||||
this.bindEvents();
|
||||
}
|
||||
static initGlobalListener() {
|
||||
document.addEventListener("click", (event) => {
|
||||
const instance = _CustomSelectV2.openInstance;
|
||||
if (instance && !instance.container.contains(event.target) && (!instance.panel || !instance.panel.contains(event.target))) {
|
||||
instance.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
createPanel() {
|
||||
const panelFragment = this.template.content.cloneNode(true);
|
||||
this.panel = panelFragment.querySelector(".custom-select-panel");
|
||||
document.body.appendChild(this.panel);
|
||||
this.panel.innerHTML = "";
|
||||
Array.from(this.nativeSelect.options).forEach((option) => {
|
||||
const item = document.createElement("a");
|
||||
item.href = "#";
|
||||
item.className = "custom-select-option block w-full text-left px-3 py-1.5 text-sm text-zinc-700 hover:bg-zinc-100 dark:text-zinc-200 dark:hover:bg-zinc-700";
|
||||
item.textContent = option.textContent;
|
||||
item.dataset.value = option.value;
|
||||
if (option.selected) {
|
||||
item.classList.add("is-selected");
|
||||
}
|
||||
this.panel.appendChild(item);
|
||||
});
|
||||
this.panel.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
const optionEl = event.target.closest(".custom-select-option");
|
||||
if (optionEl) {
|
||||
this.selectOption(optionEl);
|
||||
}
|
||||
});
|
||||
}
|
||||
bindEvents() {
|
||||
this.trigger.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
if (_CustomSelectV2.openInstance && _CustomSelectV2.openInstance !== this) {
|
||||
_CustomSelectV2.openInstance.close();
|
||||
}
|
||||
this.toggle();
|
||||
});
|
||||
}
|
||||
selectOption(optionEl) {
|
||||
const selectedValue = optionEl.dataset.value;
|
||||
if (this.nativeSelect.value !== selectedValue) {
|
||||
this.nativeSelect.value = selectedValue;
|
||||
this.nativeSelect.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
}
|
||||
this.updateTriggerText();
|
||||
this.close();
|
||||
}
|
||||
updateTriggerText() {
|
||||
const selectedOption = this.nativeSelect.options[this.nativeSelect.selectedIndex];
|
||||
if (selectedOption) {
|
||||
this.triggerText.textContent = selectedOption.textContent;
|
||||
}
|
||||
}
|
||||
toggle() {
|
||||
this.isOpen ? this.close() : this.open();
|
||||
}
|
||||
open() {
|
||||
if (this.isOpen) return;
|
||||
this.isOpen = true;
|
||||
if (!this.panel) {
|
||||
this.createPanel();
|
||||
}
|
||||
this.panel.style.display = "block";
|
||||
this.panel.offsetHeight;
|
||||
this.popperInstance = (0, import_popper_esm_min.createPopper)(this.trigger, this.panel, {
|
||||
placement: "top-start",
|
||||
modifiers: [
|
||||
{ name: "offset", options: { offset: [0, 8] } },
|
||||
{ name: "flip", options: { fallbackPlacements: ["bottom-start"] } }
|
||||
]
|
||||
});
|
||||
_CustomSelectV2.openInstance = this;
|
||||
}
|
||||
close() {
|
||||
if (!this.isOpen) return;
|
||||
this.isOpen = false;
|
||||
if (this.popperInstance) {
|
||||
this.popperInstance.destroy();
|
||||
this.popperInstance = null;
|
||||
}
|
||||
if (this.panel) {
|
||||
this.panel.remove();
|
||||
this.panel = null;
|
||||
}
|
||||
if (_CustomSelectV2.openInstance === this) {
|
||||
_CustomSelectV2.openInstance = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// frontend/js/components/filterPopover.js
|
||||
var import_popper_esm_min2 = __toESM(require_popper_esm_min());
|
||||
var import_popper_esm_min = __toESM(require_popper_esm_min());
|
||||
var FilterPopover = class {
|
||||
constructor(triggerElement, options, title) {
|
||||
if (!triggerElement || typeof import_popper_esm_min2.createPopper !== "function") {
|
||||
if (!triggerElement || typeof import_popper_esm_min.createPopper !== "function") {
|
||||
console.error("FilterPopover: Trigger element or Popper.js not found.");
|
||||
return;
|
||||
}
|
||||
@@ -1776,7 +1183,7 @@ var FilterPopover = class {
|
||||
this.title = title;
|
||||
this.selectedValues = /* @__PURE__ */ new Set();
|
||||
this._createPopoverHTML();
|
||||
this.popperInstance = (0, import_popper_esm_min2.createPopper)(this.triggerElement, this.popoverElement, {
|
||||
this.popperInstance = (0, import_popper_esm_min.createPopper)(this.triggerElement, this.popoverElement, {
|
||||
placement: "bottom-start",
|
||||
modifiers: [{ name: "offset", options: { offset: [0, 8] } }]
|
||||
});
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
apiFetchJson,
|
||||
modalManager,
|
||||
uiPatterns
|
||||
} from "./chunk-SHK62ZJN.js";
|
||||
} from "./chunk-VOGCL6QZ.js";
|
||||
import "./chunk-JSBRDJBE.js";
|
||||
|
||||
// frontend/js/vendor/sweetalert2.esm.js
|
||||
@@ -4631,8 +4631,9 @@ var pageModules = {
|
||||
// 键 'dashboard' 对应一个函数,该函数调用 import() 返回一个 Promise
|
||||
// esbuild 看到这个 import() 语法,就会自动将 dashboard.js 及其依赖打包成一个独立的 chunk 文件
|
||||
"dashboard": () => import("./dashboard-XFUWX3IN.js"),
|
||||
"keys": () => import("./keys-YEK3YJ77.js"),
|
||||
"logs": () => import("./logs-SSK3L2XT.js")
|
||||
"keys": () => import("./keys-V4KPRAAB.js"),
|
||||
"logs": () => import("./logs-4XXDCFYG.js"),
|
||||
"chat": () => import("./chat-2W4NJWMO.js")
|
||||
// 'settings': () => import('./pages/settings.js'), // 未来启用 settings 页面
|
||||
// 未来新增的页面,只需在这里添加一行映射,esbuild会自动处理
|
||||
};
|
||||
|
||||
@@ -8,47 +8,118 @@
|
||||
<!-- =================================================================== -->
|
||||
<!-- 1. 左侧栏: 会话列表 -->
|
||||
<!-- =================================================================== -->
|
||||
<!-- =================================================================== -->
|
||||
<!-- 1. 左侧栏: 会话列表 (包含新的设置面板) -->
|
||||
<!-- =================================================================== -->
|
||||
<aside class="w-[280px] h-full flex flex-col border-r border-border bg-muted/50 shrink-0">
|
||||
<!-- 侧边栏头部 -->
|
||||
<div class="p-4 border-b border-border shrink-0">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-bold tracking-tight">会话</h2>
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="新建会话">
|
||||
<button id="new-session-btn" class="btn btn-ghost btn-icon btn-sm" aria-label="新建会话">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative mt-4">
|
||||
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"></i>
|
||||
<input class="input h-9 pl-10" placeholder="搜索会话...">
|
||||
<input id="session-search-input" class="input h-9 pl-10" placeholder="搜索会话...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 会话列表 (可滚动) -->
|
||||
<div class="flex-grow overflow-y-auto main-content-scroll p-2">
|
||||
<!-- 示例会话 1: 激活状态 -->
|
||||
<a href="#" class="flex flex-col items-start gap-2 rounded-lg p-3 text-left text-sm transition-all hover:bg-accent bg-accent">
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="font-semibold">关于Python异步编程</div>
|
||||
<div class="ml-auto text-xs text-muted-foreground">3:15 PM</div>
|
||||
<div id="session-list-container" class="flex-grow overflow-y-auto main-content-scroll p-2">
|
||||
<!-- Session items will be dynamically inserted here by JavaScript -->
|
||||
</div>
|
||||
|
||||
<!-- 设置面板区域: 相对定位上下文 -->
|
||||
<div class="shrink-0 p-2 border-t border-border relative">
|
||||
|
||||
<!-- 快速设置面板: 绝对定位 -->
|
||||
<div id="quick-settings-panel"
|
||||
class="absolute bottom-full left-2 right-2 mb-2 p-3 bg-background border border-border rounded-lg shadow-lg grid grid-rows-[0] transition-all duration-300 ease-in-out overflow-hidden invisible data-[expanded=true]:visible data-[expanded=true]:grid-rows-[1]">
|
||||
<div class="min-h-0 space-y-4">
|
||||
<div class="space-y-2"><label class="text-sm font-medium">路由模式</label><div class="btn-group grid grid-cols-2 gap-px rounded-md border border-border overflow-hidden"><button data-group="routing-mode" data-value="smart" class="btn-group-item px-3 py-1.5 text-sm bg-background hover:bg-muted focus:relative data-[active=true]:bg-primary data-[active=true]:text-primary-foreground" data-active="true">智能聚合</button><button data-group="routing-mode" data-value="direct" class="btn-group-item px-3 py-1.5 text-sm bg-background hover:bg-muted focus:relative data-[active=true]:bg-primary data-[active=true]:text-primary-foreground">精准路由</button></div></div>
|
||||
<div id="direct-routing-options" class="hidden space-y-2">
|
||||
<label class="text-sm font-medium">选择群组</label>
|
||||
<div id="group-select-container" class="custom-select-v2">
|
||||
<!-- 1. The trigger button -->
|
||||
<button class="custom-select-trigger btn btn-secondary btn-sm w-full justify-between">
|
||||
<span></span> <!-- Text will be populated by JS -->
|
||||
<i class="fas fa-chevron-down text-xs"></i>
|
||||
</button>
|
||||
<!-- 2. The hidden native select for state management -->
|
||||
<select id="group-select" class="hidden">
|
||||
<option value="group-a">默认群组</option>
|
||||
<option value="group-b">测试群组</option>
|
||||
</select>
|
||||
<!-- 3. The template for the dropdown panel -->
|
||||
<template class="custom-select-panel-template">
|
||||
<div class="custom-select-panel z-50 w-[var(--radix-popper-anchor-width)] bg-background border border-border rounded-md shadow-lg p-1">
|
||||
<!-- Options will be populated by JS -->
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground line-clamp-2">好的,我明白了。所以 aiohttp 客户端的 session 应该在...</div>
|
||||
</a>
|
||||
<!-- 示例会话 2: 非激活状态 -->
|
||||
<a href="#" class="flex flex-col items-start gap-2 rounded-lg p-3 text-left text-sm transition-all hover:bg-accent">
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="font-semibold">Tailwind v4 迁移指南</div>
|
||||
<div class="ml-auto text-xs text-muted-foreground">1:45 PM</div>
|
||||
<div class="space-y-2"><label class="text-sm font-medium">接口方式</label><div class="btn-group grid grid-cols-2 gap-px rounded-md border border-border overflow-hidden"><button data-group="api-interface" data-value="gemini" class="btn-group-item px-3 py-1.5 text-sm bg-background hover:bg-muted focus:relative data-[active=true]:bg-primary data-[active=true]:text-primary-foreground" data-active="true">Gemini</button><button data-group-api-interface="api-interface" data-value="openai" class="btn-group-item px-3 py-1.5 text-sm bg-background hover:bg-muted focus:relative data-[active=true]:bg-primary data-[active=true]:text-primary-foreground">OpenAI</button></div></div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium">模型选择</label>
|
||||
<div id="model-select-container" class="custom-select-v2">
|
||||
<button id="model-select-btn" class="custom-select-trigger btn btn-secondary btn-sm w-full justify-between">
|
||||
<span>gemini-1.5-flash</span>
|
||||
<i class="fas fa-chevron-down text-xs"></i>
|
||||
</button>
|
||||
<select id="model-select" class="hidden">
|
||||
<!-- Options to be added later -->
|
||||
</select>
|
||||
<template class="custom-select-panel-template">
|
||||
<div class="custom-select-panel z-50 w-[var(--radix-popper-anchor-width)] bg-background border border-border rounded-md shadow-lg p-1"></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground line-clamp-2">我们已经成功解决了 JIT 编译器的缓存和 HSL 函数的问题...</div>
|
||||
</a>
|
||||
<!-- 示例会话 3: 非激活状态 -->
|
||||
<a href="#" class="flex flex-col items-start gap-2 rounded-lg p-3 text-left text-sm transition-all hover:bg-accent">
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="font-semibold">数据库性能优化</div>
|
||||
<div class="ml-auto text-xs text-muted-foreground">昨天</div>
|
||||
</div>
|
||||
|
||||
<!-- 会话参数面板: 绝对定位 -->
|
||||
<div id="session-params-panel"
|
||||
class="absolute bottom-full left-2 right-2 mb-2 p-3 bg-background border border-border rounded-lg shadow-lg grid grid-rows-[0] transition-all duration-300 ease-in-out overflow-hidden invisible data-[expanded=true]:visible data-[expanded=true]:grid-rows-[1]">
|
||||
<div class="min-h-0 space-y-4">
|
||||
<!-- [MODIFIED] Replaced switches with a pure Tailwind CSS component -->
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="streaming-toggle" class="text-sm font-medium">流式输出</label>
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" id="streaming-toggle" class="sr-only peer" checked>
|
||||
<div class="w-11 h-6 bg-muted rounded-full peer peer-focus:ring-2 peer-focus:ring-primary/50 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-border after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="thinking-toggle" class="text-sm font-medium">模型思考</label>
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" id="thinking-toggle" class="sr-only peer" checked>
|
||||
<div class="w-11 h-6 bg-muted rounded-full peer peer-focus:ring-2 peer-focus:ring-primary/50 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-border after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<label for="temperature-slider" class="text-sm font-medium">温度</label>
|
||||
<span id="temperature-value" class="text-sm font-mono text-muted-foreground">0.7</span>
|
||||
</div>
|
||||
<input id="temperature-slider" type="range" min="0" max="2" value="0.7" step="0.1" class="slider w-full">
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<label for="context-slider" class="text-sm font-medium">上下文</label>
|
||||
<span id="context-value" class="text-sm font-mono text-muted-foreground">5k</span>
|
||||
</div>
|
||||
<input id="context-slider" type="range" min="0" max="25" value="5" step="1" class="slider w-full">
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground line-clamp-2">索引的创建确实是关键,特别是在有大量 JOIN 操作的查询中。</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 底部触发按钮 -->
|
||||
<div class="flex items-center justify-center gap-2 mt-2">
|
||||
<button id="toggle-quick-settings" class="btn btn-ghost btn-icon data-[active=true]:bg-muted data-[active=true]:text-accent-foreground" title="快速设置"><i class="fas fa-cog"></i></button>
|
||||
<button id="toggle-session-params" class="btn btn-ghost btn-icon data-[active=true]:bg-muted data-[active=true]:text-accent-foreground" title="会话参数"><i class="fas fa-sliders-h"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -58,9 +129,9 @@
|
||||
<main class="flex-1 flex flex-col h-full">
|
||||
<!-- 聊天窗口头部 -->
|
||||
<div class="flex items-center p-4 border-b border-border shrink-0">
|
||||
<h3 class="text-lg font-semibold">关于Python异步编程</h3>
|
||||
<h3 class="text-lg font-semibold chat-header-title">新会话</h3>
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="清除会话">
|
||||
<button id="clear-session-btn" class="btn btn-ghost btn-icon btn-sm" aria-label="清除会话">
|
||||
<i class="fas fa-eraser"></i>
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="更多选项">
|
||||
@@ -70,48 +141,31 @@
|
||||
</div>
|
||||
|
||||
<!-- 消息区域 (可滚动) -->
|
||||
<div class="flex-grow p-6 overflow-y-auto main-content-scroll">
|
||||
<div class="space-y-6">
|
||||
<!-- 示例消息 1: 用户 -->
|
||||
<div class="flex items-start gap-4">
|
||||
<span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-secondary text-secondary-foreground">
|
||||
<i class="fas fa-user"></i>
|
||||
</span>
|
||||
<div class="flex-1 space-y-2">
|
||||
<div class="rounded-lg bg-muted p-3">
|
||||
<p class="text-sm text-foreground">你能给我解释一下 aiohttp 客户端的 session 和 connector 管理吗?我总是搞不清楚什么时候应该创建,什么时候应该关闭。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 示例消息 2: 助手 (Gemini) -->
|
||||
<div class="flex items-start gap-4">
|
||||
<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="rounded-lg bg-primary/10 border border-primary/20 p-3">
|
||||
<p class="text-sm text-foreground">当然可以。这是一个非常经典的问题。简单来说,`ClientSession` 应该在你的应用程序的生命周期内尽可能地保持单例存在...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow p-6 overflow-y-auto main-content-scroll" id="chat-scroll-container">
|
||||
<div class="space-y-6" id="chat-messages-container">
|
||||
<!--fill with js -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<div class="p-4 border-t border-border shrink-0 bg-background">
|
||||
<div class="relative">
|
||||
<textarea placeholder="输入消息..." class="input pr-20 resize-none" rows="1"></textarea>
|
||||
<div class="absolute top-1/2 right-3 -translate-y-1/2 flex items-center gap-2">
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="附加文件">
|
||||
<i class="fas fa-paperclip"></i>
|
||||
<form id="message-form" class="relative">
|
||||
<textarea id="message-input" placeholder="输入消息..." class="input w-full min-h-14 pr-24 resize-none overflow-y-hidden" rows="1"></textarea>
|
||||
|
||||
<div class="absolute bottom-2 right-2 flex items-center gap-2">
|
||||
<button type="button" class="btn btn-ghost btn-icon btn-sm" aria-label="附加文件" title="此功能将在未来版本中提供">
|
||||
<i class="fas fa-paperclip text-muted-foreground/60"></i>
|
||||
</button>
|
||||
<button type="submit" id="send-btn" class="btn btn-primary btn-icon w-9 h-9 shrink-0 rounded-full bg-green-500 hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700 disabled:bg-green-500/50">
|
||||
<i class="fas fa-arrow-up text-white dark:text-black"></i>
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_scripts %}
|
||||
|
||||
{% endblock page_scripts %}
|
||||
Reference in New Issue
Block a user