Current status includes: - Virtual monitor surface and components - Monitor store for state management - Tool call animations and transitions - Liquid glass shader integration Known issue to fix: Tool status display timing - "正在xx" appears after tool execution completes instead of when tool call starts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
294 lines
8.0 KiB
TypeScript
294 lines
8.0 KiB
TypeScript
import { defineStore } from 'pinia';
|
|
|
|
type PanelMode = 'files' | 'todo' | 'subAgents';
|
|
type ResizingPanel = 'left' | 'right' | null;
|
|
type MobileOverlayTarget = 'conversation' | 'workspace' | 'focus' | null;
|
|
|
|
interface QuotaToast {
|
|
message: string;
|
|
type?: string;
|
|
}
|
|
|
|
interface ToastItem {
|
|
id: number;
|
|
title?: string;
|
|
message: string;
|
|
type?: string;
|
|
closable: boolean;
|
|
timeoutId: ReturnType<typeof setTimeout> | null;
|
|
}
|
|
|
|
interface ToastOptions {
|
|
title?: string;
|
|
message?: string;
|
|
type?: string;
|
|
closable?: boolean;
|
|
duration?: number | null;
|
|
}
|
|
|
|
interface ConfirmDialogOptions {
|
|
title?: string;
|
|
message?: string;
|
|
confirmText?: string;
|
|
cancelText?: string;
|
|
}
|
|
|
|
interface ConfirmDialogState extends ConfirmDialogOptions {
|
|
visible: boolean;
|
|
}
|
|
|
|
interface EasterEggState {
|
|
active: boolean;
|
|
effect: string | null;
|
|
payload: any;
|
|
instance: any;
|
|
cleanupTimer: ReturnType<typeof setTimeout> | null;
|
|
destroying: boolean;
|
|
destroyPromise: Promise<void> | null;
|
|
}
|
|
|
|
interface UiState {
|
|
sidebarCollapsed: boolean;
|
|
workspaceCollapsed: boolean;
|
|
chatDisplayMode: 'chat' | 'monitor';
|
|
panelMode: PanelMode;
|
|
panelMenuOpen: boolean;
|
|
leftWidth: number;
|
|
rightWidth: number;
|
|
rightCollapsed: boolean;
|
|
isResizing: boolean;
|
|
resizingPanel: ResizingPanel;
|
|
minPanelWidth: number;
|
|
maxPanelWidth: number;
|
|
quotaToast: QuotaToast | null;
|
|
quotaToastTimer: ReturnType<typeof setTimeout> | null;
|
|
toastQueue: ToastItem[];
|
|
nextToastId: number;
|
|
confirmDialog: ConfirmDialogState | null;
|
|
pendingConfirmResolver: ((value: boolean) => void) | null;
|
|
easterEgg: EasterEggState;
|
|
isMobileViewport: boolean;
|
|
mobileOverlayMenuOpen: boolean;
|
|
activeMobileOverlay: MobileOverlayTarget;
|
|
}
|
|
|
|
export const useUiStore = defineStore('ui', {
|
|
state: (): UiState => ({
|
|
sidebarCollapsed: true,
|
|
workspaceCollapsed: false,
|
|
chatDisplayMode: 'chat',
|
|
panelMode: 'files',
|
|
panelMenuOpen: false,
|
|
leftWidth: 350,
|
|
rightWidth: 420,
|
|
rightCollapsed: true,
|
|
isResizing: false,
|
|
resizingPanel: null,
|
|
minPanelWidth: 350,
|
|
maxPanelWidth: 600,
|
|
quotaToast: null,
|
|
quotaToastTimer: null,
|
|
toastQueue: [],
|
|
nextToastId: 1,
|
|
confirmDialog: null,
|
|
pendingConfirmResolver: null,
|
|
easterEgg: {
|
|
active: false,
|
|
effect: null,
|
|
payload: null,
|
|
instance: null,
|
|
cleanupTimer: null,
|
|
destroying: false,
|
|
destroyPromise: null
|
|
},
|
|
isMobileViewport: false,
|
|
mobileOverlayMenuOpen: false,
|
|
activeMobileOverlay: null
|
|
}),
|
|
actions: {
|
|
setSidebarCollapsed(collapsed: boolean) {
|
|
this.sidebarCollapsed = collapsed;
|
|
},
|
|
toggleSidebar() {
|
|
this.sidebarCollapsed = !this.sidebarCollapsed;
|
|
},
|
|
setWorkspaceCollapsed(collapsed: boolean) {
|
|
this.workspaceCollapsed = collapsed;
|
|
},
|
|
setChatDisplayMode(mode: 'chat' | 'monitor') {
|
|
this.chatDisplayMode = mode;
|
|
},
|
|
toggleChatDisplayMode() {
|
|
this.chatDisplayMode = this.chatDisplayMode === 'chat' ? 'monitor' : 'chat';
|
|
},
|
|
toggleWorkspaceCollapsed() {
|
|
this.workspaceCollapsed = !this.workspaceCollapsed;
|
|
},
|
|
setPanelMode(mode: PanelMode) {
|
|
this.panelMode = mode;
|
|
},
|
|
setPanelMenuOpen(open: boolean) {
|
|
this.panelMenuOpen = open;
|
|
},
|
|
togglePanelMenu() {
|
|
this.panelMenuOpen = !this.panelMenuOpen;
|
|
},
|
|
setLeftWidth(width: number) {
|
|
this.leftWidth = width;
|
|
},
|
|
setRightWidth(width: number) {
|
|
this.rightWidth = width;
|
|
},
|
|
setRightCollapsed(collapsed: boolean) {
|
|
this.rightCollapsed = collapsed;
|
|
},
|
|
setResizing(state: boolean, panel: ResizingPanel = null) {
|
|
this.isResizing = state;
|
|
this.resizingPanel = state ? panel : null;
|
|
},
|
|
setPanelBounds(min: number, max: number) {
|
|
this.minPanelWidth = min;
|
|
this.maxPanelWidth = max;
|
|
},
|
|
setIsMobileViewport(isMobile: boolean) {
|
|
this.isMobileViewport = isMobile;
|
|
if (!isMobile) {
|
|
this.mobileOverlayMenuOpen = false;
|
|
this.activeMobileOverlay = null;
|
|
}
|
|
},
|
|
setMobileOverlayMenuOpen(open: boolean) {
|
|
this.mobileOverlayMenuOpen = open;
|
|
},
|
|
toggleMobileOverlayMenu() {
|
|
this.mobileOverlayMenuOpen = !this.mobileOverlayMenuOpen;
|
|
},
|
|
setActiveMobileOverlay(target: MobileOverlayTarget) {
|
|
this.activeMobileOverlay = target;
|
|
},
|
|
closeMobileOverlay() {
|
|
this.activeMobileOverlay = null;
|
|
},
|
|
showQuotaToastMessage(message: string, type: string = 'fast', duration = 5000) {
|
|
this.quotaToast = { message, type };
|
|
if (this.quotaToastTimer) {
|
|
clearTimeout(this.quotaToastTimer);
|
|
}
|
|
this.quotaToastTimer = setTimeout(() => {
|
|
this.dismissQuotaToast();
|
|
}, duration);
|
|
},
|
|
dismissQuotaToast() {
|
|
if (this.quotaToastTimer) {
|
|
clearTimeout(this.quotaToastTimer);
|
|
this.quotaToastTimer = null;
|
|
}
|
|
this.quotaToast = null;
|
|
},
|
|
pushToast(options: ToastOptions = {}) {
|
|
const title = options.title || '';
|
|
const message = options.message || '';
|
|
if (!title && !message) {
|
|
return 0;
|
|
}
|
|
const id = this.nextToastId++;
|
|
const entry: ToastItem = {
|
|
id,
|
|
title,
|
|
message,
|
|
type: options.type || 'info',
|
|
closable: options.closable !== false,
|
|
timeoutId: null
|
|
};
|
|
const duration = Object.prototype.hasOwnProperty.call(options, 'duration') ? options.duration : 4000;
|
|
if (duration !== null) {
|
|
entry.timeoutId = setTimeout(() => this.dismissToast(id), duration);
|
|
}
|
|
this.toastQueue.push(entry);
|
|
return id;
|
|
},
|
|
updateToast(id: number, patch: ToastOptions = {}) {
|
|
const entry = this.toastQueue.find(item => item.id === id);
|
|
if (!entry) {
|
|
return;
|
|
}
|
|
if (patch.title !== undefined) {
|
|
entry.title = patch.title;
|
|
}
|
|
if (patch.message !== undefined) {
|
|
entry.message = patch.message;
|
|
}
|
|
if (patch.type !== undefined) {
|
|
entry.type = patch.type;
|
|
}
|
|
if (patch.closable !== undefined) {
|
|
entry.closable = patch.closable;
|
|
}
|
|
if (Object.prototype.hasOwnProperty.call(patch, 'duration')) {
|
|
if (entry.timeoutId) {
|
|
clearTimeout(entry.timeoutId);
|
|
entry.timeoutId = null;
|
|
}
|
|
if (patch.duration !== null && typeof patch.duration === 'number') {
|
|
entry.timeoutId = setTimeout(() => this.dismissToast(id), patch.duration);
|
|
}
|
|
}
|
|
},
|
|
dismissToast(id: number) {
|
|
const index = this.toastQueue.findIndex(item => item.id === id);
|
|
if (index === -1) {
|
|
return;
|
|
}
|
|
const [entry] = this.toastQueue.splice(index, 1);
|
|
if (entry && entry.timeoutId) {
|
|
clearTimeout(entry.timeoutId);
|
|
}
|
|
},
|
|
requestConfirm(options: ConfirmDialogOptions = {}) {
|
|
return new Promise<boolean>((resolve) => {
|
|
if (this.pendingConfirmResolver) {
|
|
const previous = this.pendingConfirmResolver;
|
|
this.pendingConfirmResolver = null;
|
|
previous(false);
|
|
}
|
|
this.confirmDialog = {
|
|
visible: true,
|
|
title: options.title || '确认操作',
|
|
message: options.message || '请确认本次操作',
|
|
confirmText: options.confirmText || '确认',
|
|
cancelText: options.cancelText || '取消'
|
|
};
|
|
this.pendingConfirmResolver = resolve;
|
|
});
|
|
},
|
|
resolveConfirm(choice: boolean) {
|
|
if (this.confirmDialog) {
|
|
this.confirmDialog.visible = false;
|
|
}
|
|
const resolver = this.pendingConfirmResolver;
|
|
this.pendingConfirmResolver = null;
|
|
this.confirmDialog = null;
|
|
if (resolver) {
|
|
resolver(!!choice);
|
|
}
|
|
},
|
|
setEasterEggState(patch: Partial<EasterEggState>) {
|
|
this.easterEgg = {
|
|
...this.easterEgg,
|
|
...patch
|
|
};
|
|
},
|
|
clearEasterEgg() {
|
|
this.easterEgg = {
|
|
active: false,
|
|
effect: null,
|
|
payload: null,
|
|
instance: null,
|
|
cleanupTimer: null,
|
|
destroying: false,
|
|
destroyPromise: null
|
|
};
|
|
}
|
|
}
|
|
});
|