diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 8086fd8..a128902 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -64,9 +64,11 @@ def create_app(config_class=Config): # 注册蓝图 from .api import graph_bp, simulation_bp, report_bp + from .api.template import template_bp app.register_blueprint(graph_bp, url_prefix='/api/graph') app.register_blueprint(simulation_bp, url_prefix='/api/simulation') app.register_blueprint(report_bp, url_prefix='/api/report') + app.register_blueprint(template_bp, url_prefix='/api/template') # 健康检查 @app.route('/health') diff --git a/backend/app/api/template.py b/backend/app/api/template.py new file mode 100644 index 0000000..b20be28 --- /dev/null +++ b/backend/app/api/template.py @@ -0,0 +1,147 @@ +""" +Template Auto-Selection API +Analyze seed data and recommend the best simulation template +""" + +import json +import os +from flask import Blueprint, request, jsonify +from ..utils.llm_client import LLMClient +from ..utils.locale import t, get_locale, get_language_instruction +from ..utils.logger import get_logger + +logger = get_logger('crowdsight.template') + +template_bp = Blueprint('template', __name__) + +# Load templates +_templates_path = os.path.join(os.path.dirname(__file__), '..', 'templates.json') +with open(_templates_path, 'r', encoding='utf-8') as f: + _templates_data = json.load(f) + _templates = _templates_data['templates'] + + +@template_bp.route('/list', methods=['GET']) +def list_templates(): + """Return all available templates""" + locale = get_locale() + lang_key = f'prompt_{locale}' if locale in ('th', 'en', 'zh') else 'prompt_en' + + result = [] + for tmpl in _templates: + result.append({ + 'id': tmpl['id'], + 'icon': tmpl['icon'], + 'category': tmpl['category'], + 'prompt': tmpl.get(lang_key, tmpl['prompt_en']), + 'placeholders': tmpl['placeholders'], + 'name': t(f'templates.{tmpl["id"]}'), + }) + + return jsonify({'success': True, 'templates': result}) + + +@template_bp.route('/auto-select', methods=['POST']) +def auto_select_template(): + """ + Analyze seed data and recommend the best template + pre-fill prompt. + + Request JSON: + { + "text": "extracted text from uploaded documents", + "simulation_requirement": "optional user requirement" + } + + Response JSON: + { + "success": true, + "template_id": "news_event", + "prompt": "pre-filled prompt with actual values", + "confidence": 0.85, + "reasoning": "why this template was selected" + } + """ + try: + data = request.get_json() or {} + text = data.get('text', '')[:5000] # Limit to 5000 chars + simulation_requirement = data.get('simulation_requirement', '') + + if not text: + return jsonify({'success': False, 'error': 'No text provided'}), 400 + + locale = get_locale() + lang_instruction = get_language_instruction() + + # Build template descriptions for the LLM + template_desc = [] + lang_key = f'prompt_{locale}' if locale in ('th', 'en', 'zh') else 'prompt_en' + for tmpl in _templates: + template_desc.append(f"- {tmpl['id']}: {tmpl.get(lang_key, tmpl['prompt_en'])}") + + templates_list = '\n'.join(template_desc) + + system_prompt = f"""You are a smart assistant that analyzes document content and recommends the best simulation template. + +Available templates: +{templates_list} + +Your task: +1. Analyze the document content +2. Select the MOST appropriate template +3. Fill in the template placeholders with actual values from the document +4. Return the result in JSON format + +{lang_instruction}""" + + user_prompt = f"""Document content: +{text[:3000]} + +{f'User requirement: {simulation_requirement}' if simulation_requirement else ''} + +Return JSON: +{{ + "template_id": "best_template_id", + "prompt": "the template with placeholders filled in with actual values from the document", + "confidence": 0.0-1.0, + "reasoning": "brief explanation of why this template was chosen" +}}""" + + llm = LLMClient() + result = llm.chat_json( + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt} + ], + temperature=0.3 + ) + + # Validate template_id + valid_ids = [t['id'] for t in _templates] + if result.get('template_id') not in valid_ids: + result['template_id'] = 'news_event' # Default fallback + + return jsonify({ + 'success': True, + 'template_id': result.get('template_id', 'news_event'), + 'prompt': result.get('prompt', ''), + 'confidence': result.get('confidence', 0.5), + 'reasoning': result.get('reasoning', ''), + }) + + except Exception as e: + logger.error(f"Template auto-select failed: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + + +@template_bp.route('//filter-rules', methods=['GET']) +def get_filter_rules(template_id): + """Return entity filter rules for a template (used by Step 1)""" + for tmpl in _templates: + if tmpl['id'] == template_id: + return jsonify({ + 'success': True, + 'template_id': template_id, + 'filter_rules': tmpl.get('entity_filter', {}) + }) + + return jsonify({'success': False, 'error': 'Template not found'}), 404 diff --git a/backend/app/templates.json b/backend/app/templates.json new file mode 100644 index 0000000..914b057 --- /dev/null +++ b/backend/app/templates.json @@ -0,0 +1,70 @@ +{ + "templates": [ + { + "id": "news_event", + "icon": "📰", + "category": "news", + "prompt_th": "จำลองปฏิกิริยาของประชาชนบนโซเชียลมีเดียหลังจาก [เหตุการณ์] ในช่วง [ระยะเวลา] ชั่วโมง", + "prompt_en": "Simulate public reactions on social media after [event] within [timeframe] hours", + "prompt_zh": "模拟[事件]发生后公众在社交媒体上的反应,时间范围[时间]小时", + "placeholders": ["event", "timeframe"], + "entity_filter": { + "exclude_types": [], + "focus": "affected_parties, public, media, officials" + } + }, + { + "id": "policy_regulation", + "icon": "📋", + "category": "policy", + "prompt_th": "จำลองผลกระทบของนโยบาย [ชื่อนโยบาย] ต่อกลุ่มต่างๆ ในสังคม รวมถึงการโต้ตอบบนโซเชียลมีเดีย", + "prompt_en": "Simulate the impact of [policy name] on different social groups, including social media interactions", + "prompt_zh": "模拟[政策名称]对不同社会群体的影响,包括社交媒体互动", + "placeholders": ["policy_name"], + "entity_filter": { + "exclude_types": [], + "focus": "citizens, experts, media, government, affected_groups" + } + }, + { + "id": "business_ad", + "icon": "📢", + "category": "business", + "prompt_th": "จำลองการแพร่กระจายและผลตอบรับของ [แคมเปญ/สินค้า] บนโซเชียลมีเดีย รวมถึงปฏิกิริยาของกลุ่มเป้าหมาย", + "prompt_en": "Simulate the spread and reception of [campaign/product] on social media, including target audience reactions", + "prompt_zh": "模拟[活动/产品]在社交媒体上的传播和反馈,包括目标受众的反应", + "placeholders": ["campaign_product"], + "entity_filter": { + "exclude_self": true, + "exclude_types": ["Advertiser", "BrandOwner"], + "focus": "target_audience, competitors, influencers, media" + } + }, + { + "id": "fiction_story", + "icon": "📖", + "category": "fiction", + "prompt_th": "จำลองเรื่องราวต่อจาก [เนื้อเรื่อง] โดยให้ตัวละครแต่ละตัวมีปฏิสัมพันธ์กันบนโซเชียลมีเดีย และทำนายตอนจบ", + "prompt_en": "Simulate the story continuation from [plot] with characters interacting on social media, and predict the ending", + "prompt_zh": "模拟从[情节]开始的故事延续,角色在社交媒体上互动,并预测结局", + "placeholders": ["plot"], + "entity_filter": { + "exclude_types": ["Author", "Narrator"], + "focus": "characters, story_entities" + } + }, + { + "id": "social_culture", + "icon": "🌍", + "category": "social", + "prompt_th": "จำลองผลกระทบของ [กระแสสังคม] ต่อกลุ่มคนต่างๆ บนโซเชียลมีเดีย รวมถึงการแพร่กระจายของข้อมูล", + "prompt_en": "Simulate the impact of [social trend] on different groups on social media, including information spread", + "prompt_zh": "模拟[社会潮流]对不同群体在社交媒体上的影响,包括信息传播", + "placeholders": ["social_trend"], + "entity_filter": { + "exclude_types": [], + "focus": "public_figures, communities, media, influencers, general_public" + } + } + ] +} diff --git a/frontend/src/api/template.js b/frontend/src/api/template.js new file mode 100644 index 0000000..58ac594 --- /dev/null +++ b/frontend/src/api/template.js @@ -0,0 +1,24 @@ +import service from './index' + +/** + * Get all available templates + */ +export const getTemplates = () => { + return service.get('/api/template/list') +} + +/** + * Auto-select template based on seed data + * @param {Object} data - { text, simulation_requirement? } + */ +export const autoSelectTemplate = (data) => { + return service.post('/api/template/auto-select', data) +} + +/** + * Get entity filter rules for a template + * @param {string} templateId + */ +export const getTemplateFilterRules = (templateId) => { + return service.get(`/api/template/${templateId}/filter-rules`) +} diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index a5f68c2..9abc8c3 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -202,6 +202,43 @@ + +
+
+ {{ $t('templates.title') }} + +
+
+ + +
+
+
{{ $t('home.inputParams') }} @@ -247,10 +284,11 @@