Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/routes/origin-flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export default function OriginFlow() {
const cached = ORIGIN_PRESETS.find((item) => item.id === id)!; const preset = result?.preset ?? cached;
const total = preset.sources.reduce((sum, item) => sum + item.count, 0); const japan = preset.sources.filter((item) => item.country === "日本").reduce((sum, item) => sum + item.count, 0); const unknown = preset.sources.filter((item) => item.country === "不明").reduce((sum, item) => sum + item.count, 0); const overseas = total - japan - unknown;
const referenced = preset.expected.filter((item) => preset.sources.some((source) => source.domain === item.domain)).length; const gap = preset.expected.length ? Math.round(100 * (1 - referenced / preset.expected.length)) : null;
async function measure(event: React.FormEvent) { event.preventDefault(); if (!topic.trim() || !model.trim()) return; setLoading(true); setError(""); setResult(null); setIdeal(false); try { const response = await fetch("/api/measure", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ topic, model }) }); const data = await response.json() as Measurement & { error?: string }; if (!response.ok) throw new Error(data.error ?? "測定に失敗しました。"); setResult(data); } catch (reason) { setError(reason instanceof Error ? reason.message : "測定に失敗しました。"); } finally { setLoading(false); } }
async function measure(event: React.FormEvent) { event.preventDefault(); if (!topic.trim()) return; setLoading(true); setError(""); setResult(null); setIdeal(false); try { const response = await fetch("/api/measure", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ topic, model }) }); const data = await response.json() as Measurement & { error?: string }; if (!response.ok) throw new Error(data.error ?? "測定に失敗しました。"); setResult(data); } catch (reason) { setError(reason instanceof Error ? reason.message : "測定に失敗しました。"); } finally { setLoading(false); } }
return <div className="origin-wrap"><header><p className="eyebrow">ORIGIN FLOW / AI参照情報源の発信国</p><h1>AIは、どこの情報を根拠にしたか。</h1><p className="lede">ナラティブを入力すると、Web検索付きAI回答と参照情報源の発信国・種別を表示します。</p></header>
<form className="origin-measure" onSubmit={measure}><label htmlFor="topic">調べたいナラティブ・質問</label><textarea id="topic" value={topic} onChange={(event) => setTopic(event.target.value)} placeholder="例: 日本の災害時の避難情報は十分か" required /><label htmlFor="model">OpenRouter モデルID</label><input id="model" list="model-examples" value={model} onChange={(event) => setModel(event.target.value)} required /><datalist id="model-examples"><option value="openai/gpt-4.1-mini"/><option value="openai/gpt-5.2"/><option value="anthropic/claude-sonnet-4.6"/><option value="google/gemini-2.5-flash"/></datalist><button type="submit" disabled={loading}>{loading ? "Web検索・分類中…" : "測定する"}</button><p>OpenRouterで利用できる任意のモデルIDを指定できます。Web検索は最大20件です。入力した言語(日本語・英語・繁体字中国語・韓国語)に合わせてプロンプトを切り替えます。</p></form>
<form className="origin-measure" onSubmit={measure}><label htmlFor="topic">調べたいナラティブ・質問</label><textarea id="topic" value={topic} onChange={(event) => setTopic(event.target.value)} placeholder="例: 日本の災害時の避難情報は十分か" required /><label htmlFor="model">OpenRouter モデルID(任意)</label><input id="model" value={model} onChange={(event) => setModel(event.target.value)} placeholder="例: anthropic/claude-sonnet-4.6" /><button type="submit" disabled={loading}>{loading ? "Web検索・分類中…" : "測定する"}</button><p>OpenRouterで利用可能な任意のモデルIDを入力できます。空欄では環境変数または既定モデルを使用します。Web検索は最大20件です。</p></form>
{error && <p className="origin-error" role="alert">{error}</p>}{result?.answer && <section className="origin-answer"><h2>AIの回答</h2><p>{result.answer}</p></section>}
<div className="origin-controls"><label>キャッシュ再生 <select value={id} onChange={(event) => { setId(event.target.value); setResult(null); setIdeal(false); }}>{ORIGIN_PRESETS.map((item) => <option key={item.id} value={item.id}>{item.topic}</option>)}</select></label><button type="button" aria-pressed={ideal} onClick={() => setIdeal(!ideal)}>{ideal ? "実測に戻す" : "本来あるべき姿"}</button></div>
<p className="origin-notice"><b>{result ? "今回の測定結果。" : "キャッシュ済みデモデータ。"}</b> 回答モデル: {preset.model}{preset.classifierModel ? ` / 分類モデル: ${preset.classifierModel}` : ""} / {preset.runAt} / 検索クエリ: {preset.query}{preset.language ? ` / プロンプト言語: ${LANGUAGE_LABEL[preset.language]}` : ""}</p>
Expand Down
Loading