GitHub Actions
commited on
Commit
ยท
a587664
1
Parent(s):
9851df5
Auto-deploy from GitHub Actions - 2025-12-12 09:03:43
Browse files- app/__init__.py +5 -1
- app/database.py +2 -0
- app/routes.py +163 -0
- templates/admin_webnovels.html +60 -2
app/__init__.py
CHANGED
|
@@ -234,7 +234,7 @@ def migrate_database(app: Flask) -> None:
|
|
| 234 |
# Boolean ํ์
์ DB์ ๋ฐ๋ผ ๋ค๋ฅผ ์ ์์ผ๋ฏ๋ก ์ฃผ์ (PostgreSQL/SQLite ๋ชจ๋ BOOLEAN ์ง์)
|
| 235 |
conn.execute(text("ALTER TABLE \"user\" ADD COLUMN must_change_password BOOLEAN DEFAULT FALSE NOT NULL"))
|
| 236 |
|
| 237 |
-
# 2. uploaded_file ํ
์ด๋ธ - uploaded_by, parent_file_id ์ปฌ๋ผ
|
| 238 |
if inspector.has_table('uploaded_file'):
|
| 239 |
columns = [c['name'] for c in inspector.get_columns('uploaded_file')]
|
| 240 |
with db.engine.begin() as conn:
|
|
@@ -245,6 +245,10 @@ def migrate_database(app: Flask) -> None:
|
|
| 245 |
if 'parent_file_id' not in columns:
|
| 246 |
logger.info("uploaded_file ํ
์ด๋ธ์ parent_file_id ์ปฌ๋ผ ์ถ๊ฐ ์ค...")
|
| 247 |
conn.execute(text("ALTER TABLE uploaded_file ADD COLUMN parent_file_id INTEGER"))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
|
| 249 |
# 3. document_chunk ํ
์ด๋ธ - chunk_metadata ์ปฌ๋ผ
|
| 250 |
if inspector.has_table('document_chunk'):
|
|
|
|
| 234 |
# Boolean ํ์
์ DB์ ๋ฐ๋ผ ๋ค๋ฅผ ์ ์์ผ๋ฏ๋ก ์ฃผ์ (PostgreSQL/SQLite ๋ชจ๋ BOOLEAN ์ง์)
|
| 235 |
conn.execute(text("ALTER TABLE \"user\" ADD COLUMN must_change_password BOOLEAN DEFAULT FALSE NOT NULL"))
|
| 236 |
|
| 237 |
+
# 2. uploaded_file ํ
์ด๋ธ - uploaded_by, parent_file_id, tags ์ปฌ๋ผ
|
| 238 |
if inspector.has_table('uploaded_file'):
|
| 239 |
columns = [c['name'] for c in inspector.get_columns('uploaded_file')]
|
| 240 |
with db.engine.begin() as conn:
|
|
|
|
| 245 |
if 'parent_file_id' not in columns:
|
| 246 |
logger.info("uploaded_file ํ
์ด๋ธ์ parent_file_id ์ปฌ๋ผ ์ถ๊ฐ ์ค...")
|
| 247 |
conn.execute(text("ALTER TABLE uploaded_file ADD COLUMN parent_file_id INTEGER"))
|
| 248 |
+
|
| 249 |
+
if 'tags' not in columns:
|
| 250 |
+
logger.info("uploaded_file ํ
์ด๋ธ์ tags ์ปฌ๋ผ ์ถ๊ฐ ์ค...")
|
| 251 |
+
conn.execute(text("ALTER TABLE uploaded_file ADD COLUMN tags TEXT"))
|
| 252 |
|
| 253 |
# 3. document_chunk ํ
์ด๋ธ - chunk_metadata ์ปฌ๋ผ
|
| 254 |
if inspector.has_table('document_chunk'):
|
app/database.py
CHANGED
|
@@ -47,6 +47,7 @@ class UploadedFile(db.Model):
|
|
| 47 |
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
| 48 |
uploaded_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
|
| 49 |
parent_file_id = db.Column(db.Integer, db.ForeignKey('uploaded_file.id'), nullable=True) # ์ด์ด์ ์
๋ก๋ํ ๊ฒฝ์ฐ ์๋ณธ ํ์ผ ID
|
|
|
|
| 50 |
|
| 51 |
# ๊ด๊ณ
|
| 52 |
parent_file = db.relationship('UploadedFile', remote_side=[id], backref='child_files')
|
|
@@ -65,6 +66,7 @@ class UploadedFile(db.Model):
|
|
| 65 |
'uploaded_at': self.uploaded_at.isoformat() if self.uploaded_at else None,
|
| 66 |
'uploaded_by': self.uploaded_by,
|
| 67 |
'parent_file_id': self.parent_file_id,
|
|
|
|
| 68 |
'chunk_count': chunk_count,
|
| 69 |
'child_count': len(self.child_files) if self.child_files else 0
|
| 70 |
}
|
|
|
|
| 47 |
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
| 48 |
uploaded_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
|
| 49 |
parent_file_id = db.Column(db.Integer, db.ForeignKey('uploaded_file.id'), nullable=True) # ์ด์ด์ ์
๋ก๋ํ ๊ฒฝ์ฐ ์๋ณธ ํ์ผ ID
|
| 50 |
+
tags = db.Column(db.Text, nullable=True) # AI๊ฐ ์์ฑํ ํ๊ทธ (JSON ๋ฌธ์์ด ๋๋ ์ผํ ๊ตฌ๋ถ)
|
| 51 |
|
| 52 |
# ๊ด๊ณ
|
| 53 |
parent_file = db.relationship('UploadedFile', remote_side=[id], backref='child_files')
|
|
|
|
| 66 |
'uploaded_at': self.uploaded_at.isoformat() if self.uploaded_at else None,
|
| 67 |
'uploaded_by': self.uploaded_by,
|
| 68 |
'parent_file_id': self.parent_file_id,
|
| 69 |
+
'tags': self.tags,
|
| 70 |
'chunk_count': chunk_count,
|
| 71 |
'child_count': len(self.child_files) if self.child_files else 0
|
| 72 |
}
|
app/routes.py
CHANGED
|
@@ -4943,6 +4943,169 @@ def process_graph(file_id):
|
|
| 4943 |
except Exception as e:
|
| 4944 |
return jsonify({'error': f'Graph Extraction ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}', 'step': 'graph'}), 500
|
| 4945 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4946 |
@main_bp.route('/api/files/<int:file_id>/metadata', methods=['POST'])
|
| 4947 |
@login_required
|
| 4948 |
def create_file_metadata(file_id):
|
|
|
|
| 4943 |
except Exception as e:
|
| 4944 |
return jsonify({'error': f'Graph Extraction ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}', 'step': 'graph'}), 500
|
| 4945 |
|
| 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 |
+
|
| 4953 |
+
# ๊ถํ ํ์ธ
|
| 4954 |
+
if not current_user.is_admin and file.uploaded_by != current_user.id:
|
| 4955 |
+
return jsonify({'error': '๊ถํ์ด ์์ต๋๋ค.'}), 403
|
| 4956 |
+
|
| 4957 |
+
# ๋ชจ๋ธ ํ์ธ
|
| 4958 |
+
if not file.model_name:
|
| 4959 |
+
return jsonify({'error': 'ํ์ผ์ ์ฐ๊ฒฐ๋ AI ๋ชจ๋ธ์ด ์์ต๋๋ค.'}), 400
|
| 4960 |
+
|
| 4961 |
+
# ๋ฐ์ดํฐ ์์ง
|
| 4962 |
+
# 1. Parent Chunk
|
| 4963 |
+
parent_chunk = ParentChunk.query.filter_by(file_id=file_id).first()
|
| 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(10).all()
|
| 4975 |
+
analysis_text = ""
|
| 4976 |
+
if analyses:
|
| 4977 |
+
analysis_text = "[์ฃผ์ ํ์ฐจ ๋ถ์ ๋ด์ฉ]\n"
|
| 4978 |
+
for analysis in analyses:
|
| 4979 |
+
# ๋ด์ฉ์ด ๋๋ฌด ๊ธธ๋ฉด ์๋ถ๋ถ๋ง ์ฌ์ฉ
|
| 4980 |
+
content_preview = analysis.analysis_content[:500] + "..." if len(analysis.analysis_content) > 500 else 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(20).all()
|
| 4985 |
+
entity_text = ""
|
| 4986 |
+
if entities:
|
| 4987 |
+
entity_names = [e.entity_name for e in entities]
|
| 4988 |
+
entity_text = f"[์ฃผ์ ๋ฑ์ฅ ์์]\n{', '.join(entity_names)}"
|
| 4989 |
+
|
| 4990 |
+
# ๋ฐ์ดํฐ๊ฐ ๋๋ฌด ์์ผ๋ฉด ํ๊ทธ ์์ฑ ๋ถ๊ฐ
|
| 4991 |
+
if not parent_chunk_text and not analysis_text and not entity_text:
|
| 4992 |
+
return jsonify({'error': 'ํ๊ทธ๋ฅผ ์์ฑํ ์ถฉ๋ถํ ๋ฐ์ดํฐ(Parent Chunk, ํ์ฐจ ๋ถ์ ๋ฑ)๊ฐ ์์ต๋๋ค.'}), 400
|
| 4993 |
+
|
| 4994 |
+
# ํ๋กฌํํธ ๊ตฌ์ฑ
|
| 4995 |
+
prompt = f"""
|
| 4996 |
+
๋ค์์ ์น์์ค์ ๋ถ์ ๋ฐ์ดํฐ์
๋๋ค. ์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์ผ๋ก ์ด ์ํ์ ์ ์ค๋ช
ํ๋ ํ๊ทธ 10๊ฐ~20๊ฐ๋ฅผ ์์ฑํด์ฃผ์ธ์.
|
| 4997 |
+
|
| 4998 |
+
{parent_chunk_text}
|
| 4999 |
+
|
| 5000 |
+
{analysis_text}
|
| 5001 |
+
|
| 5002 |
+
{entity_text}
|
| 5003 |
+
|
| 5004 |
+
์๊ตฌ์ฌํญ:
|
| 5005 |
+
1. ์ฅ๋ฅด, ๋ถ์๊ธฐ, ์ฃผ์ ์์ฌ, ์บ๋ฆญํฐ ํน์ฑ ๋ฑ์ ํฌํจํ๋ ํ๊ทธ๋ฅผ ์์ฑํ์ธ์.
|
| 5006 |
+
2. ๋ฐ๋์ JSON ๋ฐฐ์ด ํ์์ผ๋ก๋ง ์๋ตํ์ธ์. (์: ["ํํ์ง", "๋ก๋งจ์ค", "์ฑ์ฅ๋ฌผ", "๋ง๋ฒ"])
|
| 5007 |
+
3. ๋ค๋ฅธ ์ค๋ช
์์ด JSON ๋ฐฐ์ด๋ง ์ถ๋ ฅํ์ธ์.
|
| 5008 |
+
"""
|
| 5009 |
+
|
| 5010 |
+
# AI ํธ์ถ
|
| 5011 |
+
print(f"[ํ๊ทธ ์์ฑ] '{file.original_filename}' ํ๊ทธ ์์ฑ ์์ฒญ (๋ชจ๋ธ: {file.model_name})")
|
| 5012 |
+
|
| 5013 |
+
response_text = None
|
| 5014 |
+
|
| 5015 |
+
# Gemini ๋ชจ๋ธ์ธ ๊ฒฝ์ฐ
|
| 5016 |
+
if 'gemini' in file.model_name.lower():
|
| 5017 |
+
try:
|
| 5018 |
+
gemini_model_name = file.model_name
|
| 5019 |
+
if gemini_model_name.lower().startswith('gemini:'):
|
| 5020 |
+
gemini_model_name = gemini_model_name.split(':', 1)[1].strip()
|
| 5021 |
+
|
| 5022 |
+
gemini_client = get_gemini_client()
|
| 5023 |
+
if gemini_client.is_configured():
|
| 5024 |
+
result = gemini_client.generate_response(
|
| 5025 |
+
prompt=prompt,
|
| 5026 |
+
model_name=gemini_model_name,
|
| 5027 |
+
temperature=0.7,
|
| 5028 |
+
max_output_tokens=1024
|
| 5029 |
+
)
|
| 5030 |
+
if not result['error'] and result.get('response'):
|
| 5031 |
+
response_text = result['response'].strip()
|
| 5032 |
+
except Exception as e:
|
| 5033 |
+
print(f"[ํ๊ทธ ์์ฑ] Gemini ์ค๋ฅ: {str(e)}")
|
| 5034 |
+
|
| 5035 |
+
# Ollama ๋ชจ๋ธ์ธ ๊ฒฝ์ฐ (๋๋ Gemini ์คํจ ์)
|
| 5036 |
+
if not response_text:
|
| 5037 |
+
try:
|
| 5038 |
+
ollama_model_name = file.model_name
|
| 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()
|
| 5050 |
+
|
| 5051 |
+
ollama_response = requests.post(
|
| 5052 |
+
f"{config.OLLAMA_API_URL}/api/generate",
|
| 5053 |
+
json={
|
| 5054 |
+
'model': file.model_name,
|
| 5055 |
+
'prompt': prompt,
|
| 5056 |
+
'stream': False,
|
| 5057 |
+
'options': {'temperature': 0.7}
|
| 5058 |
+
},
|
| 5059 |
+
timeout=120
|
| 5060 |
+
)
|
| 5061 |
+
|
| 5062 |
+
if ollama_response.status_code == 200:
|
| 5063 |
+
response_data = ollama_response.json()
|
| 5064 |
+
response_text = response_data.get('response', '').strip()
|
| 5065 |
+
except Exception as e:
|
| 5066 |
+
print(f"[ํ๊ทธ ์์ฑ] Ollama ์ค๋ฅ: {str(e)}")
|
| 5067 |
+
|
| 5068 |
+
if not response_text:
|
| 5069 |
+
return jsonify({'error': 'AI ์๋ต์ ๋ฐ์ง ๋ชปํ์ต๋๋ค.'}), 500
|
| 5070 |
+
|
| 5071 |
+
# JSON ํ์ฑ
|
| 5072 |
+
import json
|
| 5073 |
+
import re
|
| 5074 |
+
|
| 5075 |
+
tags = []
|
| 5076 |
+
try:
|
| 5077 |
+
# JSON ์ถ์ถ ์๋
|
| 5078 |
+
json_match = re.search(r'\[.*\]', response_text, re.DOTALL)
|
| 5079 |
+
if json_match:
|
| 5080 |
+
tags = json.loads(json_match.group(0))
|
| 5081 |
+
else:
|
| 5082 |
+
# JSON ํ์์ด ์๋๋ฉด ์ผํ๋ก ๊ตฌ๋ถํ์ฌ ์ฒ๋ฆฌ ์๋
|
| 5083 |
+
tags = [t.strip() for t in response_text.replace('[', '').replace(']', '').replace('"', '').split(',')]
|
| 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(tags, ensure_ascii=False)
|
| 5097 |
+
db.session.commit()
|
| 5098 |
+
|
| 5099 |
+
return jsonify({
|
| 5100 |
+
'message': 'ํ๊ทธ๊ฐ ์์ฑ๋์์ต๋๋ค.',
|
| 5101 |
+
'tags': tags,
|
| 5102 |
+
'file_id': file.id
|
| 5103 |
+
}), 200
|
| 5104 |
+
|
| 5105 |
+
except Exception as e:
|
| 5106 |
+
db.session.rollback()
|
| 5107 |
+
return jsonify({'error': f'ํ๊ทธ ์์ฑ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}'}), 500
|
| 5108 |
+
|
| 5109 |
@main_bp.route('/api/files/<int:file_id>/metadata', methods=['POST'])
|
| 5110 |
@login_required
|
| 5111 |
def create_file_metadata(file_id):
|
templates/admin_webnovels.html
CHANGED
|
@@ -992,8 +992,28 @@
|
|
| 992 |
const toggleButtonText = isPublic ? '๋น๊ณต๊ฐ๋ก' : '๊ณต๊ฐ๋ก';
|
| 993 |
const toggleButtonClass = isPublic ? 'btn-warning' : 'btn-success';
|
| 994 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 995 |
row.innerHTML = `
|
| 996 |
-
<td
|
|
|
|
|
|
|
|
|
|
| 997 |
<td>${modelName}</td>
|
| 998 |
<td class="file-size">${fileSize}</td>
|
| 999 |
<td>${uploadDate}</td>
|
|
@@ -1004,12 +1024,13 @@
|
|
| 1004 |
</button>
|
| 1005 |
</td>
|
| 1006 |
<td>
|
| 1007 |
-
<div class="file-actions">
|
| 1008 |
${(file.has_parent_chunk !== undefined && file.has_parent_chunk) ?
|
| 1009 |
`<button class="btn btn-primary" onclick="viewParentChunk(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">Parent Chunk</button>` :
|
| 1010 |
`<button class="btn btn-secondary" onclick="createParentChunk(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">Parent Chunk ์์ฑ</button>`
|
| 1011 |
}
|
| 1012 |
<button class="btn btn-info" onclick="createMetadata(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">๋ฉํ๋ฐ์ดํฐ ์์ฑ</button>
|
|
|
|
| 1013 |
<button class="btn btn-primary" onclick="continueUpload(${file.id})" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">์ด์ด์ ์
๋ก๋</button>
|
| 1014 |
<button class="btn btn-secondary" onclick="deleteFile(${file.id})" style="padding: 4px 8px; font-size: 12px;">์ญ์ </button>
|
| 1015 |
</div>
|
|
@@ -1896,6 +1917,43 @@
|
|
| 1896 |
}
|
| 1897 |
}
|
| 1898 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1899 |
// ๋ชจ๋ฌ ์ธ๋ถ ํด๋ฆญ ์ ๋ซ๊ธฐ
|
| 1900 |
window.addEventListener('click', (event) => {
|
| 1901 |
const modal = document.getElementById('parentChunkModal');
|
|
|
|
| 992 |
const toggleButtonText = isPublic ? '๋น๊ณต๊ฐ๋ก' : '๊ณต๊ฐ๋ก';
|
| 993 |
const toggleButtonClass = isPublic ? 'btn-warning' : 'btn-success';
|
| 994 |
|
| 995 |
+
// ํ๊ทธ ํ์
|
| 996 |
+
let tagsHtml = '';
|
| 997 |
+
if (file.tags) {
|
| 998 |
+
try {
|
| 999 |
+
const tags = JSON.parse(file.tags);
|
| 1000 |
+
if (tags && tags.length > 0) {
|
| 1001 |
+
tagsHtml = '<div style="margin-top: 6px; display: flex; flex-wrap: wrap; gap: 4px;">';
|
| 1002 |
+
tags.forEach(tag => {
|
| 1003 |
+
tagsHtml += `<span style="background: #e8f0fe; color: #1a73e8; padding: 2px 6px; border-radius: 4px; font-size: 11px; border: 1px solid #d2e3fc;">#${escapeHtml(tag)}</span>`;
|
| 1004 |
+
});
|
| 1005 |
+
tagsHtml += '</div>';
|
| 1006 |
+
}
|
| 1007 |
+
} catch (e) {
|
| 1008 |
+
console.error('ํ๊ทธ ํ์ฑ ์ค๋ฅ:', e);
|
| 1009 |
+
}
|
| 1010 |
+
}
|
| 1011 |
+
|
| 1012 |
row.innerHTML = `
|
| 1013 |
+
<td>
|
| 1014 |
+
<div>${fileNameDisplay}</div>
|
| 1015 |
+
${tagsHtml}
|
| 1016 |
+
</td>
|
| 1017 |
<td>${modelName}</td>
|
| 1018 |
<td class="file-size">${fileSize}</td>
|
| 1019 |
<td>${uploadDate}</td>
|
|
|
|
| 1024 |
</button>
|
| 1025 |
</td>
|
| 1026 |
<td>
|
| 1027 |
+
<div class="file-actions" style="flex-wrap: wrap; row-gap: 4px;">
|
| 1028 |
${(file.has_parent_chunk !== undefined && file.has_parent_chunk) ?
|
| 1029 |
`<button class="btn btn-primary" onclick="viewParentChunk(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">Parent Chunk</button>` :
|
| 1030 |
`<button class="btn btn-secondary" onclick="createParentChunk(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">Parent Chunk ์์ฑ</button>`
|
| 1031 |
}
|
| 1032 |
<button class="btn btn-info" onclick="createMetadata(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">๋ฉํ๋ฐ์ดํฐ ์์ฑ</button>
|
| 1033 |
+
<button class="btn" style="background: #673ab7; color: white; padding: 4px 8px; font-size: 12px; margin-right: 4px;" onclick="createTags(${file.id}, '${escapeHtml(file.original_filename)}')">ํ๊ทธ ์์ฑ</button>
|
| 1034 |
<button class="btn btn-primary" onclick="continueUpload(${file.id})" style="padding: 4px 8px; font-size: 12px; margin-right: 4px;">์ด์ด์ ์
๋ก๋</button>
|
| 1035 |
<button class="btn btn-secondary" onclick="deleteFile(${file.id})" style="padding: 4px 8px; font-size: 12px;">์ญ์ </button>
|
| 1036 |
</div>
|
|
|
|
| 1917 |
}
|
| 1918 |
}
|
| 1919 |
|
| 1920 |
+
// ํ๊ทธ ์์ฑ
|
| 1921 |
+
async function createTags(fileId, fileName) {
|
| 1922 |
+
if (!confirm(`"${fileName}" ํ์ผ์ ํ๊ทธ๋ฅผ ์์ฑํ์๊ฒ ์ต๋๊น?\n\n์ด ์์
์ AI ๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ Parent Chunk์ ํ์ฐจ๋ณ ๋ถ์ ๋ฐ์ดํฐ๋ฅผ ์ข
ํฉํ์ฌ ๋ถ์ํฉ๋๋ค.`)) {
|
| 1923 |
+
return;
|
| 1924 |
+
}
|
| 1925 |
+
|
| 1926 |
+
const button = event.target;
|
| 1927 |
+
const originalText = button.textContent;
|
| 1928 |
+
button.disabled = true;
|
| 1929 |
+
button.textContent = '์์ฑ ์ค...';
|
| 1930 |
+
|
| 1931 |
+
try {
|
| 1932 |
+
const response = await fetch(`/api/files/${fileId}/process/tags`, {
|
| 1933 |
+
method: 'POST',
|
| 1934 |
+
credentials: 'include'
|
| 1935 |
+
});
|
| 1936 |
+
const data = await response.json();
|
| 1937 |
+
|
| 1938 |
+
if (response.ok) {
|
| 1939 |
+
let message = 'ํ๊ทธ ์์ฑ์ด ์๋ฃ๋์์ต๋๋ค.';
|
| 1940 |
+
if (data.tags && data.tags.length > 0) {
|
| 1941 |
+
message += `\n์์ฑ๋ ํ๊ทธ: ${data.tags.join(', ')}`;
|
| 1942 |
+
}
|
| 1943 |
+
showAlert(message, 'success');
|
| 1944 |
+
loadFiles(); // ํ์ผ ๋ชฉ๋ก ์๋ก๊ณ ์นจ
|
| 1945 |
+
} else {
|
| 1946 |
+
showAlert(`ํ๊ทธ ์์ฑ ์คํจ: ${data.error || '์ ์ ์๋ ์ค๋ฅ'}`, 'error');
|
| 1947 |
+
}
|
| 1948 |
+
} catch (error) {
|
| 1949 |
+
showAlert(`ํ๊ทธ ์์ฑ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: ${error.message}`, 'error');
|
| 1950 |
+
console.error('ํ๊ทธ ์์ฑ ์ค๋ฅ:', error);
|
| 1951 |
+
} finally {
|
| 1952 |
+
button.disabled = false;
|
| 1953 |
+
button.textContent = originalText;
|
| 1954 |
+
}
|
| 1955 |
+
}
|
| 1956 |
+
|
| 1957 |
// ๋ชจ๋ฌ ์ธ๋ถ ํด๋ฆญ ์ ๋ซ๊ธฐ
|
| 1958 |
window.addEventListener('click', (event) => {
|
| 1959 |
const modal = document.getElementById('parentChunkModal');
|