🎨 引入mvu功能

Signed-off-by: Echo <1711788888@qq.com>
This commit is contained in:
2026-03-02 01:13:58 +08:00
parent de6015c77e
commit 2b8be78fdc

286
web-app/src/store/index.ts Normal file
View File

@@ -0,0 +1,286 @@
/**
* MVU (Model-View-Update) 状态管理 Store
* 基于 Zustand 实现的轻量级状态管理
*/
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
// ============= Model (状态模型) =============
export interface User {
id: number
username: string
email: string
avatar?: string
}
export interface Character {
id: number
name: string
avatar: string
description: string
}
export interface Conversation {
id: number
characterId: number
title: string
lastMessage?: string
updatedAt: string
}
export interface Message {
id: number
role: 'user' | 'assistant'
content: string
timestamp: string
}
export interface AppState {
// 用户状态
user: User | null
isAuthenticated: boolean
// 当前选中的实体
currentCharacter: Character | null
currentConversation: Conversation | null
// 消息列表
messages: Message[]
// UI 状态
sidebarOpen: boolean
loading: boolean
error: string | null
// 变量系统
variables: Record<string, string>
}
// ============= Update (状态更新) =============
export interface AppActions {
// 用户操作
setUser: (user: User | null) => void
login: (user: User) => void
logout: () => void
// 角色操作
setCurrentCharacter: (character: Character | null) => void
// 对话操作
setCurrentConversation: (conversation: Conversation | null) => void
// 消息操作
setMessages: (messages: Message[]) => void
addMessage: (message: Message) => void
updateMessage: (id: number, content: string) => void
deleteMessage: (id: number) => void
clearMessages: () => void
// UI 操作
toggleSidebar: () => void
setSidebarOpen: (open: boolean) => void
setLoading: (loading: boolean) => void
setError: (error: string | null) => void
// 变量系统操作
setVariable: (key: string, value: string) => void
getVariable: (key: string) => string | undefined
deleteVariable: (key: string) => void
clearVariables: () => void
// 批量更新(用于复杂操作)
batchUpdate: (updater: (state: AppState) => Partial<AppState>) => void
}
// ============= Store (状态容器) =============
const initialState: AppState = {
user: null,
isAuthenticated: false,
currentCharacter: null,
currentConversation: null,
messages: [],
sidebarOpen: true,
loading: false,
error: null,
variables: {
user: '',
char: '',
},
}
export const useAppStore = create<AppState & AppActions>()(
devtools(
persist(
(set, get) => ({
...initialState,
// 用户操作
setUser: (user) => set({ user, isAuthenticated: !!user }),
login: (user) => set({
user,
isAuthenticated: true,
variables: {
...get().variables,
user: user.username,
}
}),
logout: () => set({
user: null,
isAuthenticated: false,
currentCharacter: null,
currentConversation: null,
messages: [],
variables: {
user: '',
char: '',
}
}),
// 角色操作
setCurrentCharacter: (character) => set({
currentCharacter: character,
variables: {
...get().variables,
char: character?.name || '',
}
}),
// 对话操作
setCurrentConversation: (conversation) => set({ currentConversation: conversation }),
// 消息操作
setMessages: (messages) => set({ messages }),
addMessage: (message) => set((state) => ({
messages: [...state.messages, message]
})),
updateMessage: (id, content) => set((state) => ({
messages: state.messages.map(msg =>
msg.id === id ? { ...msg, content } : msg
)
})),
deleteMessage: (id) => set((state) => ({
messages: state.messages.filter(msg => msg.id !== id)
})),
clearMessages: () => set({ messages: [] }),
// UI 操作
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
setSidebarOpen: (open) => set({ sidebarOpen: open }),
setLoading: (loading) => set({ loading }),
setError: (error) => set({ error }),
// 变量系统操作
setVariable: (key, value) => set((state) => ({
variables: { ...state.variables, [key]: value }
})),
getVariable: (key) => get().variables[key],
deleteVariable: (key) => set((state) => {
const { [key]: _, ...rest } = state.variables
return { variables: rest }
}),
clearVariables: () => set({
variables: { user: get().user?.username || '', char: get().currentCharacter?.name || '' }
}),
// 批量更新
batchUpdate: (updater) => set((state) => updater(state)),
}),
{
name: 'app-storage',
partialize: (state) => ({
user: state.user,
isAuthenticated: state.isAuthenticated,
sidebarOpen: state.sidebarOpen,
variables: state.variables,
}),
}
)
)
)
// ============= Selectors (状态选择器) =============
export const selectUser = (state: AppState & AppActions) => state.user
export const selectIsAuthenticated = (state: AppState & AppActions) => state.isAuthenticated
export const selectCurrentCharacter = (state: AppState & AppActions) => state.currentCharacter
export const selectCurrentConversation = (state: AppState & AppActions) => state.currentConversation
export const selectMessages = (state: AppState & AppActions) => state.messages
export const selectSidebarOpen = (state: AppState & AppActions) => state.sidebarOpen
export const selectLoading = (state: AppState & AppActions) => state.loading
export const selectError = (state: AppState & AppActions) => state.error
export const selectVariables = (state: AppState & AppActions) => state.variables
// ============= 变量替换工具函数 =============
/**
* 替换文本中的变量
* 支持的变量格式:{{user}}, {{char}}, {{random}}, {{time}}, {{date}} 等
*/
export function substituteVariables(text: string, customVars?: Record<string, string>): string {
const state = useAppStore.getState()
const variables = { ...state.variables, ...customVars }
let result = text
// 替换用户定义的变量
Object.entries(variables).forEach(([key, value]) => {
const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'gi')
result = result.replace(regex, value)
})
// 替换时间变量
const now = new Date()
result = result.replace(/\{\{time\}\}/gi, now.toLocaleTimeString('en-US', { hour12: false }))
result = result.replace(/\{\{time_12h\}\}/gi, now.toLocaleTimeString('en-US', { hour12: true }))
result = result.replace(/\{\{date\}\}/gi, now.toLocaleDateString('en-CA')) // YYYY-MM-DD
result = result.replace(/\{\{date_short\}\}/gi, now.toLocaleDateString('en-US')) // MM/DD/YYYY
result = result.replace(/\{\{datetime\}\}/gi, now.toLocaleString())
result = result.replace(/\{\{timestamp\}\}/gi, now.getTime().toString())
result = result.replace(/\{\{weekday\}\}/gi, now.toLocaleDateString('en-US', { weekday: 'long' }))
result = result.replace(/\{\{month\}\}/gi, now.toLocaleDateString('en-US', { month: 'long' }))
result = result.replace(/\{\{year\}\}/gi, now.getFullYear().toString())
// 替换随机数变量
result = result.replace(/\{\{random:(\d+)-(\d+)\}\}/gi, (_, min, max) => {
const minNum = parseInt(min)
const maxNum = parseInt(max)
return Math.floor(Math.random() * (maxNum - minNum + 1) + minNum).toString()
})
result = result.replace(/\{\{random\}\}/gi, () => Math.floor(Math.random() * 100).toString())
// 随机选择 {{pick:option1|option2|option3}}
result = result.replace(/\{\{pick:([^}]+)\}\}/gi, (_, options) => {
const optionList = options.split('|')
return optionList[Math.floor(Math.random() * optionList.length)]
})
// 替换特殊字符
result = result.replace(/\{\{newline\}\}/gi, '\n')
result = result.replace(/\{\{tab\}\}/gi, '\t')
result = result.replace(/\{\{space\}\}/gi, ' ')
result = result.replace(/\{\{empty\}\}/gi, '')
return result
}
/**
* 从文本中提取变量
*/
export function extractVariables(text: string): string[] {
const regex = /\{\{([^}]+)\}\}/g
const matches = text.matchAll(regex)
return Array.from(matches, m => m[1])
}