GitHub Actions
commited on
Commit
Β·
1995f8f
1
Parent(s):
5a77b24
Auto-deploy from GitHub Actions - 2025-12-12 16:41:27
Browse files- app/database.py +24 -0
- app/routes.py +271 -1
- templates/admin.html +2 -0
- templates/admin_chatbot_prompts.html +317 -0
- templates/admin_files.html +2 -0
- templates/admin_messages.html +2 -0
- templates/admin_prompts.html +2 -0
- templates/admin_settings.html +2 -0
- templates/admin_tags.html +2 -0
- templates/admin_tokens.html +2 -0
- templates/admin_utils.html +2 -0
- templates/admin_webnovels.html +79 -0
app/database.py
CHANGED
|
@@ -370,3 +370,27 @@ class GraphEvent(db.Model):
|
|
| 370 |
}
|
| 371 |
|
| 372 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
}
|
| 371 |
|
| 372 |
|
| 373 |
+
# νμΌλ³ μ±λ΄ ν둬ννΈ μ μ₯ λͺ¨λΈ (μΌλ°/μμΈ)
|
| 374 |
+
class ChatbotPrompt(db.Model):
|
| 375 |
+
__tablename__ = 'chatbot_prompt'
|
| 376 |
+
id = db.Column(db.Integer, primary_key=True)
|
| 377 |
+
file_id = db.Column(db.Integer, db.ForeignKey('uploaded_file.id'), nullable=False, unique=True)
|
| 378 |
+
simple_prompt = db.Column(db.Text, nullable=True)
|
| 379 |
+
detailed_prompt = db.Column(db.Text, nullable=True)
|
| 380 |
+
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
| 381 |
+
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
| 382 |
+
|
| 383 |
+
# κ΄κ³
|
| 384 |
+
file = db.relationship('UploadedFile', backref='chatbot_prompt')
|
| 385 |
+
|
| 386 |
+
def to_dict(self):
|
| 387 |
+
return {
|
| 388 |
+
'id': self.id,
|
| 389 |
+
'file_id': self.file_id,
|
| 390 |
+
'simple_prompt': self.simple_prompt,
|
| 391 |
+
'detailed_prompt': self.detailed_prompt,
|
| 392 |
+
'created_at': self.created_at.isoformat() if self.created_at else None,
|
| 393 |
+
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
|
app/routes.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
from flask import Blueprint, render_template, request, jsonify, send_from_directory, redirect, url_for, flash, send_file
|
| 2 |
from flask_login import login_user, logout_user, login_required, current_user
|
| 3 |
from werkzeug.utils import secure_filename
|
| 4 |
-
from app.database import db, UploadedFile, User, ChatSession, ChatMessage, DocumentChunk, ParentChunk, SystemConfig, EpisodeAnalysis, GraphEntity, GraphRelationship, GraphEvent
|
| 5 |
from app.vector_db import get_vector_db
|
| 6 |
from app.gemini_client import get_gemini_client
|
| 7 |
import requests
|
|
@@ -13,6 +13,156 @@ import json
|
|
| 13 |
|
| 14 |
main_bp = Blueprint('main', __name__)
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
def admin_required(f):
|
| 17 |
"""κ΄λ¦¬μ κΆνμ΄ νμν λ°μ½λ μ΄ν°"""
|
| 18 |
from functools import wraps
|
|
@@ -1997,6 +2147,13 @@ def admin_tags():
|
|
| 1997 |
"""νκ·Έ 보기 νμ΄μ§"""
|
| 1998 |
return render_template('admin_tags.html')
|
| 1999 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2000 |
@main_bp.route('/admin/utils')
|
| 2001 |
@admin_required
|
| 2002 |
def admin_utils():
|
|
@@ -4412,6 +4569,119 @@ def get_files():
|
|
| 4412 |
except Exception as e:
|
| 4413 |
return jsonify({'error': f'νμΌ λͺ©λ‘ μ‘°ν μ€ μ€λ₯κ° λ°μνμ΅λλ€: {str(e)}'}), 500
|
| 4414 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4415 |
@main_bp.route('/api/files/<int:file_id>/chunks', methods=['GET'])
|
| 4416 |
@login_required
|
| 4417 |
def get_file_chunks(file_id):
|
|
|
|
| 1 |
from flask import Blueprint, render_template, request, jsonify, send_from_directory, redirect, url_for, flash, send_file
|
| 2 |
from flask_login import login_user, logout_user, login_required, current_user
|
| 3 |
from werkzeug.utils import secure_filename
|
| 4 |
+
from app.database import db, UploadedFile, User, ChatSession, ChatMessage, DocumentChunk, ParentChunk, SystemConfig, EpisodeAnalysis, GraphEntity, GraphRelationship, GraphEvent, ChatbotPrompt
|
| 5 |
from app.vector_db import get_vector_db
|
| 6 |
from app.gemini_client import get_gemini_client
|
| 7 |
import requests
|
|
|
|
| 13 |
|
| 14 |
main_bp = Blueprint('main', __name__)
|
| 15 |
|
| 16 |
+
|
| 17 |
+
def ensure_chatbot_prompt_table_exists():
|
| 18 |
+
"""chatbot_prompt ν
μ΄λΈμ΄ μμΌλ©΄ μμ± (μ΄μ νκ²½μμ λ§μ΄κ·Έλ μ΄μ
λλ½ λλΉ)"""
|
| 19 |
+
try:
|
| 20 |
+
from sqlalchemy import inspect
|
| 21 |
+
inspector = inspect(db.engine)
|
| 22 |
+
if 'chatbot_prompt' not in inspector.get_table_names():
|
| 23 |
+
print("[ChatbotPrompt] chatbot_prompt ν
μ΄λΈμ΄ μμ΄ μμ±ν©λλ€.")
|
| 24 |
+
ChatbotPrompt.__table__.create(db.engine, checkfirst=True)
|
| 25 |
+
except Exception as e:
|
| 26 |
+
print(f"[ChatbotPrompt] ν
μ΄λΈ μμ± νμΈ μ€ μ€λ₯: {e}")
|
| 27 |
+
# create_allμ μ΄λ―Έ app initμμ μνλμ§λ§, μ¬κΈ°μλ μμ νκ² ν λ² λ μλ
|
| 28 |
+
try:
|
| 29 |
+
db.create_all()
|
| 30 |
+
except Exception as e2:
|
| 31 |
+
print(f"[ChatbotPrompt] db.create_all μ€ν¨: {e2}")
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def extract_simple_and_detailed_tags(file_tags_json):
|
| 35 |
+
"""UploadedFile.tags(JSON λ¬Έμμ΄)μμ μΌλ°/μμΈ νκ·Έλ₯Ό λΆλ¦¬νμ¬ λ°ν"""
|
| 36 |
+
if not file_tags_json:
|
| 37 |
+
return None, None, 'no_tags'
|
| 38 |
+
try:
|
| 39 |
+
tags_data = json.loads(file_tags_json)
|
| 40 |
+
except Exception:
|
| 41 |
+
return None, None, 'invalid_json'
|
| 42 |
+
|
| 43 |
+
tag_type = (tags_data.get('type') if isinstance(tags_data, dict) else None) or 'legacy'
|
| 44 |
+
|
| 45 |
+
simple_tags = None
|
| 46 |
+
detailed_tags = None
|
| 47 |
+
|
| 48 |
+
if isinstance(tags_data, dict) and tag_type == 'simple':
|
| 49 |
+
simple_tags = tags_data.get('tags') or tags_data
|
| 50 |
+
detailed_tags = tags_data.get('detailed_tags')
|
| 51 |
+
elif isinstance(tags_data, dict) and tag_type == 'detailed':
|
| 52 |
+
detailed_tags = tags_data.get('tags') or tags_data
|
| 53 |
+
simple_tags = tags_data.get('simple_tags')
|
| 54 |
+
else:
|
| 55 |
+
# legacy νμμ μμΈλ‘ μ·¨κΈ(κΈ°μ‘΄ λ°©μ)
|
| 56 |
+
detailed_tags = tags_data if isinstance(tags_data, dict) else None
|
| 57 |
+
simple_tags = None
|
| 58 |
+
|
| 59 |
+
# μμ νκ² tags/type ν€ μ κ±°(legacyμμ tags_data μμ²΄κ° λ€μ΄μ€λ κ²½μ°)
|
| 60 |
+
if isinstance(simple_tags, dict) and 'type' in simple_tags:
|
| 61 |
+
simple_tags = {k: v for k, v in simple_tags.items() if k != 'type'}
|
| 62 |
+
if isinstance(detailed_tags, dict) and 'type' in detailed_tags:
|
| 63 |
+
detailed_tags = {k: v for k, v in detailed_tags.items() if k != 'type'}
|
| 64 |
+
|
| 65 |
+
return simple_tags, detailed_tags, tag_type
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def build_story_chatbot_prompt(file_obj, tags_obj, prompt_kind='simple'):
|
| 69 |
+
"""νκ·Έ κΈ°λ°μΌλ‘ 'μ€ν 리 μ±λ΄'μμ μ¬μ©ν ν둬ννΈ ν
μ€νΈ μμ±"""
|
| 70 |
+
def normalize_tags(value):
|
| 71 |
+
if isinstance(value, list):
|
| 72 |
+
return [str(v).strip() for v in value if str(v).strip()]
|
| 73 |
+
return []
|
| 74 |
+
|
| 75 |
+
def render_section(title, section_dict, key_labels):
|
| 76 |
+
if not isinstance(section_dict, dict):
|
| 77 |
+
return ""
|
| 78 |
+
out = [f"## {title}"]
|
| 79 |
+
any_added = False
|
| 80 |
+
for key, label in key_labels.items():
|
| 81 |
+
tags_list = normalize_tags(section_dict.get(key))
|
| 82 |
+
if tags_list:
|
| 83 |
+
any_added = True
|
| 84 |
+
out.append(f"- {label}: " + " ".join([f"#{t}" for t in tags_list]))
|
| 85 |
+
if not any_added:
|
| 86 |
+
return ""
|
| 87 |
+
return "\n".join(out)
|
| 88 |
+
|
| 89 |
+
prompt_title = "μΌλ° ν둬ννΈ" if prompt_kind == 'simple' else "μμΈ ν둬ννΈ"
|
| 90 |
+
lines = [
|
| 91 |
+
"[μ€ν 리 μ±λ΄ ν둬ννΈ]",
|
| 92 |
+
f"- νμΌ: {file_obj.original_filename}",
|
| 93 |
+
f"- κΈ°μ€: {prompt_title} (νκ·Έ κΈ°λ°)",
|
| 94 |
+
"",
|
| 95 |
+
"[μ¬μ© μ§μΉ¨]",
|
| 96 |
+
"- μλ νκ·Έλ₯Ό μΈκ³κ΄/μΈλ¬Ό/μ¬κ±΄/κ΄κ³μ 'μ¬μ€'λ‘ κ°μ£Όνκ³ , μ§λ¬Έμ μΌκ΄λκ² λ΅λ³νμΈμ.",
|
| 97 |
+
"- λͺ¨λ₯΄λ λ΄μ©μ μΆμΈ‘νμ§ λ§κ³ , νμνλ©΄ μ¬μ©μμκ² μΆκ° μ 보λ₯Ό μμ²νμΈμ.",
|
| 98 |
+
""
|
| 99 |
+
]
|
| 100 |
+
|
| 101 |
+
# μΉμ
λ§€ν (admin_tagsμ ꡬ쑰μ λ§μΆ€)
|
| 102 |
+
if isinstance(tags_obj, dict):
|
| 103 |
+
parent = tags_obj.get('parent_chunk')
|
| 104 |
+
episodes = tags_obj.get('episodes')
|
| 105 |
+
graph_total = tags_obj.get('graph_rag_total')
|
| 106 |
+
graph_by_episode = tags_obj.get('graph_rag_by_episode')
|
| 107 |
+
graph_by_character = tags_obj.get('graph_rag_by_character')
|
| 108 |
+
graph_by_event = tags_obj.get('graph_rag_by_event')
|
| 109 |
+
graph_detail = tags_obj.get('graph_rag_detail')
|
| 110 |
+
|
| 111 |
+
lines.append(render_section("Parent Chunk", parent, {
|
| 112 |
+
'world_view': 'οΏ½οΏ½κ³κ΄',
|
| 113 |
+
'characters': 'μΈλ¬Ό',
|
| 114 |
+
'story': 'μ€ν 리',
|
| 115 |
+
'others': 'κΈ°ν'
|
| 116 |
+
}))
|
| 117 |
+
|
| 118 |
+
lines.append(render_section("νμ°¨λ³", episodes, {
|
| 119 |
+
'story': 'μ€ν 리',
|
| 120 |
+
'events': 'μ¬κ±΄',
|
| 121 |
+
'characters': 'λ±μ₯ μΈλ¬Ό',
|
| 122 |
+
'relationships_change': 'κ΄κ³ λ³ν',
|
| 123 |
+
'appearance': 'μΈλͺ¨',
|
| 124 |
+
'clothing': 'μ볡',
|
| 125 |
+
'items': 'μμ΄ν
/μν',
|
| 126 |
+
'others': 'κΈ°ν'
|
| 127 |
+
}))
|
| 128 |
+
|
| 129 |
+
lines.append(render_section("GraphRAG (μ 체)", graph_total, {
|
| 130 |
+
'characters': 'μΈλ¬Ό',
|
| 131 |
+
'locations': 'μ₯μ',
|
| 132 |
+
'relationships': 'κ΄κ³',
|
| 133 |
+
'events': 'μ¬κ±΄'
|
| 134 |
+
}))
|
| 135 |
+
|
| 136 |
+
lines.append(render_section("GraphRAG (νμ°¨λ³)", graph_by_episode, {
|
| 137 |
+
'characters': 'μΈλ¬Ό',
|
| 138 |
+
'locations': 'μ₯μ',
|
| 139 |
+
'relationships': 'κ΄κ³',
|
| 140 |
+
'events': 'μ¬κ±΄'
|
| 141 |
+
}))
|
| 142 |
+
|
| 143 |
+
lines.append(render_section("GraphRAG (μΈλ¬Όλ³)", graph_by_character, {
|
| 144 |
+
'characters': 'μΈλ¬Ό',
|
| 145 |
+
'locations': 'μ₯μ',
|
| 146 |
+
'relationships': 'κ΄κ³',
|
| 147 |
+
'events': 'μ¬κ±΄'
|
| 148 |
+
}))
|
| 149 |
+
|
| 150 |
+
lines.append(render_section("GraphRAG (μ¬κ±΄λ³)", graph_by_event, {
|
| 151 |
+
'characters': 'μΈλ¬Ό',
|
| 152 |
+
'locations': 'μ₯μ',
|
| 153 |
+
'relationships': 'κ΄κ³',
|
| 154 |
+
'events': 'μ¬κ±΄'
|
| 155 |
+
}))
|
| 156 |
+
|
| 157 |
+
lines.append(render_section("GraphRAG (μμΈ)", graph_detail, {
|
| 158 |
+
'person_person_relationships_chronological': 'μΈλ¬Ό-μΈλ¬Ό κ΄κ³(μκ°μ)',
|
| 159 |
+
'event_person_relationships': 'μ¬κ±΄-μΈλ¬Ό κ΄κ³'
|
| 160 |
+
}))
|
| 161 |
+
|
| 162 |
+
# λΉ μΉμ
μ κ±°
|
| 163 |
+
rendered = "\n\n".join([s for s in lines if isinstance(s, str) and s.strip()])
|
| 164 |
+
return rendered.strip() + "\n"
|
| 165 |
+
|
| 166 |
def admin_required(f):
|
| 167 |
"""κ΄λ¦¬μ κΆνμ΄ νμν λ°μ½λ μ΄ν°"""
|
| 168 |
from functools import wraps
|
|
|
|
| 2147 |
"""νκ·Έ 보기 νμ΄μ§"""
|
| 2148 |
return render_template('admin_tags.html')
|
| 2149 |
|
| 2150 |
+
|
| 2151 |
+
@main_bp.route('/admin/chatbot-prompts')
|
| 2152 |
+
@admin_required
|
| 2153 |
+
def admin_chatbot_prompts():
|
| 2154 |
+
"""μ±λ΄ ν둬ννΈ(μΌλ°/μμΈ) 보기 νμ΄μ§"""
|
| 2155 |
+
return render_template('admin_chatbot_prompts.html')
|
| 2156 |
+
|
| 2157 |
@main_bp.route('/admin/utils')
|
| 2158 |
@admin_required
|
| 2159 |
def admin_utils():
|
|
|
|
| 4569 |
except Exception as e:
|
| 4570 |
return jsonify({'error': f'νμΌ λͺ©λ‘ μ‘°ν μ€ μ€λ₯κ° λ°μνμ΅λλ€: {str(e)}'}), 500
|
| 4571 |
|
| 4572 |
+
|
| 4573 |
+
@main_bp.route('/api/files/<int:file_id>/process/prompts/simple', methods=['POST'])
|
| 4574 |
+
@login_required
|
| 4575 |
+
def generate_simple_chatbot_prompt(file_id):
|
| 4576 |
+
"""μΌλ° νκ·Έ κΈ°λ° 'μ€ν 리 μ±λ΄' ν둬ννΈ μμ±/μ μ₯"""
|
| 4577 |
+
try:
|
| 4578 |
+
ensure_chatbot_prompt_table_exists()
|
| 4579 |
+
file = UploadedFile.query.get_or_404(file_id)
|
| 4580 |
+
|
| 4581 |
+
if not current_user.is_admin and file.uploaded_by != current_user.id:
|
| 4582 |
+
return jsonify({'error': 'κΆνμ΄ μμ΅λλ€.'}), 403
|
| 4583 |
+
|
| 4584 |
+
simple_tags, detailed_tags, tag_type = extract_simple_and_detailed_tags(file.tags)
|
| 4585 |
+
if not simple_tags:
|
| 4586 |
+
return jsonify({'error': 'μΌλ° νκ·Έκ° μμ΄ μΌλ° ν둬ννΈλ₯Ό μμ±ν μ μμ΅λλ€. (μΌλ° νκ·Έ μμ± ν λ€μ μλνμΈμ)'}), 400
|
| 4587 |
+
|
| 4588 |
+
prompt_text = build_story_chatbot_prompt(file, simple_tags, prompt_kind='simple')
|
| 4589 |
+
|
| 4590 |
+
record = ChatbotPrompt.query.filter_by(file_id=file.id).first()
|
| 4591 |
+
if not record:
|
| 4592 |
+
record = ChatbotPrompt(file_id=file.id, simple_prompt=prompt_text)
|
| 4593 |
+
db.session.add(record)
|
| 4594 |
+
else:
|
| 4595 |
+
record.simple_prompt = prompt_text
|
| 4596 |
+
db.session.commit()
|
| 4597 |
+
|
| 4598 |
+
return jsonify({'message': 'μΌλ° ν둬ννΈκ° μμ±/μ μ₯λμμ΅λλ€.', 'file_id': file.id, 'simple_prompt': prompt_text}), 200
|
| 4599 |
+
except Exception as e:
|
| 4600 |
+
db.session.rollback()
|
| 4601 |
+
return jsonify({'error': f'μΌλ° ν둬ννΈ μμ± μ€ μ€λ₯: {str(e)}'}), 500
|
| 4602 |
+
|
| 4603 |
+
|
| 4604 |
+
@main_bp.route('/api/files/<int:file_id>/process/prompts/detailed', methods=['POST'])
|
| 4605 |
+
@login_required
|
| 4606 |
+
def generate_detailed_chatbot_prompt(file_id):
|
| 4607 |
+
"""μμΈ νκ·Έ κΈ°λ° 'μ€ν 리 μ±λ΄' ν둬ννΈ μμ±/μ μ₯"""
|
| 4608 |
+
try:
|
| 4609 |
+
ensure_chatbot_prompt_table_exists()
|
| 4610 |
+
file = UploadedFile.query.get_or_404(file_id)
|
| 4611 |
+
|
| 4612 |
+
if not current_user.is_admin and file.uploaded_by != current_user.id:
|
| 4613 |
+
return jsonify({'error': 'κΆνμ΄ μμ΅λλ€.'}), 403
|
| 4614 |
+
|
| 4615 |
+
simple_tags, detailed_tags, tag_type = extract_simple_and_detailed_tags(file.tags)
|
| 4616 |
+
if not detailed_tags:
|
| 4617 |
+
return jsonify({'error': 'μμΈ νκ·Έκ° μμ΄ μμΈ ν둬ννΈλ₯Ό μμ±ν μ μμ΅λλ€. (μμΈ νκ·Έ μμ± ν λ€μ μλνμΈμ)'}), 400
|
| 4618 |
+
|
| 4619 |
+
prompt_text = build_story_chatbot_prompt(file, detailed_tags, prompt_kind='detailed')
|
| 4620 |
+
|
| 4621 |
+
record = ChatbotPrompt.query.filter_by(file_id=file.id).first()
|
| 4622 |
+
if not record:
|
| 4623 |
+
record = ChatbotPrompt(file_id=file.id, detailed_prompt=prompt_text)
|
| 4624 |
+
db.session.add(record)
|
| 4625 |
+
else:
|
| 4626 |
+
record.detailed_prompt = prompt_text
|
| 4627 |
+
db.session.commit()
|
| 4628 |
+
|
| 4629 |
+
return jsonify({'message': 'μμΈ ν둬ννΈκ° μμ±/μ μ₯λμμ΅λλ€.', 'file_id': file.id, 'detailed_prompt': prompt_text}), 200
|
| 4630 |
+
except Exception as e:
|
| 4631 |
+
db.session.rollback()
|
| 4632 |
+
return jsonify({'error': f'μμΈ ν둬ννΈ μμ± μ€ μ€λ₯: {str(e)}'}), 500
|
| 4633 |
+
|
| 4634 |
+
|
| 4635 |
+
@main_bp.route('/api/files/<int:file_id>/chatbot-prompts', methods=['GET'])
|
| 4636 |
+
@login_required
|
| 4637 |
+
def get_chatbot_prompts(file_id):
|
| 4638 |
+
"""νμΌμ μ μ₯λ μ±λ΄ ν둬ννΈ(μΌλ°/μμΈ) μ‘°ν"""
|
| 4639 |
+
try:
|
| 4640 |
+
ensure_chatbot_prompt_table_exists()
|
| 4641 |
+
file = UploadedFile.query.get_or_404(file_id)
|
| 4642 |
+
|
| 4643 |
+
if not current_user.is_admin and file.uploaded_by != current_user.id:
|
| 4644 |
+
return jsonify({'error': 'κΆνμ΄ μμ΅λλ€.'}), 403
|
| 4645 |
+
|
| 4646 |
+
record = ChatbotPrompt.query.filter_by(file_id=file.id).first()
|
| 4647 |
+
if not record:
|
| 4648 |
+
return jsonify({'file_id': file.id, 'simple_prompt': None, 'detailed_prompt': None}), 200
|
| 4649 |
+
|
| 4650 |
+
return jsonify({'file_id': file.id, 'simple_prompt': record.simple_prompt, 'detailed_prompt': record.detailed_prompt}), 200
|
| 4651 |
+
except Exception as e:
|
| 4652 |
+
return jsonify({'error': f'μ±λ΄ ν둬ννΈ μ‘°ν μ€ μ€λ₯: {str(e)}'}), 500
|
| 4653 |
+
|
| 4654 |
+
|
| 4655 |
+
@main_bp.route('/api/admin/chatbot-prompts/files', methods=['GET'])
|
| 4656 |
+
@admin_required
|
| 4657 |
+
def list_files_with_chatbot_prompts():
|
| 4658 |
+
"""μ±λ΄ ν둬ννΈκ° νλλΌλ μ μ₯λ νμΌ λͺ©λ‘(κ΄λ¦¬μμ©)"""
|
| 4659 |
+
try:
|
| 4660 |
+
ensure_chatbot_prompt_table_exists()
|
| 4661 |
+
# ν둬ννΈκ° μλ κ²λ§
|
| 4662 |
+
q = (
|
| 4663 |
+
db.session.query(UploadedFile, ChatbotPrompt)
|
| 4664 |
+
.join(ChatbotPrompt, ChatbotPrompt.file_id == UploadedFile.id)
|
| 4665 |
+
.filter(
|
| 4666 |
+
(ChatbotPrompt.simple_prompt.isnot(None)) | (ChatbotPrompt.detailed_prompt.isnot(None))
|
| 4667 |
+
)
|
| 4668 |
+
.order_by(UploadedFile.uploaded_at.desc(), UploadedFile.id.desc())
|
| 4669 |
+
)
|
| 4670 |
+
|
| 4671 |
+
items = []
|
| 4672 |
+
for f, p in q.all():
|
| 4673 |
+
items.append({
|
| 4674 |
+
'file_id': f.id,
|
| 4675 |
+
'original_filename': f.original_filename,
|
| 4676 |
+
'uploaded_at': f.uploaded_at.isoformat() if f.uploaded_at else None,
|
| 4677 |
+
'has_simple_prompt': bool(p.simple_prompt),
|
| 4678 |
+
'has_detailed_prompt': bool(p.detailed_prompt),
|
| 4679 |
+
})
|
| 4680 |
+
|
| 4681 |
+
return jsonify({'files': items}), 200
|
| 4682 |
+
except Exception as e:
|
| 4683 |
+
return jsonify({'error': f'ν둬ννΈ νμΌ λͺ©λ‘ μ‘°ν μ€ μ€λ₯: {str(e)}'}), 500
|
| 4684 |
+
|
| 4685 |
@main_bp.route('/api/files/<int:file_id>/chunks', methods=['GET'])
|
| 4686 |
@login_required
|
| 4687 |
def get_file_chunks(file_id):
|
templates/admin.html
CHANGED
|
@@ -701,6 +701,7 @@
|
|
| 701 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 702 |
<div class="dropdown-menu">
|
| 703 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 704 |
</div>
|
| 705 |
</div>
|
| 706 |
|
|
@@ -741,6 +742,7 @@
|
|
| 741 |
|
| 742 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 743 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 744 |
|
| 745 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 746 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
|
|
|
| 701 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 702 |
<div class="dropdown-menu">
|
| 703 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
| 704 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="dropdown-item">μ±λ΄ ν둬ννΈ</a>
|
| 705 |
</div>
|
| 706 |
</div>
|
| 707 |
|
|
|
|
| 742 |
|
| 743 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 744 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
| 745 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ±λ΄ ν둬ννΈ</a>
|
| 746 |
|
| 747 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 748 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
templates/admin_chatbot_prompts.html
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="ko">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>μ±λ΄ ν둬ννΈ - SOY NV AI</title>
|
| 7 |
+
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
| 9 |
+
<style>
|
| 10 |
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 11 |
+
body {
|
| 12 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
| 13 |
+
background: #f8f9fa;
|
| 14 |
+
color: #202124;
|
| 15 |
+
overflow-x: hidden;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
.header {
|
| 19 |
+
background: white;
|
| 20 |
+
border-bottom: 1px solid #dadce0;
|
| 21 |
+
padding: 16px 24px;
|
| 22 |
+
display: flex;
|
| 23 |
+
align-items: center;
|
| 24 |
+
justify-content: space-between;
|
| 25 |
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
| 26 |
+
}
|
| 27 |
+
.header-title { font-size: 20px; font-weight: 500; display:flex; align-items:center; gap: 12px; }
|
| 28 |
+
.header-actions { display:flex; gap: 8px; align-items:center; flex-wrap: wrap; }
|
| 29 |
+
|
| 30 |
+
.dropdown { position: relative; display: inline-block; z-index: 10001; }
|
| 31 |
+
.dropdown::after { content:''; position:absolute; left:0; right:0; top:100%; height:8px; }
|
| 32 |
+
.dropdown-toggle {
|
| 33 |
+
padding: 8px 16px;
|
| 34 |
+
background: #f1f3f4;
|
| 35 |
+
color: #202124;
|
| 36 |
+
border: none;
|
| 37 |
+
border-radius: 6px;
|
| 38 |
+
font-size: 14px;
|
| 39 |
+
font-weight: 500;
|
| 40 |
+
cursor: pointer;
|
| 41 |
+
transition: all 0.2s;
|
| 42 |
+
display:flex; align-items:center; gap:6px;
|
| 43 |
+
}
|
| 44 |
+
.dropdown-toggle:hover { background: #e8eaed; }
|
| 45 |
+
.dropdown-toggle::after { content:'βΌ'; font-size: 10px; transition: transform 0.2s; }
|
| 46 |
+
.dropdown:hover .dropdown-toggle::after { transform: rotate(180deg); }
|
| 47 |
+
.dropdown-menu {
|
| 48 |
+
position: absolute;
|
| 49 |
+
top: calc(100% + 4px);
|
| 50 |
+
left: 0;
|
| 51 |
+
background: white;
|
| 52 |
+
border: 1px solid #dadce0;
|
| 53 |
+
border-radius: 6px;
|
| 54 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
| 55 |
+
min-width: 200px;
|
| 56 |
+
opacity: 0;
|
| 57 |
+
visibility: hidden;
|
| 58 |
+
transform: translateY(-8px);
|
| 59 |
+
transition: all 0.2s ease;
|
| 60 |
+
z-index: 10002;
|
| 61 |
+
padding: 4px 0;
|
| 62 |
+
pointer-events: none;
|
| 63 |
+
white-space: nowrap;
|
| 64 |
+
}
|
| 65 |
+
.dropdown:hover .dropdown-menu {
|
| 66 |
+
opacity: 1; visibility: visible; transform: translateY(0); pointer-events: auto;
|
| 67 |
+
}
|
| 68 |
+
.dropdown-item {
|
| 69 |
+
display:block;
|
| 70 |
+
padding: 10px 16px;
|
| 71 |
+
color: #202124;
|
| 72 |
+
text-decoration: none;
|
| 73 |
+
font-size: 14px;
|
| 74 |
+
transition: background 0.2s;
|
| 75 |
+
}
|
| 76 |
+
.dropdown-item:hover { background: #f8f9fa; }
|
| 77 |
+
|
| 78 |
+
.btn {
|
| 79 |
+
padding: 8px 16px;
|
| 80 |
+
border: none;
|
| 81 |
+
border-radius: 6px;
|
| 82 |
+
font-size: 14px;
|
| 83 |
+
font-weight: 500;
|
| 84 |
+
cursor: pointer;
|
| 85 |
+
transition: all 0.2s;
|
| 86 |
+
text-decoration: none;
|
| 87 |
+
display: inline-flex;
|
| 88 |
+
align-items: center;
|
| 89 |
+
justify-content: center;
|
| 90 |
+
}
|
| 91 |
+
.btn-secondary { background: #f1f3f4; color:#202124; }
|
| 92 |
+
.btn-secondary:hover { background: #e8eaed; }
|
| 93 |
+
.btn-primary { background: #1a73e8; color: white; }
|
| 94 |
+
.btn-primary:hover { background: #1557b0; }
|
| 95 |
+
|
| 96 |
+
.container { max-width: 1200px; margin: 24px auto; padding: 0 24px; }
|
| 97 |
+
.file-selector {
|
| 98 |
+
background: white;
|
| 99 |
+
border: 1px solid #dadce0;
|
| 100 |
+
border-radius: 10px;
|
| 101 |
+
padding: 16px;
|
| 102 |
+
margin-bottom: 16px;
|
| 103 |
+
display:flex;
|
| 104 |
+
gap: 12px;
|
| 105 |
+
align-items: center;
|
| 106 |
+
flex-wrap: wrap;
|
| 107 |
+
}
|
| 108 |
+
.file-selector label { font-weight: 500; font-size: 14px; color:#202124; }
|
| 109 |
+
.file-selector select {
|
| 110 |
+
min-width: 320px;
|
| 111 |
+
padding: 10px 12px;
|
| 112 |
+
border: 1px solid #dadce0;
|
| 113 |
+
border-radius: 6px;
|
| 114 |
+
font-size: 14px;
|
| 115 |
+
background: white;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.grid {
|
| 119 |
+
display: grid;
|
| 120 |
+
grid-template-columns: 1fr 1fr;
|
| 121 |
+
gap: 16px;
|
| 122 |
+
}
|
| 123 |
+
@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } }
|
| 124 |
+
|
| 125 |
+
.panel {
|
| 126 |
+
background: white;
|
| 127 |
+
border: 1px solid #dadce0;
|
| 128 |
+
border-radius: 10px;
|
| 129 |
+
overflow: hidden;
|
| 130 |
+
display:flex;
|
| 131 |
+
flex-direction: column;
|
| 132 |
+
min-height: 360px;
|
| 133 |
+
}
|
| 134 |
+
.panel-header {
|
| 135 |
+
padding: 12px 14px;
|
| 136 |
+
border-bottom: 1px solid #eee;
|
| 137 |
+
display:flex;
|
| 138 |
+
align-items:center;
|
| 139 |
+
justify-content: space-between;
|
| 140 |
+
gap: 8px;
|
| 141 |
+
}
|
| 142 |
+
.panel-title { font-size: 14px; font-weight: 600; }
|
| 143 |
+
.panel-title.simple { color: #2e7d32; }
|
| 144 |
+
.panel-title.detailed { color: #512da8; }
|
| 145 |
+
.panel-body {
|
| 146 |
+
padding: 12px 14px;
|
| 147 |
+
overflow: auto;
|
| 148 |
+
flex: 1;
|
| 149 |
+
}
|
| 150 |
+
pre {
|
| 151 |
+
white-space: pre-wrap;
|
| 152 |
+
word-break: break-word;
|
| 153 |
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
| 154 |
+
font-size: 12.5px;
|
| 155 |
+
line-height: 1.5;
|
| 156 |
+
}
|
| 157 |
+
.empty, .loading, .error { font-size: 13px; color: #5f6368; }
|
| 158 |
+
.error { color: #c5221f; }
|
| 159 |
+
</style>
|
| 160 |
+
</head>
|
| 161 |
+
<body>
|
| 162 |
+
<div class="header">
|
| 163 |
+
<div class="header-title">μ±λ΄ ν둬ννΈ</div>
|
| 164 |
+
<div class="header-actions">
|
| 165 |
+
<div class="dropdown">
|
| 166 |
+
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 167 |
+
<div class="dropdown-menu">
|
| 168 |
+
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
| 169 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="dropdown-item">μ±λ΄ ν둬ννΈ</a>
|
| 170 |
+
</div>
|
| 171 |
+
</div>
|
| 172 |
+
<a href="{{ url_for('main.admin') }}" class="btn btn-secondary">κ΄λ¦¬μ</a>
|
| 173 |
+
<a href="{{ url_for('main.logout') }}" class="btn btn-secondary">λ‘κ·Έμμ</a>
|
| 174 |
+
</div>
|
| 175 |
+
</div>
|
| 176 |
+
|
| 177 |
+
<div class="container">
|
| 178 |
+
<div class="file-selector">
|
| 179 |
+
<label for="fileSelect">νμΌ μ ν:</label>
|
| 180 |
+
<select id="fileSelect">
|
| 181 |
+
<option value="">ν둬ννΈκ° μ μ₯λ νμΌμ μ ννμΈμ...</option>
|
| 182 |
+
</select>
|
| 183 |
+
</div>
|
| 184 |
+
|
| 185 |
+
<div class="grid">
|
| 186 |
+
<div class="panel">
|
| 187 |
+
<div class="panel-header">
|
| 188 |
+
<div class="panel-title simple">μΌλ° ν둬ννΈ</div>
|
| 189 |
+
<button class="btn btn-secondary" style="padding:6px 10px; font-size:12px;" onclick="copyPrompt('simplePrompt')">볡μ¬</button>
|
| 190 |
+
</div>
|
| 191 |
+
<div class="panel-body">
|
| 192 |
+
<div id="simplePrompt" class="loading">νμΌμ μ ννλ©΄ ν둬ννΈκ° νμλ©λλ€.</div>
|
| 193 |
+
</div>
|
| 194 |
+
</div>
|
| 195 |
+
<div class="panel">
|
| 196 |
+
<div class="panel-header">
|
| 197 |
+
<div class="panel-title detailed">μμΈ ν둬ννΈ</div>
|
| 198 |
+
<button class="btn btn-secondary" style="padding:6px 10px; font-size:12px;" onclick="copyPrompt('detailedPrompt')">볡μ¬</button>
|
| 199 |
+
</div>
|
| 200 |
+
<div class="panel-body">
|
| 201 |
+
<div id="detailedPrompt" class="loading">νμΌμ μ ννλ©΄ ν둬ννΈκ° νμλ©λλ€.</div>
|
| 202 |
+
</div>
|
| 203 |
+
</div>
|
| 204 |
+
</div>
|
| 205 |
+
</div>
|
| 206 |
+
|
| 207 |
+
<script>
|
| 208 |
+
function escapeHtml(text) {
|
| 209 |
+
const div = document.createElement('div');
|
| 210 |
+
div.textContent = text || '';
|
| 211 |
+
return div.innerHTML;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
async function copyPrompt(elementId) {
|
| 215 |
+
const el = document.getElementById(elementId);
|
| 216 |
+
const text = el?.dataset?.raw || el?.textContent || '';
|
| 217 |
+
if (!text.trim()) {
|
| 218 |
+
alert('볡μ¬ν λ΄μ©μ΄ μμ΅λλ€.');
|
| 219 |
+
return;
|
| 220 |
+
}
|
| 221 |
+
try {
|
| 222 |
+
await navigator.clipboard.writeText(text);
|
| 223 |
+
alert('ν΄λ¦½λ³΄λμ 볡μ¬λμμ΅λλ€.');
|
| 224 |
+
} catch (e) {
|
| 225 |
+
// fallback
|
| 226 |
+
const ta = document.createElement('textarea');
|
| 227 |
+
ta.value = text;
|
| 228 |
+
document.body.appendChild(ta);
|
| 229 |
+
ta.select();
|
| 230 |
+
document.execCommand('copy');
|
| 231 |
+
document.body.removeChild(ta);
|
| 232 |
+
alert('ν΄λ¦½λ³΄λμ 볡μ¬λμμ΅λλ€.');
|
| 233 |
+
}
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
async function loadFiles() {
|
| 237 |
+
const select = document.getElementById('fileSelect');
|
| 238 |
+
try {
|
| 239 |
+
const response = await fetch('/api/admin/chatbot-prompts/files', { credentials: 'include' });
|
| 240 |
+
if (!response.ok) throw new Error('νμΌ λͺ©λ‘μ λΆλ¬μ¬ μ μμ΅λλ€.');
|
| 241 |
+
const data = await response.json();
|
| 242 |
+
const files = data.files || [];
|
| 243 |
+
|
| 244 |
+
while (select.options.length > 1) select.remove(1);
|
| 245 |
+
|
| 246 |
+
files.forEach(f => {
|
| 247 |
+
const option = document.createElement('option');
|
| 248 |
+
option.value = f.file_id;
|
| 249 |
+
const badge = `${f.has_simple_prompt ? 'S' : '-'}${f.has_detailed_prompt ? 'D' : '-'}`;
|
| 250 |
+
option.textContent = `${f.original_filename} [${badge}]`;
|
| 251 |
+
select.appendChild(option);
|
| 252 |
+
});
|
| 253 |
+
} catch (e) {
|
| 254 |
+
console.error('νμΌ λͺ©λ‘ λ‘λ μ€λ₯:', e);
|
| 255 |
+
}
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
async function loadPrompts(fileId) {
|
| 259 |
+
const simpleEl = document.getElementById('simplePrompt');
|
| 260 |
+
const detailedEl = document.getElementById('detailedPrompt');
|
| 261 |
+
|
| 262 |
+
if (!fileId) {
|
| 263 |
+
simpleEl.className = 'loading';
|
| 264 |
+
simpleEl.textContent = 'νμΌμ μ ννλ©΄ ν둬ννΈκ° νμλ©λλ€.';
|
| 265 |
+
simpleEl.dataset.raw = '';
|
| 266 |
+
detailedEl.className = 'loading';
|
| 267 |
+
detailedEl.textContent = 'νμΌμ μ ννλ©΄ ν둬ννΈκ° νμλ©λλ€.';
|
| 268 |
+
detailedEl.dataset.raw = '';
|
| 269 |
+
return;
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
simpleEl.className = 'loading';
|
| 273 |
+
simpleEl.textContent = 'λΆλ¬μ€λ μ€...';
|
| 274 |
+
detailedEl.className = 'loading';
|
| 275 |
+
detailedEl.textContent = 'λΆλ¬μ€λ μ€...';
|
| 276 |
+
|
| 277 |
+
try {
|
| 278 |
+
const response = await fetch(`/api/files/${fileId}/chatbot-prompts`, { credentials: 'include' });
|
| 279 |
+
if (!response.ok) throw new Error('ν둬ννΈλ₯Ό λΆλ¬μ¬ μ μμ΅λλ€.');
|
| 280 |
+
const data = await response.json();
|
| 281 |
+
|
| 282 |
+
if (data.simple_prompt) {
|
| 283 |
+
simpleEl.className = '';
|
| 284 |
+
simpleEl.innerHTML = `<pre>${escapeHtml(data.simple_prompt)}</pre>`;
|
| 285 |
+
simpleEl.dataset.raw = data.simple_prompt;
|
| 286 |
+
} else {
|
| 287 |
+
simpleEl.className = 'empty';
|
| 288 |
+
simpleEl.textContent = 'μ μ₯λ μΌλ° ν둬ννΈκ° μμ΅λλ€.';
|
| 289 |
+
simpleEl.dataset.raw = '';
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
if (data.detailed_prompt) {
|
| 293 |
+
detailedEl.className = '';
|
| 294 |
+
detailedEl.innerHTML = `<pre>${escapeHtml(data.detailed_prompt)}</pre>`;
|
| 295 |
+
detailedEl.dataset.raw = data.detailed_prompt;
|
| 296 |
+
} else {
|
| 297 |
+
detailedEl.className = 'empty';
|
| 298 |
+
detailedEl.textContent = 'μ μ₯λ μμΈ ν둬ννΈκ° μμ΅λλ€.';
|
| 299 |
+
detailedEl.dataset.raw = '';
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
} catch (e) {
|
| 303 |
+
console.error('ν둬ννΈ λ‘λ μ€λ₯:', e);
|
| 304 |
+
simpleEl.className = 'error';
|
| 305 |
+
simpleEl.textContent = 'ν둬ννΈλ₯Ό λΆλ¬μ€λ μ€ μ€λ₯κ° λ°μνμ΅λλ€.';
|
| 306 |
+
detailedEl.className = 'error';
|
| 307 |
+
detailedEl.textContent = 'ν둬ννΈλ₯Ό λΆλ¬μ€λ μ€ μ€λ₯κ° λ°μνμ΅λλ€.';
|
| 308 |
+
}
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
document.getElementById('fileSelect').addEventListener('change', (e) => loadPrompts(e.target.value));
|
| 312 |
+
loadFiles();
|
| 313 |
+
</script>
|
| 314 |
+
</body>
|
| 315 |
+
</html>
|
| 316 |
+
|
| 317 |
+
|
templates/admin_files.html
CHANGED
|
@@ -620,6 +620,7 @@
|
|
| 620 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 621 |
<div class="dropdown-menu">
|
| 622 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 623 |
</div>
|
| 624 |
</div>
|
| 625 |
|
|
@@ -660,6 +661,7 @@
|
|
| 660 |
|
| 661 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 662 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 663 |
|
| 664 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 665 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
|
|
|
| 620 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 621 |
<div class="dropdown-menu">
|
| 622 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
| 623 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="dropdown-item">μ±λ΄ ν둬ννΈ</a>
|
| 624 |
</div>
|
| 625 |
</div>
|
| 626 |
|
|
|
|
| 661 |
|
| 662 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 663 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
| 664 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ±λ΄ ν둬ννΈ</a>
|
| 665 |
|
| 666 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 667 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
templates/admin_messages.html
CHANGED
|
@@ -606,6 +606,7 @@
|
|
| 606 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 607 |
<div class="dropdown-menu">
|
| 608 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 609 |
</div>
|
| 610 |
</div>
|
| 611 |
|
|
@@ -646,6 +647,7 @@
|
|
| 646 |
|
| 647 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 648 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 649 |
|
| 650 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 651 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
|
|
|
| 606 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 607 |
<div class="dropdown-menu">
|
| 608 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
| 609 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="dropdown-item">μ±λ΄ ν둬ννΈ</a>
|
| 610 |
</div>
|
| 611 |
</div>
|
| 612 |
|
|
|
|
| 647 |
|
| 648 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 649 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
| 650 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ±λ΄ ν둬ννΈ</a>
|
| 651 |
|
| 652 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 653 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
templates/admin_prompts.html
CHANGED
|
@@ -460,6 +460,7 @@
|
|
| 460 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 461 |
<div class="dropdown-menu">
|
| 462 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 463 |
</div>
|
| 464 |
</div>
|
| 465 |
|
|
@@ -500,6 +501,7 @@
|
|
| 500 |
|
| 501 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 502 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 503 |
|
| 504 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 505 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
|
|
|
| 460 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 461 |
<div class="dropdown-menu">
|
| 462 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
| 463 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="dropdown-item">μ±λ΄ ν둬ννΈ</a>
|
| 464 |
</div>
|
| 465 |
</div>
|
| 466 |
|
|
|
|
| 501 |
|
| 502 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 503 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
| 504 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ±λ΄ ν둬ννΈ</a>
|
| 505 |
|
| 506 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 507 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
templates/admin_settings.html
CHANGED
|
@@ -423,6 +423,7 @@
|
|
| 423 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 424 |
<div class="dropdown-menu">
|
| 425 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 426 |
</div>
|
| 427 |
</div>
|
| 428 |
|
|
@@ -463,6 +464,7 @@
|
|
| 463 |
|
| 464 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 465 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 466 |
|
| 467 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 468 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
|
|
|
| 423 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 424 |
<div class="dropdown-menu">
|
| 425 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
| 426 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="dropdown-item">μ±λ΄ ν둬ννΈ</a>
|
| 427 |
</div>
|
| 428 |
</div>
|
| 429 |
|
|
|
|
| 464 |
|
| 465 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 466 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
| 467 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ±λ΄ ν둬ννΈ</a>
|
| 468 |
|
| 469 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 470 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
templates/admin_tags.html
CHANGED
|
@@ -422,6 +422,7 @@
|
|
| 422 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 423 |
<div class="dropdown-menu">
|
| 424 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 425 |
</div>
|
| 426 |
</div>
|
| 427 |
|
|
@@ -462,6 +463,7 @@
|
|
| 462 |
|
| 463 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 464 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 465 |
|
| 466 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 467 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
|
|
|
| 422 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 423 |
<div class="dropdown-menu">
|
| 424 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
| 425 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="dropdown-item">μ±λ΄ ν둬ννΈ</a>
|
| 426 |
</div>
|
| 427 |
</div>
|
| 428 |
|
|
|
|
| 463 |
|
| 464 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 465 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
| 466 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ±λ΄ ν둬ννΈ</a>
|
| 467 |
|
| 468 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 469 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
templates/admin_tokens.html
CHANGED
|
@@ -485,6 +485,7 @@
|
|
| 485 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 486 |
<div class="dropdown-menu">
|
| 487 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 488 |
</div>
|
| 489 |
</div>
|
| 490 |
|
|
@@ -525,6 +526,7 @@
|
|
| 525 |
|
| 526 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 527 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 528 |
|
| 529 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 530 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
|
|
|
| 485 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 486 |
<div class="dropdown-menu">
|
| 487 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
| 488 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="dropdown-item">μ±λ΄ ν둬ννΈ</a>
|
| 489 |
</div>
|
| 490 |
</div>
|
| 491 |
|
|
|
|
| 526 |
|
| 527 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 528 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
| 529 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ±λ΄ ν둬ννΈ</a>
|
| 530 |
|
| 531 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 532 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
templates/admin_utils.html
CHANGED
|
@@ -506,6 +506,7 @@
|
|
| 506 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 507 |
<div class="dropdown-menu">
|
| 508 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 509 |
</div>
|
| 510 |
</div>
|
| 511 |
|
|
@@ -546,6 +547,7 @@
|
|
| 546 |
|
| 547 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 548 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 549 |
|
| 550 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 551 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
|
|
|
| 506 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 507 |
<div class="dropdown-menu">
|
| 508 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
| 509 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="dropdown-item">μ±λ΄ ν둬ννΈ</a>
|
| 510 |
</div>
|
| 511 |
</div>
|
| 512 |
|
|
|
|
| 547 |
|
| 548 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 549 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
| 550 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ±λ΄ ν둬ννΈ</a>
|
| 551 |
|
| 552 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 553 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
templates/admin_webnovels.html
CHANGED
|
@@ -838,6 +838,7 @@
|
|
| 838 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 839 |
<div class="dropdown-menu">
|
| 840 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 841 |
</div>
|
| 842 |
</div>
|
| 843 |
|
|
@@ -878,6 +879,7 @@
|
|
| 878 |
|
| 879 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 880 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
|
|
|
| 881 |
|
| 882 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 883 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
|
@@ -1352,6 +1354,15 @@
|
|
| 1352 |
<button class="btn btn-info" onclick="createMetadata(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 6px 10px; font-size: 12px;">λ©νλ°μ΄ν° μμ±</button>
|
| 1353 |
<button class="btn" style="background: #4caf50; color: white; padding: 6px 10px; font-size: 12px;" onclick="createSimpleTags(${file.id}, '${escapeHtml(file.original_filename)}')">μΌλ° νκ·Έ μμ±</button>
|
| 1354 |
<button class="btn" style="background: #673ab7; color: white; padding: 6px 10px; font-size: 12px;" onclick="createDetailedTags(${file.id}, '${escapeHtml(file.original_filename)}')">μμΈ νκ·Έ μμ±</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1355 |
<button class="btn btn-primary" onclick="continueUpload(${file.id})" style="padding: 6px 10px; font-size: 12px;">μ΄μ΄μ μ
λ‘λ</button>
|
| 1356 |
<button class="btn btn-secondary" onclick="deleteFile(${file.id})" style="padding: 6px 10px; font-size: 12px;">μμ </button>
|
| 1357 |
</div>
|
|
@@ -2333,6 +2344,74 @@
|
|
| 2333 |
}
|
| 2334 |
}
|
| 2335 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2336 |
// λͺ¨λ¬ μΈλΆ ν΄λ¦ μ λ«κΈ°
|
| 2337 |
window.addEventListener('click', (event) => {
|
| 2338 |
const modal = document.getElementById('parentChunkModal');
|
|
|
|
| 838 |
<button type="button" class="dropdown-toggle">μ±λ΄</button>
|
| 839 |
<div class="dropdown-menu">
|
| 840 |
<a href="{{ url_for('main.admin_tags') }}" class="dropdown-item">νκ·Έ/ν둬ννΈ</a>
|
| 841 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="dropdown-item">μ±λ΄ ν둬ννΈ</a>
|
| 842 |
</div>
|
| 843 |
</div>
|
| 844 |
|
|
|
|
| 879 |
|
| 880 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">μ±λ΄</div>
|
| 881 |
<a href="{{ url_for('main.admin_tags') }}" class="mobile-menu-item" onclick="closeMobileMenu()">νκ·Έ/ν둬ννΈ</a>
|
| 882 |
+
<a href="{{ url_for('main.admin_chatbot_prompts') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ±λ΄ ν둬ννΈ</a>
|
| 883 |
|
| 884 |
<div style="padding: 8px 20px; font-size: 11px; font-weight: 600; color: #5f6368; text-transform: uppercase; border-bottom: 1px solid #f1f3f4; margin-top: 8px;">νΈμκΈ°λ₯</div>
|
| 885 |
<a href="{{ url_for('main.admin_utils') }}" class="mobile-menu-item" onclick="closeMobileMenu()">μ νΈ</a>
|
|
|
|
| 1354 |
<button class="btn btn-info" onclick="createMetadata(${file.id}, '${escapeHtml(file.original_filename)}')" style="padding: 6px 10px; font-size: 12px;">λ©νλ°μ΄ν° μμ±</button>
|
| 1355 |
<button class="btn" style="background: #4caf50; color: white; padding: 6px 10px; font-size: 12px;" onclick="createSimpleTags(${file.id}, '${escapeHtml(file.original_filename)}')">μΌλ° νκ·Έ μμ±</button>
|
| 1356 |
<button class="btn" style="background: #673ab7; color: white; padding: 6px 10px; font-size: 12px;" onclick="createDetailedTags(${file.id}, '${escapeHtml(file.original_filename)}')">μμΈ νκ·Έ μμ±</button>
|
| 1357 |
+
<div class="dropdown">
|
| 1358 |
+
<button class="dropdown-toggle" style="padding: 6px 10px; font-size: 12px; background: #fff; border: 1px solid #dadce0;">
|
| 1359 |
+
ν둬ννΈ μμ±
|
| 1360 |
+
</button>
|
| 1361 |
+
<div class="dropdown-menu" style="min-width: 220px;">
|
| 1362 |
+
<a href="#" class="dropdown-item" onclick="createSimplePrompt(${file.id}, '${escapeHtml(file.original_filename)}'); return false;">μΌλ° ν둬ννΈ μμ±</a>
|
| 1363 |
+
<a href="#" class="dropdown-item" onclick="createDetailedPrompt(${file.id}, '${escapeHtml(file.original_filename)}'); return false;">μμΈ ν둬ννΈ μμ±</a>
|
| 1364 |
+
</div>
|
| 1365 |
+
</div>
|
| 1366 |
<button class="btn btn-primary" onclick="continueUpload(${file.id})" style="padding: 6px 10px; font-size: 12px;">μ΄μ΄μ μ
λ‘λ</button>
|
| 1367 |
<button class="btn btn-secondary" onclick="deleteFile(${file.id})" style="padding: 6px 10px; font-size: 12px;">μμ </button>
|
| 1368 |
</div>
|
|
|
|
| 2344 |
}
|
| 2345 |
}
|
| 2346 |
|
| 2347 |
+
// μ±λ΄ ν둬ννΈ μμ± (μΌλ°)
|
| 2348 |
+
async function createSimplePrompt(fileId, fileName) {
|
| 2349 |
+
if (!confirm(`"${fileName}" νμΌμ μΌλ° ν둬ννΈλ₯Ό μμ±νμκ² μ΅λκΉ?\n\nμΌλ° νκ·Έ κΈ°λ°μΌλ‘ 'μ€ν 리 μ±λ΄'μμ μ¬μ©ν ν둬ννΈλ₯Ό μμ±/μ μ₯ν©λλ€.`)) {
|
| 2350 |
+
return;
|
| 2351 |
+
}
|
| 2352 |
+
|
| 2353 |
+
try {
|
| 2354 |
+
const response = await fetch(`/api/files/${fileId}/process/prompts/simple`, {
|
| 2355 |
+
method: 'POST',
|
| 2356 |
+
credentials: 'include'
|
| 2357 |
+
});
|
| 2358 |
+
|
| 2359 |
+
let data;
|
| 2360 |
+
try {
|
| 2361 |
+
data = await response.json();
|
| 2362 |
+
} catch {
|
| 2363 |
+
const text = await response.text();
|
| 2364 |
+
console.error('[μΌλ° ν둬ννΈ μμ±] JSON νμ± μ€ν¨, μλ΅ ν
μ€νΈ:', text);
|
| 2365 |
+
showAlert(`μΌλ° ν둬ννΈ μμ± μ€ν¨: μλ² μλ΅ μ€λ₯ (${response.status})`, 'error');
|
| 2366 |
+
return;
|
| 2367 |
+
}
|
| 2368 |
+
|
| 2369 |
+
if (response.ok) {
|
| 2370 |
+
showAlert('μΌλ° ν둬ννΈ μμ±μ΄ μλ£λμμ΅λλ€. (μ±λ΄ ν둬ννΈ λ©λ΄μμ νμΈ)', 'success');
|
| 2371 |
+
} else {
|
| 2372 |
+
const errorMsg = data.error || data.message || `μλ² μ€λ₯ (${response.status})`;
|
| 2373 |
+
showAlert(`μΌλ° ν둬ννΈ μμ± μ€ν¨: ${errorMsg}`, 'error');
|
| 2374 |
+
}
|
| 2375 |
+
} catch (error) {
|
| 2376 |
+
console.error('μΌλ° ν둬ννΈ μμ± μ€λ₯:', error);
|
| 2377 |
+
showAlert(`μΌλ° ν둬ννΈ μμ± μ€ μ€λ₯: ${error.message}`, 'error');
|
| 2378 |
+
}
|
| 2379 |
+
}
|
| 2380 |
+
|
| 2381 |
+
// μ±λ΄ ���둬ννΈ μμ± (μμΈ)
|
| 2382 |
+
async function createDetailedPrompt(fileId, fileName) {
|
| 2383 |
+
if (!confirm(`"${fileName}" νμΌμ μμΈ ν둬ννΈλ₯Ό μμ±νμκ² μ΅λκΉ?\n\nμμΈ νκ·Έ κΈ°λ°μΌλ‘ 'μ€ν 리 μ±λ΄'μμ μ¬μ©ν ν둬ννΈλ₯Ό μμ±/μ μ₯ν©λλ€.`)) {
|
| 2384 |
+
return;
|
| 2385 |
+
}
|
| 2386 |
+
|
| 2387 |
+
try {
|
| 2388 |
+
const response = await fetch(`/api/files/${fileId}/process/prompts/detailed`, {
|
| 2389 |
+
method: 'POST',
|
| 2390 |
+
credentials: 'include'
|
| 2391 |
+
});
|
| 2392 |
+
|
| 2393 |
+
let data;
|
| 2394 |
+
try {
|
| 2395 |
+
data = await response.json();
|
| 2396 |
+
} catch {
|
| 2397 |
+
const text = await response.text();
|
| 2398 |
+
console.error('[μμΈ ν둬ννΈ μμ±] JSON νμ± μ€ν¨, μλ΅ ν
μ€νΈ:', text);
|
| 2399 |
+
showAlert(`μμΈ ν둬ννΈ μμ± μ€ν¨: μλ² μλ΅ μ€λ₯ (${response.status})`, 'error');
|
| 2400 |
+
return;
|
| 2401 |
+
}
|
| 2402 |
+
|
| 2403 |
+
if (response.ok) {
|
| 2404 |
+
showAlert('μμΈ ν둬ννΈ μμ±μ΄ μλ£λμμ΅λλ€. (μ±λ΄ ν둬ννΈ λ©λ΄μμ νμΈ)', 'success');
|
| 2405 |
+
} else {
|
| 2406 |
+
const errorMsg = data.error || data.message || `μλ² μ€λ₯ (${response.status})`;
|
| 2407 |
+
showAlert(`μμΈ ν둬ννΈ μμ± μ€ν¨: ${errorMsg}`, 'error');
|
| 2408 |
+
}
|
| 2409 |
+
} catch (error) {
|
| 2410 |
+
console.error('μμΈ ν둬ννΈ μμ± μ€λ₯:', error);
|
| 2411 |
+
showAlert(`μμΈ ν둬ννΈ μμ± μ€ μ€λ₯: ${error.message}`, 'error');
|
| 2412 |
+
}
|
| 2413 |
+
}
|
| 2414 |
+
|
| 2415 |
// λͺ¨λ¬ μΈλΆ ν΄λ¦ μ λ«κΈ°
|
| 2416 |
window.addEventListener('click', (event) => {
|
| 2417 |
const modal = document.getElementById('parentChunkModal');
|