# 请将标题行设为全宽,即与界面全宽,计时器可叠加到其上
import sys
import os
import base64
import json
import requests
import time
from datetime import datetime
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QComboBox, QTextEdit, QFileDialog, QGroupBox, QSlider,
QDoubleSpinBox, QProgressBar, QFrame, QSplitter, QMessageBox,
QListWidget, QListWidgetItem)
from PyQt5.QtGui import QPixmap, QImage, QFont, QPalette, QColor, QIcon, QPainter, QLinearGradient, QBrush
from PyQt5.QtCore import Qt, QSize, QThread, pyqtSignal, QPoint, QTimer
# 配置Ollama API设置 - 使用默认端口11434
OLLAMA_HOST = "http://localhost:11434"
class ModelLoaderThread(QThread):
models_loaded = pyqtSignal(list)
error_occurred = pyqtSignal(str)
def __init__(self, parent=None):
super().__init__(parent)
def run(self):
try:
health_url = f"{OLLAMA_HOST}"
try:
response = requests.get(health_url, timeout=10)
if response.status_code != 200:
raise Exception(f"Ollama服务不可用 (HTTP {response.status_code})")
except Exception as e:
self.error_occurred.emit(f"Ollama服务检查失败: {str(e)}")
return
response = requests.get(f"{OLLAMA_HOST}/api/tags", timeout=15)
if response.status_code == 200:
models_data = response.json()
model_list = [model["name"] for model in models_data.get("models", [])]
if model_list:
self.models_loaded.emit(model_list)
else:
self.error_occurred.emit("Ollama服务返回了空模型列表")
else:
self.error_occurred.emit(f"API错误 ({response.status_code}): {response.text[:200]}")
except requests.exceptions.ConnectionError:
self.error_occurred.emit("无法连接到Ollama服务。请确保Ollama已启动并正在运行。")
except requests.exceptions.Timeout:
self.error_occurred.emit("连接Ollama服务超时。请检查网络连接或增加超时设置。")
except Exception as e:
self.error_occurred.emit(f"加载模型时发生错误: {str(e)}")
class StreamAnalysisThread(QThread):
chunk_received = pyqtSignal(str)
analysis_complete = pyqtSignal(str)
progress_updated = pyqtSignal(int)
error_occurred = pyqtSignal(str)
def __init__(self, model, image_path, temperature, max_tokens, prompt, parent=None):
super().__init__(parent)
self.model = model
self.image_path = image_path
self.temperature = temperature
self.max_tokens = max_tokens
self.prompt = prompt
self._is_running = True
def stop(self):
self._is_running = False
def run(self):
try:
if not os.path.exists(self.image_path):
self.error_occurred.emit(f"图片文件不存在: {self.image_path}")
return
self.progress_updated.emit(10)
with open(self.image_path, "rb") as image_file:
base64_image = base64.b64encode(image_file.read()).decode("utf-8")
data = {
"model": self.model,
"prompt": self.prompt,
"images": [base64_image],
"options": {
"temperature": self.temperature,
"num_predict": self.max_tokens
},
"stream": True
}
self.progress_updated.emit(30)
response = requests.post(
f"{OLLAMA_HOST}/api/generate",
json=data,
headers={"Content-Type": "application/json"},
stream=True,
timeout=180
)
if response.status_code == 200:
full_response = ""
for line in response.iter_lines():
if not self._is_running:
break
if line:
try:
json_response = json.loads(line.decode('utf-8'))
if 'response' in json_response:
chunk = json_response['response']
full_response += chunk
self.chunk_received.emit(chunk)
if json_response.get('done', False):
break
except json.JSONDecodeError:
continue
self.progress_updated.emit(90)
self.analysis_complete.emit(full_response)
else:
self.error_occurred.emit(f"API错误 ({response.status_code}): {response.text[:200]}")
self.progress_updated.emit(100)
except requests.exceptions.ConnectionError:
self.error_occurred.emit("无法连接到Ollama服务。请确保Ollama已启动并正在运行。")
except requests.exceptions.Timeout:
self.error_occurred.emit("分析请求超时。请尝试减小图片大小或降低模型复杂度。")
except Exception as e:
self.error_occurred.emit(f"分析图片时发生错误: {str(e)}")
class GradientLabel(QLabel):
def __init__(self, text, parent=None):
super().__init__(text, parent)
self.setAlignment(Qt.AlignCenter)
self.setMinimumHeight(60)
def paintEvent(self, event):
painter = QPainter(self)
gradient = QLinearGradient(0, 0, self.width(), 0)
gradient.setColorAt(0, QColor("#6a11cb"))
gradient.setColorAt(1, QColor("#2575fc"))
painter.fillRect(self.rect(), QBrush(gradient))
painter.setPen(Qt.white)
painter.setFont(QFont("Microsoft YaHei UI", 18, QFont.Bold))
painter.drawText(self.rect(), Qt.AlignCenter, self.text())
class MultiModalApp(QMainWindow):
def __init__(self):
super().__init__()
self.image_path = ""
self.analysis_thread = None
self.model_loader_thread = None
self.current_response = ""
self.start_time = None
self.timer = QTimer()
self.elapsed_time = 0
self.initUI()
self.setWindowTitle("多模态大模型图片解读系统")
self.setGeometry(100, 100, 1400, 900)
self.timer.timeout.connect(self.update_timer)
QTimer.singleShot(500, self.load_models)
def initUI(self):
self.setStyleSheet("""
QMainWindow { background-color: #f0f4f8; }
QGroupBox {
border: 2px solid #4a86e8; border-radius: 12px; margin-top: 20px;
background-color: rgba(255, 255, 255, 0.95); color: #2c3e50;
font-weight: bold; font-size: 12pt;
}
QGroupBox::title {
subcontrol-origin: margin; subcontrol-position: top center;
padding: 8px 20px; background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #6a11cb, stop:1 #2575fc);
color: white; border-radius: 8px; top: -0px; min-height: 35px;
}
QLabel { color: #2c3e50; }
QPushButton {
background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #4a86e8, stop:1 #6a11cb);
color: white; border: none; border-radius: 8px; padding: 8px 16px;
font-weight: bold; font-size: 11pt; min-height: 35px;
}
QPushButton:hover {
background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #5a96f8, stop:1 #7a21db);
}
QPushButton:pressed {
background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #3a76d8, stop:1 #5a01bb);
}
QPushButton:disabled { background-color: #b0c4de; color: #777777; }
QComboBox {
background-color: white; color: #2c3e50; border: 1px solid #cccccc;
border-radius: 8px; padding: 5px 10px; min-height: 30px; font-size: 11pt;
}
QComboBox::drop-down { border: none; }
QComboBox QAbstractItemView {
background-color: white; color: #2c3e50; selection-background-color: #e0f0ff;
border-radius: 8px; border: 1px solid #cccccc; font-size: 11pt;
}
QTextEdit {
background-color: white; color: #2c3e50; border: 1px solid #cccccc;
border-radius: 8px; padding: 10px; font-size: 10pt;
}
QSlider::groove:horizontal {
border: 1px solid #cccccc; height: 10px; background: #e0e0e0;
margin: 2px 0; border-radius: 5px;
}
QSlider::handle:horizontal {
background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #6a11cb, stop:1 #4a86e8);
border: 1px solid #4a76b0; width: 22px; margin: -6px 0; border-radius: 11px;
}
QDoubleSpinBox {
background-color: white; color: #2c3e50; border: 1px solid #cccccc;
border-radius: 8px; padding: 5px 10px; min-height: 30px; font-size: 11pt;
}
QProgressBar {
border: 1px solid #cccccc; border-radius: 8px; text-align: center;
color: #2c3e50; background-color: white; font-size: 11pt; height: 25px;
}
QProgressBar::chunk {
background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #6a11cb, stop:1 #4a86e8);
border-radius: 7px;
}
QListWidget {
background-color: white; color: #2c3e50; border: 1px solid #cccccc;
border-radius: 8px; font-size: 11pt;
}
QSplitter::handle { background-color: #4a86e8; width: 4px; }
""")
app_font = QFont("Microsoft YaHei UI", 10)
app_font.setBold(False)
QApplication.setFont(app_font)
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
main_layout.setSpacing(10)
main_layout.setContentsMargins(15, 10, 15, 15)
timer_row = QWidget()
timer_row.setFixedHeight(80)
timer_layout = QHBoxLayout(timer_row)
timer_layout.setContentsMargins(0, 0, 0, 0)
title_label = QLabel("多模态大模型图片解读系统")
title_label.setStyleSheet("""
background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #6a11cb, stop:1 #2575fc);
color: white; font-size: 24pt; font-weight: bold; padding: 15px; border-radius: 12px;
""")
title_label.setAlignment(Qt.AlignCenter)
timer_widget = QWidget()
timer_widget.setFixedWidth(200)
timer_widget_layout = QVBoxLayout(timer_widget)
timer_widget_layout.setContentsMargins(10, 5, 10, 5)
timer_label = QLabel("分析用时")
timer_label.setStyleSheet("""
font-weight: bold; font-size: 12pt; color: white;
background-color: rgba(255, 255, 255, 0.2); padding: 5px; border-radius: 5px;
""")
timer_label.setAlignment(Qt.AlignCenter)
self.timer_display = QLabel("00:00:00")
self.timer_display.setStyleSheet("""
background-color: rgba(255, 255, 255, 0.9); color: #2c3e50; font-size: 18pt;
font-weight: bold; padding: 8px 12px; border-radius: 8px;
border: 2px solid rgba(255, 255, 255, 0.5);
""")
self.timer_display.setAlignment(Qt.AlignCenter)
self.timer_display.setFixedHeight(45)
timer_widget_layout.addWidget(timer_label)
timer_widget_layout.addWidget(self.timer_display)
timer_layout.addWidget(title_label, 1)
timer_layout.addWidget(timer_widget)
main_layout.addWidget(timer_row)
splitter = QSplitter(Qt.Horizontal)
splitter.setChildrenCollapsible(False)
left_panel = QWidget()
left_layout = QVBoxLayout(left_panel)
left_layout.setSpacing(15)
left_layout.setContentsMargins(10, 15, 10, 10)
self.image_group = QGroupBox("图片预览")
self.image_group.setMinimumHeight(350)
image_layout = QVBoxLayout(self.image_group)
image_layout.setContentsMargins(10, 35, 10, 10)
self.image_label = QLabel()
self.image_label.setAlignment(Qt.AlignCenter)
self.image_label.setStyleSheet("""
background-color: #ffffff; border: 1px solid #cccccc;
border-radius: 8px; padding: 10px;
""")
self.image_label.setText("请选择图片进行分析")
self.image_label.setFont(QFont("Microsoft YaHei UI", 12))
image_layout.addWidget(self.image_label)
control_group = QGroupBox("分析控制")
control_layout = QVBoxLayout(control_group)
control_layout.setContentsMargins(15, 35, 15, 15)
model_layout = QHBoxLayout()
model_label = QLabel("选择模型:")
model_label.setFixedWidth(100)
model_label.setStyleSheet("font-weight: bold;")
self.model_list = QListWidget()
self.model_list.setMaximumHeight(150)
self.model_list.addItem("正在加载模型...")
model_layout.addWidget(model_label)
model_layout.addWidget(self.model_list)
control_layout.addLayout(model_layout)
temp_layout = QHBoxLayout()
temp_label = QLabel("温度:")
temp_label.setFixedWidth(100)
temp_label.setStyleSheet("font-weight: bold;")
self.temp_slider = QSlider(Qt.Horizontal)
self.temp_slider.setRange(0, 100)
self.temp_slider.setValue(50)
self.temp_value = QDoubleSpinBox()
self.temp_value.setRange(0.0, 1.0)
self.temp_value.setSingleStep(0.1)
self.temp_value.setValue(0.5)
self.temp_value.setDecimals(1)
self.temp_slider.valueChanged.connect(lambda val: self.temp_value.setValue(val / 100))
self.temp_value.valueChanged.connect(lambda val: self.temp_slider.setValue(int(val * 100)))
temp_layout.addWidget(temp_label)
temp_layout.addWidget(self.temp_slider)
temp_layout.addWidget(self.temp_value)
control_layout.addLayout(temp_layout)
token_layout = QHBoxLayout()
token_label = QLabel("最大Token:")
token_label.setFixedWidth(100)
token_label.setStyleSheet("font-weight: bold;")
self.token_spin = QDoubleSpinBox()
self.token_spin.setRange(100, 5000)
self.token_spin.setValue(1000)
self.token_spin.setSingleStep(100)
token_layout.addWidget(token_label)
token_layout.addWidget(self.token_spin)
control_layout.addLayout(token_layout)
prompt_layout = QVBoxLayout()
prompt_label = QLabel("提示词:")
prompt_label.setStyleSheet("font-weight: bold;")
self.prompt_edit = QTextEdit()
self.prompt_edit.setPlainText(
"请用中文详细描述这张图片的内容,要求描述清晰、有条理,分段落呈现,各段首行按要求缩进2个汉字。")
self.prompt_edit.setMaximumHeight(100)
prompt_layout.addWidget(prompt_label)
prompt_layout.addWidget(self.prompt_edit)
control_layout.addLayout(prompt_layout)
button_layout = QHBoxLayout()
self.load_button = QPushButton("加载图片")
self.load_button.setIcon(QIcon.fromTheme("document-open"))
self.load_button.clicked.connect(self.load_image)
self.analyze_button = QPushButton("分析图片")
self.analyze_button.setIcon(QIcon.fromTheme("system-search"))
self.analyze_button.clicked.connect(self.analyze_image)
self.analyze_button.setEnabled(False)
self.clear_button = QPushButton("清除结果")
self.clear_button.setIcon(QIcon.fromTheme("edit-clear"))
self.clear_button.clicked.connect(self.clear_results)
self.refresh_models_button = QPushButton("刷新模型")
self.refresh_models_button.setIcon(QIcon.fromTheme("view-refresh"))
self.refresh_models_button.clicked.connect(self.load_models)
self.stop_button = QPushButton("停止分析")
self.stop_button.setIcon(QIcon.fromTheme("process-stop"))
self.stop_button.clicked.connect(self.stop_analysis)
self.stop_button.setEnabled(False)
button_layout.addWidget(self.load_button)
button_layout.addWidget(self.analyze_button)
button_layout.addWidget(self.stop_button)
button_layout.addWidget(self.clear_button)
button_layout.addWidget(self.refresh_models_button)
control_layout.addLayout(button_layout)
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(0)
self.progress_bar.setTextVisible(True)
self.progress_bar.setFormat("等待操作...")
control_layout.addWidget(self.progress_bar)
right_panel = QWidget()
right_layout = QVBoxLayout(right_panel)
right_layout.setContentsMargins(5, 5, 5, 5)
result_group = QGroupBox("分析结果")
result_layout = QVBoxLayout(result_group)
result_layout.setContentsMargins(10, 35, 10, 10)
self.result_edit = QTextEdit()
self.result_edit.setReadOnly(True)
self.result_edit.setStyleSheet("""
font-size: 10pt; line-height: 1.15; background-color: white;
color: #2c3e50; padding: 15px; border: 1px solid #cccccc; border-radius: 8px;
""")
result_layout.addWidget(self.result_edit)
left_layout.addWidget(self.image_group)
left_layout.addWidget(control_group)
right_layout.addWidget(result_group)
splitter.addWidget(left_panel)
splitter.addWidget(right_panel)
splitter.setSizes([500, 900])
main_layout.addWidget(splitter)
self.statusBar().setStyleSheet("""
background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #6a11cb, stop:1 #2575fc);
color: white; font-weight: bold; padding-left: 10px;
""")
self.statusBar().showMessage("正在初始化...")
def update_timer(self):
self.elapsed_time += 1
hours = self.elapsed_time // 3600
minutes = (self.elapsed_time % 3600) // 60
seconds = self.elapsed_time % 60
self.timer_display.setText(f"{hours:02d}:{minutes:02d}:{seconds:02d}")
def start_timer(self):
self.elapsed_time = 0
self.timer.start(1000)
self.timer_display.setText("00:00:00")
def stop_timer(self):
self.timer.stop()
def show_error_dialog(self, title, message):
msg = QMessageBox(self)
msg.setIcon(QMessageBox.Critical)
msg.setWindowTitle(title)
msg.setText("发生错误")
msg.setInformativeText(message)
if "Ollama" in title:
msg.addButton("打开Ollama网站", QMessageBox.ActionRole)
msg.addButton("重试", QMessageBox.ActionRole)
msg.addButton(QMessageBox.Ok)
msg.setStyleSheet("""
QMessageBox { background-color: #f0f4f8; border: 2px solid #4a86e8; border-radius: 12px; }
QLabel { color: #2c3e50; }
QPushButton {
background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #4a86e8, stop:1 #6a11cb);
color: white; border: none; border-radius: 8px; padding: 8px 16px;
min-width: 80px; font-weight: bold;
}
QPushButton:hover {
background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #5a96f8, stop:1 #7a21db);
}
""")
result = msg.exec_()
if result == QMessageBox.Ok:
pass
elif msg.clickedButton().text() == "打开Ollama网站":
import webbrowser
webbrowser.open("https://ollama.com/")
elif msg.clickedButton().text() == "重试":
self.load_models()
def load_models(self):
self.model_list.clear()
self.model_list.addItem("正在加载模型...")
self.refresh_models_button.setEnabled(False)
self.statusBar().showMessage("正在从Ollama获取模型列表...")
self.model_loader_thread = ModelLoaderThread()
self.model_loader_thread.models_loaded.connect(self.update_model_list)
self.model_loader_thread.error_occurred.connect(self.handle_model_load_error)
self.model_loader_thread.finished.connect(self.model_loader_finished)
self.model_loader_thread.start()
def update_model_list(self, model_list):
self.model_list.clear()
if model_list:
multimodal_models = [model for model in model_list if "vision" in model.lower() or "llava" in model.lower()]
title_item = QListWidgetItem("=== 多模态大模型 ===")
self.model_list.addItem(title_item)
title_item.setForeground(QColor("#4a86e8"))
title_item.setFont(QFont("Microsoft YaHei UI", 10, QFont.Bold))
title_item.setFlags(Qt.NoItemFlags)
for model in multimodal_models:
item = QListWidgetItem(f"● {model}")
item.setForeground(QColor("#2c3e50"))
item.setFont(QFont("Microsoft YaHei UI", 10))
item.setData(Qt.UserRole, model)
self.model_list.addItem(item)
other_models = [model for model in model_list if model not in multimodal_models]
if other_models:
title_item = QListWidgetItem("=== 其他模型 ===")
self.model_list.addItem(title_item)
title_item.setForeground(QColor("#4a86e8"))
title_item.setFont(QFont("Microsoft YaHei UI", 10, QFont.Bold))
title_item.setFlags(Qt.NoItemFlags)
for model in other_models:
item = QListWidgetItem(f"○ {model}")
item.setForeground(QColor("#2c3e50"))
item.setFont(QFont("Microsoft YaHei UI", 10))
item.setData(Qt.UserRole, model)
self.model_list.addItem(item)
self.statusBar().showMessage(f"已加载 {len(model_list)} 个模型")
if multimodal_models:
self.model_list.setCurrentRow(1)
else:
self.model_list.addItem("未找到可用模型")
self.statusBar().showMessage("未找到可用模型")
def handle_model_load_error(self, error):
self.model_list.clear()
self.model_list.addItem("加载模型失败")
self.statusBar().showMessage(error)
self.show_error_dialog("模型加载错误",
f"{error}\n\n可能原因:\n1. Ollama服务未启动 (当前地址: {OLLAMA_HOST})\n2. Ollama未正确安装\n3. 网络连接问题\n\n解决方案:\n1. 下载并安装Ollama: https://ollama.com/download\n2. 启动Ollama服务\n3. 检查网络连接")
def model_loader_finished(self):
self.refresh_models_button.setEnabled(True)
def load_image(self):
file_path, _ = QFileDialog.getOpenFileName(
self, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif)"
)
if file_path:
try:
self.image_path = file_path
pixmap = QPixmap(file_path)
if pixmap.isNull():
raise Exception("无法加载图片文件,可能格式不支持或文件已损坏")
self.original_pixmap = pixmap.copy()
label_width = self.image_label.width() - 20
label_height = self.image_label.height() - 20
scaled_pixmap = pixmap.scaled(
label_width, label_height, Qt.KeepAspectRatio, Qt.SmoothTransformation
)
self.image_label.setPixmap(scaled_pixmap)
self.analyze_button.setEnabled(True)
self.statusBar().showMessage(f"已加载图片: {os.path.basename(file_path)}")
self.progress_bar.setFormat("图片已加载,准备分析")
except Exception as e:
self.statusBar().showMessage(f"错误: {str(e)}")
self.show_error_dialog("图片加载错误", f"无法加载图片:\n{str(e)}")
def analyze_image(self):
if not self.image_path:
self.statusBar().showMessage("错误: 请先加载图片")
return
selected_items = self.model_list.selectedItems()
if not selected_items:
self.statusBar().showMessage("错误: 请选择模型")
return
selected_item = selected_items[0]
if selected_item.text().startswith("==="):
self.statusBar().showMessage("错误: 请选择有效的模型")
return
model_name = selected_item.data(Qt.UserRole)
self.current_response = ""
self.start_timer()
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.result_edit.setHtml(f"""
<div style='background: linear-gradient(to right, #6a11cb, #2575fc); color: white; font-size: 16pt; font-weight: bold; padding: 12px; border-radius: 8px; margin-bottom: 15px;'>图片分析结果</div>
<div style='color: #2c3e50; font-size: 10pt; line-height: 1.15;'><p style='color: #4a86e8; font-weight: bold;'>正在分析图片,请稍候...</p></div>
<div style='margin-top: 20px; color: #7f8c8d; font-size: 9pt; border-top: 1px solid #ecf0f1; padding-top: 10px;'>
<span style='color: #6a11cb; font-weight: bold;'>模型:</span> {model_name} <span style='color: #6a11cb; font-weight: bold;'>开始时间:</span> {timestamp}
</div>
""")
self.progress_bar.setValue(0)
self.progress_bar.setFormat("正在分析图片...")
self.set_buttons_enabled(False)
self.stop_button.setEnabled(True)
temperature = self.temp_value.value()
max_tokens = int(self.token_spin.value())
prompt = self.prompt_edit.toPlainText().strip()
self.analysis_thread = StreamAnalysisThread(model_name, self.image_path, temperature, max_tokens, prompt)
self.analysis_thread.chunk_received.connect(self.handle_stream_chunk)
self.analysis_thread.analysis_complete.connect(self.handle_analysis_result)
self.analysis_thread.progress_updated.connect(self.update_progress)
self.analysis_thread.error_occurred.connect(self.handle_analysis_error)
self.analysis_thread.finished.connect(self.analysis_finished)
self.analysis_thread.start()
def handle_stream_chunk(self, chunk):
self.current_response += chunk
formatted_result = self.format_result(self.current_response)
selected_items = self.model_list.selectedItems()
model_name = selected_items[0].text()[2:] if selected_items else "未知模型"
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
result_html = f"""
<div style='background: linear-gradient(to right, #6a11cb, #2575fc); color: white; font-size: 16pt; font-weight: bold; padding: 12px; border-radius: 8px; margin-bottom: 15px;'>图片分析结果</div>
<div style='color: #2c3e50; font-size: 10pt; line-height: 1.15;'>{formatted_result}</div>
<div style='margin-top: 20px; color: #7f8c8d; font-size: 9pt; border-top: 1px solid #ecf0f1; padding-top: 10px;'>
<span style='color: #6a11cb; font-weight: bold;'>模型:</span> {model_name} <span style='color: #6a11cb; font-weight: bold;'>时间:</span> {timestamp}
<span style='color: #27ae60; font-weight: bold;'>● 流式传输中...</span>
</div>
"""
self.result_edit.setHtml(result_html)
cursor = self.result_edit.textCursor()
cursor.movePosition(cursor.End)
self.result_edit.setTextCursor(cursor)
self.result_edit.ensureCursorVisible()
def handle_analysis_result(self, result):
self.stop_timer()
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
selected_items = self.model_list.selectedItems()
model_name = selected_items[0].text()[2:] if selected_items else "未知模型"
current_html = self.result_edit.toHtml()
updated_html = current_html.replace(
'<span style=\'color: #27ae60; font-weight: bold;\'>● 流式传输中...</span>',
f'<span style=\'color: #2ecc71; font-weight: bold;\'>✓ 分析完成 - 用时: {self.timer_display.text()}</span>'
)
self.result_edit.setHtml(updated_html)
self.statusBar().showMessage("图片分析完成")
def stop_analysis(self):
if self.analysis_thread and self.analysis_thread.isRunning():
self.analysis_thread.stop()
self.analysis_thread.quit()
self.analysis_thread.wait(2000)
self.stop_timer()
self.set_buttons_enabled(True)
self.stop_button.setEnabled(False)
self.progress_bar.setFormat("分析已停止")
self.statusBar().showMessage("分析已停止")
def format_result(self, result):
formatted = result
formatted = formatted.replace("# ", "").replace("## ", "").replace("### ", "")
formatted = formatted.replace("**", "").replace("*", "").replace("__", "").replace("_", "")
formatted = formatted.replace("- ", "• ").replace("• ", " • ")
formatted = formatted.replace(".", "。").replace(",", ",").replace(":", ":")
paragraphs = []
double_newline_paras = formatted.split("\n\n")
for para in double_newline_paras:
if "\n" in para:
sub_paras = para.split("\n")
paragraphs.extend(sub_paras)
else:
paragraphs.append(para)
if len(paragraphs) == 1:
paragraphs = formatted.split("。")
paragraphs = [p.strip() + ("。" if i < len(paragraphs) - 1 else "") for i, p in enumerate(paragraphs) if
p.strip()]
html_paragraphs = []
for i, p in enumerate(paragraphs):
p = p.strip()
if p:
if i == 0:
html_paragraphs.append(
f"<div style='margin: 10px 0; padding: 10px; background-color: #f8f9fa; border-left: 4px solid #4a86e8;'><p style='font-size: 12pt; font-weight: bold; color: #2c3e50; margin: 0; line-height: 1.15;'>{p}</p></div>")
else:
if p.strip().startswith("•"):
html_paragraphs.append(
f"<div style='margin: 8px 0; padding-left: 20px;'><p style='font-size: 10pt; color: #2c3e50; margin: 0; line-height: 1.15;'>{p}</p></div>")
else:
html_paragraphs.append(
f"<div style='margin: 8px 0; padding: 8px; background-color: #ffffff; border-radius: 5px; border: 1px solid #f0f0f0;'><p style='font-size: 10pt; color: #2c3e50; margin: 0; text-indent: 2em; line-height: 1.15;'>{p}</p></div>")
return "".join(html_paragraphs)
def handle_analysis_error(self, error):
self.stop_timer()
self.result_edit.setPlainText(f"错误: {error}")
self.statusBar().showMessage(f"错误: {error}")
self.progress_bar.setFormat("分析失败")
self.show_error_dialog("分析错误", error)
def update_progress(self, value):
self.progress_bar.setValue(value)
if value < 30:
self.progress_bar.setFormat("准备分析... %p%")
elif value < 70:
self.progress_bar.setFormat("发送请求到Ollama... %p%")
elif value < 90:
self.progress_bar.setFormat("处理响应... %p%")
else:
self.progress_bar.setFormat("完成分析... %p%")
def analysis_finished(self):
self.set_buttons_enabled(True)
self.stop_button.setEnabled(False)
self.progress_bar.setFormat("分析完成")
def set_buttons_enabled(self, enabled):
self.load_button.setEnabled(enabled)
self.analyze_button.setEnabled(enabled and bool(self.image_path))
self.clear_button.setEnabled(enabled)
self.refresh_models_button.setEnabled(enabled)
def clear_results(self):
self.result_edit.clear()
self.image_label.clear()
self.image_label.setText("请选择图片进行分析")
self.image_path = ""
self.analyze_button.setEnabled(False)
self.progress_bar.setValue(0)
self.progress_bar.setFormat("等待操作...")
self.statusBar().showMessage("已清除结果")
self.stop_timer()
self.timer_display.setText("00:00:00")
def closeEvent(self, event):
if self.analysis_thread and self.analysis_thread.isRunning():
self.analysis_thread.stop()
self.analysis_thread.quit()
if not self.analysis_thread.wait(2000):
self.analysis_thread.terminate()
if self.model_loader_thread and self.model_loader_thread.isRunning():
self.model_loader_thread.quit()
if not self.model_loader_thread.wait(2000):
self.model_loader_thread.terminate()
self.timer.stop()
event.accept()
if __name__ == "__main__":
def exception_handler(exctype, value, traceback):
error_msg = f"程序发生未捕获的异常:\n\n类型: {exctype.__name__}\n\n描述: {value}"
print(error_msg)
try:
app = QApplication.instance()
if app is not None:
msg = QMessageBox()
msg.setIcon(QMessageBox.Critical)
msg.setText("程序遇到严重错误")
msg.setInformativeText(error_msg)
msg.setWindowTitle("未处理的异常")
msg.setStandardButtons(QMessageBox.Ok)
msg.exec_()
except:
pass
sys.__excepthook__(exctype, value, traceback)
sys.excepthook = exception_handler
app = QApplication(sys.argv)
app.setApplicationName("多模态大模型图片解读系统")
app.setStyle("Fusion")
palette = QPalette()
palette.setColor(QPalette.Window, QColor(200, 104, 248))
palette.setColor(QPalette.WindowText, QColor(44, 62, 20))
palette.setColor(QPalette.Base, QColor(255, 255, 255))
palette.setColor(QPalette.AlternateBase, QColor(240, 240, 240))
palette.setColor(QPalette.ToolTipBase, Qt.white)
palette.setColor(QPalette.ToolTipText, Qt.black)
palette.setColor(QPalette.Text, QColor(44, 62, 20))
palette.setColor(QPalette.Button, QColor(74, 134, 232))
palette.setColor(QPalette.ButtonText, Qt.white)
palette.setColor(QPalette.BrightText, Qt.red)
palette.setColor(QPalette.Highlight, QColor(106, 17, 203))
palette.setColor(QPalette.HighlightedText, Qt.white)
app.setPalette(palette)
try:
window = MultiModalApp()
window.show()
sys.exit(app.exec_())
except Exception as e:
print(f"应用程序启动失败: {str(e)}")
QMessageBox.critical(None, "启动错误", f"应用程序启动失败:\n{str(e)}")
最新发布