|
|
@@ -1,5 +1,10 @@
|
|
|
import React, { useEffect, useMemo, useState } from 'react'
|
|
|
|
|
|
+const AXIS_LABEL = {
|
|
|
+ 实质: '实质(3-4层)',
|
|
|
+ 形式: '形式(3-4层)',
|
|
|
+}
|
|
|
+
|
|
|
function params() {
|
|
|
return new URLSearchParams(window.location.search)
|
|
|
}
|
|
|
@@ -28,6 +33,116 @@ function platformCounts(row) {
|
|
|
return ['xiaohongshu', 'weixin', 'douyin'].map((key) => platforms[key]?.status || '未跑').join(' / ')
|
|
|
}
|
|
|
|
|
|
+function uniqueAxisValues(items, axis) {
|
|
|
+ const seen = new Set()
|
|
|
+ const out = []
|
|
|
+ for (const item of items || []) {
|
|
|
+ const value = item.parts?.[axis]
|
|
|
+ if (value && !seen.has(value)) {
|
|
|
+ seen.add(value)
|
|
|
+ out.push(value)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return out
|
|
|
+}
|
|
|
+
|
|
|
+function axisValues(data, family, axis) {
|
|
|
+ if (axis === '实质' || axis === '形式') {
|
|
|
+ return uniqueAxisValues(family.items, axis)
|
|
|
+ }
|
|
|
+ const key = axis === '作用/感受/意图' ? '目的池' : axis
|
|
|
+ return data.axis_values?.[key] || uniqueAxisValues(family.items, key)
|
|
|
+}
|
|
|
+
|
|
|
+function AxisColumn({ data, family, axis }) {
|
|
|
+ const values = axisValues(data, family, axis)
|
|
|
+ return (
|
|
|
+ <div className="axcol">
|
|
|
+ <div className="axhd">
|
|
|
+ <span>{AXIS_LABEL[axis] || axis}</span>
|
|
|
+ <span className="gn">{values.length}</span>
|
|
|
+ </div>
|
|
|
+ <div className="axlist">
|
|
|
+ {values.map((value) => <div className="axv" key={value}>{value}</div>)}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+function QueryGenerationPreview() {
|
|
|
+ const [data, setData] = useState(null)
|
|
|
+ const [activeKey, setActiveKey] = useState('f1')
|
|
|
+ const [err, setErr] = useState('')
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ setErr('')
|
|
|
+ fetch('/api/query-generation/preview?per=30&dry=true')
|
|
|
+ .then((r) => (r.ok ? r.json() : Promise.reject(new Error('query 生成预览读取失败'))))
|
|
|
+ .then((payload) => {
|
|
|
+ setData(payload)
|
|
|
+ const firstKey = payload.families?.[0]?.key
|
|
|
+ if (firstKey) setActiveKey(firstKey)
|
|
|
+ })
|
|
|
+ .catch((e) => setErr(e.message || '读取失败'))
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ if (err) return <div className="empty">{err}</div>
|
|
|
+ if (!data) return <div className="empty">加载中...</div>
|
|
|
+
|
|
|
+ const families = data.families || []
|
|
|
+ const family = families.find((row) => row.key === activeKey) || families[0]
|
|
|
+ const kept = (family?.items || []).filter((item) => item.keep !== false).length
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ <h1>创作知识 · Query 正交 Demo</h1>
|
|
|
+ <div className="sub">
|
|
|
+ 只显示当前激活的 2 种正交方式:f1 / f2。生成逻辑来自正式 query builder;此页只预览,不写 DB、不发起搜索。
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="famcols two">
|
|
|
+ {families.map((row) => (
|
|
|
+ <button
|
|
|
+ className={`fbtn ${row.key === family.key ? 'on' : ''}`}
|
|
|
+ key={row.key}
|
|
|
+ type="button"
|
|
|
+ onClick={() => setActiveKey(row.key)}
|
|
|
+ >
|
|
|
+ <span className="fbtn-pre">{row.axes?.[0] || row.key}</span>
|
|
|
+ <span className="fbtn-suf">{(row.axes || []).slice(1).join(' × ')}</span>
|
|
|
+ </button>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="cdemo">
|
|
|
+ {(family.axes || []).map((axis) => <AxisColumn key={axis} axis={axis} data={data} family={family} />)}
|
|
|
+ <div className="axcol qcol">
|
|
|
+ <div className="axhd qhd">
|
|
|
+ <span>Query 词</span>
|
|
|
+ <span className="qhd-metrics">
|
|
|
+ <span className="avgcreation pending">预览</span>
|
|
|
+ <span className="gn">留 {kept}/{family.items?.length || 0}</span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div className="axlist">
|
|
|
+ {(family.items || []).map((item, index) => (
|
|
|
+ <div className={`qrow ${item.keep === false ? 'drop' : 'keep'}`} key={`${item.query}-${index}`} title={item.reason || ''}>
|
|
|
+ <span className="qmark">{item.keep === false ? '✕' : '✓'}</span>
|
|
|
+ {typeof item.valid === 'number' && (
|
|
|
+ <span className={`qvalid ${item.valid >= 6 ? 'ok' : 'low'}`}>语义{item.valid}</span>
|
|
|
+ )}
|
|
|
+ <span className="qtext">{item.query}</span>
|
|
|
+ <span className="qdetail">未搜索</span>
|
|
|
+ {item.reason && <span className="qreason">{item.reason}</span>}
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
export default function CreationDemo() {
|
|
|
const [batch, setBatch] = useState(null)
|
|
|
const [summary, setSummary] = useState(null)
|
|
|
@@ -57,12 +172,7 @@ export default function CreationDemo() {
|
|
|
}, [batchId, runId])
|
|
|
|
|
|
if (!batchId) {
|
|
|
- return (
|
|
|
- <div>
|
|
|
- <h1>创作知识采集工作台</h1>
|
|
|
- <div className="empty">请在 URL 加上 <code>?batch_id=...</code>,可选 <code>&run_id=...</code> 查看正式云端批次。</div>
|
|
|
- </div>
|
|
|
- )
|
|
|
+ return <QueryGenerationPreview />
|
|
|
}
|
|
|
if (err) return <div className="empty">{err}</div>
|
|
|
if (!batch) return <div className="empty">加载中...</div>
|