GitHub Actions
commited on
Commit
ยท
d2a4597
1
Parent(s):
9ed5c17
Auto-deploy from GitHub Actions - 2025-12-12 09:16:33
Browse files- app/routes.py +66 -46
- templates/admin_files.html +121 -0
app/routes.py
CHANGED
|
@@ -4946,7 +4946,7 @@ def process_graph(file_id):
|
|
| 4946 |
@main_bp.route('/api/files/<int:file_id>/process/tags', methods=['POST'])
|
| 4947 |
@login_required
|
| 4948 |
def generate_tags(file_id):
|
| 4949 |
-
"""ํ๊ทธ ์์ฑ (Parent Chunk, ํ์ฐจ ๋ถ์, GraphRAG ํ์ฉ)"""
|
| 4950 |
try:
|
| 4951 |
file = UploadedFile.query.get_or_404(file_id)
|
| 4952 |
|
|
@@ -4964,47 +4964,79 @@ def generate_tags(file_id):
|
|
| 4964 |
parent_chunk_text = ""
|
| 4965 |
if parent_chunk:
|
| 4966 |
parent_chunk_text = f"""
|
| 4967 |
-
[
|
| 4968 |
์ธ๊ณ๊ด: {parent_chunk.world_view or '์์'}
|
| 4969 |
์ฃผ์ ์บ๋ฆญํฐ: {parent_chunk.characters or '์์'}
|
| 4970 |
์ฃผ์ ์คํ ๋ฆฌ: {parent_chunk.story or '์์'}
|
|
|
|
|
|
|
| 4971 |
"""
|
| 4972 |
|
| 4973 |
# 2. ํ์ฐจ ๋ถ์ (์์ฝ)
|
| 4974 |
-
analyses = EpisodeAnalysis.query.filter_by(file_id=file_id).order_by(EpisodeAnalysis.id).limit(
|
| 4975 |
analysis_text = ""
|
| 4976 |
if analyses:
|
| 4977 |
-
analysis_text = "[
|
| 4978 |
for analysis in analyses:
|
| 4979 |
# ๋ด์ฉ์ด ๋๋ฌด ๊ธธ๋ฉด ์๋ถ๋ถ๋ง ์ฌ์ฉ
|
| 4980 |
-
content_preview = analysis.analysis_content[:
|
| 4981 |
analysis_text += f"- {analysis.episode_title}: {content_preview}\n"
|
| 4982 |
|
| 4983 |
-
# 3. GraphRAG (์ฃผ์ ์ธ๋ฌผ,
|
| 4984 |
-
entities = GraphEntity.query.filter_by(file_id=file_id).limit(
|
| 4985 |
-
|
| 4986 |
-
|
| 4987 |
-
|
| 4988 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4989 |
|
| 4990 |
# ๋ฐ์ดํฐ๊ฐ ๋๋ฌด ์์ผ๋ฉด ํ๊ทธ ์์ฑ ๋ถ๊ฐ
|
| 4991 |
-
if not parent_chunk_text and not analysis_text and not
|
| 4992 |
return jsonify({'error': 'ํ๊ทธ๋ฅผ ์์ฑํ ์ถฉ๋ถํ ๋ฐ์ดํฐ(Parent Chunk, ํ์ฐจ ๋ถ์ ๋ฑ)๊ฐ ์์ต๋๋ค.'}), 400
|
| 4993 |
|
| 4994 |
# ํ๋กฌํํธ ๊ตฌ์ฑ
|
| 4995 |
prompt = f"""
|
| 4996 |
-
๋ค์์ ์น์์ค์ ๋ถ์ ๋ฐ์ดํฐ์
๋๋ค. ์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์ผ๋ก
|
| 4997 |
|
| 4998 |
{parent_chunk_text}
|
| 4999 |
|
| 5000 |
{analysis_text}
|
| 5001 |
|
| 5002 |
-
{
|
| 5003 |
|
| 5004 |
-
|
| 5005 |
-
|
| 5006 |
-
|
| 5007 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5008 |
"""
|
| 5009 |
|
| 5010 |
# AI ํธ์ถ
|
|
@@ -5024,8 +5056,8 @@ def generate_tags(file_id):
|
|
| 5024 |
result = gemini_client.generate_response(
|
| 5025 |
prompt=prompt,
|
| 5026 |
model_name=gemini_model_name,
|
| 5027 |
-
temperature=0.
|
| 5028 |
-
max_output_tokens=
|
| 5029 |
)
|
| 5030 |
if not result['error'] and result.get('response'):
|
| 5031 |
response_text = result['response'].strip()
|
|
@@ -5035,15 +5067,7 @@ def generate_tags(file_id):
|
|
| 5035 |
# Ollama ๋ชจ๋ธ์ธ ๊ฒฝ์ฐ (๋๋ Gemini ์คํจ ์)
|
| 5036 |
if not response_text:
|
| 5037 |
try:
|
| 5038 |
-
|
| 5039 |
-
if ollama_model_name.lower().startswith('gemini:'): # Gemini ๋ชจ๋ธ๋ช
์ด์ง๋ง Ollama๋ก ์๋ํ๋ ๊ฒฝ์ฐ (์ค์ ์ค๋ฅ ๋ฑ)
|
| 5040 |
-
# ๊ธฐ๋ณธ Ollama ๋ชจ๋ธ ์ฌ์ฉํ๊ฑฐ๋ ์๋ฌ ์ฒ๋ฆฌ
|
| 5041 |
-
pass
|
| 5042 |
-
|
| 5043 |
-
# Ollama ํธ์ถ ๋ก์ง (generate_response_with_ollama ์ฌ์ฉ)
|
| 5044 |
-
# ์ฌ๊ธฐ์๋ ์ง์ ํธ์ถ ๋์ ๊ธฐ์กด ํจ์ ํ์ฉ
|
| 5045 |
-
# app/routes.py์ ollama ๊ด๋ จ ํจ์๊ฐ ์๋์ง ํ์ธ ํ์ํ์ง๋ง,
|
| 5046 |
-
# ๊ฐ๋จํ๊ฒ requests๋ก ํธ์ถ ๊ตฌํ
|
| 5047 |
import requests
|
| 5048 |
from app.core.config import get_config
|
| 5049 |
config = get_config()
|
|
@@ -5054,9 +5078,10 @@ def generate_tags(file_id):
|
|
| 5054 |
'model': file.model_name,
|
| 5055 |
'prompt': prompt,
|
| 5056 |
'stream': False,
|
| 5057 |
-
'options': {'temperature': 0.
|
|
|
|
| 5058 |
},
|
| 5059 |
-
timeout=
|
| 5060 |
)
|
| 5061 |
|
| 5062 |
if ollama_response.status_code == 200:
|
|
@@ -5072,33 +5097,28 @@ def generate_tags(file_id):
|
|
| 5072 |
import json
|
| 5073 |
import re
|
| 5074 |
|
| 5075 |
-
|
| 5076 |
try:
|
| 5077 |
# JSON ์ถ์ถ ์๋
|
| 5078 |
-
json_match = re.search(r'\
|
| 5079 |
if json_match:
|
| 5080 |
-
|
|
|
|
| 5081 |
else:
|
| 5082 |
-
# JSON
|
| 5083 |
-
|
|
|
|
| 5084 |
except Exception as e:
|
| 5085 |
print(f"[ํ๊ทธ ์์ฑ] ํ์ฑ ์ค๋ฅ: {str(e)}")
|
| 5086 |
-
|
| 5087 |
-
tags = [t.strip() for t in response_text.split(',')]
|
| 5088 |
-
|
| 5089 |
-
# ๋น ํ๊ทธ ์ ๊ฑฐ ๋ฐ ๋ฌธ์์ด๋ก ๋ณํํ์ฌ ์ ์ฅ
|
| 5090 |
-
tags = [t for t in tags if t]
|
| 5091 |
-
|
| 5092 |
-
if not tags:
|
| 5093 |
-
return jsonify({'error': 'ํ๊ทธ๋ฅผ ์ถ์ถํ์ง ๋ชปํ์ต๋๋ค.'}), 500
|
| 5094 |
|
| 5095 |
# DB ์ ์ฅ (JSON ๋ฌธ์์ด๋ก ์ ์ฅ)
|
| 5096 |
-
file.tags = json.dumps(
|
| 5097 |
db.session.commit()
|
| 5098 |
|
| 5099 |
return jsonify({
|
| 5100 |
'message': 'ํ๊ทธ๊ฐ ์์ฑ๋์์ต๋๋ค.',
|
| 5101 |
-
'tags':
|
| 5102 |
'file_id': file.id
|
| 5103 |
}), 200
|
| 5104 |
|
|
|
|
| 4946 |
@main_bp.route('/api/files/<int:file_id>/process/tags', methods=['POST'])
|
| 4947 |
@login_required
|
| 4948 |
def generate_tags(file_id):
|
| 4949 |
+
"""ํ๊ทธ ์์ฑ (Parent Chunk, ํ์ฐจ ๋ถ์, GraphRAG ํ์ฉ) - ๊ณ์ธต์ ๊ตฌ์กฐ"""
|
| 4950 |
try:
|
| 4951 |
file = UploadedFile.query.get_or_404(file_id)
|
| 4952 |
|
|
|
|
| 4964 |
parent_chunk_text = ""
|
| 4965 |
if parent_chunk:
|
| 4966 |
parent_chunk_text = f"""
|
| 4967 |
+
[Parent Chunk]
|
| 4968 |
์ธ๊ณ๊ด: {parent_chunk.world_view or '์์'}
|
| 4969 |
์ฃผ์ ์บ๋ฆญํฐ: {parent_chunk.characters or '์์'}
|
| 4970 |
์ฃผ์ ์คํ ๋ฆฌ: {parent_chunk.story or '์์'}
|
| 4971 |
+
์ํผ์๋ ์์ฝ: {parent_chunk.episodes or '์์'}
|
| 4972 |
+
๊ธฐํ: {parent_chunk.others or '์์'}
|
| 4973 |
"""
|
| 4974 |
|
| 4975 |
# 2. ํ์ฐจ ๋ถ์ (์์ฝ)
|
| 4976 |
+
analyses = EpisodeAnalysis.query.filter_by(file_id=file_id).order_by(EpisodeAnalysis.id).limit(20).all()
|
| 4977 |
analysis_text = ""
|
| 4978 |
if analyses:
|
| 4979 |
+
analysis_text = "[ํ์ฐจ๋ณ ๋ถ์ (์์ฝ)]\n"
|
| 4980 |
for analysis in analyses:
|
| 4981 |
# ๋ด์ฉ์ด ๋๋ฌด ๊ธธ๋ฉด ์๋ถ๋ถ๋ง ์ฌ์ฉ
|
| 4982 |
+
content_preview = analysis.analysis_content[:300] + "..." if len(analysis.analysis_content) > 300 else analysis.analysis_content
|
| 4983 |
analysis_text += f"- {analysis.episode_title}: {content_preview}\n"
|
| 4984 |
|
| 4985 |
+
# 3. GraphRAG (์ฃผ์ ์ธ๋ฌผ, ๊ด๊ณ, ์ฌ๊ฑด)
|
| 4986 |
+
entities = GraphEntity.query.filter_by(file_id=file_id).limit(30).all()
|
| 4987 |
+
relationships = GraphRelationship.query.filter_by(file_id=file_id).limit(30).all()
|
| 4988 |
+
events = GraphEvent.query.filter_by(file_id=file_id).limit(30).all()
|
| 4989 |
+
|
| 4990 |
+
graph_text = ""
|
| 4991 |
+
if entities or relationships or events:
|
| 4992 |
+
graph_text = "[GraphRAG ๋ฐ์ดํฐ]\n"
|
| 4993 |
+
if entities:
|
| 4994 |
+
entity_names = [f"{e.entity_name}({e.entity_type})" for e in entities]
|
| 4995 |
+
graph_text += f"์ํฐํฐ: {', '.join(entity_names)}\n"
|
| 4996 |
+
if relationships:
|
| 4997 |
+
rel_strs = [f"{r.source}->{r.target}({r.relationship_type})" for r in relationships]
|
| 4998 |
+
graph_text += f"๊ด๊ณ: {', '.join(rel_strs)}\n"
|
| 4999 |
+
if events:
|
| 5000 |
+
event_names = [e.event_name for e in events]
|
| 5001 |
+
graph_text += f"์ฌ๊ฑด: {', '.join(event_names)}\n"
|
| 5002 |
|
| 5003 |
# ๋ฐ์ดํฐ๊ฐ ๋๋ฌด ์์ผ๋ฉด ํ๊ทธ ์์ฑ ๋ถ๊ฐ
|
| 5004 |
+
if not parent_chunk_text and not analysis_text and not graph_text:
|
| 5005 |
return jsonify({'error': 'ํ๊ทธ๋ฅผ ์์ฑํ ์ถฉ๋ถํ ๋ฐ์ดํฐ(Parent Chunk, ํ์ฐจ ๋ถ์ ๋ฑ)๊ฐ ์์ต๋๋ค.'}), 400
|
| 5006 |
|
| 5007 |
# ํ๋กฌํํธ ๊ตฌ์ฑ
|
| 5008 |
prompt = f"""
|
| 5009 |
+
๋ค์์ ์น์์ค์ ๋ถ์ ๋ฐ์ดํฐ์
๋๋ค. ์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์ผ๋ก ๊ฐ ์น์
๋ณ๋ก ํต์ฌ ํ๊ทธ๋ฅผ ์ถ์ถํด์ฃผ์ธ์.
|
| 5010 |
|
| 5011 |
{parent_chunk_text}
|
| 5012 |
|
| 5013 |
{analysis_text}
|
| 5014 |
|
| 5015 |
+
{graph_text}
|
| 5016 |
|
| 5017 |
+
[์๊ตฌ์ฌํญ]
|
| 5018 |
+
๋ค์ JSON ํ์์ผ๋ก ํ๊ทธ๋ฅผ ์์ฑํ์ธ์. ๊ฐ ํญ๋ชฉ๋น 5~10๊ฐ์ ํต์ฌ ํค์๋๋ฅผ ์ถ์ถํ์ธ์. ์๋ ํญ๋ชฉ์ ๋น ๋ฐฐ์ด๋ก ๋์ธ์.
|
| 5019 |
+
|
| 5020 |
+
{{
|
| 5021 |
+
"parent_chunk": {{
|
| 5022 |
+
"world_view": ["ํ๊ทธ1", "ํ๊ทธ2"],
|
| 5023 |
+
"characters": ["ํ๊ทธ1", "ํ๊ทธ2"],
|
| 5024 |
+
"story": ["ํ๊ทธ1", "ํ๊ทธ2"],
|
| 5025 |
+
"others": ["ํ๊ทธ1", "ํ๊ทธ2"]
|
| 5026 |
+
}},
|
| 5027 |
+
"episodes": {{
|
| 5028 |
+
"world_view": ["ํ๊ทธ1", "ํ๊ทธ2"],
|
| 5029 |
+
"characters": ["ํ๊ทธ1", "ํ๊ทธ2"],
|
| 5030 |
+
"items": ["ํ๊ทธ1", "ํ๊ทธ2"]
|
| 5031 |
+
}},
|
| 5032 |
+
"graph_rag": {{
|
| 5033 |
+
"characters": ["ํ๊ทธ1", "ํ๊ทธ2"],
|
| 5034 |
+
"relationships": ["ํ๊ทธ1", "ํ๊ทธ2"],
|
| 5035 |
+
"events": ["ํ๊ทธ1", "ํ๊ทธ2"]
|
| 5036 |
+
}}
|
| 5037 |
+
}}
|
| 5038 |
+
|
| 5039 |
+
๋ฐ๋์ JSON ํ์์ผ๋ก๋ง ์๋ตํ์ธ์. ๋ค๋ฅธ ์ค๋ช
์ ํฌํจํ์ง ๋ง์ธ์. JSON ๊ตฌ๋ฌธ์ ์ ํํ ์ง์ผ์ฃผ์ธ์.
|
| 5040 |
"""
|
| 5041 |
|
| 5042 |
# AI ํธ์ถ
|
|
|
|
| 5056 |
result = gemini_client.generate_response(
|
| 5057 |
prompt=prompt,
|
| 5058 |
model_name=gemini_model_name,
|
| 5059 |
+
temperature=0.5,
|
| 5060 |
+
max_output_tokens=2048
|
| 5061 |
)
|
| 5062 |
if not result['error'] and result.get('response'):
|
| 5063 |
response_text = result['response'].strip()
|
|
|
|
| 5067 |
# Ollama ๋ชจ๋ธ์ธ ๊ฒฝ์ฐ (๋๋ Gemini ์คํจ ์)
|
| 5068 |
if not response_text:
|
| 5069 |
try:
|
| 5070 |
+
# Ollama ํธ์ถ ๋ก์ง
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5071 |
import requests
|
| 5072 |
from app.core.config import get_config
|
| 5073 |
config = get_config()
|
|
|
|
| 5078 |
'model': file.model_name,
|
| 5079 |
'prompt': prompt,
|
| 5080 |
'stream': False,
|
| 5081 |
+
'options': {'temperature': 0.5},
|
| 5082 |
+
'format': 'json' # Ollama JSON ๋ชจ๋ ํ์ฑํ
|
| 5083 |
},
|
| 5084 |
+
timeout=180
|
| 5085 |
)
|
| 5086 |
|
| 5087 |
if ollama_response.status_code == 200:
|
|
|
|
| 5097 |
import json
|
| 5098 |
import re
|
| 5099 |
|
| 5100 |
+
tags_data = {}
|
| 5101 |
try:
|
| 5102 |
# JSON ์ถ์ถ ์๋
|
| 5103 |
+
json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
| 5104 |
if json_match:
|
| 5105 |
+
json_str = json_match.group(0)
|
| 5106 |
+
tags_data = json.loads(json_str)
|
| 5107 |
else:
|
| 5108 |
+
# JSON ํ์ฑ ์คํจ ์
|
| 5109 |
+
print(f"[ํ๊ทธ ์์ฑ] JSON ํ์์ด ์๋: {response_text[:100]}...")
|
| 5110 |
+
return jsonify({'error': 'AI ์๋ต์ด ์ฌ๋ฐ๋ฅธ JSON ํ์์ด ์๋๋๋ค.'}), 500
|
| 5111 |
except Exception as e:
|
| 5112 |
print(f"[ํ๊ทธ ์์ฑ] ํ์ฑ ์ค๋ฅ: {str(e)}")
|
| 5113 |
+
return jsonify({'error': f'ํ๊ทธ ํ์ฑ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}'}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5114 |
|
| 5115 |
# DB ์ ์ฅ (JSON ๋ฌธ์์ด๋ก ์ ์ฅ)
|
| 5116 |
+
file.tags = json.dumps(tags_data, ensure_ascii=False)
|
| 5117 |
db.session.commit()
|
| 5118 |
|
| 5119 |
return jsonify({
|
| 5120 |
'message': 'ํ๊ทธ๊ฐ ์์ฑ๋์์ต๋๋ค.',
|
| 5121 |
+
'tags': tags_data,
|
| 5122 |
'file_id': file.id
|
| 5123 |
}), 200
|
| 5124 |
|
templates/admin_files.html
CHANGED
|
@@ -674,6 +674,19 @@
|
|
| 674 |
</div>
|
| 675 |
</div>
|
| 676 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
<!-- vis-network ๋ผ์ด๋ธ๋ฌ๋ฆฌ -->
|
| 678 |
<script type="text/javascript" src="https://unpkg.com/vis-network@latest/standalone/umd/vis-network.min.js"></script>
|
| 679 |
|
|
@@ -798,6 +811,7 @@
|
|
| 798 |
<div class="file-actions">
|
| 799 |
<button class="btn btn-secondary" onclick="viewSummary(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">์์ฝ ๋ด์ฉ ๋ณด๊ธฐ</button>
|
| 800 |
<button class="btn btn-info" onclick="viewChunks(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">์ฒญํฌ ๋ณด๊ธฐ</button>
|
|
|
|
| 801 |
<button class="btn btn-primary" onclick="viewGraphRAG(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">GraphRAG ๋ณด๊ธฐ</button>
|
| 802 |
<button class="btn btn-success" onclick="viewGraphRAGVisualization(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px;">๊ทธ๋ํ ์๊ฐํ</button>
|
| 803 |
</div>
|
|
@@ -1835,6 +1849,113 @@
|
|
| 1835 |
}
|
| 1836 |
});
|
| 1837 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1838 |
// ํ์ด์ง ๋ก๋ ์ ์ด๊ธฐํ
|
| 1839 |
window.addEventListener('load', () => {
|
| 1840 |
loadModelFilter();
|
|
|
|
| 674 |
</div>
|
| 675 |
</div>
|
| 676 |
|
| 677 |
+
<!-- ํ๊ทธ ๋ณด๊ธฐ ๋ชจ๋ฌ -->
|
| 678 |
+
<div id="tagsModal" class="modal">
|
| 679 |
+
<div class="modal-content">
|
| 680 |
+
<div class="modal-header">
|
| 681 |
+
<div class="modal-title" id="tagsModalTitle">ํ๊ทธ ๋ชฉ๋ก</div>
|
| 682 |
+
<button class="modal-close" onclick="closeTagsModal()">×</button>
|
| 683 |
+
</div>
|
| 684 |
+
<div id="tagsContent" class="modal-body">
|
| 685 |
+
ํ๊ทธ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค...
|
| 686 |
+
</div>
|
| 687 |
+
</div>
|
| 688 |
+
</div>
|
| 689 |
+
|
| 690 |
<!-- vis-network ๋ผ์ด๋ธ๋ฌ๋ฆฌ -->
|
| 691 |
<script type="text/javascript" src="https://unpkg.com/vis-network@latest/standalone/umd/vis-network.min.js"></script>
|
| 692 |
|
|
|
|
| 811 |
<div class="file-actions">
|
| 812 |
<button class="btn btn-secondary" onclick="viewSummary(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">์์ฝ ๋ด์ฉ ๋ณด๊ธฐ</button>
|
| 813 |
<button class="btn btn-info" onclick="viewChunks(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">์ฒญํฌ ๋ณด๊ธฐ</button>
|
| 814 |
+
<button class="btn" onclick="viewTags(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px; margin-right: 4px; background: #673ab7; color: white;">ํ๊ทธ ๋ณด๊ธฐ</button>
|
| 815 |
<button class="btn btn-primary" onclick="viewGraphRAG(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">GraphRAG ๋ณด๊ธฐ</button>
|
| 816 |
<button class="btn btn-success" onclick="viewGraphRAGVisualization(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px;">๊ทธ๋ํ ์๊ฐํ</button>
|
| 817 |
</div>
|
|
|
|
| 1849 |
}
|
| 1850 |
});
|
| 1851 |
|
| 1852 |
+
async function viewTags(fileId, fileName) {
|
| 1853 |
+
const modal = document.getElementById('tagsModal');
|
| 1854 |
+
const title = document.getElementById('tagsModalTitle');
|
| 1855 |
+
const content = document.getElementById('tagsContent');
|
| 1856 |
+
|
| 1857 |
+
title.textContent = `ํ๊ทธ ๋ชฉ๋ก - ${fileName}`;
|
| 1858 |
+
content.innerHTML = '<div style="text-align: center; padding: 24px; color: #5f6368;">ํ๊ทธ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค...</div>';
|
| 1859 |
+
modal.classList.add('active');
|
| 1860 |
+
|
| 1861 |
+
try {
|
| 1862 |
+
// ํ์ผ ์ ๋ณด ๋ค์ ์กฐํ (ํ๊ทธ ์ ๋ณด ํฌํจ)
|
| 1863 |
+
const response = await fetch('/api/files', { credentials: 'include' });
|
| 1864 |
+
const data = await response.json();
|
| 1865 |
+
const file = data.files.find(f => f.id === fileId);
|
| 1866 |
+
|
| 1867 |
+
if (file && file.tags) {
|
| 1868 |
+
try {
|
| 1869 |
+
const tags = JSON.parse(file.tags);
|
| 1870 |
+
let html = '';
|
| 1871 |
+
|
| 1872 |
+
// ๊ณ์ธต์ ๊ตฌ์กฐ ๋ ๋๋ง
|
| 1873 |
+
|
| 1874 |
+
// Parent Chunk
|
| 1875 |
+
if (tags.parent_chunk) {
|
| 1876 |
+
html += '<div style="margin-bottom: 24px;">';
|
| 1877 |
+
html += '<h3 style="font-size: 15px; font-weight: 700; color: #1a73e8; margin-bottom: 12px; border-bottom: 2px solid #e8f0fe; padding-bottom: 8px;">Parent Chunk (์ ์ฒด ์์ฝ)</h3>';
|
| 1878 |
+
html += renderTagsGroup(tags.parent_chunk);
|
| 1879 |
+
html += '</div>';
|
| 1880 |
+
}
|
| 1881 |
+
|
| 1882 |
+
// Episodes
|
| 1883 |
+
if (tags.episodes) {
|
| 1884 |
+
html += '<div style="margin-bottom: 24px;">';
|
| 1885 |
+
html += '<h3 style="font-size: 15px; font-weight: 700; color: #1a73e8; margin-bottom: 12px; border-bottom: 2px solid #e8f0fe; padding-bottom: 8px;">ํ์ฐจ๋ณ ๋ถ์</h3>';
|
| 1886 |
+
html += renderTagsGroup(tags.episodes);
|
| 1887 |
+
html += '</div>';
|
| 1888 |
+
}
|
| 1889 |
+
|
| 1890 |
+
// GraphRAG
|
| 1891 |
+
if (tags.graph_rag) {
|
| 1892 |
+
html += '<div style="margin-bottom: 24px;">';
|
| 1893 |
+
html += '<h3 style="font-size: 15px; font-weight: 700; color: #1a73e8; margin-bottom: 12px; border-bottom: 2px solid #e8f0fe; padding-bottom: 8px;">GraphRAG (์ง์ ๊ทธ๋ํ)</h3>';
|
| 1894 |
+
html += renderTagsGroup(tags.graph_rag);
|
| 1895 |
+
html += '</div>';
|
| 1896 |
+
}
|
| 1897 |
+
|
| 1898 |
+
// ๊ตฌ๋ฒ์ ํ๊ทธ (๋ฐฐ์ด) ํธํ์ฑ - ๊ณ์ธต์ ๊ตฌ์กฐ๊ฐ ์๋ ๊ฒฝ์ฐ
|
| 1899 |
+
if (!tags.parent_chunk && !tags.episodes && !tags.graph_rag && Array.isArray(tags)) {
|
| 1900 |
+
html += '<div style="display: flex; flex-wrap: wrap; gap: 8px;">';
|
| 1901 |
+
tags.forEach(tag => {
|
| 1902 |
+
html += `<span style="background: #e8f0fe; color: #1a73e8; padding: 6px 12px; border-radius: 16px; font-size: 13px; border: 1px solid #d2e3fc;">#${escapeHtml(tag)}</span>`;
|
| 1903 |
+
});
|
| 1904 |
+
html += '</div>';
|
| 1905 |
+
}
|
| 1906 |
+
|
| 1907 |
+
if (!html) {
|
| 1908 |
+
html = '<div style="text-align: center; padding: 20px; color: #5f6368;">ํ๊ทธ ํ์์ด ์ฌ๋ฐ๋ฅด์ง ์๊ฑฐ๋ ๋น์ด์์ต๋๋ค.</div>';
|
| 1909 |
+
}
|
| 1910 |
+
|
| 1911 |
+
content.innerHTML = html;
|
| 1912 |
+
} catch (e) {
|
| 1913 |
+
console.error('ํ๊ทธ ํ์ฑ ์ค๋ฅ:', e);
|
| 1914 |
+
content.innerHTML = '<div style="text-align: center; padding: 20px; color: #c5221f;">ํ๊ทธ ๋ฐ์ดํฐ๋ฅผ ํ์ฑํ ์ ์์ต๋๋ค.</div>';
|
| 1915 |
+
}
|
| 1916 |
+
} else {
|
| 1917 |
+
content.innerHTML = '<div style="text-align: center; padding: 20px; color: #5f6368;">์์ฑ๋ ํ๊ทธ๊ฐ ์์ต๋๋ค.</div>';
|
| 1918 |
+
}
|
| 1919 |
+
} catch (error) {
|
| 1920 |
+
console.error('ํ๊ทธ ๋ก๋ ์ค๋ฅ:', error);
|
| 1921 |
+
content.innerHTML = '<div style="text-align: center; padding: 20px; color: #c5221f;">ํ๊ทธ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.</div>';
|
| 1922 |
+
}
|
| 1923 |
+
}
|
| 1924 |
+
|
| 1925 |
+
function renderTagsGroup(group) {
|
| 1926 |
+
let html = '<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px;">';
|
| 1927 |
+
|
| 1928 |
+
// ํค ์ด๋ฆ์ ํ๊ธ๋ก ๋งคํ
|
| 1929 |
+
const keyMap = {
|
| 1930 |
+
'world_view': '์ธ๊ณ๊ด',
|
| 1931 |
+
'characters': '์ธ๋ฌผ',
|
| 1932 |
+
'story': '์คํ ๋ฆฌ',
|
| 1933 |
+
'others': '๊ธฐํ',
|
| 1934 |
+
'items': '์์ดํ
/์์ฌ',
|
| 1935 |
+
'relationships': '๊ด๊ณ',
|
| 1936 |
+
'events': '์ฌ๊ฑด'
|
| 1937 |
+
};
|
| 1938 |
+
|
| 1939 |
+
for (const [key, values] of Object.entries(group)) {
|
| 1940 |
+
if (values && values.length > 0) {
|
| 1941 |
+
const label = keyMap[key] || key;
|
| 1942 |
+
html += `<div style="background: #f8f9fa; padding: 12px; border-radius: 8px; border: 1px solid #e8eaed;">`;
|
| 1943 |
+
html += `<div style="font-size: 13px; font-weight: 700; color: #5f6368; margin-bottom: 8px;">${escapeHtml(label)}</div>`;
|
| 1944 |
+
html += `<div style="display: flex; flex-wrap: wrap; gap: 6px;">`;
|
| 1945 |
+
values.forEach(tag => {
|
| 1946 |
+
html += `<span style="background: white; border: 1px solid #dadce0; padding: 4px 8px; border-radius: 4px; font-size: 12px; color: #3c4043;">${escapeHtml(tag)}</span>`;
|
| 1947 |
+
});
|
| 1948 |
+
html += `</div></div>`;
|
| 1949 |
+
}
|
| 1950 |
+
}
|
| 1951 |
+
html += '</div>';
|
| 1952 |
+
return html;
|
| 1953 |
+
}
|
| 1954 |
+
|
| 1955 |
+
function closeTagsModal() {
|
| 1956 |
+
document.getElementById('tagsModal').classList.remove('active');
|
| 1957 |
+
}
|
| 1958 |
+
|
| 1959 |
// ํ์ด์ง ๋ก๋ ์ ์ด๊ธฐํ
|
| 1960 |
window.addEventListener('load', () => {
|
| 1961 |
loadModelFilter();
|