TopicTableQueryPanel.jsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import { useState } from 'react'
  2. import { generateTopicTableQueries, getTopicTableQueryPrompt } from '../../api/workbench.js'
  3. const EXAMPLE = {
  4. topicBuildId: '1229',
  5. topicId: '1392',
  6. }
  7. function PromptModal({ payload, loading, error, onClose }) {
  8. return (
  9. <div className="manual-modal-mask" onClick={onClose}>
  10. <section className="manual-modal-panel topic-prompt-modal" onClick={(event) => event.stopPropagation()}>
  11. <header className="manual-modal-head">
  12. <div>
  13. <span className="topic-eyebrow">选题表信息 → 大模型判断 → 搜索词</span>
  14. <h2>生成 Prompt</h2>
  15. <p>{payload ? `${payload.name} · ${payload.version}` : '读取当前线上使用的完整 Prompt'}</p>
  16. </div>
  17. <button className="manual-close" type="button" onClick={onClose} aria-label="关闭">×</button>
  18. </header>
  19. {loading && <div className="topic-inline-state">正在读取 Prompt…</div>}
  20. {error && <div className="manual-error">{error}</div>}
  21. {payload && <pre className="topic-prompt-code">{payload.system_prompt}</pre>}
  22. </section>
  23. </div>
  24. )
  25. }
  26. function GenerationRows({ result }) {
  27. const rows = result.field_mappings || []
  28. return (
  29. <section className="topic-route-map">
  30. <div className="topic-route-map-head">
  31. <strong>选题表里用到的信息</strong>
  32. <strong>大模型判断什么</strong>
  33. <strong>生成哪些具体搜索词</strong>
  34. </div>
  35. <div className="topic-route-map-body">
  36. {rows.map((row) => (
  37. <article className="topic-route-row" key={row.field_key}>
  38. <div className="topic-route-input">
  39. <span>{row.field_label}</span>
  40. <code>{row.field_path}</code>
  41. <p>{row.field_value}</p>
  42. </div>
  43. <div className="topic-route-judgement">
  44. <p>{row.judgement}</p>
  45. </div>
  46. <div className="topic-route-queries">
  47. {row.queries.map((query, index) => (
  48. <div key={`${query}-${index}`}>
  49. <b>{index + 1}</b>
  50. <p>{query}</p>
  51. </div>
  52. ))}
  53. </div>
  54. </article>
  55. ))}
  56. </div>
  57. </section>
  58. )
  59. }
  60. export default function TopicTableQueryPanel() {
  61. const [topicBuildId, setTopicBuildId] = useState(EXAMPLE.topicBuildId)
  62. const [topicId, setTopicId] = useState(EXAMPLE.topicId)
  63. const [result, setResult] = useState(null)
  64. const [loading, setLoading] = useState(false)
  65. const [error, setError] = useState('')
  66. const [promptOpen, setPromptOpen] = useState(false)
  67. const [prompt, setPrompt] = useState(null)
  68. const [promptLoading, setPromptLoading] = useState(false)
  69. const [promptError, setPromptError] = useState('')
  70. const showPrompt = async () => {
  71. setPromptOpen(true)
  72. if (prompt || promptLoading) return
  73. setPromptLoading(true)
  74. setPromptError('')
  75. try {
  76. setPrompt(await getTopicTableQueryPrompt())
  77. } catch (e) {
  78. setPromptError(e.message || 'Prompt 读取失败')
  79. } finally {
  80. setPromptLoading(false)
  81. }
  82. }
  83. const generate = async (event) => {
  84. event.preventDefault()
  85. setLoading(true)
  86. setError('')
  87. try {
  88. const payload = {
  89. topic_build_id: Number(topicBuildId),
  90. topic_id: topicId ? Number(topicId) : null,
  91. max_queries: 18,
  92. }
  93. setResult(await generateTopicTableQueries(payload))
  94. } catch (e) {
  95. setError(e.message || '搜索词生成失败')
  96. } finally {
  97. setLoading(false)
  98. }
  99. }
  100. return (
  101. <div className="topic-table-panel">
  102. <header className="topic-table-intro">
  103. <div>
  104. <span className="topic-eyebrow">第四种生成方式</span>
  105. <h2>根据选题表生成搜索词</h2>
  106. <p>每一行都是一个真实字段,对应一项大模型判断和由它生成的搜索词。</p>
  107. </div>
  108. <span className="topic-preview-only">这里只生成搜索词,不会开始搜索</span>
  109. </header>
  110. <form className="topic-table-form" onSubmit={generate}>
  111. <label>
  112. <span>选题表编号</span>
  113. <input value={topicBuildId} onChange={(e) => setTopicBuildId(e.target.value)} type="number" min="1" required />
  114. </label>
  115. <label>
  116. <span>选题编号</span>
  117. <input value={topicId} onChange={(e) => setTopicId(e.target.value)} type="number" min="1" placeholder="不填则取 mature 选题" />
  118. </label>
  119. <button className="topic-example-button" type="button" onClick={() => {
  120. setTopicBuildId(EXAMPLE.topicBuildId)
  121. setTopicId(EXAMPLE.topicId)
  122. setResult(null)
  123. setError('')
  124. }}>载入真实示例 1229</button>
  125. <button className="topic-prompt-button" type="button" onClick={showPrompt}>查看生成 Prompt</button>
  126. <button className="topic-generate-button" type="submit" disabled={loading}>
  127. {loading ? '大模型正在生成…' : '生成搜索词'}
  128. </button>
  129. </form>
  130. {error && <div className="manual-error topic-generation-error">{error}</div>}
  131. {!result && !loading && (
  132. <section className="topic-example-map">
  133. <div className="topic-example-cell">
  134. <span>实质字段</span>
  135. <code>topics[id=1392].composition_items[id=25686].element_name</code>
  136. <p>彩虹光晕</p>
  137. </div>
  138. <div className="topic-example-cell">
  139. <span>大模型判断什么</span>
  140. <p>这个画面里的彩虹光晕,怎样才能稳定拍出来?</p>
  141. </div>
  142. <div className="topic-example-cell">
  143. <span>生成的搜索词</span>
  144. <ul>
  145. <li>逆光人像怎么拍出自然彩虹光晕</li>
  146. <li>低角度拍摄时太阳和镜头怎么配合</li>
  147. </ul>
  148. </div>
  149. </section>
  150. )}
  151. {loading && <div className="topic-loading-card"><span />正在读取真实字段,并逐字段生成搜索词…</div>}
  152. {result && <GenerationRows result={result} />}
  153. {promptOpen && (
  154. <PromptModal
  155. payload={prompt}
  156. loading={promptLoading}
  157. error={promptError}
  158. onClose={() => setPromptOpen(false)}
  159. />
  160. )}
  161. </div>
  162. )
  163. }