286
web-app/src/store/index.ts
Normal file
286
web-app/src/store/index.ts
Normal 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])
|
||||
}
|
||||
Reference in New Issue
Block a user