Internal error occurred but is skipped: FindTagsByCommitIDs

Files
CamTalk/frontend/src/components/SessionSidebar/index.tsx

161 lines
5.3 KiB
TypeScript
Raw Normal View History

// ============================================================
// SessionSidebar — 会话历史侧边栏
// 职责:展示会话列表、新建/切换/删除/重命名会话
// ============================================================
import { useState } from "react";
import { useI18n } from "../../lib/i18n";
import type { SessionSummary } from "../../types";
interface SessionSidebarProps {
sessions: SessionSummary[];
activeSessionId: string | null;
collapsed: boolean;
onToggleCollapse: () => void;
onNewSession: () => void;
onSelectSession: (id: string) => void;
onDeleteSession: (id: string) => void;
onRenameSession: (id: string, title: string) => void;
}
/** 格式化相对时间 */
function formatRelativeTime(ts: number): string {
const now = Date.now();
const diff = now - ts;
const min = 60 * 1000;
const hour = 60 * min;
const day = 24 * hour;
if (diff < min) return "刚刚";
if (diff < hour) return `${Math.floor(diff / min)}分钟前`;
if (diff < day) return `${Math.floor(diff / hour)}小时前`;
if (diff < 2 * day) return "昨天";
if (diff < 7 * day) return `${Math.floor(diff / day)}天前`;
const d = new Date(ts);
return `${d.getMonth() + 1}/${d.getDate()}`;
}
export function SessionSidebar({
sessions,
activeSessionId,
collapsed,
onToggleCollapse,
onNewSession,
onSelectSession,
onDeleteSession,
onRenameSession,
}: SessionSidebarProps) {
const { t } = useI18n();
const [editingId, setEditingId] = useState<string | null>(null);
const [editTitle, setEditTitle] = useState("");
const handleStartRename = (id: string, currentTitle: string) => {
setEditingId(id);
setEditTitle(currentTitle);
};
const handleConfirmRename = () => {
if (editingId && editTitle.trim()) {
onRenameSession(editingId, editTitle.trim());
}
setEditingId(null);
setEditTitle("");
};
const handleCancelRename = () => {
setEditingId(null);
setEditTitle("");
};
if (collapsed) {
return (
<div className="sidebar sidebar--collapsed">
<button className="sidebar__toggle" onClick={onToggleCollapse} title={t("sidebar.expand")}>
</button>
<button className="sidebar__new-btn" onClick={onNewSession} title={t("sidebar.new")}>
</button>
</div>
);
}
return (
<div className="sidebar">
<div className="sidebar__header">
<button className="sidebar__new-btn sidebar__new-btn--full" onClick={onNewSession}>
{t("sidebar.new")}
</button>
<button className="sidebar__toggle" onClick={onToggleCollapse} title={t("sidebar.collapse")}>
</button>
</div>
<div className="sidebar__list">
{sessions.length === 0 ? (
<div className="sidebar__empty">{t("sidebar.empty")}</div>
) : (
sessions.map((session) => (
<div
key={session.id}
className={`sidebar__item ${session.id === activeSessionId ? "sidebar__item--active" : ""}`}
onClick={() => onSelectSession(session.id)}
>
{editingId === session.id ? (
<div className="sidebar__edit" onClick={(e) => e.stopPropagation()}>
<input
className="sidebar__edit-input"
value={editTitle}
onChange={(e) => setEditTitle(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") handleConfirmRename();
if (e.key === "Escape") handleCancelRename();
}}
autoFocus
/>
<button className="sidebar__edit-btn" onClick={handleConfirmRename}></button>
<button className="sidebar__edit-btn" onClick={handleCancelRename}></button>
</div>
) : (
<>
<div className="sidebar__item-content">
<div className="sidebar__item-title">{session.title}</div>
<div className="sidebar__item-meta">
{session.messageCount > 0 && (
<span>{session.messageCount}{t("sidebar.messages")}</span>
)}
<span>{formatRelativeTime(session.lastActiveAt)}</span>
</div>
</div>
<div className="sidebar__item-actions">
<button
className="sidebar__action-btn"
onClick={(e) => {
e.stopPropagation();
handleStartRename(session.id, session.title);
}}
title={t("sidebar.rename")}
>
</button>
<button
className="sidebar__action-btn sidebar__action-btn--danger"
onClick={(e) => {
e.stopPropagation();
onDeleteSession(session.id);
}}
title={t("sidebar.delete")}
>
🗑
</button>
</div>
</>
)}
</div>
))
)}
</div>
</div>
);
}