remove frontend directory from git tracking

This commit is contained in:
2026-09-14 17:56:43 +08:00
parent 4f296eb58c
commit 6d44b8ff37
34 changed files with 0 additions and 2648 deletions
-16
View File
@@ -1,16 +0,0 @@
"use client";
import Link from "next/link";
import { ArrowLeft, Trash2, Upload } from "lucide-react";
import { useEffect, useState } from "react";
import { apiFetch } from "@/lib/api";
interface DocumentItem { id?: string; doc_id?: string; title?: string; filename?: string; status?: string; }
export default function KnowledgePage() {
const [items, setItems] = useState<DocumentItem[]>([]); const [file, setFile] = useState<File | null>(null); const [message, setMessage] = useState(""); const [isAdmin, setIsAdmin] = useState(false);
async function load() { try { setItems(await apiFetch<DocumentItem[]>("/knowledge/documents")); } catch (error) { setMessage(error instanceof Error ? error.message : "知识库加载失败"); } }
useEffect(() => { void apiFetch<{ user: { user_type?: string } }>("/auth/me").then(({ user }) => { const allowed = user.user_type === "ADMIN"; setIsAdmin(allowed); if (allowed) void load(); else setMessage("知识库管理仅对 ADMIN 开放"); }).catch(() => setMessage("请先登录管理员账号")); }, []);
async function upload(path: "/knowledge/documents/preview" | "/knowledge/documents/confirm") { if (!file) return; const body = new FormData(); body.append("file", file); body.append("title", file.name); body.append("strategy", "default"); const token = localStorage.getItem("huaxia_token"); const response = await fetch(`/backend${path}`, { method: "POST", headers: token ? { Authorization: `Bearer ${token}` } : undefined, body }); const payload = await response.json(); setMessage(payload.message ?? (response.ok ? "操作成功" : "操作失败")); if (response.ok && path.endsWith("confirm")) await load(); }
async function remove(id: string) { await apiFetch(`/knowledge/documents/${id}`, { method: "DELETE" }); await load(); }
return <main className="min-h-screen bg-slate-50 px-10 py-10"><div className="mx-auto max-w-6xl"><Link href="/admin/common" className="inline-flex items-center gap-2 text-sm text-slate-500"><ArrowLeft className="h-4 w-4" />返回员工工作区</Link><h1 className="mt-8 text-3xl font-semibold text-[var(--brand-navy)]">知识库管理</h1>{!isAdmin ? <p className="mt-6 rounded-xl border border-amber-200 bg-amber-50 p-5 text-sm text-amber-800">知识库管理仅对 ADMIN 开放。</p> : <><div className="mt-6 rounded-xl border border-slate-200 bg-white p-6"><input type="file" onChange={(event) => setFile(event.target.files?.[0] ?? null)} /><div className="mt-4 flex gap-3"><button onClick={() => void upload("/knowledge/documents/preview")} className="flex items-center gap-2 rounded-lg border px-4 py-2 text-sm"><Upload className="h-4 w-4" />预览</button><button onClick={() => void upload("/knowledge/documents/confirm")} className="rounded-lg bg-[var(--brand-primary)] px-4 py-2 text-sm text-white">确认入库</button></div>{message && <p className="mt-3 text-sm text-slate-500">{message}</p>}</div><div className="mt-6 divide-y divide-slate-100 rounded-xl border border-slate-200 bg-white">{items.map((item) => { const id = item.id ?? item.doc_id ?? ""; return <div key={id} className="flex items-center justify-between p-5 text-sm"><span>{item.title ?? item.filename ?? id} <small className="ml-3 text-slate-400">{item.status ?? "已入库"}</small></span><button onClick={() => void remove(id)} aria-label="删除文档" className="text-rose-500"><Trash2 className="h-4 w-4" /></button></div>; })}{!items.length && <p className="p-8 text-sm text-slate-400">暂无知识库文档</p>}</div></>}</div></main>;
}
-53
View File
@@ -1,53 +0,0 @@
"use client";
import Link from "next/link";
import { ArrowLeft, Download, RefreshCw, Send } from "lucide-react";
import { FormEvent, useEffect, useState } from "react";
import { ApiError, apiFetch } from "@/lib/api";
interface HistoryItem { query_id?: string; question?: string; status?: string; row_count?: number; create_time?: string; }
export default function Nl2SqlPage() {
const [tab, setTab] = useState<"query" | "history" | "config">("query");
const [question, setQuestion] = useState("");
const [result, setResult] = useState<unknown>(null);
const [history, setHistory] = useState<HistoryItem[]>([]);
const [config, setConfig] = useState<Record<string, unknown>>({});
const [message, setMessage] = useState("");
const [loading, setLoading] = useState(false);
const [userType, setUserType] = useState<string | null>(null);
const isAdmin = userType === "ADMIN";
const canQuery = userType === "EMPLOYEE";
useEffect(() => {
void apiFetch<{ user: { user_type?: string } }>("/auth/me")
.then(({ user }) => {
const nextType = user.user_type ?? null;
setUserType(nextType);
if (nextType === "ADMIN") setTab("config");
})
.catch(() => setUserType(null));
}, []);
useEffect(() => {
if (tab === "history" && canQuery) void loadHistory();
if (tab === "config" && isAdmin) void loadConfig();
}, [tab, isAdmin, canQuery]);
async function query(event: FormEvent) {
event.preventDefault(); setLoading(true); setMessage("");
if (!canQuery) { setMessage("当前账号仅可管理 NL2SQL 配置,不能执行数据查询。"); setLoading(false); return; }
try {
setResult(await apiFetch("/nl2sql/query", { method: "POST", body: JSON.stringify({ question, output_format: "json", include_sql: true, page: 1, page_size: 100 }) }));
} catch (error) {
setMessage(error instanceof ApiError && error.status === 403 ? "当前账号没有 NL2SQL 查询权限,请使用员工账号登录。" : error instanceof Error ? error.message : "查询失败");
} finally { setLoading(false); }
}
async function loadHistory() { try { const response = await apiFetch<{ items?: HistoryItem[] }>("/nl2sql/query-history?page=1&page_size=50"); setHistory(response.items ?? []); } catch (error) { setMessage(error instanceof Error ? error.message : "历史加载失败"); } }
async function loadConfig() { try { setConfig(await apiFetch<Record<string, unknown>>("/nl2sql/admin/runtime-config")); } catch (error) { setMessage(error instanceof Error ? error.message : "配置加载失败"); } }
async function saveConfig() { try { await apiFetch("/nl2sql/admin/runtime-config", { method: "PATCH", body: JSON.stringify(config) }); setMessage("运行配置已保存"); } catch (error) { setMessage(error instanceof Error ? error.message : "配置保存失败"); } }
if (userType === null) return <main className="min-h-screen bg-slate-50 px-10 py-10"><div className="mx-auto max-w-6xl"><Link href="/admin/common" className="inline-flex items-center gap-2 text-sm text-slate-500"><ArrowLeft className="h-4 w-4" />返回员工工作区</Link><p className="mt-10 text-sm text-slate-500">正在校验账号权限...</p></div></main>;
return <main className="min-h-screen bg-slate-50 px-10 py-10"><div className="mx-auto max-w-6xl"><Link href="/admin/common" className="inline-flex items-center gap-2 text-sm text-slate-500"><ArrowLeft className="h-4 w-4" />返回员工工作区</Link><h1 className="mt-8 text-3xl font-semibold text-[var(--brand-navy)]">NL2SQL 数据中心</h1>{isAdmin ? <p className="mt-4 rounded-lg bg-blue-50 p-4 text-sm text-blue-700">当前为 ADMIN 账号,仅开放运行配置管理。数据查询由普通员工账号使用。</p> : <p className="mt-4 rounded-lg bg-blue-50 p-4 text-sm text-blue-700">当前账号可使用查询和历史功能,运行配置仅对 ADMIN 开放。</p>}<div className="mt-6 flex gap-2 border-b border-slate-200">{canQuery && <><button onClick={() => setTab("query")} className={`border-b-2 px-4 py-3 text-sm ${tab === "query" ? "border-[var(--brand-primary)] text-[var(--brand-primary)]" : "border-transparent text-slate-500"}`}>自然语言查询</button><button onClick={() => setTab("history")} className={`border-b-2 px-4 py-3 text-sm ${tab === "history" ? "border-[var(--brand-primary)] text-[var(--brand-primary)]" : "border-transparent text-slate-500"}`}>查询历史</button></>}{isAdmin && <button onClick={() => setTab("config")} className={`border-b-2 px-4 py-3 text-sm ${tab === "config" ? "border-[var(--brand-primary)] text-[var(--brand-primary)]" : "border-transparent text-slate-500"}`}>运行配置</button>}</div>{message && <p className="mt-4 rounded-lg bg-rose-50 p-4 text-sm text-rose-600">{message}</p>}{tab === "query" && canQuery && <><form onSubmit={query} className="mt-6 flex gap-3 rounded-xl border border-slate-200 bg-white p-5"><input required value={question} onChange={(event) => setQuestion(event.target.value)} placeholder="例如:查询本月基金产品数量" className="min-w-0 flex-1 rounded-lg border border-slate-200 px-4 py-3 text-sm" /><button disabled={loading} className="flex items-center gap-2 rounded-lg bg-[var(--brand-primary)] px-4 py-3 text-sm text-white"><Send className="h-4 w-4" />查询</button></form><pre className="mt-6 min-h-64 overflow-auto rounded-xl border border-slate-200 bg-white p-6 text-xs text-slate-700">{result ? JSON.stringify(result, null, 2) : "查询结果将在这里显示"}</pre></>}{tab === "history" && canQuery && <section className="mt-6 rounded-xl border border-slate-200 bg-white p-6"><div className="flex justify-between"><h2 className="font-semibold">查询历史</h2><button onClick={() => void loadHistory()} aria-label="刷新历史" className="text-slate-500"><RefreshCw className="h-4 w-4" /></button></div><div className="mt-4 divide-y divide-slate-100">{history.map((item) => <div key={item.query_id} className="flex items-center justify-between gap-4 py-4 text-sm"><button onClick={() => void apiFetch(`/nl2sql/query-history/${item.query_id}`).then(setResult)} className="text-left"><p>{item.question ?? item.query_id}</p><p className="mt-1 text-xs text-slate-400">{item.status} · {item.row_count ?? 0} 行 · {item.create_time ?? ""}</p></button><button onClick={() => window.open(`/backend/nl2sql/query-history/${item.query_id}/export`, "_blank")} aria-label="导出查询历史" className="text-slate-500"><Download className="h-4 w-4" /></button></div>)}{!history.length && <p className="py-8 text-sm text-slate-400">暂无查询历史</p>}</div>{Boolean(result) && <pre className="mt-5 max-h-80 overflow-auto rounded-lg bg-slate-50 p-4 text-xs">{JSON.stringify(result, null, 2)}</pre>}</section>}{tab === "config" && isAdmin && <section className="mt-6 rounded-xl border border-slate-200 bg-white p-6"><div className="flex justify-between"><h2 className="font-semibold">运行配置</h2><button onClick={() => void loadConfig()} aria-label="刷新配置" className="text-slate-500"><RefreshCw className="h-4 w-4" /></button></div><textarea value={JSON.stringify(config, null, 2)} onChange={(event) => { try { setConfig(JSON.parse(event.target.value)); } catch { /* wait for valid JSON */ } }} className="mt-5 min-h-80 w-full rounded-lg border border-slate-200 p-4 font-mono text-xs" /><button onClick={() => void saveConfig()} className="mt-4 rounded-lg bg-[var(--brand-primary)] px-4 py-2 text-sm text-white">保存配置</button></section>}</div></main>;
}