kit初版,模型引入,agent优化

This commit is contained in:
2026-04-13 00:41:23 +08:00
parent 9b053e302b
commit c435ab15cf
14097 changed files with 5032 additions and 2676248 deletions

View File

@@ -0,0 +1,125 @@
"use client";
import { useEffect, useRef, useState } from "react";
import type { ImageModelInfo } from "@/lib/types";
import { fetchModels } from "@/lib/api";
const STORAGE_KEY = "epeekit-selected-image-model";
interface ModelSelectorProps {
value: string;
onChange: (modelId: string) => void;
}
export function ModelSelector({ value, onChange }: ModelSelectorProps) {
const [models, setModels] = useState<ImageModelInfo[]>([]);
const [open, setOpen] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
fetchModels()
.then(({ models: list, default: defaultId }) => {
setModels(list);
const saved = localStorage.getItem(STORAGE_KEY);
const validIds = new Set(list.map((m) => m.id));
if (saved && validIds.has(saved)) {
onChange(saved);
} else if (!value || !validIds.has(value)) {
onChange(defaultId);
}
})
.catch(() => {
// 后端不可用时静默降级
});
// 仅初始化时执行一次
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// 点击外部关闭
useEffect(() => {
if (!open) return;
const handler = (e: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
setOpen(false);
}
};
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, [open]);
const selected = models.find((m) => m.id === value);
if (models.length === 0) return null;
return (
<div ref={containerRef} className="relative">
<button
type="button"
onClick={() => setOpen(!open)}
className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs
border border-[var(--border)] bg-[var(--bg-tertiary)]
text-[var(--text-secondary)] hover:text-[var(--text-primary)]
hover:border-[var(--accent)] transition-colors cursor-pointer"
title="切换生图模型"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M12 2L2 7l10 5 10-5-10-5z" />
<path d="M2 17l10 5 10-5" />
<path d="M2 12l10 5 10-5" />
</svg>
<span className="max-w-[100px] truncate">{selected?.name ?? "模型"}</span>
<svg
width="10" height="10" viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth="2.5"
className={`transition-transform ${open ? "rotate-180" : ""}`}
>
<polyline points="6 9 12 15 18 9" />
</svg>
</button>
{open && (
<div
className="absolute bottom-full left-0 mb-1.5 w-56 rounded-lg
border border-[var(--border)] bg-[var(--bg-secondary)]
shadow-lg overflow-hidden z-50"
>
{models.map((m) => {
const isActive = m.id === value;
return (
<button
key={m.id}
onClick={() => {
onChange(m.id);
localStorage.setItem(STORAGE_KEY, m.id);
setOpen(false);
}}
className={`w-full text-left px-3 py-2.5 flex flex-col gap-0.5
transition-colors cursor-pointer
${isActive
? "bg-[var(--accent)]/10 text-[var(--accent)]"
: "text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"
}`}
>
<span className="text-sm font-medium flex items-center gap-1.5">
{m.name}
{m.supports_ref_image && (
<span className="text-[10px] px-1.5 py-0.5 rounded-full
bg-emerald-500/15 text-emerald-400 font-normal leading-none">
</span>
)}
{isActive && (
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
</svg>
)}
</span>
<span className="text-xs text-[var(--text-secondary)]">{m.description}</span>
</button>
);
})}
</div>
)}
</div>
);
}