Procházet zdrojové kódy

feat(web):详情页加「创作知识判断提示词」按钮弹窗

新增 /api/judge-prompts 原样返回分类用的真实提示词(图文 extract.txt / 视频 extract_video.txt)。
详情页顶部加按钮 → 弹窗,图文/视频两 tab 展示完整提示词原文,并说明 is_empty 即创作闸。
只读 prompts、不改 skill/creation_knowledge。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SamLee před 2 týdny
rodič
revize
1524ec2b0a

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
acquisition/web/app/dist/assets/index-AlUlj_mH.css


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
acquisition/web/app/dist/assets/index-CIU2tcc0.css


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
acquisition/web/app/dist/assets/index-Dv59HUIk.js


+ 2 - 2
acquisition/web/app/dist/index.html

@@ -4,8 +4,8 @@
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>创作知识 · 找帖子</title>
-    <script type="module" crossorigin src="/app/assets/index-B90RPVWW.js"></script>
-    <link rel="stylesheet" crossorigin href="/app/assets/index-CIU2tcc0.css">
+    <script type="module" crossorigin src="/app/assets/index-Dv59HUIk.js"></script>
+    <link rel="stylesheet" crossorigin href="/app/assets/index-AlUlj_mH.css">
   </head>
   <body>
     <div id="root"></div>

+ 5 - 0
acquisition/web/app/src/api.js

@@ -29,6 +29,11 @@ export async function fetchManualAxes() {
   try { return (await getJSON('/api/manual-axes')).axes || {} } catch { return {} }
 }
 
+// 创作知识判断提示词(图文 / 视频),给「判断提示词」弹窗用
+export async function fetchJudgePrompts() {
+  try { return (await getJSON('/api/judge-prompts')).prompts || [] } catch { return [] }
+}
+
 const EMPTY = { total: 0, page: 1, size: 30, items: [] }
 
 export async function fetchQueries({ method, run_id, page, size } = {}) {

+ 37 - 3
acquisition/web/app/src/pages/QueryDetail.jsx

@@ -1,5 +1,5 @@
 import React, { useEffect, useState } from 'react'
-import { fetchSearch } from '../api.js'
+import { fetchJudgePrompts, fetchSearch } from '../api.js'
 
 // 渠道分区展示:小红书 → 微信公众号 → 抖音;每区内「创作知识」在前、「非创作知识」单独起一行。
 const PLATFORMS = [
@@ -11,15 +11,23 @@ const PLATFORMS = [
 // 单条 query 的真实搜索结果详情:进来即「点进该 query 生成的多个视频里」。
 export default function QueryDetail({ query }) {
   const [data, setData] = useState({ total: 0, items: [] })
+  const [showPrompts, setShowPrompts] = useState(false)
   useEffect(() => { fetchSearch({ query, size: 100 }).then(setData) }, [query])
 
   const hits = data.items.filter((r) => r.ok)
 
   return (
     <div>
-      <div className="detail-top"><a className="back" href="#/">← 返回 query 列表</a></div>
+      <div className="detail-top">
+        <a className="back" href="#/">← 返回 query 列表</a>
+        <button className="btn ghost" onClick={() => setShowPrompts(true)}>📋 创作知识判断提示词</button>
+      </div>
       <h2 className="dq">🔎 {query}</h2>
-      <div className="sub">该 query 搜到的真实帖子/视频 · 共 {hits.length} 个</div>
+      <div className="sub">该 query 搜到的真实帖子/视频 · 共 {hits.length} 个 ·
+        <span className="cls yes" style={{ margin: '0 4px' }}>创作知识</span>/
+        <span className="cls no" style={{ margin: '0 4px' }}>非创作知识</span> 由模型读真实内容判定</div>
+
+      {showPrompts && <JudgePromptModal onClose={() => setShowPrompts(false)} />}
 
       {hits.length === 0 ? (
         <div className="empty">这条 query 没搜到带媒体的结果。</div>
@@ -58,6 +66,32 @@ export default function QueryDetail({ query }) {
   )
 }
 
+// 创作知识判断提示词弹窗:图文 / 视频 两套真实提示词(extract.txt / extract_video.txt)。
+function JudgePromptModal({ onClose }) {
+  const [prompts, setPrompts] = useState([])
+  const [tab, setTab] = useState(0)
+  useEffect(() => { fetchJudgePrompts().then(setPrompts) }, [])
+  return (
+    <div className="modal-mask" onClick={onClose}>
+      <div className="modal wide" onClick={(e) => e.stopPropagation()}>
+        <div className="modal-hd"><b>创作知识判断提示词</b><span className="x" onClick={onClose}>✕</span></div>
+        <div className="modal-bd">
+          <p className="note">分类直接复用 pipeline 的真实提示词:图文用 extract.txt、视频用 extract_video.txt,
+            让 Gemini 读真实内容后输出 is_empty 作为「创作 / 非创作」判据(不简化、不另造)。</p>
+          <div className="ptabs">
+            {prompts.map((p, i) => (
+              <button key={i} className={tab === i ? 'on' : ''} onClick={() => setTab(i)}>
+                {p.name} · {p.file}
+              </button>
+            ))}
+          </div>
+          {prompts[tab] && <pre className="prompt">{prompts[tab].text}</pre>}
+        </div>
+      </div>
+    </div>
+  )
+}
+
 function ClsBadge({ cls }) {
   if (!cls || cls.is_creation === null) return <span className="cls none">未分类</span>
   const yes = cls.is_creation === 1

+ 11 - 1
acquisition/web/app/src/styles.css

@@ -83,6 +83,16 @@ td.q { font-weight: 600; }
 .axsubrow { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
 .axsubkey { font-size: 12px; color: var(--accent); font-weight: 600; min-width: 56px; }
 
+/* 判断提示词弹窗 */
+.modal.wide { width: min(840px, 100%); }
+.ptabs { display: flex; gap: 8px; margin: 12px 0; flex-wrap: wrap; }
+.ptabs button { border: 1px solid var(--line); background: var(--card); color: var(--ink);
+  padding: 6px 12px; border-radius: 8px; cursor: pointer; font-size: 12.5px; }
+.ptabs button.on { background: var(--accent); border-color: var(--accent); color: #fff; }
+.prompt { background: #0f1021; color: #e6e6ea; border-radius: 10px; padding: 14px 16px;
+  font: 12px/1.65 ui-monospace, Menlo, Consolas, monospace; white-space: pre-wrap;
+  word-break: break-word; max-height: 56vh; overflow: auto; margin: 0; }
+
 /* 搜索结果卡 */
 .cards { display: grid; grid-template-columns: 1fr; gap: 12px; }
 .card { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; }
@@ -126,7 +136,7 @@ td.q { font-weight: 600; }
 .btn.off { background: #f1f1f4; color: var(--muted); cursor: default; }
 
 /* query 详情页(点进该 query 的多个视频) */
-.detail-top { margin-bottom: 8px; }
+.detail-top { margin-bottom: 8px; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
 .back { font-size: 13px; }
 .dq { font-size: 18px; margin: 10px 0 2px; }
 .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; }

+ 18 - 0
acquisition/web_api.py

@@ -9,6 +9,7 @@
 """
 from __future__ import annotations
 
+from pathlib import Path
 from typing import Optional
 
 from fastapi import APIRouter, Query
@@ -19,6 +20,10 @@ from acquisition.query import (
 )
 
 router = APIRouter(prefix="/api")
+_ROOT = Path(__file__).resolve().parent.parent
+
+# 分类「创作知识 / 非创作知识」用的真实判断提示词(图文 / 视频),前端弹窗展示。读 is_empty 为判据。
+_JUDGE_PROMPTS = [("图文(小红书 / 微信公众号)", "extract.txt"), ("视频(抖音)", "extract_video.txt")]
 
 # 各方法里【人工定义】的轴及其全部取值(实质/形式/作用来自分类树,不在此)。单一来源 = query.py。
 MANUAL_AXES = {
@@ -28,6 +33,19 @@ MANUAL_AXES = {
 }
 
 
+@router.get("/judge-prompts")
+def judge_prompts() -> dict:
+    """分类创作知识用的真实提示词(图文 extract.txt / 视频 extract_video.txt),原样返回供前端展示。"""
+    out = []
+    for label, fn in _JUDGE_PROMPTS:
+        try:
+            text = (_ROOT / "prompts" / fn).read_text("utf-8")
+        except Exception as exc:
+            text = f"(读取失败: {exc})"
+        out.append({"name": label, "file": fn, "text": text})
+    return {"prompts": out}
+
+
 @router.get("/runs")
 def runs() -> dict:
     conn = store.connect()

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů