+
+
{message.content}
+ {/* 悬停显示复制按钮 */}
+
{user &&
}
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx
index 3f42d36..02d1061 100644
--- a/src/components/layout/Sidebar.tsx
+++ b/src/components/layout/Sidebar.tsx
@@ -2,14 +2,14 @@
import Link from 'next/link';
import { usePathname, useRouter } from 'next/navigation';
-import { Plus, PanelLeft, Trash2, MoreHorizontal, Loader2 } from 'lucide-react';
+import { Plus, PanelLeft, Trash2, MoreHorizontal, Loader2, Pencil, Check, X } from 'lucide-react';
import { UserMenu } from '@/components/ui/UserMenu';
import { cn } from '@/lib/utils';
import { useConversations } from '@/hooks/useConversations';
import { useSettings } from '@/hooks/useSettings';
import { useAuth } from '@/providers/AuthProvider';
import type { Conversation } from '@/drizzle/schema';
-import { useState } from 'react';
+import { useState, useRef, useEffect } from 'react';
interface SidebarProps {
isOpen?: boolean;
@@ -19,11 +19,39 @@ interface SidebarProps {
export function Sidebar({ isOpen = true }: SidebarProps) {
const pathname = usePathname();
const router = useRouter();
- const { conversations, loading, createConversation, deleteConversation } = useConversations();
+ const { conversations, loading, createConversation, deleteConversation, updateConversation } = useConversations();
const { settings } = useSettings();
const { user } = useAuth();
const [creatingChat, setCreatingChat] = useState(false);
const [menuOpen, setMenuOpen] = useState
(null);
+ const [editingId, setEditingId] = useState(null);
+ const [editingTitle, setEditingTitle] = useState('');
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const inputRef = useRef(null);
+ const menuRef = useRef(null);
+
+ // 聚焦输入框
+ useEffect(() => {
+ if (editingId && inputRef.current) {
+ inputRef.current.focus();
+ inputRef.current.select();
+ }
+ }, [editingId]);
+
+ // 点击外部关闭下拉菜单
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
+ setMenuOpen(null);
+ }
+ };
+ if (menuOpen) {
+ document.addEventListener('mousedown', handleClickOutside);
+ }
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, [menuOpen]);
// 创建新对话
const handleNewChat = async () => {
@@ -60,6 +88,47 @@ export function Sidebar({ isOpen = true }: SidebarProps) {
}
};
+ // 开始重命名
+ const handleStartRename = (conversation: Conversation, e: React.MouseEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setEditingId(conversation.conversationId);
+ setEditingTitle(conversation.title || '');
+ setMenuOpen(null);
+ };
+
+ // 提交重命名
+ const handleSubmitRename = async (conversationId: string) => {
+ if (!editingTitle.trim() || isSubmitting) return;
+
+ try {
+ setIsSubmitting(true);
+ await updateConversation(conversationId, { title: editingTitle.trim() });
+ setEditingId(null);
+ setEditingTitle('');
+ } catch (error) {
+ console.error('Failed to rename conversation:', error);
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ // 取消重命名
+ const handleCancelRename = () => {
+ setEditingId(null);
+ setEditingTitle('');
+ };
+
+ // 处理键盘事件
+ const handleKeyDown = (e: React.KeyboardEvent, conversationId: string) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ handleSubmitRename(conversationId);
+ } else if (e.key === 'Escape') {
+ handleCancelRename();
+ }
+ };
+
// 按时间分组对话
const groupedConversations = groupConversationsByTime(conversations);
@@ -111,47 +180,103 @@ export function Sidebar({ isOpen = true }: SidebarProps) {
{items.map((conversation) => {
const isActive = pathname === `/chat/${conversation.conversationId}`;
+ const isEditing = editingId === conversation.conversationId;
return (
-
- {conversation.title}
-
+ {isEditing ? (
+ // 编辑模式 - 显示输入框
+
+ setEditingTitle(e.target.value)}
+ onKeyDown={(e) => handleKeyDown(e, conversation.conversationId)}
+ onBlur={() => handleCancelRename()}
+ className="flex-1 px-2 py-1 text-sm bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded text-[var(--color-text-primary)] focus:outline-none focus:border-[var(--color-primary)]"
+ disabled={isSubmitting}
+ />
+
+
+
+ ) : (
+ // 正常模式 - 显示链接
+
+ {conversation.title}
+
+ )}
- {/* 更多操作按钮 */}
-
+ {/* 更多操作按钮 - 非编辑模式时显示 */}
+ {!isEditing && (
+
+ )}
{/* 下拉菜单 */}
{menuOpen === conversation.conversationId && (
-
+
+
)}