From 47e0962f4b715ac9c11549cb94df8fb7faf6c823 Mon Sep 17 00:00:00 2001 From: duanfuxiang Date: Sun, 15 Jun 2025 19:02:22 +0800 Subject: [PATCH] update --- src/components/chat-view/ChatView.tsx | 129 ++++++---- src/components/chat-view/UserMessageView.tsx | 234 +++++++++++++++++++ src/database/json/base.ts | 3 - src/hooks/use-chat-history.ts | 9 - styles.css | 35 ++- 5 files changed, 347 insertions(+), 63 deletions(-) create mode 100644 src/components/chat-view/UserMessageView.tsx diff --git a/src/components/chat-view/ChatView.tsx b/src/components/chat-view/ChatView.tsx index 8477143..8764bc5 100644 --- a/src/components/chat-view/ChatView.tsx +++ b/src/components/chat-view/ChatView.tsx @@ -2,7 +2,7 @@ import * as path from 'path' import { BaseSerializedNode } from '@lexical/clipboard/clipboard' import { useMutation } from '@tanstack/react-query' -import { CircleStop, History, NotebookPen, Plus, Search, Server, SquareSlash } from 'lucide-react' +import { CircleStop, History, NotebookPen, Plus, Search, Server, SquareSlash, Undo } from 'lucide-react' import { App, Notice } from 'obsidian' import { forwardRef, @@ -70,6 +70,7 @@ import QueryProgress, { QueryProgressState } from './QueryProgress' import ReactMarkdown from './ReactMarkdown' import SearchView from './SearchView' import SimilaritySearchResults from './SimilaritySearchResults' +import UserMessageView from './UserMessageView' import WebsiteReadResults from './WebsiteReadResults' // Add an empty line here @@ -180,6 +181,9 @@ const Chat = forwardRef((props, ref) => { const [tab, setTab] = useState<'chat' | 'commands' | 'custom-mode' | 'mcp' | 'search' | 'history'>('chat') const [selectedSerializedNodes, setSelectedSerializedNodes] = useState([]) + + // 跟踪正在编辑的消息ID + const [editingMessageId, setEditingMessageId] = useState(null) useEffect(() => { const scrollContainer = chatMessagesRef.current @@ -993,18 +997,6 @@ const Chat = forwardRef((props, ref) => { > - + + registerChatUserInputRef(message.id, ref)} + initialSerializedEditorState={message.content} + onSubmit={(content, useVaultSearch) => { + if (editorStateToPlainText(content).trim() === '') return + setEditingMessageId(null) // 退出编辑模式 + handleSubmit( + [ + ...chatMessages.slice(0, index), + { + role: 'user', + applyStatus: ApplyStatus.Idle, + content: content, + promptContent: null, + id: message.id, + mentionables: message.mentionables, + }, + ], + useVaultSearch, + ) + chatUserInputRefs.current.get(inputMessage.id)?.focus() + }} + onFocus={() => { + setFocusedMessageId(message.id) + }} + onCreateCommand={handleCreateCommand} + mentionables={message.mentionables} + setMentionables={(mentionables) => { + setChatMessages((prevChatHistory) => + prevChatHistory.map((msg) => + msg.id === message.id ? { ...msg, mentionables } : msg, + ), + ) + }} + + /> + + ) : ( + { + setEditingMessageId(message.id) + setFocusedMessageId(message.id) + // 延迟聚焦,确保组件已渲染 + setTimeout(() => { + chatUserInputRefs.current.get(message.id)?.focus() + }, 0) + }} + /> + )} {message.fileReadResults && ( void +} + +const UserMessageView: React.FC = ({ + content, + mentionables, + onEdit, +}) => { + const [isExpanded, setIsExpanded] = useState(false) + + // 将编辑器状态转换为纯文本 + const plainText = content ? editorStateToPlainText(content) : '' + + // 判断是否需要截断(超过2行或超过80个字符) + const lines = plainText.split('\n') + const needsTruncation = lines.length > 3 || plainText.length > 80 + + // 显示的文本内容 + let displayText = plainText + if (needsTruncation && !isExpanded) { + // 取前2行或前80个字符,取较小值 + const truncatedByLines = lines.slice(0, 2).join('\n') + displayText = truncatedByLines.length > 80 + ? plainText.substring(0, 80) + '...' + : truncatedByLines + (lines.length > 2 ? '...' : '') + } + + return ( +
+
+ {/* 显示 mentionables */} + {mentionables.length > 0 && ( +
+ {mentionables.map((mentionable, index) => { + const Icon = getMentionableIcon(mentionable) + return ( + + {Icon && } + {mentionable.type === 'current-file' && ( + {mentionable.file.name} + )} + {mentionable.type === 'vault' && ( + Vault + )} + {mentionable.type === 'block' && ( + {mentionable.file.name} + )} + {mentionable.type === 'file' && ( + {mentionable.file.name} + )} + {mentionable.type === 'folder' && ( + {mentionable.folder.name} + )} + {mentionable.type === 'url' && ( + {mentionable.url} + )} + {mentionable.type === 'image' && ( + {mentionable.name} + )} + + ) + })} +
+ )} + + {/* 显示文本内容 */} +
+
{displayText}
+ {/* {needsTruncation && ( + + )} */} +
+
+ + {/* 编辑按钮 */} + + + +
+ ) +} + +export default UserMessageView diff --git a/src/database/json/base.ts b/src/database/json/base.ts index fbc741f..4dd67bd 100644 --- a/src/database/json/base.ts +++ b/src/database/json/base.ts @@ -55,9 +55,7 @@ export abstract class AbstractJsonRepository { // List metadata for all records by parsing file names. public async listMetadata(): Promise<(M & { fileName: string })[]> { - console.log('AbstractJsonRepository - listMetadata called for dataDir:', this.dataDir) const files = await this.app.vault.adapter.list(this.dataDir) - console.log('AbstractJsonRepository - files in directory:', files) const result = files.files .map((filePath) => path.basename(filePath)) .filter((fileName) => fileName.endsWith('.json')) @@ -68,7 +66,6 @@ export abstract class AbstractJsonRepository { .filter( (metadata): metadata is M & { fileName: string } => metadata !== null, ) - console.log('AbstractJsonRepository - parsed metadata:', result) return result } diff --git a/src/hooks/use-chat-history.ts b/src/hooks/use-chat-history.ts index 83368cd..d4d1d9b 100644 --- a/src/hooks/use-chat-history.ts +++ b/src/hooks/use-chat-history.ts @@ -26,10 +26,7 @@ export function useChatHistory(): UseChatHistory { const [chatList, setChatList] = useState([]) const fetchChatList = useCallback(async () => { - console.log('useChatHistory - fetching chat list...') const conversations = await chatManager.listChats() - console.log('useChatHistory - fetched conversations:', conversations) - console.log('useChatHistory - conversations length:', conversations.length) setChatList(conversations) }, [chatManager]) @@ -41,21 +38,17 @@ export function useChatHistory(): UseChatHistory { () => debounce( async (id: string, messages: ChatMessage[]): Promise => { - console.log('useChatHistory - createOrUpdateConversation called with id:', id, 'messages length:', messages.length) const serializedMessages = messages.map(serializeChatMessage) const existingConversation = await chatManager.findById(id) if (existingConversation) { - console.log('useChatHistory - updating existing conversation:', existingConversation.id) if (isEqual(existingConversation.messages, serializedMessages)) { - console.log('useChatHistory - messages are identical, skipping update') return } await chatManager.updateChat(existingConversation.id, { messages: serializedMessages, }) } else { - console.log('useChatHistory - creating new conversation') const firstUserMessage = messages.find((v) => v.role === 'user') as ChatUserMessage const newChat = await chatManager.createChat({ @@ -68,10 +61,8 @@ export function useChatHistory(): UseChatHistory { : 'New chat', messages: serializedMessages, }) - console.log('useChatHistory - created new chat:', newChat) } - console.log('useChatHistory - refreshing chat list after create/update') await fetchChatList() }, 300, diff --git a/styles.css b/styles.css index dffc416..3f851e5 100644 --- a/styles.css +++ b/styles.css @@ -457,6 +457,35 @@ button:not(.clickable-icon).infio-chat-list-dropdown { } } +/* Chat editing cancel button */ +.infio-chat-edit-container { + position: relative; +} + +.infio-chat-edit-cancel-button { + position: absolute; + top: 8px; + right: 8px; + z-index: 10; + display: flex; + align-items: center; + justify-content: center; + background-color: transparent !important; + border: none !important; + box-shadow: none !important; + color: var(--text-muted); + padding: 0 !important; + margin: 0 !important; + width: 24px !important; + height: 24px !important; + + &:hover { + background-color: var(--background-modifier-hover) !important; + } +} + + + /* * Interactive States * - Hover and active states @@ -750,8 +779,9 @@ input[type='text'].infio-chat-list-dropdown-item-title-input { */ .infio-chat-lexical-content-editable-root { - min-height: 46px; - max-height: 120px; + min-height: 62px; + max-height: 500px; + overflow-y: auto; } .infio-chat-lexical-content-editable-root .mention { @@ -2389,4 +2419,3 @@ button.infio-chat-input-model-select { max-height: 80vh; } -