-
Notifications
You must be signed in to change notification settings - Fork 197
feat(聊天面板): 添加标签重命名功能 #313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import { create } from 'zustand'; | ||
| import { persist } from 'zustand/middleware'; | ||
|
|
||
| export interface ChatTab { | ||
| id: string; | ||
|
|
@@ -29,6 +30,7 @@ interface ChatState { | |
| updateTab: (id: string, updates: Partial<Omit<ChatTab, 'id'>>) => void; | ||
| setDocumentReference: (reference: DocumentReference | null) => void; | ||
| setPresetMessage: (message: string | null) => void; | ||
| onRenameTab: (id: string, newTitle: string) => void; | ||
| } | ||
|
|
||
| let tabCounter = 0; | ||
|
|
@@ -39,77 +41,115 @@ function createTabId(): string { | |
| return `tab-${tabCounter}-${Date.now()}`; | ||
| } | ||
|
|
||
| export const useChatStore = create<ChatState>((set, get) => ({ | ||
| isOpen: true, | ||
| tabs: [], | ||
| activeTabId: null, | ||
| documentReference: null, | ||
| presetMessage: null, | ||
|
|
||
| setIsOpen: (isOpen) => { | ||
| set({ isOpen }); | ||
|
|
||
| if (isOpen && get().tabs.length === 0) { | ||
| get().addTab(); | ||
| } | ||
| }, | ||
|
|
||
| togglePanel: () => { | ||
| const next = !get().isOpen; | ||
| set({ isOpen: next }); | ||
|
|
||
| if (next && get().tabs.length === 0) { | ||
| get().addTab(); | ||
| } | ||
| }, | ||
|
|
||
| addTab: (tab) => { | ||
| const id = createTabId(); | ||
| const newTab: ChatTab = { | ||
| id, | ||
| title: tab?.title || '新对话', | ||
| conversationId: tab?.conversationId || null, | ||
| }; | ||
|
|
||
| set((state) => ({ | ||
| tabs: [...state.tabs, newTab], | ||
| activeTabId: id, | ||
| })); | ||
|
|
||
| return id; | ||
| }, | ||
|
|
||
| removeTab: (id) => { | ||
| set((state) => { | ||
| const newTabs = state.tabs.filter((t) => t.id !== id); | ||
| let newActiveId = state.activeTabId; | ||
|
|
||
| if (state.activeTabId === id) { | ||
| const removedIndex = state.tabs.findIndex((t) => t.id === id); | ||
|
|
||
| if (newTabs.length > 0) { | ||
| newActiveId = newTabs[Math.min(removedIndex, newTabs.length - 1)].id; | ||
| } else { | ||
| newActiveId = null; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| tabs: newTabs, | ||
| activeTabId: newActiveId, | ||
| isOpen: newTabs.length > 0 ? state.isOpen : false, | ||
| }; | ||
| }); | ||
| }, | ||
|
|
||
| setActiveTab: (id) => set({ activeTabId: id }), | ||
|
|
||
| updateTab: (id, updates) => | ||
| set((state) => ({ | ||
| tabs: state.tabs.map((t) => (t.id === id ? { ...t, ...updates } : t)), | ||
| })), | ||
|
|
||
| setDocumentReference: (reference) => set({ documentReference: reference }), | ||
|
|
||
| setPresetMessage: (message) => set({ presetMessage: message }), | ||
| })); | ||
| export const useChatStore = create<ChatState>()( | ||
| persist( | ||
| (set) => ({ | ||
| isOpen: true, | ||
| tabs: [], | ||
| activeTabId: null, | ||
| documentReference: null, | ||
| presetMessage: null, | ||
|
|
||
| setIsOpen: (isOpen) => { | ||
| set((state) => { | ||
| if (isOpen && state.tabs.length === 0) { | ||
| const id = createTabId(); | ||
| const newTab: ChatTab = { | ||
| id, | ||
| title: '新对话', | ||
| conversationId: null, | ||
| }; | ||
|
|
||
| return { | ||
| isOpen, | ||
| tabs: [newTab], | ||
| activeTabId: id, | ||
| }; | ||
| } | ||
|
|
||
| return { isOpen }; | ||
| }); | ||
| }, | ||
|
|
||
| togglePanel: () => { | ||
| set((state) => { | ||
| const next = !state.isOpen; | ||
|
|
||
| if (next && state.tabs.length === 0) { | ||
| const id = createTabId(); | ||
| const newTab: ChatTab = { | ||
| id, | ||
| title: '新对话', | ||
| conversationId: null, | ||
| }; | ||
|
|
||
| return { | ||
| isOpen: next, | ||
| tabs: [newTab], | ||
| activeTabId: id, | ||
| }; | ||
| } | ||
|
|
||
| return { isOpen: next }; | ||
| }); | ||
| }, | ||
|
|
||
| addTab: (tab) => { | ||
| const id = createTabId(); | ||
| const newTab: ChatTab = { | ||
| id, | ||
| title: tab?.title || '新对话', | ||
| conversationId: tab?.conversationId || null, | ||
| }; | ||
|
|
||
| set((state) => ({ | ||
| tabs: [...state.tabs, newTab], | ||
| activeTabId: id, | ||
| })); | ||
|
|
||
| return id; | ||
| }, | ||
|
|
||
| removeTab: (id) => { | ||
| set((state) => { | ||
| const newTabs = state.tabs.filter((t) => t.id !== id); | ||
| let newActiveId = state.activeTabId; | ||
|
|
||
| if (state.activeTabId === id) { | ||
| const removedIndex = state.tabs.findIndex((t) => t.id === id); | ||
|
|
||
| if (newTabs.length > 0) { | ||
| newActiveId = newTabs[Math.min(removedIndex, newTabs.length - 1)].id; | ||
| } else { | ||
| newActiveId = null; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| tabs: newTabs, | ||
| activeTabId: newActiveId, | ||
| isOpen: newTabs.length > 0 ? state.isOpen : false, | ||
| }; | ||
| }); | ||
| }, | ||
|
|
||
| setActiveTab: (id) => set({ activeTabId: id }), | ||
|
|
||
| updateTab: (id, updates) => | ||
| set((state) => ({ | ||
| tabs: state.tabs.map((t) => (t.id === id ? { ...t, ...updates } : t)), | ||
| })), | ||
| onRenameTab: (id, newTitle) => | ||
| set((state) => ({ | ||
| tabs: state.tabs.map((t) => (t.id === id ? { ...t, title: newTitle } : t)), | ||
| })), | ||
|
Comment on lines
+142
to
+145
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| setDocumentReference: (reference) => set({ documentReference: reference }), | ||
|
|
||
| setPresetMessage: (message) => set({ presetMessage: message }), | ||
| }), | ||
| { | ||
| name: 'chat-storage', | ||
| }, | ||
|
Comment on lines
+151
to
+153
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 使用 建议使用 {
name: 'chat-storage',
partialize: (state) => ({
isOpen: state.isOpen,
tabs: state.tabs,
activeTabId: state.activeTabId,
}),
}, |
||
| ), | ||
| ); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
当前重命名逻辑存在两个问题:
Escape键取消编辑时,会错误地保存当前输入的内容。Enter键确认重命名时,可能会触发两次保存操作(一次来自onKeyDown,一次来自onBlur)。这两个问题都源于
onBlur事件在输入框失焦时(包括程序性失焦)总是会触发保存。建议重构事件处理逻辑,使用一个ref来防止重复操作和区分取消与保存操作,以确保行为的正确性。