自定义左边细右边粗进度条

自定义 长形进度条左边细右边粗

无意间看到这个需求,自己实现了一波,技术很菜仅作记录。

上图

直接自定义View,下面贴代码

package com.wavewave.myapplication.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.Nullable;

import com.wavewave.myapplication.R;


/**
 * @author wavewave
 * @CreateDate: 2022/7/12 6:31 下午
 * @Description:
 * @Version: 1.0
 */
public class GradientWidthAndColorLine extends View {
    private Paint mPaint;
    private Paint mPaintCircle;
    private Paint mPaintNew;
    private float mStartSize = 50f;
    private float mEndSize = 10f;
    private int mStartColor = Color.BLACK;
    private int mEndColor = Color.RED;
    private int defaultHeight = 30;
    //创建point对象 参数为x坐标和y坐标
    private PointF point = new PointF(100, 100);
    private int circleRaid = 50;

    public GradientWidthAndColorLine(Context context) {
        this(context, null);
    }

    public GradientWidthAndColorLine(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public GradientWidthAndColorLine(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
//        TypedArray obtainStyledAttributes = getContext().obtainStyledAttributes(R.styleable.GradientWidthAndColorLine);
//        mStartColor = obtainStyledAttributes.getColor(R.styleable.GradientWidthAndColorLine_gradient_start_color, Color.BLACK);
//        mEndColor = obtainStyledAttributes.getColor(R.styleable.GradientWidthAndColorLine_gradient_end_color, Color.RED);
//        mStartSize = obtainStyledAttributes.getDimension(R.styleable.GradientWidthAndColorLine_gradient_start_size, 50f);
//        mEndSize = obtainStyledAttributes.getDimension(R.styleable.GradientWidthAndColorLine_gradient_end_size, 10f);
//        obtainStyledAttributes.recycle();

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mStartColor);
        mPaintCircle = new Paint();
        mPaintCircle.setColor(Color.BLUE);

        mPaintNew = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintNew.setStyle(Paint.Style.FILL);
        mPaintNew.setColor(Color.GRAY);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        LinearGradient linearGradient = new LinearGradient(
                0f, 0f, getMeasuredWidth(), getMeasuredHeight(), new int[]{Color.parseColor("#FFFEB2C5"), Color.parseColor("#FFFF678C")
        }, new float[]{0.0f, 1.0f}, Shader.TileMode.CLAMP);

        mPaint.setShader(linearGradient);
        Path path = new Path();

        int startY = 50;
        path.moveTo(20, 0 + startY);
        path.quadTo(0, 20 + startY, 20, 40 + startY);
        canvas.drawPath(path, mPaintNew);

        path.moveTo(20, 0 + startY);
        path.lineTo(20, 40 + startY);

        path.lineTo(getMeasuredWidth() - 25, 50 + startY);
        path.lineTo(getMeasuredWidth() - 25, 0f + startY);
        path.lineTo(20, 0 + startY);
        path.close();
        canvas.drawPath(path, mPaintNew);

        path.moveTo(getMeasuredWidth() - 25, 0 + startY);
        path.quadTo(getMeasuredWidth(), 25 + startY, getMeasuredWidth() - 25, 50 + startY);
        canvas.drawPath(path, mPaintNew);


        Path newPath = new Path();
        newPath.moveTo(20, 0 + startY);
        newPath.quadTo(0, 20 + startY, 20, 40 + startY);
        canvas.drawPath(newPath, mPaint);

        newPath.moveTo(20, 0 + startY);
        newPath.lineTo(20, 40 + startY);
        newPath.lineTo(point.x - 25, 50 + startY);
        newPath.lineTo(point.x - 25, 0f + startY);
        newPath.lineTo(20, 0 + startY);
        newPath.close();
        canvas.drawPath(newPath, mPaint);


        canvas.drawCircle(point.x, startY + 25, circleRaid, mPaintCircle);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //获得触摸事件
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            //ACTION_MOVE不要设置break,否则圆形不会跟随手指活动 只会手指松开屏幕的时候圆形直接到了屏幕停止的位置
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                //获取手指触摸位置的x坐标
                point.x = event.getX();
                //获取手指触摸位置的y坐标
                point.y = event.getY();
                if (point.x < circleRaid) {
                    point.x = circleRaid;
                }
                if (point.x >= getMeasuredWidth() - 25) {
                    point.x = getMeasuredWidth() - circleRaid;
                }
                //启动
                postInvalidate();
                break;
        }
        return true;
    }
}

#将计时器行合到最顶上的标题行上,优化结果文本框中的显示格式,尽量消除不必要的#、*符事情 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: # 先检查Ollama服务是否可用 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 # 请求Ollama获取可用模型列表 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 # 读取图片并转换为base64 self.progress_updated.emit(10) with open(self.image_path, "rb") as image_file: base64_image = base64.b64encode(image_file.read()).decode("utf-8") # 构造API请求数据 - 启用流式传输 data = { "model": self.model, "prompt": self.prompt, "images": [base64_image], "options": { "temperature": self.temperature, "num_predict": self.max_tokens }, "stream": True # 启用流式传输 } # 发送流式请求到Ollama API 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响应 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) # 延迟加载模型,确保UI完全初始化 QTimer.singleShot(500, self.load_models) # 修复:直接使用QTimer 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(15) main_layout.setContentsMargins(15, 15, 15, 15) # 标题 - 使用渐变背景 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) main_layout.addWidget(title_label) # 创建计时器显示 timer_layout = QHBoxLayout() timer_label = QLabel("分析用时:") timer_label.setStyleSheet("font-weight: bold; font-size: 12pt; color: #2c3e50;") self.timer_display = QLabel("00:00:00") self.timer_display.setStyleSheet(""" background-color: #2c3e50; color: #ffffff; font-size: 16pt; font-weight: bold; padding: 8px 16px; border-radius: 8px; min-width: 120px; """) self.timer_display.setAlignment(Qt.AlignCenter) timer_layout.addWidget(timer_label) timer_layout.addWidget(self.timer_display) timer_layout.addStretch() # 添加弹性空间 main_layout.addLayout(timer_layout) # 创建分割器 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) # 增加上边距确保标题显示完整(向下移动1/3标签高度) image_layout.setContentsMargins(10, 35, 10, 10) # 上边距增加10px 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) # 增加上边距确保标题显示完整(向下移动1/3标签高度) control_layout.setContentsMargins(15, 35, 15, 15) # 上边距增加10px # 模型选择 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数 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) # 增加上边距确保标题显示完整(向下移动1/3标签高度) result_layout.setContentsMargins(10, 35, 10, 10) # 上边距增加10px self.result_edit = QTextEdit() self.result_edit.setReadOnly(True) self.result_edit.setStyleSheet(""" font-size: 10pt; /* 缩小结果框字体 */ line-height: 1.5; 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): """从Ollama加载可用模型列表""" 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("=== 多模态大模型 ===") # 使用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}") # 使用QListWidgetItem 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("=== 其他模型 ===") # 使用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}") # 使用QListWidgetItem 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" "可能原因:\n" f"1. Ollama服务未启动 (当前地址: {OLLAMA_HOST})\n" "2. Ollama未正确安装\n" "3. 网络连接问题\n\n" "解决方案:\n" "1. 下载并安装Ollama: https://ollama.com/download\n" "2. 启动Ollama服务\n" "3. 检查网络连接") 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) # 从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.6;'> <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.6;'> {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) # 等待2秒 # 停止计时器 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.replace(".", "。").replace(",", ",").replace(":", ":") # 分割为段落 paragraphs = formatted.split("\n\n") # 如果只有一段,尝试按句号分割 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段落 html_paragraphs = [] for i, p in enumerate(paragraphs): if p.strip(): # 添加标题样式到第一个段落 if i == 0: html_paragraphs.append(f"<p style='margin:15px 0; font-size:11pt; font-weight: bold;'>{p}</p>") else: html_paragraphs.append(f"<p style='margin:10px 0; text-indent: 2em;'>{p}</p>") 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): # 等待2秒 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): # 等待2秒 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)}")
最新发布
10-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值