小优化,产品愿景,视觉规范,交互和导航大版本
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef, useEffect, useCallback } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { useApp } from "@/lib/app-context";
|
||||
import { getImageUrl, fetchLlmModels } from "@/lib/api";
|
||||
import { slideUp } from "@/components/ui/motion-presets";
|
||||
import type { LlmModelInfo } from "@/lib/types";
|
||||
|
||||
function timeAgo(ts: number): string {
|
||||
@@ -111,7 +113,7 @@ export function SessionList() {
|
||||
text-sm rounded-xl border border-dashed border-[var(--accent)]/30
|
||||
text-[var(--accent)]/70 hover:border-[var(--accent)]
|
||||
hover:text-[var(--accent)] hover:bg-[var(--accent)]/5
|
||||
transition-all cursor-pointer"
|
||||
transition-all cursor-pointer btn-hover-lift"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M12 5v14M5 12h14" />
|
||||
@@ -127,7 +129,7 @@ export function SessionList() {
|
||||
<div
|
||||
onClick={() => switchSession(session.id)}
|
||||
className={`group relative flex items-start gap-3 px-3 py-2.5 rounded-xl cursor-pointer
|
||||
transition-all ${
|
||||
transition-all session-hover-line ${
|
||||
session.id === activeSessionId
|
||||
? "glow-border !bg-[var(--bg-secondary)]"
|
||||
: "hover:bg-[var(--bg-tertiary)]/50 border border-transparent"
|
||||
@@ -139,7 +141,7 @@ export function SessionList() {
|
||||
src={getImageUrl(session.thumbnail)}
|
||||
alt=""
|
||||
className="w-10 h-10 rounded-lg object-cover flex-shrink-0 border border-[var(--border)]
|
||||
shadow-[0_0_8px_rgba(77,184,164,0.08)]"
|
||||
shadow-[0_0_8px_rgba(46,139,122,0.08)]"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-lg bg-[var(--bg-primary)] border border-[var(--border)]
|
||||
@@ -181,8 +183,8 @@ export function SessionList() {
|
||||
return (
|
||||
<span
|
||||
key={tid}
|
||||
className="text-[10px] px-1.5 py-px rounded-full font-medium"
|
||||
style={{ backgroundColor: tag.color + "22", color: tag.color }}
|
||||
className="typo-micro px-1.5 py-px rounded-full"
|
||||
style={{ backgroundColor: tag.color + "22", color: tag.color, textTransform: "none", fontSize: "10px" }}
|
||||
>
|
||||
{tag.name}
|
||||
</span>
|
||||
@@ -194,7 +196,7 @@ export function SessionList() {
|
||||
{llmModels.find((m) => m.id === session.llmModel)?.name || session.llmModel}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-[10px] text-[var(--text-secondary)] ml-auto flex-shrink-0">
|
||||
<span className="typo-caption ml-auto flex-shrink-0" style={{ fontSize: "10px" }}>
|
||||
{timeAgo(session.updatedAt)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -216,95 +218,100 @@ export function SessionList() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 下拉菜单:在会话项正下方展开 */}
|
||||
{menuSessionId === session.id && menuSession && (
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="mx-1 mt-1 glass-panel rounded-xl shadow-xl shadow-black/30 py-1.5
|
||||
overflow-hidden z-10 relative"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* 模型选择(折叠/展开式) */}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowModelSub(!showModelSub);
|
||||
}}
|
||||
className="w-full text-left px-3.5 py-2 text-sm text-[var(--text-primary)]
|
||||
hover:bg-[var(--bg-tertiary)] cursor-pointer transition-colors
|
||||
flex items-center justify-between"
|
||||
{/* 下拉菜单 */}
|
||||
<AnimatePresence>
|
||||
{menuSessionId === session.id && menuSession && (
|
||||
<motion.div
|
||||
ref={menuRef}
|
||||
variants={slideUp}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit={{ opacity: 0, y: 4, transition: { duration: 0.1 } }}
|
||||
className="mx-1 mt-1 surface-3 rounded-xl py-1.5
|
||||
overflow-hidden z-10 relative"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span>对话模型</span>
|
||||
<svg
|
||||
width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" strokeWidth="2"
|
||||
className={`transition-transform ${showModelSub ? "rotate-90" : ""}`}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowModelSub(!showModelSub);
|
||||
}}
|
||||
className="w-full text-left px-3.5 py-2 text-sm text-[var(--text-primary)]
|
||||
hover:bg-[var(--bg-tertiary)] cursor-pointer transition-colors
|
||||
flex items-center justify-between"
|
||||
>
|
||||
<path d="M9 18l6-6-6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
<span>对话模型</span>
|
||||
<svg
|
||||
width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" strokeWidth="2"
|
||||
className={`transition-transform ${showModelSub ? "rotate-90" : ""}`}
|
||||
>
|
||||
<path d="M9 18l6-6-6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{showModelSub && (
|
||||
<div className="border-t border-[var(--border)] mx-2 mt-1 pt-1 max-h-[240px] overflow-y-auto">
|
||||
{llmModels.map((model) => {
|
||||
const isActive = menuSession.llmModel
|
||||
? menuSession.llmModel === model.id
|
||||
: model.id === defaultLlmModel;
|
||||
return (
|
||||
<button
|
||||
key={model.id}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleSelectModel(menuSessionId!, model.id);
|
||||
}}
|
||||
className={`w-full text-left px-3 py-1.5 text-sm cursor-pointer transition-colors
|
||||
rounded-lg flex items-center gap-2 ${
|
||||
isActive
|
||||
? "text-[var(--accent)] bg-[var(--accent)]/5"
|
||||
: "text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"
|
||||
}`}
|
||||
>
|
||||
<span className="w-4 flex-shrink-0 text-center">
|
||||
{isActive && (
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
|
||||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
)}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate">{model.name}</div>
|
||||
<div className="text-[10px] text-[var(--text-secondary)] truncate">{model.description}</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{showModelSub && (
|
||||
<div className="border-t border-[var(--border)] mx-2 mt-1 pt-1 max-h-[240px] overflow-y-auto">
|
||||
{llmModels.map((model) => {
|
||||
const isActive = menuSession.llmModel
|
||||
? menuSession.llmModel === model.id
|
||||
: model.id === defaultLlmModel;
|
||||
return (
|
||||
<button
|
||||
key={model.id}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleSelectModel(menuSessionId!, model.id);
|
||||
}}
|
||||
className={`w-full text-left px-3 py-1.5 text-sm cursor-pointer transition-colors
|
||||
rounded-lg flex items-center gap-2 ${
|
||||
isActive
|
||||
? "text-[var(--accent)] bg-[var(--accent)]/5"
|
||||
: "text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"
|
||||
}`}
|
||||
>
|
||||
<span className="w-4 flex-shrink-0 text-center">
|
||||
{isActive && (
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
|
||||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
)}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate">{model.name}</div>
|
||||
<div className="text-[10px] text-[var(--text-secondary)] truncate">{model.description}</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mx-2 my-1 border-t border-[var(--border)]" />
|
||||
<div className="mx-2 my-1 border-t border-[var(--border)]" />
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
if (menuSession) startRename(menuSession.id, menuSession.title);
|
||||
}}
|
||||
className="w-full text-left px-3.5 py-2 text-sm text-[var(--text-primary)]
|
||||
hover:bg-[var(--bg-tertiary)] cursor-pointer transition-colors"
|
||||
>
|
||||
重命名
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (menuSession) startRename(menuSession.id, menuSession.title);
|
||||
}}
|
||||
className="w-full text-left px-3.5 py-2 text-sm text-[var(--text-primary)]
|
||||
hover:bg-[var(--bg-tertiary)] cursor-pointer transition-colors"
|
||||
>
|
||||
重命名
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
deleteSession(menuSessionId!);
|
||||
closeMenu();
|
||||
}}
|
||||
className="w-full text-left px-3.5 py-2 text-sm text-[var(--hot)]
|
||||
hover:bg-[var(--bg-tertiary)] cursor-pointer transition-colors"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
deleteSession(menuSessionId!);
|
||||
closeMenu();
|
||||
}}
|
||||
className="w-full text-left px-3.5 py-2 text-sm text-[var(--hot)]
|
||||
hover:bg-[var(--bg-tertiary)] cursor-pointer transition-colors"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user