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