diff --git a/app.py b/app.py index 6a3b4eb..bc545f7 100644 --- a/app.py +++ b/app.py @@ -101,64 +101,70 @@ def save_data(file_path, data): # ============ 大模型智能解析 ============ -def parse_with_llm(text, category_type, images=None): +def parse_with_llm(text, category_type, images=None, category_id=None, subcategory_id=None): """ 使用大模型解析文本/图片,提取结构化数据 支持多张图片输入,可能解析出多个产品 + 根据类别配置的参数字段进行解析 """ - # 根据类型定义字段模板 - field_templates = { - 'model': { - 'name': '模型名称', - 'organization': '厂商/组织', - 'parameters': '参数量(数字,单位B)', - 'context_length': '上下文长度(数字)', - 'architecture': '架构类型', - 'is_open_source': '是否开源(true/false)', - 'mmlu': 'MMLU分数(数字)', - 'input_price': '输入价格(数字)', - 'output_price': '输出价格(数字)', - 'license': '许可证', - 'description': '简介描述', - }, - 'gpu': { - 'name': 'GPU名称', - 'manufacturer': '厂商', - 'architecture': '架构', - 'memory_gb': '显存大小(数字,单位GB)', - 'cuda_cores': 'CUDA核心数(数字)', - 'tensor_cores': 'Tensor核心数(数字)', - 'memory_bandwidth_gbs': '显存带宽(数字,单位GB/s)', - 'fp16_tflops': 'FP16性能(数字,单位TF)', - 'price_usd': '价格(数字)', - 'release_year': '发布年份(数字)', - 'description': '简介描述', - }, - 'cpu': { - 'name': 'CPU名称', - 'manufacturer': '厂商', - 'architecture': '架构', - 'cores': '核心数(数字)', - 'threads': '线程数(数字)', - 'base_clock_ghz': '基础频率(数字,单位GHz)', - 'boost_clock_ghz': '加速频率(数字,单位GHz)', - 'l3_cache_mb': 'L3缓存(数字,单位MB)', - 'tdp_watts': 'TDP功耗(数字,单位W)', - 'price_usd': '价格(数字)', - 'description': '简介描述', - }, - 'dynamic': { + # 从类别配置中获取字段定义 + categories = load_data(CATEGORIES_FILE) + + # 确定类别ID + if category_id: + cat = next((c for c in categories if c['id'] == category_id), None) + else: + # 根据类型映射到内置类别ID + type_to_cat_id = {'model': 'ai-models', 'gpu': 'gpus', 'cpu': 'cpus'} + cat_id = type_to_cat_id.get(category_type) + cat = next((c for c in categories if c['id'] == cat_id), None) + + # 构建字段模板 + fields = {} + + if cat and 'fields' in cat: + # 使用类别配置的字段 + for field in cat['fields']: + field_desc = field['label'] + if field['type'] == 'number': + field_desc += '(数字)' + elif field['type'] == 'boolean': + field_desc += '(true/false)' + elif field['type'] == 'date': + field_desc += '(日期格式YYYY-MM-DD)' + elif field['type'] == 'json': + field_desc += '(JSON对象)' + if field.get('description'): + field_desc += f" - {field['description']}" + fields[field['key']] = field_desc + + # 如果有子类别,添加子类别的额外字段 + if subcategory_id: + subcat = next((s for s in cat.get('subcategories', []) if s['id'] == subcategory_id), None) + if subcat and 'extra_fields' in subcat: + for field in subcat['extra_fields']: + field_desc = field['label'] + if field['type'] == 'number': + field_desc += '(数字)' + elif field['type'] == 'boolean': + field_desc += '(true/false)' + elif field['type'] == 'date': + field_desc += '(日期格式YYYY-MM-DD)' + if field.get('description'): + field_desc += f" - {field['description']}" + fields[field['key']] = field_desc + else: + # 兜底:使用默认字段模板 + fields = { 'name': '名称', 'brand': '品牌', 'price': '价格(数字)', 'year': '年份(数字)', - 'specs': '规格参数', + 'specs': '规格参数(JSON对象)', 'description': '简介描述', - }, - } + } - fields = field_templates.get(category_type, field_templates['dynamic']) fields_json = json.dumps(fields, ensure_ascii=False, indent=2) # 构建消息内容 @@ -498,11 +504,13 @@ def api_parse_images(): """ 解析图片中的产品参数(预览模式,不保存) 支持多张图片,可能返回多个产品 + 根据类别配置的参数字段进行解析 """ data = request.get_json() text = data.get('text', '') images = data.get('images', []) category_type = data.get('category_type', 'dynamic') + subcategory_id = data.get('subcategory_id', '') if not text and not images: return jsonify({'error': '文本或图片不能都为空'}), 400 @@ -510,8 +518,12 @@ def api_parse_images(): if not images: return jsonify({'error': '请上传至少一张图片'}), 400 - # 调用大模型解析 - parsed_list = parse_with_llm(text, category_type, images) + # 确定类别ID + type_to_cat_id = {'model': 'ai-models', 'gpu': 'gpus', 'cpu': 'cpus', 'dynamic': None} + category_id = type_to_cat_id.get(category_type) + + # 调用大模型解析(根据类别字段配置) + parsed_list = parse_with_llm(text, category_type, images, category_id=category_id, subcategory_id=subcategory_id) return jsonify({ 'success': True, @@ -691,12 +703,13 @@ def api_smart_add_model(): data = request.get_json() text = data.get('text', '') images = data.get('images', []) + subcategory_id = data.get('subcategory_id', '') # 子类别ID if not text and not images: return jsonify({'error': '文本或图片不能都为空'}), 400 - # 大模型解析(支持多图) - parsed_list = parse_with_llm(text, 'model', images) + # 大模型解析(根据类别字段配置) + parsed_list = parse_with_llm(text, 'model', images, category_id='ai-models', subcategory_id=subcategory_id) # 处理多个产品 results = [] @@ -707,8 +720,9 @@ def api_smart_add_model(): parsed['id'] = uuid.uuid4().hex[:12] parsed['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') parsed['visible'] = True - parsed['raw_text'] = text # 保存原始文本 - parsed['images'] = images # 保存图片 + parsed['raw_text'] = text + parsed['images'] = images + parsed['subcategory_id'] = subcategory_id # 保存子类别 parsed['publish_date'] = parsed.get('publish_date', '') parsed['views'] = 0 parsed['is_pinned'] = False @@ -718,7 +732,6 @@ def api_smart_add_model(): save_data(MODELS_FILE, models) - # 返回添加的产品列表 return jsonify({'success': True, 'count': len(results), 'products': results}) @app.route('/api/gpus/smart-add', methods=['POST']) @@ -727,11 +740,12 @@ def api_smart_add_gpu(): data = request.get_json() text = data.get('text', '') images = data.get('images', []) + subcategory_id = data.get('subcategory_id', '') if not text and not images: return jsonify({'error': '文本或图片不能都为空'}), 400 - parsed_list = parse_with_llm(text, 'gpu', images) + parsed_list = parse_with_llm(text, 'gpu', images, category_id='gpus', subcategory_id=subcategory_id) results = [] gpus = load_data(GPUS_FILE) @@ -742,6 +756,7 @@ def api_smart_add_gpu(): parsed['visible'] = True parsed['raw_text'] = text parsed['images'] = images + parsed['subcategory_id'] = subcategory_id parsed['publish_date'] = parsed.get('publish_date', '') parsed['views'] = 0 parsed['is_pinned'] = False @@ -759,11 +774,12 @@ def api_smart_add_cpu(): data = request.get_json() text = data.get('text', '') images = data.get('images', []) + subcategory_id = data.get('subcategory_id', '') if not text and not images: return jsonify({'error': '文本或图片不能都为空'}), 400 - parsed_list = parse_with_llm(text, 'cpu', images) + parsed_list = parse_with_llm(text, 'cpu', images, category_id='cpus', subcategory_id=subcategory_id) results = [] cpus = load_data(CPUS_FILE) @@ -774,6 +790,7 @@ def api_smart_add_cpu(): parsed['visible'] = True parsed['raw_text'] = text parsed['images'] = images + parsed['subcategory_id'] = subcategory_id parsed['publish_date'] = parsed.get('publish_date', '') parsed['views'] = 0 parsed['is_pinned'] = False @@ -791,11 +808,13 @@ def api_smart_add_item(category_id): data = request.get_json() text = data.get('text', '') images = data.get('images', []) + subcategory_id = data.get('subcategory_id', '') if not text and not images: return jsonify({'error': '文本或图片不能都为空'}), 400 - parsed_list = parse_with_llm(text, 'dynamic', images) + # 使用类别配置的字段解析 + parsed_list = parse_with_llm(text, 'dynamic', images, category_id=category_id, subcategory_id=subcategory_id) results = [] items_file = DATA_DIR / f'items_{category_id}.json' @@ -808,6 +827,7 @@ def api_smart_add_item(category_id): parsed['visible'] = True parsed['raw_text'] = text parsed['images'] = images + parsed['subcategory_id'] = subcategory_id parsed['publish_date'] = parsed.get('publish_date', '') parsed['views'] = 0 parsed['is_pinned'] = False diff --git a/data/items_71fa2b4d818f.json b/data/items_71fa2b4d818f.json index 8a408a2..c32983c 100644 --- a/data/items_71fa2b4d818f.json +++ b/data/items_71fa2b4d818f.json @@ -49,5 +49,54 @@ "is_pinned": false, "subcategory_id": "90ce312b560d", "updated_at": "2026-04-28 12:32:50" + }, + { + "name": "EOS R7", + "brand": "佳能 (Canon)", + "year": 2022, + "specs": { + "品牌": "佳能 (Canon)", + "商品编号": "10090975539899", + "店铺": "佳能 (Canon) 数码旗舰店", + "外接电源": "支持外接电源", + "电池类型": "锂离子电池", + "接口": "Wi-Fi 蓝牙 HDMI", + "高清摄像": "4K超高清视频", + "焦点数量": "5915个", + "型号": "EOS R7", + "有效像素": "3250万", + "传感器类型": "CMOS", + "上市时间": "2022-06", + "取景器类型": "电子取景器", + "液晶屏像素": "162万", + "液晶屏尺寸": "3.2英寸", + "液晶屏类型": "侧翻屏 旋转屏", + "最大光圈": "F3.5", + "标准ISO感光度": "ISO 100-32000", + "连拍速度": "电子最高约30张/秒,机械最高约15张/秒", + "存储介质": "SD卡 SDHC卡 SDXC卡", + "功能": "Wi-Fi 4K视频 5轴防抖 高速连拍 翻转自拍", + "滤镜直径": "55mm", + "视频拍摄能力": "4K 60P", + "传感器尺寸": "APS-C", + "视频采样": "4:2:2", + "像素": "3000-4000万", + "镜头卡口": "佳能RF卡口", + "RAW照片输出": "14bit", + "适用对象": "入门级", + "类型": "机身" + }, + "description": "入门级机身", + "id": "c8c3f124b2ce", + "category_id": "71fa2b4d818f", + "created_at": "2026-04-28 16:38:03", + "visible": true, + "raw_text": "", + "images": [ + "/static/uploads/9703a1d16424_1777365365.png" + ], + "publish_date": "", + "views": 0, + "is_pinned": false } ] \ No newline at end of file diff --git a/static/uploads/9703a1d16424_1777365365.png b/static/uploads/9703a1d16424_1777365365.png new file mode 100644 index 0000000..2bdc372 Binary files /dev/null and b/static/uploads/9703a1d16424_1777365365.png differ diff --git a/templates/admin.html b/templates/admin.html index 664b66c..72407bf 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -330,12 +330,20 @@