| import os |
| import sys |
| import turtle |
| import requests |
| import json |
| import gradio as gr |
| import logging |
| from typing import List, Optional, Dict, Tuple, Any |
| from dataclasses import dataclass |
| from dotenv import load_dotenv |
| import base64 |
| import threading |
| import time |
| from logging.handlers import RotatingFileHandler |
| import functools |
| from PIL import Image |
| import io |
| from urllib.parse import urlparse |
| import tempfile |
|
|
| |
| load_dotenv() |
|
|
| |
| def setup_logging(): |
| log_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'chatbot.log') |
| handler = RotatingFileHandler( |
| log_file, |
| maxBytes=10*1024*1024, |
| backupCount=5, |
| encoding='utf-8' |
| ) |
| handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) |
| |
| logger = logging.getLogger(__name__) |
| logger.setLevel(logging.INFO) |
| logger.addHandler(handler) |
| |
| |
| console_handler = logging.StreamHandler() |
| console_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) |
| logger.addHandler(console_handler) |
| |
| return logger |
|
|
| logger = setup_logging() |
|
|
| |
| class Config: |
| |
| API_BASE_URL = os.getenv('OLLAMA_API_URL', 'http://localhost:11434') |
| GRADIO_SERVER_PORT = int(os.getenv('GRADIO_SERVER_PORT', '7588')) |
| OCR_TIMEOUT = int(os.getenv('OCR_TIMEOUT', '60')) |
| CHAT_TIMEOUT = int(os.getenv('CHAT_TIMEOUT', '30')) |
| MAX_HISTORY = int(os.getenv('MAX_HISTORY', '100')) |
| RETRY_ATTEMPTS = int(os.getenv('RETRY_ATTEMPTS', '3')) |
| REQUEST_RATE_LIMIT = int(os.getenv('REQUEST_RATE_LIMIT', '10')) |
| CACHE_TIMEOUT = int(os.getenv('CACHE_TIMEOUT', '3600')) |
| MAX_INPUT_LENGTH = int(os.getenv('MAX_INPUT_LENGTH', '1000')) |
|
|
| API_GENERATE_URL = f"{API_BASE_URL}/api/generate" |
| API_CHAT_URL = f"{API_BASE_URL}/api/chat" |
|
|
| @classmethod |
| def validate(cls): |
| if not (1024 <= cls.GRADIO_SERVER_PORT <= 65535): |
| raise ValueError("Port must be between 1024 and 65535") |
| if cls.OCR_TIMEOUT < 5: |
| logger.warning(f"OCR_TIMEOUT is too small, using 5s") |
| cls.OCR_TIMEOUT = 5 |
| if cls.CHAT_TIMEOUT < 5: |
| logger.warning(f"CHAT_TIMEOUT is too small, using 5s") |
| cls.CHAT_TIMEOUT = 5 |
| try: |
| result = urlparse(cls.API_BASE_URL) |
| if not all([result.scheme, result.netloc]): |
| raise ValueError("Invalid URL format") |
| except Exception as e: |
| raise ValueError(f"Invalid API URL configuration: {str(e)}") |
|
|
| |
| Config.validate() |
|
|
| class RateLimit: |
| def __init__(self, max_requests: int, per_seconds: int = 60): |
| self.max_requests = max_requests |
| self.per_seconds = per_seconds |
| self.requests = [] |
| self._lock = threading.Lock() |
|
|
| def is_allowed(self) -> bool: |
| now = time.time() |
| with self._lock: |
| |
| self.requests = [req_time for req_time in self.requests |
| if now - req_time < self.per_seconds] |
| |
| if len(self.requests) >= self.max_requests: |
| return False |
| |
| self.requests.append(now) |
| return True |
|
|
| class Cache: |
| def __init__(self, timeout: int = 3600): |
| self.cache = {} |
| self.timeout = timeout |
| self._lock = threading.Lock() |
|
|
| def get(self, key: str) -> Optional[str]: |
| with self._lock: |
| if key in self.cache: |
| timestamp, value = self.cache[key] |
| if time.time() - timestamp < self.timeout: |
| return value |
| del self.cache[key] |
| return None |
|
|
| def set(self, key: str, value: str) -> None: |
| with self._lock: |
| self.cache[key] = (time.time(), value) |
|
|
| class Conversation: |
| def __init__(self, history: List[str], max_length: int = Config.MAX_HISTORY): |
| self.history = history |
| self.max_length = max_length |
|
|
| def add_message(self, message: str) -> None: |
| self.history.append(message) |
| if len(self.history) > self.max_length: |
| self.history = self.history[-self.max_length:] |
|
|
| def get_context(self) -> str: |
| return "\n".join(self.history) |
|
|
| def clear(self) -> None: |
| self.history = [] |
|
|
| class ResourceManager: |
| def __init__(self, base_dir: str = None): |
| if base_dir is None: |
| base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp') |
| self.base_dir = base_dir |
| self.temp_files = set() |
| self._lock = threading.Lock() |
| self._setup_temp_dir() |
| |
| def _setup_temp_dir(self): |
| """初始化临时目录""" |
| try: |
| os.makedirs(self.base_dir, exist_ok=True) |
| |
| self.cleanup_all() |
| except Exception as e: |
| logger.error(f"Failed to setup temp directory: {e}") |
| raise |
| |
| def add_temp_file(self, filepath: str): |
| """记录临时文件""" |
| with self._lock: |
| self.temp_files.add(filepath) |
| |
| def remove_temp_file(self, filepath: str): |
| """删除临时文件""" |
| with self._lock: |
| try: |
| if os.path.exists(filepath): |
| os.remove(filepath) |
| self.temp_files.discard(filepath) |
| except Exception as e: |
| logger.error(f"Failed to remove temp file {filepath}: {e}") |
| |
| def cleanup_all(self): |
| """清理所有临时文件""" |
| with self._lock: |
| for filepath in list(self.temp_files): |
| self.remove_temp_file(filepath) |
| |
| for filename in os.listdir(self.base_dir): |
| try: |
| filepath = os.path.join(self.base_dir, filename) |
| if os.path.isfile(filepath): |
| os.remove(filepath) |
| except Exception as e: |
| logger.error(f"Failed to remove file {filepath}: {e}") |
| |
| def create_temp_file(self, suffix: str = '') -> str: |
| """创建临时文件""" |
| with self._lock: |
| try: |
| fd, temp_path = tempfile.mkstemp(suffix=suffix, dir=self.base_dir) |
| os.close(fd) |
| self.add_temp_file(temp_path) |
| return temp_path |
| except Exception as e: |
| logger.error(f"Failed to create temp file: {e}") |
| raise |
| |
| def __del__(self): |
| """确保在对象销毁时清理临时文件""" |
| self.cleanup_all() |
|
|
| class ProcessState: |
| """处理状态管理""" |
| def __init__(self): |
| self._state = {} |
| self._lock = threading.Lock() |
| |
| def set_state(self, key: str, value: Any) -> None: |
| """设置状态""" |
| with self._lock: |
| self._state[key] = value |
| |
| def get_state(self, key: str, default: Any = None) -> Any: |
| """获取状态""" |
| with self._lock: |
| return self._state.get(key, default) |
| |
| def clear_state(self, key: str) -> None: |
| """清除状态""" |
| with self._lock: |
| self._state.pop(key, None) |
| |
| def is_processing(self, key: str) -> bool: |
| """检查是否正在处理""" |
| return self.get_state(key) == 'processing' |
|
|
| def retry_on_exception(retries: int = 3, delay: float = 1.0): |
| """重试装饰器""" |
| def decorator(func): |
| @functools.wraps(func) |
| def wrapper(*args, **kwargs): |
| for attempt in range(retries): |
| try: |
| return func(*args, **kwargs) |
| except Exception as e: |
| if attempt == retries - 1: |
| raise |
| logger.warning(f"Attempt {attempt + 1} failed: {e}") |
| time.sleep(delay * (attempt + 1)) |
| return None |
| return wrapper |
| return decorator |
|
|
| class ChatBot: |
| def __init__(self): |
| self.conversation = Conversation([]) |
| self.session = requests.Session() |
| self.current_image: Optional[str] = None |
| self.current_ocr_result: Optional[str] = None |
| self.rate_limit = RateLimit(Config.REQUEST_RATE_LIMIT) |
| self.cache = Cache(Config.CACHE_TIMEOUT) |
| self.resource_manager = ResourceManager() |
| self.process_state = ProcessState() |
| |
| |
| self.session.timeout = (Config.CHAT_TIMEOUT, Config.CHAT_TIMEOUT) |
| |
| |
| retry_strategy = requests.adapters.Retry( |
| total=3, |
| backoff_factor=0.5, |
| status_forcelist=[500, 502, 503, 504] |
| ) |
| adapter = requests.adapters.HTTPAdapter(max_retries=retry_strategy) |
| self.session.mount("http://", adapter) |
| self.session.mount("https://", adapter) |
|
|
| def check_ollama_service(self): |
| return check_ollama_service() |
|
|
| def _encode_image(self, image_path: str) -> str: |
| """编码图片,支持压缩""" |
| try: |
| |
| if not os.path.exists(image_path): |
| raise ValueError("图片文件不存在") |
| |
| |
| try: |
| with Image.open(image_path) as img: |
| if img.format.upper() not in {'JPEG', 'PNG', 'GIF', 'WEBP'}: |
| raise ValueError("不支持的图片格式") |
| except Exception as e: |
| raise ValueError(f"无效的图片文件: {str(e)}") |
| |
| |
| file_size = os.path.getsize(image_path) |
| max_size = 10 * 1024 * 1024 |
| |
| if file_size > max_size: |
| compressed_path = self.resource_manager.create_temp_file( |
| suffix=os.path.splitext(image_path)[1] |
| ) |
| image_path = self.compress_image(image_path, compressed_path, max_size) |
| |
| |
| with open(image_path, "rb") as image_file: |
| return base64.b64encode(image_file.read()).decode('utf-8') |
| |
| except Exception as e: |
| logger.error(f"Image processing failed: {str(e)}") |
| raise |
| |
| def compress_image(self, input_path: str, output_path: str, max_size: int) -> str: |
| """压缩图片到指定大小以下""" |
| try: |
| with Image.open(input_path) as img: |
| format = img.format or 'JPEG' |
| quality = 95 |
| |
| while True: |
| |
| img.save(output_path, format=format, quality=quality) |
| size = os.path.getsize(output_path) |
| |
| if size <= max_size or quality <= 5: |
| break |
| |
| quality -= 5 |
| |
| logger.info(f"Image compressed from {os.path.getsize(input_path)} to {size} bytes") |
| return output_path |
| |
| except Exception as e: |
| logger.error(f"Image compression failed: {str(e)}") |
| if os.path.exists(output_path): |
| self.resource_manager.remove_temp_file(output_path) |
| raise |
|
|
| @retry_on_exception(retries=Config.RETRY_ATTEMPTS) |
| def _validate_input(self, text: str) -> str: |
| """验证并清理输入文本""" |
| if not text or not text.strip(): |
| raise ValueError("输入不能为空") |
| |
| text = text.strip() |
| if len(text) > Config.MAX_INPUT_LENGTH: |
| raise ValueError(f"输入长度不能超过{Config.MAX_INPUT_LENGTH}字符") |
| |
| |
| text = text.replace('<', '<').replace('>', '>') |
| return text |
|
|
| def get_model_list(self) -> Tuple[List[str], str]: |
| """Returns (model_list, status_message)""" |
| try: |
| response = self.session.get( |
| f"{Config.API_BASE_URL}/api/tags", |
| timeout=Config.CHAT_TIMEOUT |
| ) |
| response.raise_for_status() |
| data = response.json() |
| if not data or 'models' not in data or not data['models']: |
| return [], "No models found. Please install at least one model using 'ollama pull model_name'" |
| |
| models = [] |
| for model in data['models']: |
| name = model['name'] |
| details = model.get('details', {}) |
| families = details.get('families', []) |
| |
| if 'clip' in families: |
| name = f"🖼️ {name}" |
| models.append(name) |
| return models, "Models refreshed successfully!" |
| except requests.exceptions.ConnectionError: |
| return [], "Error: Cannot connect to Ollama server. Is it running?" |
| except requests.exceptions.Timeout: |
| return [], "Error: Request timed out. Please try again." |
| except Exception as e: |
| logger.error(f"Error fetching model list: {e}") |
| return [], f"Error: {str(e)}" |
|
|
| def generate_response(self, prompt: str, model_name: str, image: Optional[str] = None, is_ocr: bool = False) -> str: |
| """生成响应,支持OCR和问答两种模式""" |
| |
| process_id = f"{time.time()}_{model_name}" |
| |
| try: |
| |
| if self.process_state.is_processing(model_name): |
| return "Error: 模型正在处理其他请求,请稍后再试" |
| |
| |
| self.process_state.set_state(model_name, 'processing') |
| |
| |
| try: |
| prompt = self._validate_input(prompt) |
| model_name = self._validate_input(model_name) |
| except ValueError as e: |
| return f"Error: {str(e)}" |
|
|
| |
| if not self.rate_limit.is_allowed(): |
| return "Error: 请求过于频繁,请稍后再试" |
|
|
| |
| cache_key = f"{prompt}_{model_name}_{image if image else ''}" |
| cached_response = self.cache.get(cache_key) |
| if cached_response: |
| return cached_response |
|
|
| |
| mode = "OCR" if is_ocr else "问答" |
| for attempt in range(Config.RETRY_ATTEMPTS): |
| try: |
| |
| timeout = Config.OCR_TIMEOUT if is_ocr else Config.CHAT_TIMEOUT |
| |
| headers = {"Content-Type": "application/json"} |
| payload = { |
| "model": model_name.replace("🖼️ ", ""), |
| "prompt": prompt, |
| "stream": True |
| } |
| |
| if image: |
| payload["images"] = [image] |
| |
| response = self.session.post( |
| Config.API_GENERATE_URL, |
| headers=headers, |
| json=payload, |
| stream=True, |
| timeout=timeout |
| ) |
| |
| if response.status_code != 200: |
| error_msg = f"服务器返回错误 {response.status_code}" |
| try: |
| error_data = response.json() |
| if 'error' in error_data: |
| error_msg += f": {error_data['error']}" |
| except: |
| if response.text: |
| error_msg += f": {response.text}" |
| logger.error(f"{mode}请求失败: {error_msg}") |
| return f"Error: {error_msg}" |
| |
| |
| full_response = [] |
| try: |
| for line in response.iter_lines(decode_unicode=True): |
| if line: |
| try: |
| data = json.loads(line) |
| if 'response' in data: |
| full_response.append(data['response']) |
| except json.JSONDecodeError as e: |
| logger.warning(f"{mode}响应解析错误: {str(e)}, line: {line}") |
| continue |
| except requests.exceptions.ChunkedEncodingError as e: |
| error_msg = f"{mode}流式响应中断: {str(e)}" |
| logger.error(error_msg) |
| if not full_response: |
| return f"Error: {error_msg}" |
| |
| logger.warning(f"{mode}使用已接收的部分响应") |
| |
| result = "".join(full_response).strip() |
| if not result: |
| error_msg = f"{mode}生成的响应为空" |
| logger.error(error_msg) |
| return f"Error: {error_msg}" |
| |
| |
| self.cache.set(cache_key, result) |
| return result |
| |
| except requests.exceptions.Timeout: |
| error_msg = f"{mode}请求超时(尝试 {attempt + 1}/{Config.RETRY_ATTEMPTS})" |
| logger.error(error_msg) |
| if attempt == Config.RETRY_ATTEMPTS - 1: |
| return f"Error: {error_msg}" |
| continue |
| |
| except requests.exceptions.RequestException as e: |
| error_msg = f"{mode}请求异常: {str(e)}" |
| logger.error(error_msg) |
| return f"Error: {error_msg}" |
| |
| except Exception as e: |
| error_msg = f"{mode}处理过程出错: {str(e)}" |
| logger.error(error_msg) |
| return f"Error: {error_msg}" |
| |
| finally: |
| |
| self.process_state.clear_state(model_name) |
|
|
| def clear_conversation(self) -> str: |
| self.conversation.clear() |
| return "Conversation cleared." |
|
|
| def __del__(self): |
| """清理资源""" |
| try: |
| self.session.close() |
| if hasattr(self, 'resource_manager'): |
| self.resource_manager.cleanup_all() |
| except Exception as e: |
| logger.error(f"Cleanup failed: {e}") |
|
|
| def check_ollama_service(max_attempts: int = 3, wait_time: int = 2) -> bool: |
| """检查Ollama服务是否可用,支持多次重试""" |
| for attempt in range(max_attempts): |
| try: |
| response = requests.get(Config.API_BASE_URL, timeout=5) |
| if response.status_code == 200: |
| return True |
| logger.warning(f"Ollama service check failed (attempt {attempt + 1}/{max_attempts}): Status code {response.status_code}") |
| except requests.exceptions.ConnectionError: |
| logger.warning(f"Ollama service connection failed (attempt {attempt + 1}/{max_attempts})") |
| except requests.exceptions.Timeout: |
| logger.warning(f"Ollama service timeout (attempt {attempt + 1}/{max_attempts})") |
| except Exception as e: |
| logger.error(f"Unexpected error checking Ollama service: {str(e)}") |
| |
| if attempt < max_attempts - 1: |
| time.sleep(wait_time * (attempt + 1)) |
| |
| return False |
|
|
| def create_interface() -> None: |
| try: |
| chatbot = ChatBot() |
| |
| |
| models, status = chatbot.get_model_list() |
| if not models: |
| logger.error(f"无法获取模型列表: {status}") |
| raise ValueError(f"无法获取模型列表: {status}") |
| |
| |
| vision_models = [m for m in models if "🖼️" in m] |
| all_models = models |
| |
| if not vision_models: |
| logger.error("未找到支持视觉功能的模型") |
| raise ValueError("未找到支持视觉功能的模型,请确保安装了支持视觉功能的模型(如llava)") |
| |
| interface = gr.Blocks( |
| title="智能文档分析系统", |
| theme=gr.themes.Default(), |
| css=".output-text {font-size: 16px;}" |
| ) |
|
|
| with interface: |
| |
| with gr.Column(elem_classes="header"): |
| gr.Markdown("# 智能文档分析系统") |
| |
| |
| with gr.Column(): |
| gr.Markdown("### 图片识别(可选)") |
| with gr.Row(): |
| image_input = gr.Image( |
| type="filepath", |
| label="上传图片(可选)", |
| interactive=True |
| ) |
| ocr_model_selector = gr.Dropdown( |
| choices=vision_models, |
| label="选择OCR模型(可选)", |
| value=None, |
| interactive=True |
| ) |
| ocr_output = gr.Textbox( |
| label="识别结果", |
| lines=4, |
| interactive=False, |
| elem_classes="output-text" |
| ) |
| |
| |
| with gr.Column(): |
| gr.Markdown("### 问答") |
| question_input = gr.Textbox( |
| label="输入问题", |
| lines=2, |
| interactive=True |
| ) |
| with gr.Row(): |
| chat_model_selector1 = gr.Dropdown( |
| choices=all_models, |
| label="模型1", |
| value=all_models[0] if all_models else None, |
| interactive=True |
| ) |
| chat_model_selector2 = gr.Dropdown( |
| choices=all_models, |
| label="模型2(可选)", |
| value=None, |
| interactive=True |
| ) |
| chat_model_selector3 = gr.Dropdown( |
| choices=all_models, |
| label="模型3(可选)", |
| value=None, |
| interactive=True |
| ) |
| with gr.Row(): |
| chat_output1 = gr.Textbox( |
| label="模型1回答", |
| lines=4, |
| interactive=False, |
| elem_classes="output-text" |
| ) |
| chat_output2 = gr.Textbox( |
| label="模型2回答", |
| lines=4, |
| interactive=False, |
| elem_classes="output-text" |
| ) |
| chat_output3 = gr.Textbox( |
| label="模型3回答", |
| lines=4, |
| interactive=False, |
| elem_classes="output-text" |
| ) |
| |
| |
| status_text = gr.Textbox( |
| label="状态", |
| interactive=False, |
| elem_classes="status-text" |
| ) |
| |
| |
| with gr.Row(): |
| process_button = gr.Button( |
| "开始处理", |
| variant="primary", |
| elem_classes="primary", |
| interactive=True |
| ) |
| retry_button = gr.Button( |
| "重新问答", |
| variant="secondary", |
| elem_classes="secondary", |
| interactive=True |
| ) |
|
|
| |
| ocr_result_store = gr.State(value=None) |
|
|
| |
| def process_all(image_path, ocr_model, question, chat_model1, chat_model2, chat_model3, progress=gr.Progress()): |
| """一键处理所有流程""" |
| |
| if not question.strip(): |
| return ( |
| "", |
| "", |
| "", |
| "", |
| "请输入问题", |
| None |
| ) |
|
|
| if not any([chat_model1, chat_model2, chat_model3]): |
| return ( |
| "", |
| "", |
| "", |
| "", |
| "请至少选择一个问答模型", |
| None |
| ) |
|
|
| results = { |
| "ocr_output": "", |
| "chat_output1": "", |
| "chat_output2": "", |
| "chat_output3": "", |
| "status_text": "处理中...", |
| "ocr_result_store": None |
| } |
|
|
| try: |
| chatbot = ChatBot() |
| qa_prompt = question |
| |
| |
| if image_path and ocr_model: |
| try: |
| progress(0.2, desc="正在进行图片识别...") |
| base64_image = chatbot._encode_image(image_path) |
| ocr_prompt = "请识别这张图片中的文字内容,尽可能保持原有格式。" |
| ocr_result = chatbot.generate_response(ocr_prompt, ocr_model, base64_image, is_ocr=True) |
| |
| |
| if ocr_result and not ocr_result.startswith("Error:"): |
| |
| ocr_result = "\n".join(line for line in ocr_result.splitlines() if line.strip()) |
| results["ocr_output"] = ocr_result |
| results["ocr_result_store"] = ocr_result |
| |
| qa_prompt = f"{question}\n\n补充信息:\n{ocr_result}" |
| else: |
| |
| logger.error(f"OCR识别失败: {ocr_result}") |
| results["ocr_output"] = ocr_result |
| results["ocr_result_store"] = None |
| qa_prompt = question |
| except Exception as e: |
| error_msg = f"OCR处理出错: {str(e)}" |
| logger.error(error_msg) |
| results["ocr_output"] = f"错误: {error_msg}" |
| results["ocr_result_store"] = None |
| |
| qa_prompt = question |
| |
| |
| progress(0.4, desc="准备进行问答...") |
| total_models = sum(1 for m in [chat_model1, chat_model2, chat_model3] if m) |
| current_model = 0 |
| |
| if chat_model1: |
| current_model += 1 |
| progress(0.4 + 0.6 * (current_model / total_models), desc=f"获取模型{current_model}回答...") |
| try: |
| results["chat_output1"] = chatbot.generate_response(qa_prompt, chat_model1) |
| except Exception as e: |
| logger.error(f"模型1回答出错: {str(e)}") |
| results["chat_output1"] = f"错误: {str(e)}" |
| |
| if chat_model2: |
| current_model += 1 |
| progress(0.4 + 0.6 * (current_model / total_models), desc=f"获取模型{current_model}回答...") |
| try: |
| results["chat_output2"] = chatbot.generate_response(qa_prompt, chat_model2) |
| except Exception as e: |
| logger.error(f"模型2回答出错: {str(e)}") |
| results["chat_output2"] = f"错误: {str(e)}" |
| |
| if chat_model3: |
| current_model += 1 |
| progress(0.4 + 0.6 * (current_model / total_models), desc=f"获取模型{current_model}回答...") |
| try: |
| results["chat_output3"] = chatbot.generate_response(qa_prompt, chat_model3) |
| except Exception as e: |
| logger.error(f"模型3回答出错: {str(e)}") |
| results["chat_output3"] = f"错误: {str(e)}" |
| |
| progress(1.0, desc="处理完成") |
| results["status_text"] = "处理完成" |
| return ( |
| results["ocr_output"], |
| results["chat_output1"], |
| results["chat_output2"], |
| results["chat_output3"], |
| results["status_text"], |
| results["ocr_result_store"] |
| ) |
| |
| except Exception as e: |
| logger.error(f"处理过程出错: {str(e)}") |
| error_msg = f"错误: {str(e)}" |
| return ( |
| "", |
| "", |
| "", |
| "", |
| error_msg, |
| None |
| ) |
|
|
| |
| def retry_qa(question, chat_model1, chat_model2, chat_model3, ocr_result): |
| """重新进行问答""" |
| if not question.strip(): |
| return ["请输入问题"] * 3 |
|
|
| if not any([chat_model1, chat_model2, chat_model3]): |
| return ["请至少选择一个问答模型"] * 3 |
|
|
| try: |
| chatbot = ChatBot() |
| |
| qa_prompt = question |
| if ocr_result and ocr_result.strip(): |
| qa_prompt = f"{question}\n\n补充信息:\n{ocr_result}" |
| |
| results = [""] * 3 |
| if chat_model1: |
| try: |
| results[0] = chatbot.generate_response(qa_prompt, chat_model1) |
| except Exception as e: |
| logger.error(f"模型1重试出错: {str(e)}") |
| results[0] = f"错误: {str(e)}" |
| |
| if chat_model2: |
| try: |
| results[1] = chatbot.generate_response(qa_prompt, chat_model2) |
| except Exception as e: |
| logger.error(f"模型2重试出错: {str(e)}") |
| results[1] = f"错误: {str(e)}" |
| |
| if chat_model3: |
| try: |
| results[2] = chatbot.generate_response(qa_prompt, chat_model3) |
| except Exception as e: |
| logger.error(f"模型3重试出错: {str(e)}") |
| results[2] = f"错误: {str(e)}" |
| |
| return results |
| except Exception as e: |
| logger.error(f"重试过程出错: {str(e)}") |
| return [f"错误: {str(e)}"] * 3 |
|
|
| |
| process_button.click( |
| fn=process_all, |
| inputs=[ |
| image_input, |
| ocr_model_selector, |
| question_input, |
| chat_model_selector1, |
| chat_model_selector2, |
| chat_model_selector3 |
| ], |
| outputs=[ |
| ocr_output, |
| chat_output1, |
| chat_output2, |
| chat_output3, |
| status_text, |
| ocr_result_store |
| ], |
| api_name="process" |
| ) |
|
|
| |
| retry_button.click( |
| fn=retry_qa, |
| inputs=[ |
| question_input, |
| chat_model_selector1, |
| chat_model_selector2, |
| chat_model_selector3, |
| ocr_result_store |
| ], |
| outputs=[ |
| chat_output1, |
| chat_output2, |
| chat_output3 |
| ], |
| api_name="retry" |
| ) |
|
|
| |
| def clear_outputs(): |
| return [""] * 4 |
| |
| image_input.change( |
| fn=clear_outputs, |
| inputs=None, |
| outputs=[ocr_output, chat_output1, chat_output2, chat_output3] |
| ) |
| |
| ocr_model_selector.change( |
| fn=clear_outputs, |
| inputs=None, |
| outputs=[ocr_output, chat_output1, chat_output2, chat_output3] |
| ) |
|
|
| interface.launch( |
| server_name="0.0.0.0", |
| server_port=7588, |
| share=True, |
| inbrowser=True |
| ) |
| |
| except Exception as e: |
| logger.error(f"Failed to create interface: {e}") |
| sys.exit(1) |
|
|
| if __name__ == "__main__": |
| |
| log_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'chatbot.log') |
| file_handler = logging.FileHandler(log_file, encoding='utf-8') |
| file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) |
| logger.addHandler(file_handler) |
| |
| try: |
| logger.info("Starting chatbot interface...") |
| print("正在启动聊天机器人界面...") |
| if not ChatBot().check_ollama_service(): |
| error_msg = "Error: Ollama service is not running. Please start Ollama first." |
| logger.error(error_msg) |
| print(error_msg) |
| input("按回车键退出...") |
| sys.exit(1) |
| |
| create_interface() |
| except Exception as e: |
| print(f"\nError: {str(e)}") |
| logger.error(f"Application error: {str(e)}", exc_info=True) |
| print("\nPress Enter to exit...") |
| input() |
| sys.exit(1) |
|
|
|
|