Initial MVP for QA asset backend

This commit is contained in:
2026-07-05 17:44:15 +08:00
commit 95b5e09fd4
71 changed files with 6546 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
export const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://127.0.0.1:8000/api";
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(`${API_BASE}${path}`, {
...init,
cache: "no-store",
headers: {
"Content-Type": "application/json",
...(init?.headers ?? {})
}
});
if (!response.ok) {
let message = `${response.status} ${response.statusText}`;
try {
const body = await response.json();
message = body.detail ?? message;
} catch {
// keep response status as message
}
throw new Error(message);
}
return response.json() as Promise<T>;
}
export const api = {
get: <T>(path: string) => request<T>(path),
post: <T>(path: string, body: unknown = {}) =>
request<T>(path, { method: "POST", body: JSON.stringify(body) }),
patch: <T>(path: string, body: unknown) =>
request<T>(path, { method: "PATCH", body: JSON.stringify(body) })
};
export function formatDateTime(value: string | null | undefined) {
if (!value) return "-";
return new Intl.DateTimeFormat("zh-CN", {
dateStyle: "short",
timeStyle: "short"
}).format(new Date(value));
}

View File

@@ -0,0 +1,163 @@
export type DashboardSummary = {
total_sessions: number;
processed_sessions: number;
pending_review_count: number;
high_risk_count: number;
approved_qa_count: number;
standard_qa_count: number;
callable_qa_count: number;
last_task_status: string | null;
last_task_time: string | null;
last_task_error: string | null;
};
export type FeishuSession = {
id: number;
session_code: string;
title: string;
date: string | null;
teachers: string | null;
feishu_doc_url: string | null;
feishu_record_id: string | null;
source_file_name: string | null;
source_text: string | null;
process_status: string;
qa_count: number;
pending_review_count: number;
approved_count: number;
standard_qa_count: number;
failed_reason: string | null;
created_at: string;
updated_at: string;
};
export type RawQAItem = {
id: number;
session_id: number;
qa_code: string;
raw_question: string;
normalized_question: string | null;
raw_answer: string;
normalized_answer: string | null;
suggested_standard_question: string | null;
suggested_standard_answer: string | null;
answer_person: string | null;
primary_topic: string;
problem_tags: string[];
course_stage: string;
audience_tags: string[];
emotion_intensity: string | null;
risk_level: string;
risk_types: string[];
risk_notes: string | null;
need_desensitization: boolean;
desensitization_status: string;
review_status: string;
reviewer_id: number | null;
review_notes: string | null;
suggested_to_standard_qa: boolean;
source_timestamp: string | null;
created_at: string;
updated_at: string;
};
export type StandardQAItem = {
id: number;
standard_code: string;
source_raw_qa_id: number;
session_id: number;
standard_question: string;
standard_answer: string;
similar_questions: string[];
primary_topic: string;
problem_tags: string[];
course_stage: string;
audience_tags: string[];
answer_boundary: string | null;
forbidden_expressions: string[];
risk_level: string;
audit_status: string;
call_status: string;
last_reviewer_id: number | null;
disabled_reason: string | null;
created_at: string;
updated_at: string;
};
export type CallableQAItem = Pick<
StandardQAItem,
| "id"
| "standard_code"
| "standard_question"
| "standard_answer"
| "similar_questions"
| "primary_topic"
| "problem_tags"
| "course_stage"
| "audience_tags"
| "answer_boundary"
| "forbidden_expressions"
| "source_raw_qa_id"
| "session_id"
>;
export type TaskRun = {
id: number;
task_type: string;
task_status: string;
started_at: string | null;
ended_at: string | null;
sessions_scanned: number;
sessions_processed: number;
qa_created: number;
qa_failed: number;
error_message: string | null;
retry_count: number;
created_at: string;
};
export type AuditLog = {
id: number;
user_id: number | null;
target_type: string;
target_id: number;
action: string;
before_status: string | null;
after_status: string | null;
comment: string | null;
created_at: string;
};
export type SafeSettings = {
feishu_configured: boolean;
openai_configured: boolean;
model_name: string;
schedule_cron: string;
schedule_timezone: string;
mock_mode: boolean;
database_url: string;
feishu_tables_configured: Record<string, boolean>;
system_settings: Array<{ key: string; value: string | null; description: string | null; updated_at: string }>;
};
export type FeishuStatus = {
configured: boolean;
mock_mode: boolean;
app_token_configured: boolean;
wiki_node_token_configured: boolean;
table_ids: Record<string, boolean>;
token_ok: boolean;
message: string;
};
export type FeishuSetupGuide = {
required_env: string[];
required_permissions: string[];
tables: Array<{
table_key: string;
table_name: string;
env_name: string;
required_fields: string[];
}>;
notes: string[];
};