bilisum 1.13.2 → 1.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/bin/bilisum.js +157 -76
- package/package.json +7 -2
- package/runtime/VERSION +1 -0
- package/runtime/apps/service/pyproject.toml +29 -0
- package/runtime/apps/service/src/video_sum_service/__init__.py +1 -0
- package/runtime/apps/service/src/video_sum_service/__main__.py +5 -0
- package/runtime/apps/service/src/video_sum_service/app.py +480 -0
- package/runtime/apps/service/src/video_sum_service/context.py +24 -0
- package/runtime/apps/service/src/video_sum_service/integrations.py +264 -0
- package/runtime/apps/service/src/video_sum_service/knowledge/__init__.py +5 -0
- package/runtime/apps/service/src/video_sum_service/knowledge/index_service.py +408 -0
- package/runtime/apps/service/src/video_sum_service/knowledge/local_llm.py +319 -0
- package/runtime/apps/service/src/video_sum_service/knowledge/rag_service.py +492 -0
- package/runtime/apps/service/src/video_sum_service/knowledge/tag_service.py +242 -0
- package/runtime/apps/service/src/video_sum_service/main.py +31 -0
- package/runtime/apps/service/src/video_sum_service/repository.py +942 -0
- package/runtime/apps/service/src/video_sum_service/routers/__init__.py +1 -0
- package/runtime/apps/service/src/video_sum_service/routers/knowledge.py +272 -0
- package/runtime/apps/service/src/video_sum_service/routers/system.py +280 -0
- package/runtime/apps/service/src/video_sum_service/routers/tasks.py +287 -0
- package/runtime/apps/service/src/video_sum_service/routers/videos.py +766 -0
- package/runtime/apps/service/src/video_sum_service/runtime_support.py +1007 -0
- package/runtime/apps/service/src/video_sum_service/schemas.py +418 -0
- package/runtime/apps/service/src/video_sum_service/settings_manager.py +130 -0
- package/runtime/apps/service/src/video_sum_service/task_artifacts.py +102 -0
- package/runtime/apps/service/src/video_sum_service/task_exports.py +108 -0
- package/runtime/apps/service/src/video_sum_service/transcribe_worker.py +9 -0
- package/runtime/apps/service/src/video_sum_service/video_assets.py +623 -0
- package/runtime/apps/service/src/video_sum_service/worker.py +381 -0
- package/runtime/apps/web/static/apple-touch-icon.png +0 -0
- package/runtime/apps/web/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/runtime/apps/web/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/runtime/apps/web/static/assets/icons/icon-180.png +0 -0
- package/runtime/apps/web/static/assets/icons/icon-512.png +0 -0
- package/runtime/apps/web/static/assets/icons/icon.svg +17 -0
- package/runtime/apps/web/static/assets/index-Cj5PVjQg.js +388 -0
- package/runtime/apps/web/static/assets/index-DU2t4_7s.css +1 -0
- package/runtime/apps/web/static/favicon-32x32.png +0 -0
- package/runtime/apps/web/static/favicon.ico +0 -0
- package/runtime/apps/web/static/favicon.svg +17 -0
- package/runtime/apps/web/static/index.html +19 -0
- package/runtime/apps/web/static/js/api.js +123 -0
- package/runtime/apps/web/static/js/main.js +932 -0
- package/runtime/apps/web/static/js/state.js +28 -0
- package/runtime/apps/web/static/js/utils.js +81 -0
- package/runtime/apps/web/static/js/views/home.js +604 -0
- package/runtime/apps/web/static/js/views/settings.js +477 -0
- package/runtime/apps/web/static/styles.css +2206 -0
- package/runtime/packages/core/pyproject.toml +18 -0
- package/runtime/packages/core/src/video_sum_core/__init__.py +1 -0
- package/runtime/packages/core/src/video_sum_core/errors.py +22 -0
- package/runtime/packages/core/src/video_sum_core/markdown_exports.py +154 -0
- package/runtime/packages/core/src/video_sum_core/models/__init__.py +1 -0
- package/runtime/packages/core/src/video_sum_core/models/tasks.py +70 -0
- package/runtime/packages/core/src/video_sum_core/pipeline/__init__.py +1 -0
- package/runtime/packages/core/src/video_sum_core/pipeline/base.py +46 -0
- package/runtime/packages/core/src/video_sum_core/pipeline/real.py +3467 -0
- package/runtime/packages/core/src/video_sum_core/transcribe_subprocess.py +173 -0
- package/runtime/packages/core/src/video_sum_core/utils.py +127 -0
- package/runtime/packages/infra/pyproject.toml +16 -0
- package/runtime/packages/infra/src/video_sum_infra/__init__.py +1 -0
- package/runtime/packages/infra/src/video_sum_infra/app.py +39 -0
- package/runtime/packages/infra/src/video_sum_infra/config.py +393 -0
- package/runtime/packages/infra/src/video_sum_infra/db.py +34 -0
- package/runtime/packages/infra/src/video_sum_infra/logging.py +54 -0
- package/runtime/packages/infra/src/video_sum_infra/paths.py +6 -0
- package/runtime/packages/infra/src/video_sum_infra/runtime.py +485 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import Counter
|
|
4
|
+
from itertools import combinations
|
|
5
|
+
|
|
6
|
+
from fastapi import HTTPException
|
|
7
|
+
|
|
8
|
+
from video_sum_infra.config import ServiceSettings
|
|
9
|
+
from video_sum_service.knowledge.local_llm import chat_knowledge_llm, parse_json_payload
|
|
10
|
+
from video_sum_service.repository import SqliteTaskRepository
|
|
11
|
+
from video_sum_service.schemas import (
|
|
12
|
+
KnowledgeAutoTagResponse,
|
|
13
|
+
KnowledgeAutoTagVideoResult,
|
|
14
|
+
KnowledgeNetworkLink,
|
|
15
|
+
KnowledgeNetworkNode,
|
|
16
|
+
KnowledgeNetworkResponse,
|
|
17
|
+
TagItem,
|
|
18
|
+
VideoTagRecord,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TagService:
|
|
23
|
+
def __init__(self, repository: SqliteTaskRepository, settings: ServiceSettings) -> None:
|
|
24
|
+
self._repository = repository
|
|
25
|
+
self._settings = settings
|
|
26
|
+
|
|
27
|
+
def add_tag(self, video_id: str, tag: str, source: str = "manual", confidence: float = 1.0) -> bool:
|
|
28
|
+
return self._repository.add_video_tag(video_id, tag, source=source, confidence=confidence)
|
|
29
|
+
|
|
30
|
+
def remove_tag(self, video_id: str, tag: str) -> bool:
|
|
31
|
+
return self._repository.remove_video_tag(video_id, tag)
|
|
32
|
+
|
|
33
|
+
def get_tags_for_video(self, video_id: str) -> list[VideoTagRecord]:
|
|
34
|
+
return self._repository.list_video_tags(video_id)
|
|
35
|
+
|
|
36
|
+
def get_all_tags(self) -> list[TagItem]:
|
|
37
|
+
return [TagItem.model_validate(item) for item in self._repository.list_all_tags()]
|
|
38
|
+
|
|
39
|
+
def auto_tag_video(self, video_id: str, llm_model: str | None = None) -> list[str]:
|
|
40
|
+
del llm_model
|
|
41
|
+
video = self._repository.get_video_asset(video_id)
|
|
42
|
+
if video is None or video.latest_result is None:
|
|
43
|
+
raise HTTPException(status_code=404, detail="Video not found or has no summary result.")
|
|
44
|
+
|
|
45
|
+
result = video.latest_result
|
|
46
|
+
content = "\n\n".join(
|
|
47
|
+
[
|
|
48
|
+
f"标题:{video.title}",
|
|
49
|
+
f"概览:{result.overview}",
|
|
50
|
+
"知识笔记:",
|
|
51
|
+
result.knowledge_note_markdown or "",
|
|
52
|
+
"章节:",
|
|
53
|
+
"\n".join(
|
|
54
|
+
f"- {chapter.get('title')}: {chapter.get('summary')}"
|
|
55
|
+
for chapter in result.timeline
|
|
56
|
+
if isinstance(chapter, dict)
|
|
57
|
+
),
|
|
58
|
+
]
|
|
59
|
+
).strip()
|
|
60
|
+
|
|
61
|
+
response_text, _body = chat_knowledge_llm(
|
|
62
|
+
self._settings,
|
|
63
|
+
system_prompt=(
|
|
64
|
+
"你是一个知识库标签助手。请根据视频摘要和知识笔记,为视频生成 3 到 8 个简洁中文标签。"
|
|
65
|
+
"只返回合法 JSON,对象格式必须为 {\"tags\": [\"标签1\", \"标签2\"]}。"
|
|
66
|
+
),
|
|
67
|
+
user_prompt=content,
|
|
68
|
+
require_json=True,
|
|
69
|
+
max_tokens=200,
|
|
70
|
+
temperature=0.1,
|
|
71
|
+
)
|
|
72
|
+
payload = parse_json_payload(response_text)
|
|
73
|
+
raw_tags = payload.get("tags")
|
|
74
|
+
if not isinstance(raw_tags, list):
|
|
75
|
+
raise HTTPException(status_code=502, detail="知识库 LLM 返回的标签格式不正确。")
|
|
76
|
+
|
|
77
|
+
normalized_tags: list[str] = []
|
|
78
|
+
seen: set[str] = set()
|
|
79
|
+
for item in raw_tags:
|
|
80
|
+
tag = str(item or "").strip()
|
|
81
|
+
if not tag or tag in seen:
|
|
82
|
+
continue
|
|
83
|
+
seen.add(tag)
|
|
84
|
+
normalized_tags.append(tag)
|
|
85
|
+
self._repository.add_video_tag(video_id, tag, source="auto_llm", confidence=0.8)
|
|
86
|
+
return normalized_tags
|
|
87
|
+
|
|
88
|
+
def batch_auto_tag(self, video_ids: list[str] | None = None) -> KnowledgeAutoTagResponse:
|
|
89
|
+
target_video_ids = video_ids or self._repository.list_untagged_video_ids()
|
|
90
|
+
items: list[KnowledgeAutoTagVideoResult] = []
|
|
91
|
+
for video_id in target_video_ids:
|
|
92
|
+
tags = self.auto_tag_video(video_id)
|
|
93
|
+
items.append(KnowledgeAutoTagVideoResult(video_id=video_id, tags=tags))
|
|
94
|
+
return KnowledgeAutoTagResponse(items=items)
|
|
95
|
+
|
|
96
|
+
def get_network_data(
|
|
97
|
+
self,
|
|
98
|
+
selected_tags: list[str] | None = None,
|
|
99
|
+
*,
|
|
100
|
+
max_tags: int = 12,
|
|
101
|
+
max_videos: int = 8,
|
|
102
|
+
) -> KnowledgeNetworkResponse:
|
|
103
|
+
all_tags = self._repository.list_all_tags()
|
|
104
|
+
all_video_tags = self._repository.list_all_video_tags()
|
|
105
|
+
selected = [tag for tag in dict.fromkeys(selected_tags or []) if tag]
|
|
106
|
+
tag_counts = {str(item["tag"]): int(item["count"]) for item in all_tags}
|
|
107
|
+
video_tag_map: dict[str, list[str]] = {}
|
|
108
|
+
for item in all_video_tags:
|
|
109
|
+
video_tag_map.setdefault(item.video_id, []).append(item.tag)
|
|
110
|
+
|
|
111
|
+
tag_video_map: dict[str, set[str]] = {}
|
|
112
|
+
cooccurrence: Counter[tuple[str, str]] = Counter()
|
|
113
|
+
for video_id, tags in video_tag_map.items():
|
|
114
|
+
unique_tags = sorted({tag for tag in tags if tag})
|
|
115
|
+
for tag in unique_tags:
|
|
116
|
+
tag_video_map.setdefault(tag, set()).add(video_id)
|
|
117
|
+
for left, right in combinations(unique_tags, 2):
|
|
118
|
+
cooccurrence[(left, right)] += 1
|
|
119
|
+
|
|
120
|
+
tag_degree: Counter[str] = Counter()
|
|
121
|
+
for (left, right), weight in cooccurrence.items():
|
|
122
|
+
tag_degree[left] += weight
|
|
123
|
+
tag_degree[right] += weight
|
|
124
|
+
|
|
125
|
+
def tag_rank(tag: str) -> tuple[int, int, str]:
|
|
126
|
+
return (tag_degree.get(tag, 0), tag_counts.get(tag, 0), tag)
|
|
127
|
+
|
|
128
|
+
if selected:
|
|
129
|
+
existing_selected = [tag for tag in selected if tag in tag_counts]
|
|
130
|
+
related_scores: Counter[str] = Counter()
|
|
131
|
+
for current_tag in existing_selected:
|
|
132
|
+
for (left, right), weight in cooccurrence.items():
|
|
133
|
+
if left == current_tag and right not in existing_selected:
|
|
134
|
+
related_scores[right] += weight
|
|
135
|
+
elif right == current_tag and left not in existing_selected:
|
|
136
|
+
related_scores[left] += weight
|
|
137
|
+
related_tags = [
|
|
138
|
+
tag
|
|
139
|
+
for tag, _score in sorted(
|
|
140
|
+
related_scores.items(),
|
|
141
|
+
key=lambda item: (item[1], tag_degree.get(item[0], 0), tag_counts.get(item[0], 0), item[0]),
|
|
142
|
+
reverse=True,
|
|
143
|
+
)
|
|
144
|
+
]
|
|
145
|
+
visible_tags = existing_selected + [
|
|
146
|
+
tag for tag in related_tags if tag not in existing_selected
|
|
147
|
+
][: max(0, max_tags - len(existing_selected))]
|
|
148
|
+
candidate_videos = []
|
|
149
|
+
for video_id, tags in video_tag_map.items():
|
|
150
|
+
overlap = len(set(tags) & set(existing_selected))
|
|
151
|
+
if overlap <= 0:
|
|
152
|
+
continue
|
|
153
|
+
candidate_videos.append((video_id, overlap, len(tags)))
|
|
154
|
+
candidate_videos.sort(key=lambda item: (item[1], item[2], item[0]), reverse=True)
|
|
155
|
+
visible_video_ids = [video_id for video_id, _overlap, _tag_total in candidate_videos[:max_videos]]
|
|
156
|
+
mode = "focus"
|
|
157
|
+
else:
|
|
158
|
+
ranked_tags = sorted(tag_counts, key=tag_rank, reverse=True)
|
|
159
|
+
visible_tags = ranked_tags[:max_tags]
|
|
160
|
+
visible_video_ids: list[str] = []
|
|
161
|
+
mode = "overview"
|
|
162
|
+
|
|
163
|
+
visible_tag_set = set(visible_tags)
|
|
164
|
+
nodes: list[KnowledgeNetworkNode] = []
|
|
165
|
+
for tag in visible_tags:
|
|
166
|
+
nodes.append(
|
|
167
|
+
KnowledgeNetworkNode(
|
|
168
|
+
id=f"tag_{tag}",
|
|
169
|
+
label=tag,
|
|
170
|
+
type="tag",
|
|
171
|
+
count=tag_counts.get(tag, 0),
|
|
172
|
+
degree=tag_degree.get(tag, 0),
|
|
173
|
+
focus=tag in selected,
|
|
174
|
+
video_count=len(tag_video_map.get(tag, set())),
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
links: list[KnowledgeNetworkLink] = []
|
|
179
|
+
if selected:
|
|
180
|
+
for video_id in visible_video_ids:
|
|
181
|
+
video = self._repository.get_video_asset(video_id)
|
|
182
|
+
if video is None:
|
|
183
|
+
continue
|
|
184
|
+
video_tags = sorted(tag for tag in set(video_tag_map.get(video_id, [])) if tag in visible_tag_set)
|
|
185
|
+
if not video_tags:
|
|
186
|
+
continue
|
|
187
|
+
nodes.append(
|
|
188
|
+
KnowledgeNetworkNode(
|
|
189
|
+
id=f"video_{video.video_id}",
|
|
190
|
+
label=video.title,
|
|
191
|
+
type="video",
|
|
192
|
+
tags=video_tags,
|
|
193
|
+
video_count=1,
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
for tag in video_tags[:3]:
|
|
197
|
+
links.append(
|
|
198
|
+
KnowledgeNetworkLink(
|
|
199
|
+
source=f"tag_{tag}",
|
|
200
|
+
target=f"video_{video.video_id}",
|
|
201
|
+
weight=1.0,
|
|
202
|
+
kind="association",
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
for (left, right), weight in cooccurrence.most_common():
|
|
206
|
+
if left in visible_tag_set and right in visible_tag_set:
|
|
207
|
+
links.append(
|
|
208
|
+
KnowledgeNetworkLink(
|
|
209
|
+
source=f"tag_{left}",
|
|
210
|
+
target=f"tag_{right}",
|
|
211
|
+
weight=float(weight),
|
|
212
|
+
kind="cooccurrence",
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
if len([item for item in links if item.kind == "cooccurrence"]) >= max(8, len(visible_tags) * 2):
|
|
216
|
+
break
|
|
217
|
+
else:
|
|
218
|
+
fallback_links: list[KnowledgeNetworkLink] = []
|
|
219
|
+
for (left, right), weight in cooccurrence.most_common():
|
|
220
|
+
if left in visible_tag_set and right in visible_tag_set:
|
|
221
|
+
candidate = KnowledgeNetworkLink(
|
|
222
|
+
source=f"tag_{left}",
|
|
223
|
+
target=f"tag_{right}",
|
|
224
|
+
weight=float(weight),
|
|
225
|
+
kind="cooccurrence",
|
|
226
|
+
)
|
|
227
|
+
if weight >= 2:
|
|
228
|
+
links.append(candidate)
|
|
229
|
+
else:
|
|
230
|
+
fallback_links.append(candidate)
|
|
231
|
+
if len(links) >= max(10, len(visible_tags) * 2):
|
|
232
|
+
break
|
|
233
|
+
if not links:
|
|
234
|
+
links.extend(fallback_links[: max(1, min(6, len(fallback_links)))])
|
|
235
|
+
|
|
236
|
+
return KnowledgeNetworkResponse(
|
|
237
|
+
nodes=nodes,
|
|
238
|
+
links=links,
|
|
239
|
+
mode=mode,
|
|
240
|
+
hidden_tag_count=max(0, len(tag_counts) - len(visible_tags)),
|
|
241
|
+
selected_tags=selected,
|
|
242
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from uvicorn import run as uvicorn_run
|
|
4
|
+
|
|
5
|
+
from video_sum_infra.logging import configure_logging
|
|
6
|
+
from video_sum_infra.paths import ensure_directory
|
|
7
|
+
from video_sum_infra.runtime import activate_runtime_pythonpath, bootstrap_managed_runtime, prepend_runtime_path
|
|
8
|
+
from video_sum_service.app import app, settings_manager
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("video_sum_service.main")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run() -> None:
|
|
14
|
+
settings = settings_manager.current
|
|
15
|
+
bootstrap_managed_runtime(settings.runtime_channel)
|
|
16
|
+
prepend_runtime_path(settings.runtime_channel)
|
|
17
|
+
activate_runtime_pythonpath(settings.runtime_channel)
|
|
18
|
+
configure_logging()
|
|
19
|
+
logger.info(
|
|
20
|
+
"starting service host=%s port=%s data_dir=%s cache_dir=%s tasks_dir=%s runtime_channel=%s",
|
|
21
|
+
settings.host,
|
|
22
|
+
settings.port,
|
|
23
|
+
settings.data_dir,
|
|
24
|
+
settings.cache_dir,
|
|
25
|
+
settings.tasks_dir,
|
|
26
|
+
settings.runtime_channel,
|
|
27
|
+
)
|
|
28
|
+
ensure_directory(settings.data_dir)
|
|
29
|
+
ensure_directory(settings.cache_dir)
|
|
30
|
+
ensure_directory(settings.tasks_dir)
|
|
31
|
+
uvicorn_run(app, host=settings.host, port=settings.port, access_log=False)
|