init: AI培训与智能巡检系统

This commit is contained in:
selfrelease
2026-06-16 00:55:20 +08:00
commit c55598494b
201 changed files with 53131 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
import { useEffect, useState } from 'react';
import { Link, useParams } from 'react-router-dom';
import { api } from '../api/client';
import { useAuth } from '../lib/auth';
import { Button } from '../components/ui';
import { IconBack } from '../components/icons';
import type { Thread } from '../types';
export function ThreadPage() {
const { id } = useParams();
const { user } = useAuth();
const [thread, setThread] = useState<Thread | null>(null);
const [body, setBody] = useState('');
const [error, setError] = useState('');
useEffect(() => {
if (id) api.thread(Number(id)).then(setThread).catch((e) => setError(String(e)));
}, [id]);
const reply = async (e: React.FormEvent) => {
e.preventDefault();
if (!id) return;
setError('');
try {
const updated = await api.addReply(Number(id), body);
setThread(updated);
setBody('');
} catch (err) {
setError(err instanceof Error ? err.message : '回复失败');
}
};
if (error) return <p className="error">{error}</p>;
if (!thread) return <p className="muted"></p>;
const fmt = (s: string) => new Date(s + 'Z').toLocaleString('zh-CN');
return (
<div className="page thread-page">
<Link to={thread.model_id ? `/models/${thread.model_id}` : '/community'} className="back">
<IconBack size={14} />
</Link>
<h1>{thread.title}</h1>
<div className="post op">
<div className="post-head">
<b>{thread.author_name}</b>
<span className="muted">{fmt(thread.created_at)}</span>
</div>
<p className="post-body">{thread.body}</p>
</div>
<h2>{thread.reply_count} </h2>
<div className="replies">
{thread.replies?.map((r) => (
<div className="post" key={r.id}>
<div className="post-head">
<b>{r.author_name}</b>
<span className="muted">{fmt(r.created_at)}</span>
</div>
<p className="post-body">{r.body}</p>
</div>
))}
</div>
{user ? (
<form className="reply-form" onSubmit={reply}>
<textarea
placeholder="写下你的回复…"
value={body}
onChange={(e) => setBody(e.target.value)}
rows={3}
required
/>
{error && <p className="error">{error}</p>}
<Button variant="primary" type="submit"></Button>
</form>
) : (
<p className="muted"></p>
)}
</div>
);
}