configCodes.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import { useEffect, useState } from 'react'
  2. import { getAllConfigCodes } from './recall'
  3. /**
  4. * 召回维度配置 (configCode -> 中文标签)
  5. *
  6. * 数据源: video-vector 后端 GET /videoSearch/getAllConfigCodes
  7. * 启动时拉一次, 进程内缓存, 全部消费方共享.
  8. *
  9. * 在请求未返回前先吐出 SAFE_FALLBACK 避免 UI 闪烁; 拉到后切换到真实数据.
  10. * 命名约定:
  11. * VIDEO_* 视频解构维度 (选题/灵感点/关键点/目的点)
  12. * RESULT_LOG_* AI 识别维度 (选题/主题/关键词/口播)
  13. */
  14. const SAFE_FALLBACK: Record<string, string> = {
  15. VIDEO_TOPIC: '选题',
  16. VIDEO_INSPIRATION: '灵感点',
  17. }
  18. let cache: Record<string, string> | null = null
  19. let pending: Promise<Record<string, string>> | null = null
  20. function fetchOnce(): Promise<Record<string, string>> {
  21. if (cache) return Promise.resolve(cache)
  22. if (!pending) {
  23. pending = getAllConfigCodes()
  24. .then((d) => {
  25. cache = d
  26. return d
  27. })
  28. .catch((e) => {
  29. pending = null
  30. throw e
  31. })
  32. }
  33. return pending
  34. }
  35. export function useConfigCodes(): Record<string, string> {
  36. const [data, setData] = useState<Record<string, string>>(cache ?? SAFE_FALLBACK)
  37. useEffect(() => {
  38. if (cache) {
  39. setData(cache)
  40. return
  41. }
  42. fetchOnce().then(setData).catch(() => {
  43. // 失败保持 fallback 不阻塞 UI
  44. })
  45. }, [])
  46. return data
  47. }
  48. /**
  49. * 真实字典是否已经从后端拉到 (而非 SAFE_FALLBACK).
  50. * 用于"URL 自动召回"等场景需要等真实全量字典再触发, 避免只跑 fallback 的 2 个维度.
  51. */
  52. export function useConfigCodesReady(): boolean {
  53. const [ready, setReady] = useState<boolean>(cache != null)
  54. useEffect(() => {
  55. if (cache != null) {
  56. setReady(true)
  57. return
  58. }
  59. fetchOnce()
  60. .then(() => setReady(true))
  61. .catch(() => {
  62. // 失败保持 false, 由调用方决定是否兜底
  63. })
  64. }, [])
  65. return ready
  66. }
  67. /** "全部" 维度的特殊 value, 提交时拆开成所有 configCode 并发调用 */
  68. export const ALL_CONFIG_CODE = '__ALL__'
  69. /**
  70. * "内容理解-旧" 组(原 AI识别维度)中文标签覆写
  71. * 后端字典里这组的标签是 "选题"/"主题",前端要展示成 "内容选题"/"视频主题"
  72. * 其他标签(关键词/口播等)直通
  73. */
  74. const RESULT_LOG_LABEL_OVERRIDE: Record<string, string> = {
  75. 选题: '内容选题',
  76. 主题: '视频主题',
  77. }
  78. /**
  79. * 给定 configCode + 字典, 返回前端实际展示的中文标签
  80. * dropdown / 表格 / Tag 全部走这里, 保持一致
  81. */
  82. export function getConfigDisplayLabel(
  83. code: string,
  84. codes: Record<string, string>,
  85. ): string {
  86. const raw = codes[code] ?? code
  87. if (code.startsWith('RESULT_LOG_')) return RESULT_LOG_LABEL_OVERRIDE[raw] ?? raw
  88. return raw
  89. }
  90. /** Tab2 文本召回 dropdown: 按前缀分组, 顶部加"全部"快捷项 */
  91. export function buildGroupedConfigOptions(codes: Record<string, string>) {
  92. const video: { label: string; value: string }[] = []
  93. const result: { label: string; value: string }[] = []
  94. const other: { label: string; value: string }[] = []
  95. for (const [code, label] of Object.entries(codes)) {
  96. if (code.startsWith('VIDEO_')) {
  97. video.push({ label, value: code })
  98. } else if (code.startsWith('RESULT_LOG_')) {
  99. result.push({ label: RESULT_LOG_LABEL_OVERRIDE[label] ?? label, value: code })
  100. } else {
  101. other.push({ label, value: code })
  102. }
  103. }
  104. type Item = { label: string; value: string }
  105. type Group = { label: string; options: Item[] }
  106. const items: (Item | Group)[] = []
  107. // 顶部独立"全部"项 — 无 group
  108. items.push({ label: '全部', value: ALL_CONFIG_CODE })
  109. if (video.length) items.push({ label: '视频解构维度', options: video })
  110. if (result.length) items.push({ label: '内容理解-旧', options: result })
  111. if (other.length) items.push({ label: '其他', options: other })
  112. return items
  113. }
  114. /**
  115. * 给定字典,返回所有"可召回"的 configCode 列表(排除 ALL)
  116. * 用于"全部"模式下并发调用
  117. */
  118. export function listAllConfigCodes(codes: Record<string, string>): string[] {
  119. return Object.keys(codes)
  120. }
  121. /**
  122. * DeconstructTree 节点中文类型 → configCode 映射
  123. * 用于判断"以此召回"按钮该传哪个 configCode, 以及该按钮是否生效
  124. * (生效条件: 映射到的 configCode 在后端字典里存在)
  125. */
  126. export const TREE_NODE_TYPE_TO_CONFIG_CODE: Record<string, string> = {
  127. 选题: 'VIDEO_TOPIC',
  128. 灵感点: 'VIDEO_INSPIRATION',
  129. 关键点: 'VIDEO_KEYPOINT',
  130. 目的点: 'VIDEO_PURPOSE',
  131. }