From f704d5b26abb7279382297b61a3b9e491072e078 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 07:10:53 +0000 Subject: [PATCH 1/5] Initial plan From 950e827c34a313e0562bcf43b64b438805adf56d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 07:15:13 +0000 Subject: [PATCH 2/5] Add ModelScope API-Inference client support to copilot service Co-authored-by: HeDaas-Code <208586641+HeDaas-Code@users.noreply.github.com> --- src/copilot/__init__.py | 3 +- src/copilot/copilot_manager.py | 79 +++++++++-- src/copilot/modelscope_client.py | 229 +++++++++++++++++++++++++++++++ 3 files changed, 302 insertions(+), 9 deletions(-) create mode 100644 src/copilot/modelscope_client.py diff --git a/src/copilot/__init__.py b/src/copilot/__init__.py index 6d6fe20..676c740 100644 --- a/src/copilot/__init__.py +++ b/src/copilot/__init__.py @@ -7,5 +7,6 @@ from .copilot_manager import CopilotManager from .siliconflow_client import SiliconFlowClient +from .modelscope_client import ModelScopeClient -__all__ = ['CopilotManager', 'SiliconFlowClient'] +__all__ = ['CopilotManager', 'SiliconFlowClient', 'ModelScopeClient'] diff --git a/src/copilot/copilot_manager.py b/src/copilot/copilot_manager.py index be6b3b5..d064553 100644 --- a/src/copilot/copilot_manager.py +++ b/src/copilot/copilot_manager.py @@ -8,6 +8,7 @@ from typing import Dict, List, Optional, Callable from PyQt5.QtCore import QObject, pyqtSignal, QThread, Qt from .siliconflow_client import SiliconFlowClient +from .modelscope_client import ModelScopeClient from src.utils.logger import info, warning, error, debug, LogCategory from src.utils.config_manager import ConfigManager @@ -50,6 +51,7 @@ def __init__(self, config_manager: ConfigManager): self.config_manager = config_manager self.client = None self.enabled = False + self.provider = 'siliconflow' # siliconflow or modelscope self.current_mode = 'none' # none, inline, edit, creation, conversation, agent self.current_threads = [] # Store active threads @@ -82,13 +84,28 @@ def wrapper(*args, **kwargs): def _load_config(self): """Load copilot configuration""" try: + # Load provider selection + self.provider = self.config_manager.get_plugin_setting('copilot', 'provider', 'siliconflow') + api_key = self.config_manager.get_plugin_setting('copilot', 'api_key', '') - model = self.config_manager.get_plugin_setting('copilot', 'model', SiliconFlowClient.DEFAULT_MODELS['chat']) + + # Get default model based on provider + if self.provider == 'modelscope': + default_model = ModelScopeClient.DEFAULT_MODELS['chat'] + else: + default_model = SiliconFlowClient.DEFAULT_MODELS['chat'] + + model = self.config_manager.get_plugin_setting('copilot', 'model', default_model) self.enabled = self.config_manager.get_plugin_setting('copilot', 'enabled', False) if api_key: - self.client = SiliconFlowClient(api_key, model) - info("Copilot client initialized", category=LogCategory.API) + # Create client based on provider + if self.provider == 'modelscope': + self.client = ModelScopeClient(api_key, model) + info("Copilot client initialized with ModelScope", category=LogCategory.API) + else: + self.client = SiliconFlowClient(api_key, model) + info("Copilot client initialized with SiliconFlow", category=LogCategory.API) else: warning("Copilot API key not configured", category=LogCategory.CONFIG) except Exception as e: @@ -99,29 +116,75 @@ def reload_config(self): self._load_config() info("Copilot configuration reloaded", category=LogCategory.CONFIG) - def set_api_key(self, api_key: str, model: str = None): + def set_api_key(self, api_key: str, model: str = None, provider: str = None): """ Set or update API key Args: - api_key: SiliconFlow API key + api_key: API key (SiliconFlow or ModelScope) model: Optional model name + provider: Optional provider name ('siliconflow' or 'modelscope') """ self.config_manager.set_plugin_setting('copilot', 'api_key', api_key) + + # Update provider if specified + if provider: + self.provider = provider + self.config_manager.set_plugin_setting('copilot', 'provider', provider) + + # Get default model based on provider + if self.provider == 'modelscope': + default_model = ModelScopeClient.DEFAULT_MODELS['chat'] + else: + default_model = SiliconFlowClient.DEFAULT_MODELS['chat'] + if model: self.config_manager.set_plugin_setting('copilot', 'model', model) else: - model = self.config_manager.get_plugin_setting('copilot', 'model', SiliconFlowClient.DEFAULT_MODELS['chat']) + model = self.config_manager.get_plugin_setting('copilot', 'model', default_model) + + # Create client based on provider + if self.provider == 'modelscope': + self.client = ModelScopeClient(api_key, model) + info("Copilot API key updated for ModelScope", category=LogCategory.CONFIG) + else: + self.client = SiliconFlowClient(api_key, model) + info("Copilot API key updated for SiliconFlow", category=LogCategory.CONFIG) - self.client = SiliconFlowClient(api_key, model) self.enabled = True self.config_manager.set_plugin_setting('copilot', 'enabled', True) - info("Copilot API key updated", category=LogCategory.CONFIG) def is_enabled(self) -> bool: """Check if copilot is enabled""" return self.enabled and self.client is not None + def set_provider(self, provider: str): + """ + Switch to a different provider + + Args: + provider: Provider name ('siliconflow' or 'modelscope') + """ + if provider not in ['siliconflow', 'modelscope']: + error(f"Invalid provider: {provider}", category=LogCategory.CONFIG) + return + + self.provider = provider + self.config_manager.set_plugin_setting('copilot', 'provider', provider) + + # Reload configuration with new provider + self._load_config() + info(f"Provider switched to: {provider}", category=LogCategory.CONFIG) + + def get_provider(self) -> str: + """ + Get current provider + + Returns: + Current provider name + """ + return self.provider + def set_enabled(self, enabled: bool): """Enable or disable copilot""" self.enabled = enabled diff --git a/src/copilot/modelscope_client.py b/src/copilot/modelscope_client.py new file mode 100644 index 0000000..18b2666 --- /dev/null +++ b/src/copilot/modelscope_client.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +ModelScope API-Inference Client for MGit Copilot +https://www.modelscope.cn/docs/model-service/API-Inference/intro +""" + +import requests +import json +from typing import List, Dict, Optional, Generator +from src.utils.logger import info, warning, error, debug, LogCategory + +class ModelScopeClient: + """Client for ModelScope API-Inference""" + + BASE_URL = "https://api-inference.modelscope.cn/v1" + + # 默认模型列表 + DEFAULT_MODELS = { + 'chat': 'qwen/Qwen2.5-7B-Instruct', + 'completion': 'qwen/Qwen2.5-7B-Instruct', + 'embedding': 'iic/nlp_gte_sentence-embedding_chinese-base' + } + + def __init__(self, api_key: str, model: str = None): + """ + Initialize ModelScope client + + Args: + api_key: ModelScope API key + model: Model name to use (defaults to Qwen2.5-7B-Instruct) + """ + self.api_key = api_key + self.model = model or self.DEFAULT_MODELS['chat'] + self.headers = { + 'Authorization': f'Bearer {api_key}', + 'Content-Type': 'application/json' + } + info(f"ModelScope client initialized with model: {self.model}", category=LogCategory.API) + + def chat_completion( + self, + messages: List[Dict[str, str]], + temperature: float = 0.7, + max_tokens: int = 2048, + stream: bool = False + ) -> Dict: + """ + Send chat completion request + + Args: + messages: List of message dicts with 'role' and 'content' + temperature: Sampling temperature (0-2) + max_tokens: Maximum tokens to generate + stream: Whether to stream the response + + Returns: + Response dict from API + """ + # Validate temperature + if not 0 <= temperature <= 2: + warning(f"Temperature {temperature} out of range [0, 2], clamping", category=LogCategory.API) + temperature = max(0, min(2, temperature)) + + url = f"{self.BASE_URL}/chat/completions" + data = { + 'model': self.model, + 'messages': messages, + 'temperature': temperature, + 'max_tokens': max_tokens, + 'stream': stream + } + + debug(f"Sending API request to {url} with {len(messages)} messages", category=LogCategory.API) + + try: + if stream: + return self._stream_chat_completion(url, data) + else: + # Use shorter timeout to prevent UI freeze + response = requests.post(url, headers=self.headers, json=data, timeout=30) + response.raise_for_status() + result = response.json() + info(f"API request successful, response size: {len(str(result))} chars", category=LogCategory.API) + return result + except requests.exceptions.Timeout as e: + error(f"ModelScope API timeout after 30s: {str(e)}", category=LogCategory.API) + raise + except requests.exceptions.RequestException as e: + error(f"ModelScope API error: {str(e)}", category=LogCategory.API) + raise + + def _stream_chat_completion(self, url: str, data: Dict) -> Generator: + """ + Stream chat completion response + + Args: + url: API endpoint URL + data: Request data + + Yields: + Chunks of response text + """ + debug("Starting streaming API request", category=LogCategory.API) + try: + response = requests.post( + url, + headers=self.headers, + json=data, + stream=True, + timeout=30 + ) + response.raise_for_status() + + for line in response.iter_lines(): + if line: + line = line.decode('utf-8') + if line.startswith('data: '): + data_str = line[6:] + if data_str.strip() == '[DONE]': + info("Streaming completed", category=LogCategory.API) + break + try: + chunk = json.loads(data_str) + if 'choices' in chunk and len(chunk['choices']) > 0: + delta = chunk['choices'][0].get('delta', {}) + if 'content' in delta: + yield delta['content'] + except json.JSONDecodeError: + continue + except requests.exceptions.Timeout as e: + error(f"ModelScope streaming timeout after 30s: {str(e)}", category=LogCategory.API) + raise + except requests.exceptions.RequestException as e: + error(f"ModelScope streaming error: {str(e)}", category=LogCategory.API) + raise + + def text_completion( + self, + prompt: str, + temperature: float = 0.7, + max_tokens: int = 2048, + stop: Optional[List[str]] = None + ) -> str: + """ + Generate text completion + + Args: + prompt: Input prompt text + temperature: Sampling temperature + max_tokens: Maximum tokens to generate + stop: List of stop sequences + + Returns: + Generated completion text + """ + messages = [{'role': 'user', 'content': prompt}] + response = self.chat_completion(messages, temperature, max_tokens) + + if 'choices' in response and len(response['choices']) > 0: + content = response['choices'][0]['message']['content'] + + # Apply stop sequences if provided + if stop: + first_stop_index = None + for s in stop: + if not s: + continue + idx = content.find(s) + if idx != -1 and (first_stop_index is None or idx < first_stop_index): + first_stop_index = idx + if first_stop_index is not None: + content = content[:first_stop_index] + + return content + return "" + + def get_embedding(self, text: str) -> List[float]: + """ + Get text embedding + + Args: + text: Text to embed + + Returns: + Embedding vector + """ + url = f"{self.BASE_URL}/embeddings" + data = { + 'model': self.DEFAULT_MODELS['embedding'], + 'input': text + } + + debug(f"Requesting embedding for text length: {len(text)}", category=LogCategory.API) + + try: + response = requests.post(url, headers=self.headers, json=data, timeout=20) + response.raise_for_status() + result = response.json() + + if 'data' in result and len(result['data']) > 0: + embedding = result['data'][0]['embedding'] + info(f"Embedding received, dimension: {len(embedding)}", category=LogCategory.API) + return embedding + return [] + except requests.exceptions.Timeout as e: + error(f"ModelScope embedding timeout after 20s: {str(e)}", category=LogCategory.API) + raise + except requests.exceptions.RequestException as e: + error(f"ModelScope embedding error: {str(e)}", category=LogCategory.API) + raise + + def test_connection(self) -> bool: + """ + Test API connection + + Returns: + True if connection successful, False otherwise + """ + debug("Testing API connection", category=LogCategory.API) + try: + messages = [{'role': 'user', 'content': 'Hello'}] + self.chat_completion(messages, max_tokens=10) + info("API connection test successful", category=LogCategory.API) + return True + except Exception as e: + error(f"Connection test failed: {str(e)}", category=LogCategory.API) + return False From a10ff0a9b82573fa4af96668c9750db7002a1993 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 07:17:51 +0000 Subject: [PATCH 3/5] Update documentation to include ModelScope API-Inference provider Co-authored-by: HeDaas-Code <208586641+HeDaas-Code@users.noreply.github.com> --- README.md | 3 +- docs/copilot_config.md | 128 ++++++++++++++++++++++++++++++++++++----- docs/copilot_guide.md | 41 ++++++++++--- 3 files changed, 148 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 4282b31..e83babe 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ MGit是一款基于Python的Markdown笔记应用,结合了直观的编辑体 ## 主要特点 -- **AI Copilot写作助手**:集成SiliconFlow大语言模型,提供智能写作辅助 +- **AI Copilot写作助手**:集成大语言模型,提供智能写作辅助 + - **多提供商支持**:支持SiliconFlow和ModelScope API-Inference两种服务提供商 - **行内补全**:根据上下文智能生成续写内容 - **编辑模式**:根据指令优化和修改文本 - **创作模式**:从提示词生成完整文档 diff --git a/docs/copilot_config.md b/docs/copilot_config.md index 0b05ac9..1e0d3d1 100644 --- a/docs/copilot_config.md +++ b/docs/copilot_config.md @@ -9,50 +9,118 @@ Typically located at: `~/.config/mgit/config.json` or similar location depending ## Configuration Keys +### SiliconFlow Provider (Default) + ```json { "copilot": { "enabled": true, + "provider": "siliconflow", "api_key": "your-siliconflow-api-key-here", "model": "Qwen/Qwen2.5-7B-Instruct" } } ``` +### ModelScope API-Inference Provider + +```json +{ + "copilot": { + "enabled": true, + "provider": "modelscope", + "api_key": "your-modelscope-api-key-here", + "model": "qwen/Qwen2.5-7B-Instruct" + } +} +``` + +## Providers + +MGit Copilot now supports two LLM service providers: + +### 1. SiliconFlow (Default) +- **Website**: https://siliconflow.cn +- **API Documentation**: https://docs.siliconflow.cn +- **Features**: Fast inference, multiple models, competitive pricing +- **Provider ID**: `siliconflow` + +### 2. ModelScope API-Inference (New!) +- **Website**: https://www.modelscope.cn +- **API Documentation**: https://www.modelscope.cn/docs/model-service/API-Inference/intro +- **Features**: Official Alibaba Cloud service, reliable infrastructure +- **Provider ID**: `modelscope` + ## Available Models +### SiliconFlow Models + You can use any of these models with SiliconFlow: -### Qwen Series (Recommended) +#### Qwen Series (Recommended) - `Qwen/Qwen2.5-7B-Instruct` - Best balance of speed and quality (default) - `Qwen/Qwen2.5-14B-Instruct` - Higher quality, slightly slower - `Qwen/Qwen2.5-32B-Instruct` - Highest quality, slower -### DeepSeek +#### DeepSeek - `deepseek-ai/DeepSeek-V2.5` - Strong reasoning, good for technical content -### Meta LLaMA +#### Meta LLaMA - `meta-llama/Meta-Llama-3.1-8B-Instruct` - Open source alternative +### ModelScope API-Inference Models + +Available models with ModelScope: + +#### Qwen Series +- `qwen/Qwen2.5-7B-Instruct` - Best balance of speed and quality (default) +- `qwen/Qwen2.5-14B-Instruct` - Higher quality +- `qwen/Qwen2.5-32B-Instruct` - Highest quality + +**Note**: Model names use lowercase namespace with ModelScope (e.g., `qwen/` instead of `Qwen/`) + ## Getting API Key +### For SiliconFlow + 1. Visit https://siliconflow.cn 2. Register or login to your account 3. Navigate to API Keys section 4. Create a new API key 5. Copy the key to Copilot settings in MGit +### For ModelScope + +1. Visit https://www.modelscope.cn +2. Register or login with your Alibaba Cloud account +3. Navigate to the API-Inference section +4. Create a new API key +5. Copy the key to Copilot settings in MGit + ## Configuration via UI The recommended way to configure Copilot is through the UI: 1. Open MGit 2. Go to menu: `Copilot > Copilot设置` -3. Enter your API key -4. Select your preferred model -5. Check "启用 Copilot" -6. Click "测试连接" to verify -7. Click "保存" +3. Select your preferred provider (SiliconFlow or ModelScope) +4. Enter your API key +5. Select your preferred model +6. Check "启用 Copilot" +7. Click "测试连接" to verify +8. Click "保存" + +## Switching Between Providers + +You can switch between SiliconFlow and ModelScope at any time: + +1. Open Copilot settings +2. Select different provider from dropdown +3. Enter the API key for the new provider +4. Select an appropriate model for that provider +5. Test connection and save + +**Note**: Each provider requires its own API key. Make sure to obtain and configure API keys for both providers if you plan to switch between them. ## Security Notes @@ -65,29 +133,52 @@ The recommended way to configure Copilot is through the UI: You can also set API key via environment variable: +### For SiliconFlow ```bash export SILICONFLOW_API_KEY="your-api-key-here" ``` +### For ModelScope +```bash +export MODELSCOPE_API_KEY="your-api-key-here" +``` + Note: UI configuration takes precedence over environment variables. ## Troubleshooting ### API Key Not Working - Verify key is correct (no extra spaces) -- Check account balance on SiliconFlow +- Check account balance on your provider's website - Ensure network connectivity - Try "测试连接" in settings dialog +- Verify you're using the correct API key for the selected provider ### Model Not Available -- Some models may require specific API tier -- Check SiliconFlow documentation for model availability +- Some models may require specific API tier or region +- Check provider documentation for model availability - Try using default model first +- For ModelScope: Ensure model name uses correct case (lowercase namespace) +- For SiliconFlow: Ensure model name uses correct case (uppercase namespace) ### Connection Timeout - Check internet connection - Verify firewall settings - Try increasing timeout in code if needed +- Some regions may have slower access to certain providers + +### Provider-Specific Issues + +#### SiliconFlow +- Check status at https://siliconflow.cn +- Verify API tier and quotas +- Review rate limits + +#### ModelScope +- Check Alibaba Cloud account status +- Verify API-Inference service is enabled +- Review service quotas and limits +- Check region availability ## Advanced Configuration @@ -99,15 +190,22 @@ For advanced users, you can modify additional parameters by editing the source c ## Rate Limits -Be aware of SiliconFlow API rate limits: +Be aware of API rate limits from your provider: + +### SiliconFlow - Free tier: Limited requests per minute - Paid tiers: Higher limits +- Check your plan at https://siliconflow.cn -Check your plan at https://siliconflow.cn +### ModelScope API-Inference +- Limits depend on your Alibaba Cloud account tier +- Check quotas in your ModelScope console +- Visit https://www.modelscope.cn for details ## Support For issues or questions: - Check the [Copilot Guide](./copilot_guide.md) -- Submit GitHub issue -- Contact SiliconFlow support for API issues +- Submit GitHub issue for MGit-related problems +- Contact SiliconFlow support for SiliconFlow API issues +- Contact Alibaba Cloud support for ModelScope API issues diff --git a/docs/copilot_guide.md b/docs/copilot_guide.md index 009c46b..6af53ae 100644 --- a/docs/copilot_guide.md +++ b/docs/copilot_guide.md @@ -53,13 +53,29 @@ MGit Copilot 是一个专注于写作的AI助手,集成了最新的大语言 ### 1. 配置API密钥 +MGit Copilot 支持两种大模型服务提供商: + +#### 选项1: SiliconFlow (默认) 1. 访问 [SiliconFlow官网](https://siliconflow.cn) 注册账号并获取API密钥 2. 在MGit中打开 `Copilot > Copilot设置` -3. 输入API密钥 -4. 选择模型(推荐使用 Qwen/Qwen2.5-7B-Instruct) -5. 勾选"启用Copilot" -6. 点击"测试连接"验证配置 -7. 点击"保存" +3. 选择 "SiliconFlow" 作为提供商 +4. 输入API密钥 +5. 选择模型(推荐使用 Qwen/Qwen2.5-7B-Instruct) +6. 勾选"启用Copilot" +7. 点击"测试连接"验证配置 +8. 点击"保存" + +#### 选项2: ModelScope API-Inference (新增!) +1. 访问 [ModelScope官网](https://www.modelscope.cn) 注册账号并获取API密钥 +2. 在MGit中打开 `Copilot > Copilot设置` +3. 选择 "ModelScope" 作为提供商 +4. 输入API密钥 +5. 选择模型(推荐使用 qwen/Qwen2.5-7B-Instruct) +6. 勾选"启用Copilot" +7. 点击"测试连接"验证配置 +8. 点击"保存" + +**注意**: 两个提供商的模型名称格式略有不同(ModelScope使用小写命名空间) ### 2. 显示Copilot面板 @@ -208,8 +224,9 @@ MGit Copilot 是一个专注于写作的AI助手,集成了最新的大语言 **A**: 1. 检查API密钥是否正确 2. 确认网络连接正常 -3. 验证SiliconFlow账户余额 -4. 查看错误信息,按提示操作 +3. 验证服务提供商账户余额(SiliconFlow或ModelScope) +4. 确保选择的提供商与API密钥匹配 +5. 查看错误信息,按提示操作 ### Q: 生成的内容质量不理想? @@ -253,12 +270,20 @@ MGit Copilot 是一个专注于写作的AI助手,集成了最新的大语言 ## 技术支持 - **文档**: 参考本指南和项目README +- **配置指南**: [Copilot配置文档](./copilot_config.md) - **问题反馈**: 提交GitHub Issue - **功能建议**: 通过PR或Issue提出 -- **API文档**: https://docs.siliconflow.cn +- **SiliconFlow API文档**: https://docs.siliconflow.cn +- **ModelScope API文档**: https://www.modelscope.cn/docs/model-service/API-Inference/intro ## 更新日志 +### Version 1.1.0 (2026-01-07) + +- 新增ModelScope API-Inference服务提供商支持 +- 支持在SiliconFlow和ModelScope之间切换 +- 更新配置文档,详细说明两个提供商的使用 + ### Version 1.0.0 (2026-01-03) - 初始版本发布 From eaf832697737b925e80d0a26614352fc5e7d54bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 07:19:35 +0000 Subject: [PATCH 4/5] Update Copilot settings UI to support provider selection Co-authored-by: HeDaas-Code <208586641+HeDaas-Code@users.noreply.github.com> --- src/components/copilot_panel.py | 117 ++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 29 deletions(-) diff --git a/src/components/copilot_panel.py b/src/components/copilot_panel.py index d1cd4ba..f2862bd 100644 --- a/src/components/copilot_panel.py +++ b/src/components/copilot_panel.py @@ -521,12 +521,25 @@ def initUI(self): """Initialize UI""" self.setWindowTitle("Copilot 设置") self.setModal(True) - self.resize(600, 400) + self.resize(600, 450) layout = QVBoxLayout(self) + # Provider selection + layout.addWidget(QLabel("服务提供商:")) + self.provider_combo = QComboBox() + self.provider_combo.addItems(["SiliconFlow", "ModelScope"]) + + current_provider = self.config_manager.get_plugin_setting('copilot', 'provider', 'siliconflow') + provider_index = 0 if current_provider == 'siliconflow' else 1 + self.provider_combo.setCurrentIndex(provider_index) + self.provider_combo.currentIndexChanged.connect(self._on_provider_changed) + + layout.addWidget(self.provider_combo) + # API Key - layout.addWidget(QLabel("SiliconFlow API Key:")) + self.api_key_label = QLabel("API Key:") + layout.addWidget(self.api_key_label) self.api_key_edit = QLineEdit() self.api_key_edit.setPlaceholderText("输入API密钥...") self.api_key_edit.setEchoMode(QLineEdit.Password) @@ -540,18 +553,15 @@ def initUI(self): # Model selection layout.addWidget(QLabel("模型:")) self.model_combo = QComboBox() - self.model_combo.addItems([ - "Qwen/Qwen2.5-7B-Instruct", - "Qwen/Qwen2.5-14B-Instruct", - "Qwen/Qwen2.5-32B-Instruct", - "deepseek-ai/DeepSeek-V2.5", - "meta-llama/Meta-Llama-3.1-8B-Instruct" - ]) - current_model = self.config_manager.get_plugin_setting('copilot', 'model', 'Qwen/Qwen2.5-7B-Instruct') - index = self.model_combo.findText(current_model) - if index >= 0: - self.model_combo.setCurrentIndex(index) + # Initialize model list based on current provider + self._update_model_list() + + current_model = self.config_manager.get_plugin_setting('copilot', 'model', '') + if current_model: + index = self.model_combo.findText(current_model) + if index >= 0: + self.model_combo.setCurrentIndex(index) layout.addWidget(self.model_combo) @@ -564,18 +574,11 @@ def initUI(self): layout.addWidget(self.enable_checkbox) # Info text - info_text = QLabel( - "获取API密钥: https://siliconflow.cn\n\n" - "Copilot提供以下功能:\n" - "• 行内补全 - 智能代码和文本补全\n" - "• 编辑模式 - 根据指令编辑文本\n" - "• 创作模式 - 从提示生成内容\n" - "• 对话模式 - 与AI助手对话\n" - "• 代理模式 - 自动执行任务(需审计)" - ) - info_text.setWordWrap(True) - info_text.setStyleSheet("color: #666; padding: 10px;") - layout.addWidget(info_text) + self.info_text = QLabel() + self._update_info_text() + self.info_text.setWordWrap(True) + self.info_text.setStyleSheet("color: #666; padding: 10px;") + layout.addWidget(self.info_text) layout.addStretch() @@ -595,12 +598,62 @@ def initUI(self): btn_layout.addWidget(cancel_btn) layout.addLayout(btn_layout) + + def _on_provider_changed(self): + """Handle provider selection change""" + self._update_model_list() + self._update_info_text() + + def _update_model_list(self): + """Update model list based on selected provider""" + self.model_combo.clear() + + provider = self.provider_combo.currentText() + if provider == "SiliconFlow": + self.model_combo.addItems([ + "Qwen/Qwen2.5-7B-Instruct", + "Qwen/Qwen2.5-14B-Instruct", + "Qwen/Qwen2.5-32B-Instruct", + "deepseek-ai/DeepSeek-V2.5", + "meta-llama/Meta-Llama-3.1-8B-Instruct" + ]) + else: # ModelScope + self.model_combo.addItems([ + "qwen/Qwen2.5-7B-Instruct", + "qwen/Qwen2.5-14B-Instruct", + "qwen/Qwen2.5-32B-Instruct" + ]) + + def _update_info_text(self): + """Update info text based on selected provider""" + provider = self.provider_combo.currentText() + if provider == "SiliconFlow": + info = ( + "获取API密钥: https://siliconflow.cn\n" + "文档: https://docs.siliconflow.cn\n\n" + ) + else: # ModelScope + info = ( + "获取API密钥: https://www.modelscope.cn\n" + "文档: https://www.modelscope.cn/docs/model-service/API-Inference/intro\n\n" + ) + + info += ( + "Copilot提供以下功能:\n" + "• 行内补全 - 智能代码和文本补全\n" + "• 编辑模式 - 根据指令编辑文本\n" + "• 创作模式 - 从提示生成内容\n" + "• 对话模式 - 与AI助手对话\n" + "• 代理模式 - 自动执行任务(需审计)" + ) + self.info_text.setText(info) def _on_save(self): """Save settings""" api_key = self.api_key_edit.text().strip() model = self.model_combo.currentText() enabled = self.enable_checkbox.isChecked() + provider = 'siliconflow' if self.provider_combo.currentText() == 'SiliconFlow' else 'modelscope' if not api_key: QMessageBox.warning(self, "输入错误", "请输入API密钥") @@ -609,6 +662,7 @@ def _on_save(self): self.config_manager.set_plugin_setting('copilot', 'api_key', api_key) self.config_manager.set_plugin_setting('copilot', 'model', model) self.config_manager.set_plugin_setting('copilot', 'enabled', enabled) + self.config_manager.set_plugin_setting('copilot', 'provider', provider) QMessageBox.information(self, "成功", "设置已保存") self.accept() @@ -617,18 +671,23 @@ def _on_test(self): """Test API connection""" api_key = self.api_key_edit.text().strip() model = self.model_combo.currentText() + provider = self.provider_combo.currentText() if not api_key: QMessageBox.warning(self, "输入错误", "请输入API密钥") return try: - from src.copilot.siliconflow_client import SiliconFlowClient - client = SiliconFlowClient(api_key, model) + if provider == "SiliconFlow": + from src.copilot.siliconflow_client import SiliconFlowClient + client = SiliconFlowClient(api_key, model) + else: # ModelScope + from src.copilot.modelscope_client import ModelScopeClient + client = ModelScopeClient(api_key, model) if client.test_connection(): - QMessageBox.information(self, "成功", "API连接成功!") + QMessageBox.information(self, "成功", f"{provider} API连接成功!") else: - QMessageBox.warning(self, "失败", "API连接失败") + QMessageBox.warning(self, "失败", f"{provider} API连接失败") except Exception as e: QMessageBox.critical(self, "错误", f"测试失败: {str(e)}") From 667ca6af1dafde8056b9e36439153c1e50005626 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 07:23:24 +0000 Subject: [PATCH 5/5] Refactor: Use constants for provider IDs to improve code maintainability Co-authored-by: HeDaas-Code <208586641+HeDaas-Code@users.noreply.github.com> --- src/components/copilot_panel.py | 41 ++++++++++++++++++++++----------- src/copilot/__init__.py | 14 +++++++++-- src/copilot/copilot_manager.py | 22 ++++++++++-------- 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/components/copilot_panel.py b/src/components/copilot_panel.py index f2862bd..67972e5 100644 --- a/src/components/copilot_panel.py +++ b/src/components/copilot_panel.py @@ -9,15 +9,30 @@ QTextEdit, QLineEdit, QPushButton, QComboBox, QListWidget, QListWidgetItem, QTabWidget, QMessageBox, QDialog, QFileDialog, - QFormLayout, QDialogButtonBox) + QFormLayout, QDialogButtonBox, QCheckBox) from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QTextCursor from qfluentwidgets import (PushButton, LineEdit, ComboBox, TextEdit, SubtitleLabel, BodyLabel) from src.utils.logger import info, warning, error, debug, LogCategory +from src.copilot import PROVIDER_SILICONFLOW, PROVIDER_MODELSCOPE +from src.copilot.siliconflow_client import SiliconFlowClient +from src.copilot.modelscope_client import ModelScopeClient from datetime import datetime +# Provider display name mapping +PROVIDER_DISPLAY_NAMES = { + PROVIDER_SILICONFLOW: 'SiliconFlow', + PROVIDER_MODELSCOPE: 'ModelScope' +} + +PROVIDER_FROM_DISPLAY = { + 'SiliconFlow': PROVIDER_SILICONFLOW, + 'ModelScope': PROVIDER_MODELSCOPE +} + + class CopilotPanel(QWidget): """Main copilot panel widget""" @@ -528,11 +543,13 @@ def initUI(self): # Provider selection layout.addWidget(QLabel("服务提供商:")) self.provider_combo = QComboBox() - self.provider_combo.addItems(["SiliconFlow", "ModelScope"]) + self.provider_combo.addItems(list(PROVIDER_DISPLAY_NAMES.values())) - current_provider = self.config_manager.get_plugin_setting('copilot', 'provider', 'siliconflow') - provider_index = 0 if current_provider == 'siliconflow' else 1 - self.provider_combo.setCurrentIndex(provider_index) + current_provider = self.config_manager.get_plugin_setting('copilot', 'provider', PROVIDER_SILICONFLOW) + display_name = PROVIDER_DISPLAY_NAMES.get(current_provider, 'SiliconFlow') + provider_index = self.provider_combo.findText(display_name) + if provider_index >= 0: + self.provider_combo.setCurrentIndex(provider_index) self.provider_combo.currentIndexChanged.connect(self._on_provider_changed) layout.addWidget(self.provider_combo) @@ -566,7 +583,6 @@ def initUI(self): layout.addWidget(self.model_combo) # Enable checkbox - from PyQt5.QtWidgets import QCheckBox self.enable_checkbox = QCheckBox("启用 Copilot") self.enable_checkbox.setChecked( self.config_manager.get_plugin_setting('copilot', 'enabled', False) @@ -653,7 +669,8 @@ def _on_save(self): api_key = self.api_key_edit.text().strip() model = self.model_combo.currentText() enabled = self.enable_checkbox.isChecked() - provider = 'siliconflow' if self.provider_combo.currentText() == 'SiliconFlow' else 'modelscope' + provider_display = self.provider_combo.currentText() + provider = PROVIDER_FROM_DISPLAY.get(provider_display, PROVIDER_SILICONFLOW) if not api_key: QMessageBox.warning(self, "输入错误", "请输入API密钥") @@ -671,23 +688,21 @@ def _on_test(self): """Test API connection""" api_key = self.api_key_edit.text().strip() model = self.model_combo.currentText() - provider = self.provider_combo.currentText() + provider_display = self.provider_combo.currentText() if not api_key: QMessageBox.warning(self, "输入错误", "请输入API密钥") return try: - if provider == "SiliconFlow": - from src.copilot.siliconflow_client import SiliconFlowClient + if provider_display == "SiliconFlow": client = SiliconFlowClient(api_key, model) else: # ModelScope - from src.copilot.modelscope_client import ModelScopeClient client = ModelScopeClient(api_key, model) if client.test_connection(): - QMessageBox.information(self, "成功", f"{provider} API连接成功!") + QMessageBox.information(self, "成功", f"{provider_display} API连接成功!") else: - QMessageBox.warning(self, "失败", f"{provider} API连接失败") + QMessageBox.warning(self, "失败", f"{provider_display} API连接失败") except Exception as e: QMessageBox.critical(self, "错误", f"测试失败: {str(e)}") diff --git a/src/copilot/__init__.py b/src/copilot/__init__.py index 676c740..a9de73a 100644 --- a/src/copilot/__init__.py +++ b/src/copilot/__init__.py @@ -5,8 +5,18 @@ Copilot module for MGit - AI-powered writing assistant """ -from .copilot_manager import CopilotManager +from .copilot_manager import ( + CopilotManager, + PROVIDER_SILICONFLOW, + PROVIDER_MODELSCOPE +) from .siliconflow_client import SiliconFlowClient from .modelscope_client import ModelScopeClient -__all__ = ['CopilotManager', 'SiliconFlowClient', 'ModelScopeClient'] +__all__ = [ + 'CopilotManager', + 'SiliconFlowClient', + 'ModelScopeClient', + 'PROVIDER_SILICONFLOW', + 'PROVIDER_MODELSCOPE' +] diff --git a/src/copilot/copilot_manager.py b/src/copilot/copilot_manager.py index d064553..7e13d35 100644 --- a/src/copilot/copilot_manager.py +++ b/src/copilot/copilot_manager.py @@ -34,6 +34,10 @@ MAX_CONTEXT_BEFORE = 500 # Characters of context before cursor MAX_CONTEXT_AFTER = 100 # Characters of context after cursor +# Provider constants +PROVIDER_SILICONFLOW = 'siliconflow' +PROVIDER_MODELSCOPE = 'modelscope' + class CopilotManager(QObject): """ Manager for copilot functionality @@ -51,7 +55,7 @@ def __init__(self, config_manager: ConfigManager): self.config_manager = config_manager self.client = None self.enabled = False - self.provider = 'siliconflow' # siliconflow or modelscope + self.provider = PROVIDER_SILICONFLOW # siliconflow or modelscope self.current_mode = 'none' # none, inline, edit, creation, conversation, agent self.current_threads = [] # Store active threads @@ -85,12 +89,12 @@ def _load_config(self): """Load copilot configuration""" try: # Load provider selection - self.provider = self.config_manager.get_plugin_setting('copilot', 'provider', 'siliconflow') + self.provider = self.config_manager.get_plugin_setting('copilot', 'provider', PROVIDER_SILICONFLOW) api_key = self.config_manager.get_plugin_setting('copilot', 'api_key', '') # Get default model based on provider - if self.provider == 'modelscope': + if self.provider == PROVIDER_MODELSCOPE: default_model = ModelScopeClient.DEFAULT_MODELS['chat'] else: default_model = SiliconFlowClient.DEFAULT_MODELS['chat'] @@ -100,7 +104,7 @@ def _load_config(self): if api_key: # Create client based on provider - if self.provider == 'modelscope': + if self.provider == PROVIDER_MODELSCOPE: self.client = ModelScopeClient(api_key, model) info("Copilot client initialized with ModelScope", category=LogCategory.API) else: @@ -123,7 +127,7 @@ def set_api_key(self, api_key: str, model: str = None, provider: str = None): Args: api_key: API key (SiliconFlow or ModelScope) model: Optional model name - provider: Optional provider name ('siliconflow' or 'modelscope') + provider: Optional provider name (PROVIDER_SILICONFLOW or PROVIDER_MODELSCOPE) """ self.config_manager.set_plugin_setting('copilot', 'api_key', api_key) @@ -133,7 +137,7 @@ def set_api_key(self, api_key: str, model: str = None, provider: str = None): self.config_manager.set_plugin_setting('copilot', 'provider', provider) # Get default model based on provider - if self.provider == 'modelscope': + if self.provider == PROVIDER_MODELSCOPE: default_model = ModelScopeClient.DEFAULT_MODELS['chat'] else: default_model = SiliconFlowClient.DEFAULT_MODELS['chat'] @@ -144,7 +148,7 @@ def set_api_key(self, api_key: str, model: str = None, provider: str = None): model = self.config_manager.get_plugin_setting('copilot', 'model', default_model) # Create client based on provider - if self.provider == 'modelscope': + if self.provider == PROVIDER_MODELSCOPE: self.client = ModelScopeClient(api_key, model) info("Copilot API key updated for ModelScope", category=LogCategory.CONFIG) else: @@ -163,9 +167,9 @@ def set_provider(self, provider: str): Switch to a different provider Args: - provider: Provider name ('siliconflow' or 'modelscope') + provider: Provider name (PROVIDER_SILICONFLOW or PROVIDER_MODELSCOPE) """ - if provider not in ['siliconflow', 'modelscope']: + if provider not in [PROVIDER_SILICONFLOW, PROVIDER_MODELSCOPE]: error(f"Invalid provider: {provider}", category=LogCategory.CONFIG) return