|
@@ -11,11 +11,27 @@ function shortText(text, n = 220) {
|
|
|
return text.length > n ? text.slice(0, n) + '...' : text
|
|
return text.length > n ? text.slice(0, n) + '...' : text
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function mediaUrl(asset) {
|
|
|
|
|
+ return asset?.cdn_url || asset?.oss_url || asset?.source_url || ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function itemImages(item) {
|
|
|
|
|
+ return (item.media_assets || [])
|
|
|
|
|
+ .filter((asset) => ['image', 'cover', 'frame'].includes(asset.media_type))
|
|
|
|
|
+ .map(mediaUrl)
|
|
|
|
|
+ .filter(Boolean)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function itemVideo(item) {
|
|
|
|
|
+ const asset = (item.media_assets || []).find((row) => row.media_type === 'video')
|
|
|
|
|
+ return mediaUrl(asset)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function clsLabel(cls) {
|
|
function clsLabel(cls) {
|
|
|
- if (!cls || cls.is_creation === null || cls.is_creation === undefined) {
|
|
|
|
|
- return ['none', cls?.error ? '判断失败' : '未判断']
|
|
|
|
|
|
|
+ if (!cls || cls.is_creation_knowledge === null || cls.is_creation_knowledge === undefined) {
|
|
|
|
|
+ return ['none', '未判断']
|
|
|
}
|
|
}
|
|
|
- return cls.is_creation ? ['yes', '创作知识'] : ['no', '非创作知识']
|
|
|
|
|
|
|
+ return cls.is_creation_knowledge ? ['yes', '创作知识'] : ['no', '非创作知识']
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function PlatformStatus({ platform }) {
|
|
function PlatformStatus({ platform }) {
|
|
@@ -31,64 +47,57 @@ function PlatformStatus({ platform }) {
|
|
|
return <span className={`status ${status}`}>{text}</span>
|
|
return <span className={`status ${status}`}>{text}</span>
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function Media({ item, platform, onImageClick }) {
|
|
|
|
|
- if (platform === 'douyin' && item.video_url) {
|
|
|
|
|
- return <video controls playsInline src={item.video_url} poster={item.cover_url || undefined} />
|
|
|
|
|
|
|
+function Media({ item, onImageClick }) {
|
|
|
|
|
+ const video = itemVideo(item)
|
|
|
|
|
+ if (video) {
|
|
|
|
|
+ return <video controls playsInline src={video} />
|
|
|
}
|
|
}
|
|
|
- const images = item.image_urls || []
|
|
|
|
|
|
|
+ const images = itemImages(item)
|
|
|
if (images.length > 1) {
|
|
if (images.length > 1) {
|
|
|
return (
|
|
return (
|
|
|
<div className="thumbstrip" aria-label="帖子图片">
|
|
<div className="thumbstrip" aria-label="帖子图片">
|
|
|
- {images.map((u, i) => (
|
|
|
|
|
- <button className="thumbbtn" key={i} type="button" onClick={() => onImageClick?.(images, i, item.title)}>
|
|
|
|
|
- <img className="gimg" src={u} alt={`第 ${i + 1} 张`} loading="lazy" />
|
|
|
|
|
|
|
+ {images.map((url, i) => (
|
|
|
|
|
+ <button className="thumbbtn" key={url} type="button" onClick={() => onImageClick?.(images, i, item.title)}>
|
|
|
|
|
+ <img className="gimg" src={url} alt={`第 ${i + 1} 张`} loading="lazy" />
|
|
|
<span>{i + 1}</span>
|
|
<span>{i + 1}</span>
|
|
|
</button>
|
|
</button>
|
|
|
))}
|
|
))}
|
|
|
</div>
|
|
</div>
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
- const src = images[0] || item.cover_url
|
|
|
|
|
- return src ? (
|
|
|
|
|
- <button className="coverbtn" type="button" onClick={() => onImageClick?.([src], 0, item.title)}>
|
|
|
|
|
- <img className="cover" src={src} alt="" loading="lazy" />
|
|
|
|
|
|
|
+ return images[0] ? (
|
|
|
|
|
+ <button className="coverbtn" type="button" onClick={() => onImageClick?.(images, 0, item.title)}>
|
|
|
|
|
+ <img className="cover" src={images[0]} alt="" loading="lazy" />
|
|
|
</button>
|
|
</button>
|
|
|
) : null
|
|
) : null
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function ResultCard({ item, platform, tone, onImageClick }) {
|
|
|
|
|
|
|
+function ResultCard({ item, tone, onImageClick }) {
|
|
|
const [clsTone, clsText] = clsLabel(item.classification)
|
|
const [clsTone, clsText] = clsLabel(item.classification)
|
|
|
- const hasKnowledge = Boolean(item.classification?.knowledge)
|
|
|
|
|
return (
|
|
return (
|
|
|
<div className={`lane ${tone}`}>
|
|
<div className={`lane ${tone}`}>
|
|
|
- <Media item={item} platform={platform} onImageClick={onImageClick} />
|
|
|
|
|
|
|
+ <Media item={item} onImageClick={onImageClick} />
|
|
|
<div className="card-main">
|
|
<div className="card-main">
|
|
|
<div className="t">{item.title || '无标题'}</div>
|
|
<div className="t">{item.title || '无标题'}</div>
|
|
|
<span className={`cls ${clsTone}`}>{clsText}</span>
|
|
<span className={`cls ${clsTone}`}>{clsText}</span>
|
|
|
</div>
|
|
</div>
|
|
|
- {item.author && <div className="meta">{item.author}</div>}
|
|
|
|
|
|
|
+ {item.author_name && <div className="meta">{item.author_name}</div>}
|
|
|
{item.classification?.reason && <div className="meta">{item.classification.reason}</div>}
|
|
{item.classification?.reason && <div className="meta">{item.classification.reason}</div>}
|
|
|
- {item.body_text && (
|
|
|
|
|
|
|
+ {item.raw_summary && (
|
|
|
<details className="fold">
|
|
<details className="fold">
|
|
|
<summary>正文摘要</summary>
|
|
<summary>正文摘要</summary>
|
|
|
- <div className="bt">{shortText(item.body_text, 520)}</div>
|
|
|
|
|
- </details>
|
|
|
|
|
- )}
|
|
|
|
|
- {hasKnowledge && (
|
|
|
|
|
- <details className="fold knfold">
|
|
|
|
|
- <summary>创作知识</summary>
|
|
|
|
|
- <div className="knbody">{item.classification.knowledge}</div>
|
|
|
|
|
|
|
+ <div className="bt">{shortText(item.raw_summary, 520)}</div>
|
|
|
</details>
|
|
</details>
|
|
|
)}
|
|
)}
|
|
|
- {item.url && <a className="src" href={item.url} target="_blank" rel="noreferrer">打开原链接</a>}
|
|
|
|
|
|
|
+ {item.canonical_url && <a className="src" href={item.canonical_url} target="_blank" rel="noreferrer">打开原链接</a>}
|
|
|
</div>
|
|
</div>
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function PlatformGroup({ id, label, tone, data, onImageClick }) {
|
|
function PlatformGroup({ id, label, tone, data, onImageClick }) {
|
|
|
const items = data?.items || []
|
|
const items = data?.items || []
|
|
|
- const creationItems = items.filter(item => item.classification?.is_creation === 1)
|
|
|
|
|
- const otherItems = items.filter(item => item.classification?.is_creation !== 1)
|
|
|
|
|
|
|
+ const creationItems = items.filter((item) => item.classification?.is_creation_knowledge === true)
|
|
|
|
|
+ const otherItems = items.filter((item) => item.classification?.is_creation_knowledge !== true)
|
|
|
const renderRow = (rowItems, title, dim = false) => (
|
|
const renderRow = (rowItems, title, dim = false) => (
|
|
|
<div className="result-band">
|
|
<div className="result-band">
|
|
|
<div className={`subhd ${dim ? 'dim' : ''}`}>
|
|
<div className={`subhd ${dim ? 'dim' : ''}`}>
|
|
@@ -99,7 +108,7 @@ function PlatformGroup({ id, label, tone, data, onImageClick }) {
|
|
|
<div className="row-empty">{dim ? '暂无非创作知识或判断失败' : '暂无创作知识'}</div>
|
|
<div className="row-empty">{dim ? '暂无非创作知识或判断失败' : '暂无创作知识'}</div>
|
|
|
) : (
|
|
) : (
|
|
|
<div className="grid result-row">
|
|
<div className="grid result-row">
|
|
|
- {rowItems.map(item => <ResultCard key={item.id} item={item} platform={id} tone={tone} onImageClick={onImageClick} />)}
|
|
|
|
|
|
|
+ {rowItems.map((item) => <ResultCard key={item.id} item={item} tone={tone} onImageClick={onImageClick} />)}
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
</div>
|
|
</div>
|
|
@@ -109,9 +118,8 @@ function PlatformGroup({ id, label, tone, data, onImageClick }) {
|
|
|
<div className={`ghead ${tone}`}>
|
|
<div className={`ghead ${tone}`}>
|
|
|
<span>{label}</span>
|
|
<span>{label}</span>
|
|
|
<PlatformStatus platform={data} />
|
|
<PlatformStatus platform={data} />
|
|
|
- <span className="gn">{items.length}/5 条</span>
|
|
|
|
|
|
|
+ <span className="gn">{items.length} 条</span>
|
|
|
</div>
|
|
</div>
|
|
|
- {data?.error && <div className="perr">{data.error}</div>}
|
|
|
|
|
{items.length === 0 ? (
|
|
{items.length === 0 ? (
|
|
|
<div className="gnone">暂无可展示内容</div>
|
|
<div className="gnone">暂无可展示内容</div>
|
|
|
) : (
|
|
) : (
|
|
@@ -124,7 +132,7 @@ function PlatformGroup({ id, label, tone, data, onImageClick }) {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-export default function CreationQueryDetail({ query }) {
|
|
|
|
|
|
|
+export default function CreationQueryDetail({ runId, queryId }) {
|
|
|
const [data, setData] = useState(null)
|
|
const [data, setData] = useState(null)
|
|
|
const [err, setErr] = useState('')
|
|
const [err, setErr] = useState('')
|
|
|
const [lightbox, setLightbox] = useState(null)
|
|
const [lightbox, setLightbox] = useState(null)
|
|
@@ -134,7 +142,7 @@ export default function CreationQueryDetail({ query }) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const moveImage = (delta) => {
|
|
const moveImage = (delta) => {
|
|
|
- setLightbox(prev => {
|
|
|
|
|
|
|
+ setLightbox((prev) => {
|
|
|
if (!prev) return prev
|
|
if (!prev) return prev
|
|
|
const next = (prev.index + delta + prev.images.length) % prev.images.length
|
|
const next = (prev.index + delta + prev.images.length) % prev.images.length
|
|
|
return { ...prev, index: next }
|
|
return { ...prev, index: next }
|
|
@@ -144,19 +152,23 @@ export default function CreationQueryDetail({ query }) {
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
setData(null)
|
|
setData(null)
|
|
|
setErr('')
|
|
setErr('')
|
|
|
- fetch('/api/creation-search/query?display_limit=5&query=' + encodeURIComponent(query))
|
|
|
|
|
- .then(r => r.ok ? r.json() : Promise.reject(new Error('接口读取失败')))
|
|
|
|
|
|
|
+ if (!runId || !queryId) {
|
|
|
|
|
+ setErr('缺少 run_id 或 query_id')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ fetch(`/api/acquisition/runs/${encodeURIComponent(runId)}/queries/${encodeURIComponent(queryId)}`)
|
|
|
|
|
+ .then((r) => (r.ok ? r.json() : Promise.reject(new Error('接口读取失败'))))
|
|
|
.then(setData)
|
|
.then(setData)
|
|
|
- .catch(e => setErr(e.message || '读取失败'))
|
|
|
|
|
- }, [query])
|
|
|
|
|
|
|
+ .catch((e) => setErr(e.message || '读取失败'))
|
|
|
|
|
+ }, [runId, queryId])
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<div>
|
|
<div>
|
|
|
<div className="detail-top">
|
|
<div className="detail-top">
|
|
|
- <a className="back" href="#/">返回 Query Demo</a>
|
|
|
|
|
- {data?.run_id && <span className="meta">run_id: {data.run_id}</span>}
|
|
|
|
|
|
|
+ <a className="back" href="#/">返回 Query 列表</a>
|
|
|
|
|
+ {runId && <span className="meta">run_id: {runId}</span>}
|
|
|
</div>
|
|
</div>
|
|
|
- <h1 className="dq">{query}</h1>
|
|
|
|
|
|
|
+ <h1 className="dq">{data?.query?.query_text || queryId}</h1>
|
|
|
{err && <div className="empty">{err}</div>}
|
|
{err && <div className="empty">{err}</div>}
|
|
|
{!data && !err && <div className="empty">加载中...</div>}
|
|
{!data && !err && <div className="empty">加载中...</div>}
|
|
|
{data && PLATFORMS.map(([id, label, tone]) => (
|
|
{data && PLATFORMS.map(([id, label, tone]) => (
|