From 1ae125b852e760647a83c617b1156bbeb80be7d2 Mon Sep 17 00:00:00 2001 From: chessjoe Date: Tue, 21 Jan 2025 18:09:11 +0800 Subject: [PATCH 1/9] refactor(ask-panel, tool-dropdown): streamline tool selection and enhance dropdown functionality - Removed the AskButton from the AskPanel, replacing it with a ToolDropdown that directly sends the selected tool. - Updated ToolDropdown to accept a buttonDisplay prop for customizable button text. - Enhanced BaseDropdown to conditionally render buttonDisplay, improving flexibility in dropdown usage. - Adjusted event handling for tool selection to simplify user interactions and improve overall UX. --- src/components/ask-panel.tsx | 35 ++++++++-------------------- src/components/ask-tooldropdown.tsx | 17 ++++++++++---- src/components/base/BaseDropdown.tsx | 8 ++++++- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/components/ask-panel.tsx b/src/components/ask-panel.tsx index b7cbfdb..e25811d 100644 --- a/src/components/ask-panel.tsx +++ b/src/components/ask-panel.tsx @@ -8,7 +8,6 @@ import ModelDropdown from './ask/ModelDropDown'; import TextareaAutosize from 'react-textarea-autosize'; import { ArrowsPointingInIcon, ArrowsPointingOutIcon, XMarkIcon } from '@heroicons/react/20/solid'; import AskMessage from './ask-message'; -import AskButton from './ask-button'; import { ToolsPromptInterface, AIInvisibleMessage, @@ -592,17 +591,6 @@ function AskPanel(props: AskPanelProps) { } }} /> - { - setUserTools(item); - if (withCommand) { - onSend(item); // 按了 Command 键直接发送,使用临时工具 - } - }} - />
- { - onSend(); +
+ { + setUserTools(_item); + onSend(_item); // 直接发送,不需要修改按钮文字 }} - onKeyDown={e => { - if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.altKey) { - e.preventDefault(); - onSend(); - } - }}> - ➔ -
+ buttonDisplay="➔" + /> diff --git a/src/components/ask-tooldropdown.tsx b/src/components/ask-tooldropdown.tsx index 720dc7b..03395bc 100644 --- a/src/components/ask-tooldropdown.tsx +++ b/src/components/ask-tooldropdown.tsx @@ -12,6 +12,7 @@ interface ToolDropdownProps { onItemClick: (_tool: ToolsPromptInterface, _withCommand?: boolean) => void; statusListener: (_status: boolean) => void; initOpen: boolean; + buttonDisplay?: string; } const tools: ToolsPromptInterface[] = []; @@ -31,7 +32,13 @@ for (const k in defaultTools) { export { tools }; -export default function ToolDropdown({ className, onItemClick, initOpen, statusListener }: ToolDropdownProps) { +export default function ToolDropdown({ + className, + onItemClick, + initOpen, + statusListener, + buttonDisplay, +}: ToolDropdownProps) { const [allTools, setAllTools] = useState([]); const [selectedTool, setSelectedTool] = useState(null); const [selectedToolName, setSelectedToolName] = useState('Frame'); // 默认显示 Frame @@ -71,8 +78,8 @@ export default function ToolDropdown({ className, onItemClick, initOpen, statusL }, []); const handleToolClick = async (tool: ToolsPromptInterface, isCommandPressed: boolean) => { - // Command+Enter 不保存设置 - if (!isCommandPressed) { + // TODO: Use cmd to pin the tool, it's not working now + if (isCommandPressed) { await StorageManager.setCurrentTool(tool.id); setSelectedTool(tool.id); setSelectedToolName(tool.name); @@ -107,7 +114,7 @@ export default function ToolDropdown({ className, onItemClick, initOpen, statusL className={`ml-2 opacity-0 transition-all duration-100 ${active || 'group-hover:opacity-100'} ${ active && 'opacity-100' }`}> - [{navigator.platform.includes('Mac') ? '⌘' : 'Ctrl'} + enter] + [{navigator.platform.includes('Mac') ? '⌘' : 'Ctrl'} + Enter to Pin] @@ -126,6 +133,8 @@ export default function ToolDropdown({ className, onItemClick, initOpen, statusL selectedId={selectedTool} shortcutKey={navigator.platform.includes('Mac') ? '⌘ K' : 'Ctrl K'} renderItem={renderToolItem} + align="right" + buttonDisplay={buttonDisplay} /> {showPreview && } diff --git a/src/components/base/BaseDropdown.tsx b/src/components/base/BaseDropdown.tsx index 6d59686..1c7f9bc 100644 --- a/src/components/base/BaseDropdown.tsx +++ b/src/components/base/BaseDropdown.tsx @@ -20,6 +20,7 @@ export interface BaseDropdownProps { selectedId?: string; showShortcut?: boolean; align?: 'left' | 'right'; + buttonDisplay?: string; } export function BaseDropdown({ @@ -34,6 +35,7 @@ export function BaseDropdown({ selectedId, showShortcut = true, align = 'left', + buttonDisplay, }: BaseDropdownProps) { const [isOpened, setIsOpen] = useState(initOpen); const [isCommandPressed, setIsCommandPressed] = useState(false); @@ -174,7 +176,11 @@ export function BaseDropdown({ {showShortcut && {shortcutKey}} - + {buttonDisplay ? ( + {buttonDisplay} + ) : ( + + )} ); }} From 6338185b9378e59173f021d7ac33a92c29bfd1f7 Mon Sep 17 00:00:00 2001 From: chessjoe Date: Tue, 21 Jan 2025 18:35:12 +0800 Subject: [PATCH 2/9] refactor(ask-panel): improve dropdown navigation with arrow key support - Updated event handling for arrow key navigation between tool, model, and system prompt dropdowns. - Enhanced user experience by ensuring consistent dropdown behavior when navigating with ArrowLeft and ArrowRight keys. - Simplified the logic for showing and hiding dropdowns based on their current state, improving code readability. --- src/components/ask-panel.tsx | 41 ++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/components/ask-panel.tsx b/src/components/ask-panel.tsx index e25811d..2666db2 100644 --- a/src/components/ask-panel.tsx +++ b/src/components/ask-panel.tsx @@ -267,19 +267,19 @@ function AskPanel(props: AskPanelProps) { e.stopPropagation(); if (e.key === 'ArrowRight') { - if (isToolDropdownOpen) { + if (isSystemPromptDropdownOpen) { showModelDropdown(); } else if (isModelDropdownOpen) { - showSystemPromptDropdown(); - } else if (isSystemPromptDropdownOpen) { showToolDropdown(); + } else if (isToolDropdownOpen) { + showSystemPromptDropdown(); } } else if (e.key === 'ArrowLeft') { - if (isToolDropdownOpen) { - showSystemPromptDropdown(); - } else if (isModelDropdownOpen) { + if (isSystemPromptDropdownOpen) { showToolDropdown(); - } else if (isSystemPromptDropdownOpen) { + } else if (isModelDropdownOpen) { + showSystemPromptDropdown(); + } else if (isToolDropdownOpen) { showModelDropdown(); } } @@ -538,17 +538,26 @@ function AskPanel(props: AskPanelProps) { } // 检测左右方向键 if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { - if (isToolDropdownOpen || isModelDropdownOpen) { + if (isToolDropdownOpen || isModelDropdownOpen || isSystemPromptDropdownOpen) { e.preventDefault(); e.stopPropagation(); - if (isToolDropdownOpen && e.key === 'ArrowRight') { - showModelDropdown(); - } else if (isModelDropdownOpen && e.key === 'ArrowRight') { - showToolDropdown(); - } else if (isToolDropdownOpen && e.key === 'ArrowLeft') { - showModelDropdown(); - } else if (isModelDropdownOpen && e.key === 'ArrowLeft') { - showToolDropdown(); + + if (e.key === 'ArrowRight') { + if (isSystemPromptDropdownOpen) { + showModelDropdown(); + } else if (isModelDropdownOpen) { + showToolDropdown(); + } else if (isToolDropdownOpen) { + showSystemPromptDropdown(); + } + } else if (e.key === 'ArrowLeft') { + if (isSystemPromptDropdownOpen) { + showToolDropdown(); + } else if (isModelDropdownOpen) { + showSystemPromptDropdown(); + } else if (isToolDropdownOpen) { + showModelDropdown(); + } } return; } From 7ddb16e7699dd3265014405e79123a158596df72 Mon Sep 17 00:00:00 2001 From: chessjoe Date: Tue, 21 Jan 2025 21:06:17 +0800 Subject: [PATCH 3/9] refactor(ask-panel, base-dropdown): enhance dropdown behavior and ESC key handling - Implemented ESC key functionality to close dropdowns in AskPanel and BaseDropdown, improving user experience. - Added event handling to reset dropdown states when ESC is pressed, ensuring consistent behavior across components. - Removed unnecessary console logs in toggleTheme function for cleaner code. --- src/components/ask-panel.tsx | 39 +++++++++++++++++++++-- src/components/base/BaseDropdown.tsx | 17 ++++++++++ src/pages/content/injected/toggleTheme.ts | 2 -- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/components/ask-panel.tsx b/src/components/ask-panel.tsx index 2666db2..ad43bfb 100644 --- a/src/components/ask-panel.tsx +++ b/src/components/ask-panel.tsx @@ -92,7 +92,6 @@ function AskPanel(props: AskPanelProps) { const [isSystemPromptDropdownOpen, setIsSystemPromptDropdownOpen] = useState(false); const showToolDropdown = () => { - // console.log('isToolDropdownOpen = ' + isToolDropdownOpen, 'set to true'); setIsToolDropdownOpen(true); setIsQuoteDropdownOpen(false); setIsModelDropdownOpen(false); @@ -388,6 +387,21 @@ function AskPanel(props: AskPanelProps) { : 'w-[473px] min-w-80 max-w-lg min-h-[155px]', `${askPanelVisible ? 'visible' : 'invisible'}`, )} + onKeyDown={e => { + if ( + e.key === 'Escape' && + (isQuoteDropdownOpen || isToolDropdownOpen || isModelDropdownOpen || isSystemPromptDropdownOpen) + ) { + setIsQuoteDropdownOpen(false); + setIsToolDropdownOpen(false); + setIsModelDropdownOpen(false); + setIsSystemPromptDropdownOpen(false); + inputRef.current?.focus(); + e.stopPropagation(); + e.preventDefault(); + } + }} + tabIndex={-1} {...rest}>
@@ -492,7 +506,28 @@ function AskPanel(props: AskPanelProps) { onKeyDown={e => { // 检测 ESC 键 if (e.key === 'Escape') { - if (isQuoteDropdownOpen || isToolDropdownOpen || isModelDropdownOpen) { + console.log('ESC key detected in textarea', { + isQuoteDropdownOpen, + isToolDropdownOpen, + isModelDropdownOpen, + isSystemPromptDropdownOpen, + }); + if ( + isQuoteDropdownOpen || + isToolDropdownOpen || + isModelDropdownOpen || + isSystemPromptDropdownOpen + ) { + setIsQuoteDropdownOpen(false); + setIsToolDropdownOpen(false); + setIsModelDropdownOpen(false); + setIsSystemPromptDropdownOpen(false); + e.stopPropagation(); + e.preventDefault(); + return; + } else { + setAskPanelVisible(false); + onHide(); e.stopPropagation(); e.preventDefault(); return; diff --git a/src/components/base/BaseDropdown.tsx b/src/components/base/BaseDropdown.tsx index 1c7f9bc..79bfb26 100644 --- a/src/components/base/BaseDropdown.tsx +++ b/src/components/base/BaseDropdown.tsx @@ -67,6 +67,15 @@ export function BaseDropdown({ setIsCommandPressed(true); } + // ESC 键处理 + if (e.key === 'Escape' && isOpened) { + e.preventDefault(); + e.stopPropagation(); + setIsOpen(false); + statusListener(false); + return; + } + // Enter 时直接发送 if (e.key === 'Enter') { e.preventDefault(); @@ -199,6 +208,14 @@ export function BaseDropdown({ className={`absolute ${ align === 'left' ? 'left-0' : 'right-0' } mt-0 min-w-[10rem] origin-top-right divide-y divide-gray-100 rounded bg-white shadow-lg ring-1 ring-black/5 focus:outline-none z-10`} + onKeyDown={e => { + if (e.key === 'Escape') { + e.preventDefault(); + e.stopPropagation(); + setIsOpen(false); + statusListener(false); + } + }} onMouseEnter={() => { setIsOpen(true); }} diff --git a/src/pages/content/injected/toggleTheme.ts b/src/pages/content/injected/toggleTheme.ts index d5be6e6..91fd906 100644 --- a/src/pages/content/injected/toggleTheme.ts +++ b/src/pages/content/injected/toggleTheme.ts @@ -1,9 +1,7 @@ import exampleThemeStorage from '@src/shared/storages/exampleThemeStorage'; async function toggleTheme() { - console.log('initial theme', await exampleThemeStorage.get()); await exampleThemeStorage.toggle(); - console.log('toggled theme', await exampleThemeStorage.get()); } void toggleTheme(); From 310a99d9326595d3513dbd27b6adedcaab003025 Mon Sep 17 00:00:00 2001 From: chessjoe Date: Tue, 21 Jan 2025 21:28:33 +0800 Subject: [PATCH 4/9] refactor(ask-panel, model-dropdown): add model name simplification function - Introduced a new helper function to simplify the display of model names in the ModelDropdown component, enhancing readability. - Updated the dropdown display logic to utilize the new function, ensuring cleaner and more user-friendly model name presentation. --- src/components/ask/ModelDropDown.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/ask/ModelDropDown.tsx b/src/components/ask/ModelDropDown.tsx index 5afba90..1efd8b4 100644 --- a/src/components/ask/ModelDropDown.tsx +++ b/src/components/ask/ModelDropDown.tsx @@ -22,6 +22,14 @@ export default function ModelDropdown({ className, onItemClick, statusListener, const [selectedModelName, setSelectedModelName] = useState('free'); // 默认显示 free const baseDropdownRef = useRef(null); + // 辅助函数:简化模型名称显示 + const simplifyModelName = (name: string): string => { + if (!name) return name; + const parts = name.split('/').filter(part => part.trim() !== ''); + if (parts.length === 0) return name; + return parts[parts.length - 1] || parts[parts.length - 2] || name; + }; + useEffect(() => { const fetchModels = async () => { const userModels = (await configStorage.getModelConfig()) || []; @@ -93,7 +101,7 @@ export default function ModelDropdown({ className, onItemClick, statusListener, return (
Date: Tue, 21 Jan 2025 21:48:21 +0800 Subject: [PATCH 5/9] refactor(dropdowns, useToolPreview): enhance tool preview positioning and dropdown behavior - Updated the showToolPreview function to accept alignment parameters ('left' or 'right') for improved positioning of tool previews in the dropdowns. - Adjusted the ToolDropdown and SystemPromptDropdown components to utilize the new alignment feature, enhancing user experience by ensuring consistent preview display. - Improved code readability and maintainability by refactoring the position calculation logic in useToolPreview. --- src/components/ask-tooldropdown.tsx | 4 ++- src/components/system-prompt-dropdown.tsx | 2 +- src/shared/hooks/useToolPreview.ts | 31 ++++++++++++++++------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/components/ask-tooldropdown.tsx b/src/components/ask-tooldropdown.tsx index 03395bc..be9996d 100644 --- a/src/components/ask-tooldropdown.tsx +++ b/src/components/ask-tooldropdown.tsx @@ -95,8 +95,10 @@ export default function ToolDropdown({ active ? 'bg-black text-white' : 'text-gray-900' } group flex w-full items-center rounded-md px-2 py-2 text-sm focus:outline-none`} onMouseEnter={e => { + // e.current is any item of dropdown menu + // baseDropdownRef is the outsite button of the dropdown menu if (baseDropdownRef.current) { - showToolPreview(e.currentTarget, baseDropdownRef.current, tool.hbs); + showToolPreview(e.currentTarget, baseDropdownRef.current, 'right', tool.hbs); } }} onMouseLeave={hideToolPreview} diff --git a/src/components/system-prompt-dropdown.tsx b/src/components/system-prompt-dropdown.tsx index 48043e2..e6ff59b 100644 --- a/src/components/system-prompt-dropdown.tsx +++ b/src/components/system-prompt-dropdown.tsx @@ -65,7 +65,7 @@ export default function SystemPromptDropdown({ }} onMouseEnter={e => { if (baseDropdownRef.current) { - showToolPreview(e.currentTarget, baseDropdownRef.current, preset.hbs); + showToolPreview(e.currentTarget, baseDropdownRef.current, 'left', preset.hbs); } }} onMouseLeave={hideToolPreview}> diff --git a/src/shared/hooks/useToolPreview.ts b/src/shared/hooks/useToolPreview.ts index c8a49af..08df4bd 100644 --- a/src/shared/hooks/useToolPreview.ts +++ b/src/shared/hooks/useToolPreview.ts @@ -5,19 +5,32 @@ export function useToolPreview() { const [previewPos, setPreviewPos] = useState({ x: 0, y: 0 }); const [previewContent, setPreviewContent] = useState(''); - const showToolPreview = (element: HTMLElement, baseDropdownElement: HTMLElement, content: string) => { - const buttonRect = element.getBoundingClientRect(); - const dropdownRect = baseDropdownElement.getBoundingClientRect(); + const showToolPreview = ( + menuItem: HTMLElement, + menuButton: HTMLElement, + align: 'left' | 'right', + content: string, + ) => { + const menuItemRect = menuItem.getBoundingClientRect(); + const menuButtonRect = menuButton.getBoundingClientRect(); // Calculate the relative position between the menu item and dropdown - const relativeY = buttonRect.top - dropdownRect.top; + const relativeY = menuItemRect.top - menuButtonRect.top; - // console.log('buttonRect', buttonRect, 'dropdownRect', dropdownRect); + console.log('menuItemRect-width', menuItemRect.width, 'menuButtonRect-width', menuButtonRect.width); + + if (align === 'right') { + setPreviewPos({ + x: menuItemRect.width, + y: relativeY, + }); + } else { + setPreviewPos({ + x: menuButtonRect.width, + y: relativeY, + }); + } - setPreviewPos({ - x: dropdownRect.width, - y: relativeY, - }); setPreviewContent(content); setShowPreview(true); }; From e4d33b95924c559a949e916e5259cc9ce152a7c5 Mon Sep 17 00:00:00 2001 From: chessjoe Date: Wed, 22 Jan 2025 01:43:18 +0800 Subject: [PATCH 6/9] refactor(dropdowns): add main button click handler to enhance dropdown functionality - Introduced a new `handleMainButtonClick` function in the ToolDropdown component to streamline tool selection. - Updated BaseDropdown to accept an `onMainButtonClick` prop, allowing for customizable click handling. - Enhanced the dropdown behavior by ensuring the main button click triggers the appropriate tool action, improving user interaction. --- .gitignore | 3 ++- src/components/ask-tooldropdown.tsx | 11 +++++++++++ src/components/base/BaseDropdown.tsx | 14 +++++++++++++- src/utils/StorageManager.ts | 12 ++++++------ 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 39404a3..dffe57b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ public/manifest.json *.code-workspace .shire .vscode -.cursor \ No newline at end of file +.cursor +.cursorrules \ No newline at end of file diff --git a/src/components/ask-tooldropdown.tsx b/src/components/ask-tooldropdown.tsx index be9996d..ba4a4a8 100644 --- a/src/components/ask-tooldropdown.tsx +++ b/src/components/ask-tooldropdown.tsx @@ -45,6 +45,16 @@ export default function ToolDropdown({ const { showPreview, previewPos, previewContent, showToolPreview, hideToolPreview } = useToolPreview(); const baseDropdownRef = useRef(null); + const handleMainButtonClick = (_e: React.MouseEvent) => { + // 直接使用当前选中的工具或第一个工具 + const targetTool = selectedTool ? allTools.find(t => t.id === selectedTool) : allTools[0]; + + if (targetTool) { + console.log('[AskToolDropdown] main button clicked, sending tool:', targetTool.name); + handleToolClick(targetTool, _e.metaKey || _e.ctrlKey); + } + }; + useEffect(() => { const fetchTools = async () => { try { @@ -137,6 +147,7 @@ export default function ToolDropdown({ renderItem={renderToolItem} align="right" buttonDisplay={buttonDisplay} + onMainButtonClick={handleMainButtonClick} /> {showPreview && }
diff --git a/src/components/base/BaseDropdown.tsx b/src/components/base/BaseDropdown.tsx index 79bfb26..22b1684 100644 --- a/src/components/base/BaseDropdown.tsx +++ b/src/components/base/BaseDropdown.tsx @@ -21,6 +21,7 @@ export interface BaseDropdownProps { showShortcut?: boolean; align?: 'left' | 'right'; buttonDisplay?: string; + onMainButtonClick?: (_e: React.MouseEvent) => void; } export function BaseDropdown({ @@ -36,6 +37,7 @@ export function BaseDropdown({ showShortcut = true, align = 'left', buttonDisplay, + onMainButtonClick, }: BaseDropdownProps) { const [isOpened, setIsOpen] = useState(initOpen); const [isCommandPressed, setIsCommandPressed] = useState(false); @@ -52,7 +54,6 @@ export function BaseDropdown({ buttonRef.current?.click(); } }, [initOpen, isOpened]); - useEffect(() => { statusListener(isOpened); if (isOpened) { @@ -163,6 +164,17 @@ export function BaseDropdown({ { + e.stopPropagation(); + // const target = e.target as Element; + // const currentTarget = e.currentTarget as Element; + // 区分真实点击和虚拟点击 + if (e.isTrusted) { + // console.log('[BaseDropdown] MenuButton real click - target:', target.tagName, 'currentTarget:', currentTarget.tagName); + // 调用外部传入的点击处理函数 + onMainButtonClick?.(e); + } + }} onMouseEnter={() => { setIsOpen(true); }} diff --git a/src/utils/StorageManager.ts b/src/utils/StorageManager.ts index 52bc1fe..a2fa71a 100644 --- a/src/utils/StorageManager.ts +++ b/src/utils/StorageManager.ts @@ -61,7 +61,7 @@ export const StorageManager = { }, get: (key: string) => { return chrome.storage.local.get([key]).then(result => { - logger.debug(`Value for ${key} is`, result[key]); + // logger.debug(`Value for ${key} is`, result[key]); return result[key]; }); }, @@ -114,20 +114,20 @@ export const StorageManager = { const systemConfigPath = chrome.runtime.getURL('assets/conf/preferences.toml'); const systemConfigResponse = await fetch(systemConfigPath); const systemConfigStr = await systemConfigResponse.text(); - logger.debug('Loaded preferences.toml:', systemConfigStr); + // logger.debug('Loaded preferences.toml:', systemConfigStr); const parsedConfig = TOML.parse(systemConfigStr); - logger.debug('Parsed config:', parsedConfig); + // logger.debug('Parsed config:', parsedConfig); const defaultPreferences: UserPreferences = { USER_LANGUAGE: parsedConfig.USER_LANGUAGE as string, ASK_BUTTON: parsedConfig.ASK_BUTTON as boolean, ASK_BUTTON_BLOCK_PAGE: parsedConfig.ASK_BUTTON_BLOCK_PAGE as string[], }; - logger.debug('Default preferences:', defaultPreferences); + // logger.debug('Default preferences:', defaultPreferences); const preferences = await StorageManager.get(USER_PREFERENCES_KEY); - logger.debug('Stored preferences:', preferences); + // logger.debug('Stored preferences:', preferences); if (preferences) { const mergedPreferences = { @@ -138,7 +138,7 @@ export const StorageManager = { ASK_BUTTON_BLOCK_PAGE: preferences.ASK_BUTTON_BLOCK_PAGE, }), }; - logger.debug('Merged preferences:', mergedPreferences); + // logger.debug('Merged preferences:', mergedPreferences); return mergedPreferences; } return defaultPreferences; From b2a901a48a06b525ce911ee1d8ccbd1bba952d28 Mon Sep 17 00:00:00 2001 From: chessjoe Date: Wed, 22 Jan 2025 02:18:35 +0800 Subject: [PATCH 7/9] refactor(chat, ask-message, ask-panel): standardize message structure and improve role handling - Updated the ChatCoreContext to ensure system prompts and messages utilize the 'content' property for consistency. - Refactored AskMessage component to replace 'name' with 'role' for better clarity in message types. - Adjusted AskPanel to accommodate the new 'role' structure, enhancing the handling of user and assistant messages. - Improved code readability and maintainability by streamlining message rendering logic across components. --- src/chat/chat.ts | 12 ++++++------ src/components/ask-message.tsx | 8 ++++---- src/components/ask-panel.tsx | 25 ++++++++++++++++++------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/chat/chat.ts b/src/chat/chat.ts index 01c9c8d..1277a55 100644 --- a/src/chat/chat.ts +++ b/src/chat/chat.ts @@ -53,7 +53,7 @@ export class ChatCoreContext implements ChatCoreInterface { private async initSystemMessage() { try { const systemPrompt = await StorageManager.getSystemPrompt(); - this.history.push(new SystemInvisibleMessage(systemPrompt)); + this.history.push(new SystemInvisibleMessage(systemPrompt.content)); } catch (e) { console.error('Failed to initialize system message:', e); } @@ -188,7 +188,7 @@ export class ChatCoreContext implements ChatCoreInterface { await this.createModelClient(currentModel); // 2. 系统提示词:临时提示词优先于存储的提示词 - const systemPrompt = options?.overrideSystem || (await StorageManager.getSystemPrompt()); + const systemPrompt = options?.overrideSystem || (await StorageManager.getSystemPrompt()).content; // Remove old system message if exists this.history = this.history.filter(msg => !(msg instanceof SystemInvisibleMessage)); // Add new system message @@ -222,7 +222,7 @@ export class ChatCoreContext implements ChatCoreInterface { return; } - this.history.push(new HumanMessage({ content: prompt, name: 'human' })); + this.history.push(new HumanMessage({ content: prompt })); if (this._onDataListener) { setTimeout(() => this._onDataListener(this.history)); } @@ -233,7 +233,7 @@ export class ChatCoreContext implements ChatCoreInterface { console.warn('no this._onDataListener'); } - const pendingResponse = new AIMessage({ content: 'Just Guessing ...', name: 'hint' }); + const pendingResponse = new AIMessage({ content: 'Thinking ...' }); let hasResponse = false; setTimeout(() => { this.history.push(pendingResponse); @@ -250,11 +250,11 @@ export class ChatCoreContext implements ChatCoreInterface { for await (const chunk of stream) { chunks.push(chunk); const content = chunks.reduce((acc, cur) => acc + cur.content, ''); - const name = chunk.name; + // const name = chunk.name; if (content.trim() === '') continue; pendingResponse.content = content; - pendingResponse.name = name || 'ai'; + // pendingResponse.name = name || 'ai'; hasResponse = true; if (this._onDataListener) { setTimeout(() => this._onDataListener(this.history)); diff --git a/src/components/ask-message.tsx b/src/components/ask-message.tsx index 5d57d13..e2a2034 100644 --- a/src/components/ask-message.tsx +++ b/src/components/ask-message.tsx @@ -15,7 +15,7 @@ export enum AskMessageType { interface AskMessageItem { type: AskMessageType | string; text: string; - name?: string; + role?: string; } function decodeEntities(text: string): string { @@ -25,7 +25,7 @@ function decodeEntities(text: string): string { } function AskMessage(props: AskMessageItem) { - const { type, text, name } = props; + const { type, text, role } = props; const [codeHover, setCodeHover] = useState(null); const { isVisible, handlers } = useCopyButton(type !== AskMessageType.CODE); let messageItem =
{text}
; @@ -93,11 +93,11 @@ function AskMessage(props: AskMessageItem) {
-
{messageItem}
+
{messageItem}
{isVisible && type !== AskMessageType.CODE && ( )} diff --git a/src/components/ask-panel.tsx b/src/components/ask-panel.tsx index ad43bfb..d44cd6c 100644 --- a/src/components/ask-panel.tsx +++ b/src/components/ask-panel.tsx @@ -22,6 +22,7 @@ import SystemPromptDropdown from './system-prompt-dropdown'; import { StorageManager } from '../utils/StorageManager'; import { Handlebars } from '../../third-party/kbn-handlebars/src/handlebars'; import { SCROLLBAR_STYLES_HIDDEN_X } from '../styles/common'; +import { HumanMessage } from '@langchain/core/messages'; interface AskPanelProps extends React.HTMLAttributes { code: string; @@ -77,7 +78,7 @@ function AskPanel(props: AskPanelProps) { const [userInput, setUserInput] = useState(''); const [askPanelVisible, setAskPanelVisible] = useState(visible); //TODO 需要定义一个可渲染、可序列号的类型,疑似是 StoredMessage - const [history, setHistory] = useState<{ id: string; name: string; type: string; text: string }[]>([]); + const [history, setHistory] = useState<{ id: string; role: string; type: string; text: string }[]>([]); const [initQuotes, setInitQuotes] = useState>([]); const [pageContext, setPageContext] = useState(new QuoteContext()); const inputRef = useRef(null); @@ -198,15 +199,25 @@ function AskPanel(props: AskPanelProps) { ), ) .map((message, idx) => { + let role = 'assistant'; + if (message instanceof HumanMessage) { + role = 'user'; + } if (message instanceof HumanAskMessage) { - return { type: 'text', id: `history-${idx}`, text: message.rendered, name: message.name }; + return { + type: 'text', + id: `history-${idx}`, + text: message.rendered, + role: role, + name: 'HumanAskMessage', + }; } else if (typeof message.content == 'string') { - return { type: 'text', id: `history-${idx}`, text: message.content, name: message.name }; + return { type: 'text', id: `history-${idx}`, text: message.content, role: role, name: 'AIMessage' }; } else if (message.content instanceof Array) { return { type: 'text', id: `history-${idx}`, - name: message.name, + role: role, text: message.content.reduce((acc, cur) => { if (cur.type == 'text') { return acc + '\n' + cur.text; @@ -436,9 +447,9 @@ function AskPanel(props: AskPanelProps) {
- {history.map(message => ( - - ))} + {history.map(message => { + return ; + })} {history.length > 0 && (
From feac22c09253fe01738e9245286cac24ed4f6f65 Mon Sep 17 00:00:00 2001 From: chessjoe Date: Wed, 22 Jan 2025 03:02:39 +0800 Subject: [PATCH 8/9] feat(ask-panel): add clear history functionality and new chat button - Implemented a new `clearHistory` function to reset chat history while retaining system messages. - Added a "New Chat" button to the AskPanel, allowing users to start a fresh chat session easily. - Enhanced user experience by focusing the input field after clearing history, streamlining the chat process. --- src/components/ask-panel.tsx | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/components/ask-panel.tsx b/src/components/ask-panel.tsx index d44cd6c..48ec853 100644 --- a/src/components/ask-panel.tsx +++ b/src/components/ask-panel.tsx @@ -6,7 +6,7 @@ import { ChatPopupContext } from '../chat/chat'; import ToolDropdown, { tools } from './ask-tooldropdown'; import ModelDropdown from './ask/ModelDropDown'; import TextareaAutosize from 'react-textarea-autosize'; -import { ArrowsPointingInIcon, ArrowsPointingOutIcon, XMarkIcon } from '@heroicons/react/20/solid'; +import { ArrowsPointingInIcon, ArrowsPointingOutIcon, XMarkIcon, PlusIcon } from '@heroicons/react/20/solid'; import AskMessage from './ask-message'; import { ToolsPromptInterface, @@ -386,6 +386,17 @@ function AskPanel(props: AskPanelProps) { setDropdownPosition({ left, top }); } + // 清空历史记录和输入框 + const clearHistory = () => { + // 保留系统消息 + const systemMessages = chatContext.history.filter(message => message instanceof SystemInvisibleMessage); + chatContext.history = systemMessages; + // 清空可见消息 + setHistory([]); + // 清空输入框 + setUserInput(''); + }; + // myObject.test('你是谁'); // console.log('history = ' + JSON.stringify(history)); return ( @@ -427,6 +438,21 @@ function AskPanel(props: AskPanelProps) {
+ +