diff options
| author | yyamashita <yyamashita@hetzner.yyamashita.com> | 2026-08-15 18:57:59 +0900 |
|---|---|---|
| committer | yyamashita <yyamashita@hetzner.yyamashita.com> | 2026-08-15 18:57:59 +0900 |
| commit | 59a9f4984c3acd29bc746a78e30849f50fa555ee (patch) | |
| tree | b9dab024c0c5d1d68e023b8f49eb0716b97965bd /app/routes/band-auto-register.tsx | |
| parent | a100fb28f92fa7c5d2cf701d118c4c7b898c9bf2 (diff) | |
Add Claude-powered band auto-registration from live info text
Extracts band/artist names from pasted live info via Claude (API or CLI
adapter), matches against existing bands, and lets the user review and
bulk-register new ones through a polling job queue. Also adds a
livehouse events import script and recent-bands/artists home page
sections.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'app/routes/band-auto-register.tsx')
| -rw-r--r-- | app/routes/band-auto-register.tsx | 88 |
1 files changed, 88 insertions, 0 deletions
diff --git a/app/routes/band-auto-register.tsx b/app/routes/band-auto-register.tsx new file mode 100644 index 0000000..227908d --- /dev/null +++ b/app/routes/band-auto-register.tsx @@ -0,0 +1,88 @@ +import { Form, Link, redirect, useLoaderData, useNavigation } from "react-router"; +import type { ActionFunctionArgs } from "react-router"; +import { createParseJob, getIpAddress, listRecentParseJobs } from "~/lib/db.server"; + +export function loader() { + return { jobs: listRecentParseJobs(10) }; +} + +export async function action({ request }: ActionFunctionArgs) { + const fd = await request.formData(); + const liveText = (fd.get("liveText") as string | null)?.trim() ?? ""; + if (!liveText) return { error: "テキストを入力してください" }; + const job = createParseJob(liveText); + void getIpAddress(request); // unused but kept for symmetry + return redirect(`/bands/auto-register/jobs/${job.id}`); +} + +const STATUS_LABEL: Record<string, string> = { + pending: "待機中", + processing: "解析中", + done: "完了", + error: "エラー", +}; + +const STATUS_COLOR: Record<string, string> = { + pending: "#6b7280", + processing: "#fbbf24", + done: "#34d399", + error: "#f87171", +}; + +export default function BandAutoRegister() { + const { jobs } = useLoaderData<typeof loader>(); + const nav = useNavigation(); + const busy = nav.state !== "idle"; + + return ( + <main> + <div className="page-header"> + <Link to="/bands" className="back">←</Link> + <h1>ライブ情報から自動登録</h1> + </div> + + <Form method="post"> + <div> + <label>ライブ情報テキスト <span className="req">*</span></label> + <textarea + name="liveText" + rows={10} + placeholder={"出演バンド・アーティストの情報をペーストしてください。\n\n例:\n2024.08.10 @ 渋谷CLUB QUATTRO\nopen 17:30 / start 18:00\n\nband1 / Band Two / バンド三"} + /> + </div> + <div className="actions"> + <button type="submit" disabled={busy}> + {busy ? "送信中..." : "キューに追加する"} + </button> + </div> + </Form> + + {jobs.length > 0 && ( + <section style={{ marginTop: "2rem" }}> + <h2>最近のジョブ</h2> + <div className="job-list"> + {jobs.map((job) => ( + <Link + key={job.id} + to={`/bands/auto-register/jobs/${job.id}`} + className="job-row" + > + <span + className="job-status" + style={{ color: STATUS_COLOR[job.status] ?? "#6b7280" }} + > + {STATUS_LABEL[job.status] ?? job.status} + </span> + <span className="job-preview"> + {job.input_text.slice(0, 60).replace(/\n/g, " ")} + {job.input_text.length > 60 ? "…" : ""} + </span> + <span className="job-time">{job.created_at.slice(0, 16).replace("T", " ")}</span> + </Link> + ))} + </div> + </section> + )} + </main> + ); +} |
