tab1.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. #!/usr/bin/env python3
  2. """
  3. Tab1内容生成器 - 选题点(灵感点、目的点、关键点)
  4. """
  5. import html as html_module
  6. from typing import Dict, Any, List
  7. def render_inspiration_card(item: Dict[str, Any], idx: int) -> str:
  8. """渲染灵感点卡片"""
  9. point_text = item.get('灵感点', '')
  10. description = item.get('描述', '')
  11. category = item.get('分类', '')
  12. features = item.get('提取的特征', [])
  13. scoring = item.get('scoring', {})
  14. reasoning = item.get('推理', '')
  15. derivation = item.get('推导说明', '')
  16. card_id = f'inspiration-{idx}'
  17. has_details = description or features or scoring or reasoning or derivation
  18. html = f'<div class="point-card inspiration-card" data-card-id="{card_id}">\n'
  19. html += '<div class="point-card-header" onclick="toggleCardDetails(\'' + card_id + '\')">\n'
  20. html += f'<span class="point-number">#{idx}</span>\n'
  21. html += f'<span class="point-text">{html_module.escape(point_text)}</span>\n'
  22. if category:
  23. html += f'<span class="point-category">{html_module.escape(category)}</span>\n'
  24. if has_details:
  25. html += '<span class="toggle-icon">▼</span>\n'
  26. html += '</div>\n'
  27. if has_details:
  28. html += f'<div class="point-card-details" id="{card_id}-details">\n'
  29. if description:
  30. html += '<div class="detail-section">\n'
  31. html += '<strong>描述:</strong>\n'
  32. html += f'<div class="detail-text">{html_module.escape(description)}</div>\n'
  33. html += '</div>\n'
  34. if reasoning:
  35. html += '<div class="detail-section">\n'
  36. html += '<strong>推理:</strong>\n'
  37. html += f'<div class="detail-text">{html_module.escape(reasoning)}</div>\n'
  38. html += '</div>\n'
  39. if derivation:
  40. html += '<div class="detail-section">\n'
  41. html += '<strong>推导说明:</strong>\n'
  42. html += f'<div class="detail-text">{html_module.escape(derivation)}</div>\n'
  43. html += '</div>\n'
  44. if features:
  45. html += '<div class="detail-section">\n'
  46. html += '<strong>提取的特征:</strong>\n'
  47. html += '<div class="feature-tags">\n'
  48. for feature in features:
  49. feature_name = feature.get('特征名称', '')
  50. weight = feature.get('权重', 0)
  51. dimension = feature.get('维度分类', '')
  52. html += f'<span class="feature-tag">{html_module.escape(feature_name)} '
  53. if dimension:
  54. html += f'<em class="feature-dimension">({html_module.escape(dimension)})</em> '
  55. html += f'<strong class="feature-weight">权重:{weight}</strong></span>\n'
  56. html += '</div>\n'
  57. html += '</div>\n'
  58. if scoring:
  59. html += '<div class="detail-section">\n'
  60. html += '<strong>评分:</strong>\n'
  61. html += '<div class="scoring-badges">\n'
  62. if '人设契合度' in scoring:
  63. html += f'<span class="scoring-badge">人设契合度: {scoring["人设契合度"]}/10</span>\n'
  64. if '触发可能性' in scoring:
  65. html += f'<span class="scoring-badge">触发可能性: {scoring["触发可能性"]}/10</span>\n'
  66. if '内容解释力' in scoring:
  67. html += f'<span class="scoring-badge">内容解释力: {scoring["内容解释力"]}/10</span>\n'
  68. if '总分' in scoring:
  69. html += f'<span class="scoring-badge total-score">总分: {scoring["总分"]}</span>\n'
  70. if '评分说明' in scoring:
  71. html += f'<div class="scoring-note">{html_module.escape(scoring["评分说明"])}</div>\n'
  72. html += '</div>\n'
  73. html += '</div>\n'
  74. html += '</div>\n'
  75. html += '</div>\n'
  76. return html
  77. def render_purpose_card(item: Dict[str, Any], idx: int) -> str:
  78. """渲染目的点卡片"""
  79. point_text = item.get('目的点', '')
  80. description = item.get('描述', '')
  81. dimension = item.get('维度', {})
  82. features = item.get('提取的特征', [])
  83. reasoning = item.get('推理', '')
  84. card_id = f'purpose-{idx}'
  85. has_details = description or features or reasoning
  86. html = f'<div class="point-card purpose-card" data-card-id="{card_id}">\n'
  87. html += '<div class="point-card-header" onclick="toggleCardDetails(\'' + card_id + '\')">\n'
  88. html += f'<span class="point-number">#{idx}</span>\n'
  89. html += f'<span class="point-text">{html_module.escape(point_text)}</span>\n'
  90. # 显示维度标签
  91. if dimension:
  92. html += '<div class="dimension-tags">\n'
  93. if '一级分类' in dimension:
  94. html += f'<span class="dimension-tag level-1">{html_module.escape(dimension["一级分类"])}</span>\n'
  95. if '二级分类' in dimension:
  96. html += f'<span class="dimension-tag level-2">{html_module.escape(dimension["二级分类"])}</span>\n'
  97. html += '</div>\n'
  98. if has_details:
  99. html += '<span class="toggle-icon">▼</span>\n'
  100. html += '</div>\n'
  101. if has_details:
  102. html += f'<div class="point-card-details" id="{card_id}-details">\n'
  103. if description:
  104. html += '<div class="detail-section">\n'
  105. html += '<strong>描述:</strong>\n'
  106. html += f'<div class="detail-text">{html_module.escape(description)}</div>\n'
  107. html += '</div>\n'
  108. if reasoning:
  109. html += '<div class="detail-section">\n'
  110. html += '<strong>推理:</strong>\n'
  111. html += f'<div class="detail-text">{html_module.escape(reasoning)}</div>\n'
  112. html += '</div>\n'
  113. if features:
  114. html += '<div class="detail-section">\n'
  115. html += '<strong>提取的特征:</strong>\n'
  116. html += '<div class="feature-tags">\n'
  117. for feature in features:
  118. feature_name = feature.get('特征名称', '')
  119. weight = feature.get('权重', 0)
  120. feature_class = feature.get('特征分类', '')
  121. html += f'<span class="feature-tag">{html_module.escape(feature_name)} '
  122. if feature_class:
  123. html += f'<em class="feature-dimension">({html_module.escape(feature_class)})</em> '
  124. html += f'<strong class="feature-weight">权重:{weight}</strong></span>\n'
  125. html += '</div>\n'
  126. html += '</div>\n'
  127. html += '</div>\n'
  128. html += '</div>\n'
  129. return html
  130. def render_keypoint_card(item: Dict[str, Any], idx: int, level: int = 0) -> str:
  131. """渲染关键点卡片(支持嵌套)"""
  132. point_text = item.get('关键点', '')
  133. description = item.get('描述', '')
  134. dimension = item.get('维度', '') or item.get('维度细分', '')
  135. dimension_category = item.get('维度大类', '')
  136. features = item.get('提取的特征', [])
  137. children = item.get('children', [])
  138. child_reason = item.get('作为子节点的原因', '')
  139. card_id = f'keypoint-{idx}-l{level}'
  140. has_details = description or features or child_reason
  141. has_children = len(children) > 0
  142. indent_class = f'keypoint-level-{level}' if level > 0 else ''
  143. html = f'<div class="point-card keypoint-card {indent_class}" data-card-id="{card_id}" data-level="{level}">\n'
  144. html += '<div class="point-card-header" onclick="toggleCardDetails(\'' + card_id + '\')">\n'
  145. html += f'<span class="point-number">#{item.get("候选编号", idx)}</span>\n'
  146. html += f'<span class="point-text">{html_module.escape(point_text)}</span>\n'
  147. # 显示维度大类标签(实质类/形式类)
  148. if dimension_category:
  149. category_label = '形式类' if dimension_category == '形式' else '实质类'
  150. category_class = 'dimension-category-form' if dimension_category == '形式' else 'dimension-category-substance'
  151. html += f'<span class="dimension-category {category_class}">{html_module.escape(category_label)}</span>\n'
  152. # 显示维度标签(维度细分)
  153. if dimension:
  154. html += f'<span class="dimension-tag keypoint-dimension">{html_module.escape(dimension)}</span>\n'
  155. if has_details or has_children:
  156. html += '<span class="toggle-icon">▼</span>\n'
  157. html += '</div>\n'
  158. if has_details or has_children:
  159. html += f'<div class="point-card-details" id="{card_id}-details">\n'
  160. if description:
  161. html += '<div class="detail-section">\n'
  162. html += '<strong>描述:</strong>\n'
  163. html += f'<div class="detail-text">{html_module.escape(description)}</div>\n'
  164. html += '</div>\n'
  165. if child_reason:
  166. html += '<div class="detail-section">\n'
  167. html += '<strong>作为子节点的原因:</strong>\n'
  168. html += f'<div class="detail-text">{html_module.escape(child_reason)}</div>\n'
  169. html += '</div>\n'
  170. if features:
  171. html += '<div class="detail-section">\n'
  172. html += '<strong>提取的特征:</strong>\n'
  173. html += '<div class="feature-tags">\n'
  174. for feature in features:
  175. feature_name = feature.get('特征名称', '')
  176. weight = feature.get('权重', 0)
  177. feature_dimension = feature.get('维度', '')
  178. html += f'<span class="feature-tag">{html_module.escape(feature_name)} '
  179. if feature_dimension:
  180. html += f'<em class="feature-dimension">({html_module.escape(feature_dimension)})</em> '
  181. html += f'<strong class="feature-weight">权重:{weight}</strong></span>\n'
  182. html += '</div>\n'
  183. html += '</div>\n'
  184. # 递归渲染子关键点
  185. if has_children:
  186. html += '<div class="detail-section keypoint-children">\n'
  187. html += '<strong>子关键点:</strong>\n'
  188. html += '<div class="keypoint-children-list">\n'
  189. for child_idx, child in enumerate(children, 1):
  190. html += render_keypoint_card(child, child_idx, level + 1)
  191. html += '</div>\n'
  192. html += '</div>\n'
  193. html += '</div>\n'
  194. html += '</div>\n'
  195. return html
  196. def generate_tab1_content(data: Dict[str, Any]) -> str:
  197. """生成Tab1内容:选题、灵感点、目的点、关键点"""
  198. html = '<div class="tab-content" id="tab1">\n'
  199. # 添加CSS样式
  200. html += '''
  201. <style>
  202. .point-card {
  203. background: white;
  204. border-radius: 8px;
  205. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  206. margin-bottom: 16px;
  207. transition: all 0.3s ease;
  208. overflow: hidden;
  209. }
  210. .point-card:hover {
  211. box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
  212. transform: translateY(-2px);
  213. }
  214. .point-card-header {
  215. display: flex;
  216. align-items: center;
  217. gap: 12px;
  218. padding: 16px 20px;
  219. cursor: pointer;
  220. user-select: none;
  221. border-bottom: 1px solid #f0f0f0;
  222. }
  223. .point-card-header:hover {
  224. background-color: #f8f8f8;
  225. }
  226. .point-number {
  227. color: #667eea;
  228. font-weight: 600;
  229. font-size: 14px;
  230. min-width: 40px;
  231. }
  232. .point-text {
  233. flex: 1;
  234. font-size: 16px;
  235. font-weight: 500;
  236. color: #333;
  237. }
  238. .point-category {
  239. background-color: #e3f2fd;
  240. color: #1976d2;
  241. padding: 4px 12px;
  242. border-radius: 12px;
  243. font-size: 12px;
  244. font-weight: 500;
  245. }
  246. .dimension-tags {
  247. display: flex;
  248. gap: 8px;
  249. }
  250. .dimension-tag {
  251. background-color: #f3e5f5;
  252. color: #7b1fa2;
  253. padding: 4px 12px;
  254. border-radius: 12px;
  255. font-size: 12px;
  256. font-weight: 500;
  257. }
  258. .dimension-tag.level-1 {
  259. background-color: #e1bee7;
  260. color: #6a1b9a;
  261. }
  262. .dimension-tag.level-2 {
  263. background-color: #f3e5f5;
  264. color: #7b1fa2;
  265. }
  266. .dimension-category {
  267. padding: 4px 12px;
  268. border-radius: 12px;
  269. font-size: 12px;
  270. font-weight: 600;
  271. }
  272. .dimension-category-form {
  273. background-color: #fff3e0;
  274. color: #e65100;
  275. }
  276. .dimension-category-substance {
  277. background-color: #e8f5e9;
  278. color: #2e7d32;
  279. }
  280. .toggle-icon {
  281. color: #999;
  282. font-size: 12px;
  283. transition: transform 0.3s ease;
  284. }
  285. .point-card.expanded .toggle-icon {
  286. transform: rotate(180deg);
  287. }
  288. .point-card-details {
  289. display: none;
  290. padding: 20px;
  291. background-color: #fafafa;
  292. }
  293. .point-card.expanded .point-card-details {
  294. display: block;
  295. }
  296. .detail-section {
  297. margin-bottom: 20px;
  298. }
  299. .detail-section:last-child {
  300. margin-bottom: 0;
  301. }
  302. .detail-section strong {
  303. display: block;
  304. color: #667eea;
  305. font-size: 14px;
  306. font-weight: 600;
  307. margin-bottom: 8px;
  308. }
  309. .detail-text {
  310. background-color: white;
  311. padding: 12px 16px;
  312. border-radius: 6px;
  313. line-height: 1.6;
  314. color: #444;
  315. font-size: 14px;
  316. border-left: 3px solid #667eea;
  317. }
  318. .feature-tags {
  319. display: flex;
  320. flex-wrap: wrap;
  321. gap: 8px;
  322. margin-top: 8px;
  323. }
  324. .feature-tag {
  325. background-color: #e8eaf6;
  326. color: #3f51b5;
  327. padding: 6px 12px;
  328. border-radius: 15px;
  329. font-size: 13px;
  330. }
  331. .feature-dimension {
  332. color: #7986cb;
  333. font-style: italic;
  334. }
  335. .feature-weight {
  336. color: #1a237e;
  337. margin-left: 4px;
  338. }
  339. .scoring-badges {
  340. display: flex;
  341. flex-wrap: wrap;
  342. gap: 8px;
  343. margin-top: 8px;
  344. }
  345. .scoring-badge {
  346. background-color: #e0f2f1;
  347. color: #00695c;
  348. padding: 6px 12px;
  349. border-radius: 15px;
  350. font-size: 13px;
  351. font-weight: 500;
  352. }
  353. .scoring-badge.total-score {
  354. background-color: #667eea;
  355. color: white;
  356. font-weight: 600;
  357. }
  358. .scoring-note {
  359. margin-top: 12px;
  360. padding: 12px;
  361. background-color: #fff3cd;
  362. border-left: 3px solid #ffc107;
  363. border-radius: 4px;
  364. font-size: 13px;
  365. color: #856404;
  366. line-height: 1.6;
  367. }
  368. /* 关键点嵌套样式 */
  369. .keypoint-card.keypoint-level-1 {
  370. margin-left: 24px;
  371. border-left: 3px solid #4facfe;
  372. }
  373. .keypoint-card.keypoint-level-2 {
  374. margin-left: 48px;
  375. border-left: 3px solid #43e97b;
  376. }
  377. .keypoint-card.keypoint-level-3 {
  378. margin-left: 72px;
  379. border-left: 3px solid #fa709a;
  380. }
  381. .keypoint-children {
  382. margin-top: 16px;
  383. }
  384. .keypoint-children-list {
  385. margin-top: 12px;
  386. }
  387. .inspiration-card {
  388. border-left: 4px solid #667eea;
  389. }
  390. .purpose-card {
  391. border-left: 4px solid #f093fb;
  392. }
  393. .keypoint-card {
  394. border-left: 4px solid #4facfe;
  395. }
  396. </style>
  397. '''
  398. # 添加JavaScript
  399. html += '''
  400. <script>
  401. function toggleCardDetails(cardId) {
  402. const card = document.querySelector(`[data-card-id="${cardId}"]`);
  403. if (card) {
  404. card.classList.toggle('expanded');
  405. }
  406. }
  407. </script>
  408. '''
  409. # 选题描述
  410. if '选题描述' in data:
  411. topic = data['选题描述']
  412. html += '<div class="section">\n'
  413. html += '<h3>选题描述</h3>\n'
  414. if '主题' in topic:
  415. html += f'<div class="topic-theme"><strong>主题:</strong>{html_module.escape(topic["主题"])}</div>\n'
  416. if '描述' in topic:
  417. html += f'<div class="topic-desc"><strong>描述:</strong>{html_module.escape(topic["描述"])}</div>\n'
  418. html += '</div>\n'
  419. # 灵感点
  420. if '灵感点' in data:
  421. inspiration = data['灵感点']
  422. html += '<div class="section">\n'
  423. html += '<h3>灵感点</h3>\n'
  424. html += '<div class="point-cards-list">\n'
  425. if isinstance(inspiration, list):
  426. for idx, item in enumerate(inspiration, 1):
  427. html += render_inspiration_card(item, idx)
  428. html += '</div>\n'
  429. html += '</div>\n'
  430. # 目的点
  431. if '目的点' in data:
  432. purpose = data['目的点']
  433. html += '<div class="section">\n'
  434. html += '<h3>目的点</h3>\n'
  435. html += '<div class="point-cards-list">\n'
  436. if isinstance(purpose, dict) and 'purposes' in purpose:
  437. purpose_list = purpose['purposes']
  438. elif isinstance(purpose, list):
  439. purpose_list = purpose
  440. else:
  441. purpose_list = []
  442. if isinstance(purpose_list, list):
  443. for idx, item in enumerate(purpose_list, 1):
  444. html += render_purpose_card(item, idx)
  445. html += '</div>\n'
  446. html += '</div>\n'
  447. # 关键点
  448. if '关键点' in data:
  449. keypoint = data['关键点']
  450. html += '<div class="section">\n'
  451. html += '<h3>关键点</h3>\n'
  452. html += '<div class="point-cards-list">\n'
  453. # 处理关键点数据:可能是列表,也可能是包含key_points的对象
  454. keypoint_list = keypoint
  455. if isinstance(keypoint, dict) and 'key_points' in keypoint:
  456. keypoint_list = keypoint['key_points']
  457. if isinstance(keypoint_list, list):
  458. for idx, item in enumerate(keypoint_list, 1):
  459. html += render_keypoint_card(item, idx, 0)
  460. html += '</div>\n'
  461. html += '</div>\n'
  462. html += '</div>\n'
  463. return html