mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-02-15 03:23:05 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2b93310e2 | |||
| f3b1e7f411 | |||
| a55c6ccfdb | |||
| b32433c336 | |||
| 6f11260cd1 | |||
| ddd6aba091 | |||
| e3f464c54e | |||
| e86e58337a | |||
| dbd47387be | |||
| 50e1276ab2 | |||
| 50c948ccfe | |||
| 949dad4fd2 |
8
.github/workflows/build_cmake.yml
vendored
8
.github/workflows/build_cmake.yml
vendored
@ -45,17 +45,13 @@ jobs:
|
||||
cc: "clang", cxx: "clang++"
|
||||
}
|
||||
qt_config:
|
||||
- {
|
||||
qt_version: "6.8.3",
|
||||
qt_creator_version: "16.0.2"
|
||||
}
|
||||
- {
|
||||
qt_version: "6.9.2",
|
||||
qt_creator_version: "17.0.2"
|
||||
}
|
||||
- {
|
||||
qt_version: "6.10.0",
|
||||
qt_creator_version: "18.0.0"
|
||||
qt_version: "6.10.1",
|
||||
qt_creator_version: "18.0.1"
|
||||
}
|
||||
|
||||
steps:
|
||||
|
||||
@ -38,14 +38,6 @@ SerializationResult ChatSerializer::saveToFile(const ChatModel *model, const QSt
|
||||
return {false, "Failed to create directory structure"};
|
||||
}
|
||||
|
||||
QString contentFolder = getChatContentFolder(filePath);
|
||||
QDir dir;
|
||||
if (!dir.exists(contentFolder)) {
|
||||
if (!dir.mkpath(contentFolder)) {
|
||||
LOG_MESSAGE(QString("Warning: Failed to create content folder: %1").arg(contentFolder));
|
||||
}
|
||||
}
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
return {false, QString("Failed to open file for writing: %1").arg(filePath)};
|
||||
@ -88,21 +80,22 @@ SerializationResult ChatSerializer::loadFromFile(ChatModel *model, const QString
|
||||
return {true, QString()};
|
||||
}
|
||||
|
||||
QJsonObject ChatSerializer::serializeMessage(const ChatModel::Message &message, const QString &chatFilePath)
|
||||
QJsonObject ChatSerializer::serializeMessage(
|
||||
const ChatModel::Message &message, const QString &chatFilePath)
|
||||
{
|
||||
QJsonObject messageObj;
|
||||
messageObj["role"] = static_cast<int>(message.role);
|
||||
messageObj["content"] = message.content;
|
||||
messageObj["id"] = message.id;
|
||||
|
||||
|
||||
if (message.isRedacted) {
|
||||
messageObj["isRedacted"] = true;
|
||||
}
|
||||
|
||||
|
||||
if (!message.signature.isEmpty()) {
|
||||
messageObj["signature"] = message.signature;
|
||||
}
|
||||
|
||||
|
||||
if (!message.attachments.isEmpty()) {
|
||||
QJsonArray attachmentsArray;
|
||||
for (const auto &attachment : message.attachments) {
|
||||
@ -113,7 +106,7 @@ QJsonObject ChatSerializer::serializeMessage(const ChatModel::Message &message,
|
||||
}
|
||||
messageObj["attachments"] = attachmentsArray;
|
||||
}
|
||||
|
||||
|
||||
if (!message.images.isEmpty()) {
|
||||
QJsonArray imagesArray;
|
||||
for (const auto &image : message.images) {
|
||||
@ -125,11 +118,12 @@ QJsonObject ChatSerializer::serializeMessage(const ChatModel::Message &message,
|
||||
}
|
||||
messageObj["images"] = imagesArray;
|
||||
}
|
||||
|
||||
|
||||
return messageObj;
|
||||
}
|
||||
|
||||
ChatModel::Message ChatSerializer::deserializeMessage(const QJsonObject &json, const QString &chatFilePath)
|
||||
ChatModel::Message ChatSerializer::deserializeMessage(
|
||||
const QJsonObject &json, const QString &chatFilePath)
|
||||
{
|
||||
ChatModel::Message message;
|
||||
message.role = static_cast<ChatModel::ChatRole>(json["role"].toInt());
|
||||
@ -137,7 +131,7 @@ ChatModel::Message ChatSerializer::deserializeMessage(const QJsonObject &json, c
|
||||
message.id = json["id"].toString();
|
||||
message.isRedacted = json["isRedacted"].toBool(false);
|
||||
message.signature = json["signature"].toString();
|
||||
|
||||
|
||||
if (json.contains("attachments")) {
|
||||
QJsonArray attachmentsArray = json["attachments"].toArray();
|
||||
for (const auto &attachmentValue : attachmentsArray) {
|
||||
@ -148,7 +142,7 @@ ChatModel::Message ChatSerializer::deserializeMessage(const QJsonObject &json, c
|
||||
message.attachments.append(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (json.contains("images")) {
|
||||
QJsonArray imagesArray = json["images"].toArray();
|
||||
for (const auto &imageValue : imagesArray) {
|
||||
@ -160,7 +154,7 @@ ChatModel::Message ChatSerializer::deserializeMessage(const QJsonObject &json, c
|
||||
message.images.append(image);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
@ -178,7 +172,8 @@ QJsonObject ChatSerializer::serializeChat(const ChatModel *model, const QString
|
||||
return root;
|
||||
}
|
||||
|
||||
bool ChatSerializer::deserializeChat(ChatModel *model, const QJsonObject &json, const QString &chatFilePath)
|
||||
bool ChatSerializer::deserializeChat(
|
||||
ChatModel *model, const QJsonObject &json, const QString &chatFilePath)
|
||||
{
|
||||
QJsonArray messagesArray = json["messages"].toArray();
|
||||
QVector<ChatModel::Message> messages;
|
||||
@ -189,17 +184,24 @@ bool ChatSerializer::deserializeChat(ChatModel *model, const QJsonObject &json,
|
||||
}
|
||||
|
||||
model->clear();
|
||||
|
||||
|
||||
model->setLoadingFromHistory(true);
|
||||
|
||||
|
||||
for (const auto &message : messages) {
|
||||
model->addMessage(message.content, message.role, message.id, message.attachments, message.images, message.isRedacted, message.signature);
|
||||
model->addMessage(
|
||||
message.content,
|
||||
message.role,
|
||||
message.id,
|
||||
message.attachments,
|
||||
message.images,
|
||||
message.isRedacted,
|
||||
message.signature);
|
||||
LOG_MESSAGE(QString("Loaded message with %1 image(s), isRedacted=%2, signature length=%3")
|
||||
.arg(message.images.size())
|
||||
.arg(message.isRedacted)
|
||||
.arg(message.signature.length()));
|
||||
}
|
||||
|
||||
|
||||
model->setLoadingFromHistory(false);
|
||||
|
||||
return true;
|
||||
@ -217,12 +219,14 @@ bool ChatSerializer::validateVersion(const QString &version)
|
||||
if (version == VERSION) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (version == "0.1") {
|
||||
LOG_MESSAGE("Loading chat from old format 0.1 - images folder structure has changed from _images to _content");
|
||||
LOG_MESSAGE(
|
||||
"Loading chat from old format 0.1 - images folder structure has changed from _images "
|
||||
"to _content");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -234,10 +238,11 @@ QString ChatSerializer::getChatContentFolder(const QString &chatFilePath)
|
||||
return QDir(dirPath).filePath(baseName + "_content");
|
||||
}
|
||||
|
||||
bool ChatSerializer::saveContentToStorage(const QString &chatFilePath,
|
||||
const QString &fileName,
|
||||
const QString &base64Data,
|
||||
QString &storedPath)
|
||||
bool ChatSerializer::saveContentToStorage(
|
||||
const QString &chatFilePath,
|
||||
const QString &fileName,
|
||||
const QString &base64Data,
|
||||
QString &storedPath)
|
||||
{
|
||||
QString contentFolder = getChatContentFolder(chatFilePath);
|
||||
QDir dir;
|
||||
@ -247,34 +252,34 @@ bool ChatSerializer::saveContentToStorage(const QString &chatFilePath,
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QFileInfo originalFileInfo(fileName);
|
||||
QString extension = originalFileInfo.suffix();
|
||||
QString baseName = originalFileInfo.completeBaseName();
|
||||
QString uniqueName = QString("%1_%2.%3")
|
||||
.arg(baseName)
|
||||
.arg(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8))
|
||||
.arg(extension);
|
||||
|
||||
.arg(baseName)
|
||||
.arg(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8))
|
||||
.arg(extension);
|
||||
|
||||
QString fullPath = QDir(contentFolder).filePath(uniqueName);
|
||||
|
||||
|
||||
QByteArray contentData = QByteArray::fromBase64(base64Data.toUtf8());
|
||||
QFile file(fullPath);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
LOG_MESSAGE(QString("Failed to open file for writing: %1").arg(fullPath));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (file.write(contentData) == -1) {
|
||||
LOG_MESSAGE(QString("Failed to write content data: %1").arg(file.errorString()));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
file.close();
|
||||
|
||||
|
||||
storedPath = uniqueName;
|
||||
LOG_MESSAGE(QString("Saved content: %1 to %2").arg(fileName, fullPath));
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -282,16 +287,16 @@ QString ChatSerializer::loadContentFromStorage(const QString &chatFilePath, cons
|
||||
{
|
||||
QString contentFolder = getChatContentFolder(chatFilePath);
|
||||
QString fullPath = QDir(contentFolder).filePath(storedPath);
|
||||
|
||||
|
||||
QFile file(fullPath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
LOG_MESSAGE(QString("Failed to open content file: %1").arg(fullPath));
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
QByteArray contentData = file.readAll();
|
||||
file.close();
|
||||
|
||||
|
||||
return contentData.toBase64();
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"Id" : "qodeassist",
|
||||
"Name" : "QodeAssist",
|
||||
"Version" : "0.9.5",
|
||||
"Version" : "0.9.8",
|
||||
"CompatVersion" : "${IDE_VERSION}",
|
||||
"Vendor" : "Petr Mironychev",
|
||||
"VendorId" : "petrmironychev",
|
||||
|
||||
34
README.md
34
README.md
@ -125,16 +125,34 @@ For more information, visit the [QodeAssistUpdater repository](https://github.co
|
||||
|
||||
## Configuration
|
||||
|
||||
QodeAssist supports multiple LLM providers. Choose your preferred provider and follow the configuration guide:
|
||||
### Quick Setup (Recommended for Beginners)
|
||||
|
||||
### Supported Providers
|
||||
The Quick Setup feature provides one-click configuration for popular cloud AI models. Get started in 3 easy steps:
|
||||
<details>
|
||||
<summary>Quick setup: (click to expand)</summary>
|
||||
<img width="600" alt="Quick Setup" src="https://github.com/user-attachments/assets/20df9155-9095-420c-8387-908bd931bcfa">
|
||||
</details>
|
||||
|
||||
1. **Open QodeAssist Settings**
|
||||
2. **Select a Preset** - Choose from the Quick Setup dropdown:
|
||||
- **Anthropic Claude** (Sonnet 4.5, Haiku 4.5, Opus 4.5)
|
||||
- **OpenAI** (gpt-5.2-codex)
|
||||
- **Mistral AI** (Codestral 2501)
|
||||
- **Google AI** (Gemini 2.5 Flash)
|
||||
3. **Configure API Key** - Click "Configure API Key" button and enter your API key in Provider Settings
|
||||
|
||||
All settings (provider, model, template, URL) are configured automatically. Just add your API key and you're ready to go!
|
||||
|
||||
### Manual Provider Configuration
|
||||
|
||||
For advanced users or local models, choose your preferred provider and follow the detailed configuration guide:
|
||||
|
||||
- **[Ollama](docs/ollama-configuration.md)** - Local LLM provider
|
||||
- **[llama.cpp](docs/llamacpp-configuration.md)** - Local LLM server
|
||||
- **[Anthropic Claude](docs/claude-configuration.md)** - Сloud provider
|
||||
- **[OpenAI](docs/openai-configuration.md)** - Сloud provider (includes Responses API support)
|
||||
- **[Mistral AI](docs/mistral-configuration.md)** - Сloud provider
|
||||
- **[Google AI](docs/google-ai-configuration.md)** - Сloud provider
|
||||
- **[Anthropic Claude](docs/claude-configuration.md)** - Cloud provider (manual setup)
|
||||
- **[OpenAI](docs/openai-configuration.md)** - Cloud provider (includes Responses API support)
|
||||
- **[Mistral AI](docs/mistral-configuration.md)** - Cloud provider
|
||||
- **[Google AI](docs/google-ai-configuration.md)** - Cloud provider
|
||||
- **LM Studio** - Local LLM provider
|
||||
- **OpenAI-compatible** - Custom providers (OpenRouter, etc.)
|
||||
|
||||
@ -159,6 +177,7 @@ For optimal coding assistance, we recommend using these top-tier models:
|
||||
### Additional Configuration
|
||||
|
||||
- **[Agent Roles](docs/agent-roles.md)** - Create AI personas with specialized system prompts
|
||||
- **[Chat Summarization](docs/chat-summarization.md)** - Compress conversations to save context tokens
|
||||
- **[Project Rules](docs/project-rules.md)** - Customize AI behavior for your project
|
||||
- **[Ignoring Files](docs/ignoring-files.md)** - Exclude files from context using `.qodeassistignore`
|
||||
|
||||
@ -196,6 +215,7 @@ Configure in: `Tools → Options → QodeAssist → Code Completion → General
|
||||
- Chat history with auto-save and restore
|
||||
- Token usage monitoring
|
||||
- **[Agent Roles](docs/agent-roles.md)** - Switch between AI personas (Developer, Reviewer, custom roles)
|
||||
- **[Chat Summarization](docs/chat-summarization.md)** - Compress long conversations into AI-generated summaries
|
||||
- **[File Context](docs/file-context.md)** - Attach or link files for better context
|
||||
- Automatic syncing with open editor files (optional)
|
||||
- Extended thinking mode (Claude, other providers in plan) - Enable deeper reasoning for complex tasks
|
||||
@ -358,7 +378,7 @@ See [Project Rules Documentation](docs/project-rules.md), [Agent Roles Guide](do
|
||||
| Qt Creator Version | QodeAssist Version |
|
||||
|-------------------|-------------------|
|
||||
| 17.0.0+ | 0.6.0 - 0.x.x |
|
||||
| 16.0.2 | 0.5.13 - 0.x.x |
|
||||
| 16.0.2 | 0.5.13 - 0.9.6 |
|
||||
| 16.0.1 | 0.5.7 - 0.5.13 |
|
||||
| 16.0.0 | 0.5.2 - 0.5.6 |
|
||||
| 15.0.1 | 0.4.8 - 0.5.1 |
|
||||
|
||||
@ -15,13 +15,16 @@ Agent Roles are reusable system prompt configurations that modify how the AI ass
|
||||
|
||||
## Default Roles
|
||||
|
||||
QodeAssist comes with two built-in roles:
|
||||
QodeAssist comes with three built-in roles:
|
||||
|
||||
### Developer
|
||||
General coding assistance focused on writing clean, maintainable code following industry standards. Best for implementation tasks, debugging, and code explanations.
|
||||
Experienced Qt/C++ developer with a structured workflow: analyze the problem, propose a solution, wait for approval, then implement. Best for implementation tasks where you want thoughtful, minimal code changes.
|
||||
|
||||
### Code Reviewer
|
||||
Expert code review persona that identifies bugs, performance issues, and adherence to best practices. Provides constructive feedback with specific suggestions for improvement.
|
||||
Expert C++/QML code reviewer specializing in C++20 and Qt6. Checks for bugs, memory leaks, thread safety, Qt patterns, and production readiness. Provides direct, specific feedback with code examples.
|
||||
|
||||
### Researcher
|
||||
Research-oriented developer who investigates problems and explores solutions. Analyzes problems, presents multiple approaches with trade-offs, and recommends the best option. Does not write implementation code — focuses on helping you make informed decisions.
|
||||
|
||||
## Using Agent Roles
|
||||
|
||||
|
||||
115
docs/chat-summarization.md
Normal file
115
docs/chat-summarization.md
Normal file
@ -0,0 +1,115 @@
|
||||
# Chat Summarization
|
||||
|
||||
Chat Summarization allows you to compress long conversations into concise AI-generated summaries. This helps save context tokens and makes it easier to continue work on complex topics without losing important information.
|
||||
|
||||
## Overview
|
||||
|
||||
When conversations grow long, they consume more context tokens with each message. Chat Summarization uses your configured Chat Assistant provider to create an intelligent summary that preserves:
|
||||
|
||||
- Key decisions and conclusions
|
||||
- Technical details and code references
|
||||
- Important context for continuing the conversation
|
||||
|
||||
**Key Features:**
|
||||
- **One-click compression**: Summarize directly from the chat toolbar
|
||||
- **Preserves original**: Creates a new chat file, keeping the original intact
|
||||
- **Smart summaries**: AI extracts the most relevant information
|
||||
- **Markdown formatted**: Summaries are well-structured and readable
|
||||
|
||||
## Using Chat Summarization
|
||||
|
||||
### Compressing a Chat
|
||||
|
||||
1. Open any chat with conversation history
|
||||
2. Click the **Compress** button (📦) in the chat top bar
|
||||
3. Wait for the AI to generate the summary
|
||||
4. A new chat opens with the compressed summary
|
||||
|
||||
### What Gets Preserved
|
||||
|
||||
The summarization process:
|
||||
- Maintains chronological flow of the discussion
|
||||
- Keeps technical details, code snippets, and file references
|
||||
- Preserves key decisions and conclusions
|
||||
- Aims for 30-40% of the original conversation length
|
||||
|
||||
### What Gets Filtered
|
||||
|
||||
The following message types are excluded from summarization:
|
||||
- Tool call results (file reads, searches)
|
||||
- File edit blocks
|
||||
- Thinking/reasoning blocks
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ CHAT SUMMARIZATION │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 1. Original chat messages are collected │
|
||||
│ 2. Tool/thinking messages are filtered out │
|
||||
│ 3. AI generates a structured summary │
|
||||
│ 4. New chat file is created with summary as first message │
|
||||
│ 5. Original chat remains unchanged │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### File Naming
|
||||
|
||||
Compressed chats are saved with a unique suffix:
|
||||
```
|
||||
original_chat.json → original_chat_a1b2c.json
|
||||
```
|
||||
|
||||
Both files appear in your chat history, allowing you to switch between the full conversation and the summary.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Summarize at natural breakpoints**: Compress after completing a major task or topic
|
||||
2. **Review the summary**: Ensure important details were captured before continuing
|
||||
3. **Keep originals**: Don't delete original chats until you've verified the summary is sufficient
|
||||
4. **Use for long sessions**: Most beneficial for conversations with 20+ messages
|
||||
|
||||
## When to Use
|
||||
|
||||
**Good candidates for summarization:**
|
||||
- Long debugging sessions with resolved issues
|
||||
- Feature implementation discussions with final decisions
|
||||
- Research conversations where conclusions were reached
|
||||
- Any chat approaching context limits
|
||||
|
||||
**Consider keeping full history for:**
|
||||
- Ongoing work that may need exact message references
|
||||
- Conversations with important code snippets you'll copy
|
||||
- Discussions where the reasoning process matters
|
||||
|
||||
## Configuration
|
||||
|
||||
Chat Summarization uses your current Chat Assistant settings:
|
||||
- **Provider**: Same as Chat Assistant (Settings → QodeAssist → General)
|
||||
- **Model**: Same as Chat Assistant
|
||||
- **Template**: Same as Chat Assistant
|
||||
|
||||
No additional configuration is required.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Compression Button Not Visible
|
||||
- Ensure you have an active chat with messages
|
||||
- Check that the chat top bar is visible
|
||||
|
||||
### Compression Fails
|
||||
- Verify your Chat Assistant provider is configured correctly
|
||||
- Check network connectivity
|
||||
- Ensure the model supports chat completions
|
||||
|
||||
### Summary Missing Details
|
||||
- The AI aims for 30-40% compression; some details may be condensed
|
||||
- For critical information, keep the original chat
|
||||
- Consider summarizing smaller conversation segments
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Agent Roles](agent-roles.md) - Switch between AI personas
|
||||
- [File Context](file-context.md) - Attach files to chat
|
||||
- [Project Rules](project-rules.md) - Customize AI behavior
|
||||
@ -250,8 +250,6 @@ public:
|
||||
editorContextMenu->addAction(requestAction.command(), Core::Constants::G_DEFAULT_THREE);
|
||||
editorContextMenu->addAction(showChatViewAction.command(),
|
||||
Core::Constants::G_DEFAULT_THREE);
|
||||
editorContextMenu->addAction(closeChatViewAction.command(),
|
||||
Core::Constants::G_DEFAULT_THREE);
|
||||
}
|
||||
|
||||
Chat::ChatFileManager::cleanupGlobalIntermediateStorage();
|
||||
|
||||
@ -124,6 +124,9 @@ void AgentRolesManager::ensureDefaultRoles()
|
||||
|
||||
if (!dir.exists("reviewer.json"))
|
||||
saveRole(getDefaultReviewerRole());
|
||||
|
||||
if (!dir.exists("researcher.json"))
|
||||
saveRole(getDefaultResearcherRole());
|
||||
}
|
||||
|
||||
AgentRole AgentRolesManager::getDefaultDeveloperRole()
|
||||
@ -131,12 +134,34 @@ AgentRole AgentRolesManager::getDefaultDeveloperRole()
|
||||
return AgentRole{
|
||||
"developer",
|
||||
"Developer",
|
||||
"General coding assistance and implementation",
|
||||
"You are an advanced AI assistant specializing in C++, Qt, and QML development. "
|
||||
"Your role is to provide helpful, accurate, and detailed responses to questions "
|
||||
"about coding, debugging, and best practices in these technologies. "
|
||||
"Focus on writing clean, maintainable code following industry standards.",
|
||||
false};
|
||||
"Experienced Qt/C++ developer for implementation tasks",
|
||||
"You are an experienced Qt/C++ developer working on a Qt Creator plugin.\n\n"
|
||||
"Your workflow:\n"
|
||||
"1. **Analyze** - understand the problem and what needs to be done\n"
|
||||
"2. **Propose solution** - explain your approach in 2-3 sentences\n"
|
||||
"3. **Wait for approval** - don't write code until the solution is confirmed\n"
|
||||
"4. **Implement** - write clean, minimal code that solves the task\n\n"
|
||||
"When analyzing:\n"
|
||||
"- Ask clarifying questions if requirements are unclear\n"
|
||||
"- Check existing code for similar patterns\n"
|
||||
"- Consider edge cases and potential issues\n\n"
|
||||
"When proposing:\n"
|
||||
"- Explain what you'll change and why\n"
|
||||
"- Mention files you'll modify\n"
|
||||
"- Note any architectural implications\n\n"
|
||||
"When implementing:\n"
|
||||
"- Use C++20, Qt6, follow existing codebase style\n"
|
||||
"- Write only what's needed (MVP approach)\n"
|
||||
"- Include file paths and necessary changes\n"
|
||||
"- Handle errors properly\n"
|
||||
"- Make sure it compiles\n\n"
|
||||
"Keep it practical:\n"
|
||||
"- Short explanations, let code speak\n"
|
||||
"- No over-engineering or unnecessary refactoring\n"
|
||||
"- No TODOs, debug code, or unfinished work\n"
|
||||
"- Point out non-obvious things\n\n"
|
||||
"You're a pragmatic team member who thinks before coding.",
|
||||
true};
|
||||
}
|
||||
|
||||
AgentRole AgentRolesManager::getDefaultReviewerRole()
|
||||
@ -144,16 +169,58 @@ AgentRole AgentRolesManager::getDefaultReviewerRole()
|
||||
return AgentRole{
|
||||
"reviewer",
|
||||
"Code Reviewer",
|
||||
"Code review, quality assurance, and best practices",
|
||||
"You are an expert code reviewer specializing in C++, Qt, and QML. "
|
||||
"Your role is to:\n"
|
||||
"- Identify potential bugs, memory leaks, and performance issues\n"
|
||||
"- Check adherence to coding standards and best practices\n"
|
||||
"- Suggest improvements for readability and maintainability\n"
|
||||
"- Verify proper error handling and edge cases\n"
|
||||
"- Ensure thread safety and proper Qt object lifetime management\n"
|
||||
"Provide constructive, specific feedback with examples.",
|
||||
false};
|
||||
"Expert C++/QML code reviewer for quality assurance",
|
||||
"You are an expert C++/QML code reviewer specializing in C++20 and Qt6.\n\n"
|
||||
"What you check:\n"
|
||||
"- Bugs, memory leaks, undefined behavior\n"
|
||||
"- C++20 compliance and Qt6 patterns\n"
|
||||
"- RAII, move semantics, smart pointers\n"
|
||||
"- Qt parent-child ownership and signal/slot correctness\n"
|
||||
"- Thread safety and Qt concurrent usage\n"
|
||||
"- const-correctness and Qt container usage\n"
|
||||
"- Performance bottlenecks\n"
|
||||
"- Production readiness: error handling, no debug leftovers\n\n"
|
||||
"What you do:\n"
|
||||
"- Point out problems with clear explanations\n"
|
||||
"- Suggest specific fixes with code examples\n"
|
||||
"- Remove unnecessary comments, keep essential docs only\n"
|
||||
"- Flag anything that's not production-ready\n"
|
||||
"- Recommend optimizations when you spot them\n\n"
|
||||
"Focus on: correctness, performance, maintainability, Qt idioms.\n\n"
|
||||
"Be direct and specific. Show, don't just tell.",
|
||||
true};
|
||||
}
|
||||
|
||||
AgentRole AgentRolesManager::getDefaultResearcherRole()
|
||||
{
|
||||
return AgentRole{
|
||||
"researcher",
|
||||
"Researcher",
|
||||
"Research-oriented developer for exploring solutions",
|
||||
"You are a research-oriented Qt/C++ developer who investigates problems and explores "
|
||||
"solutions.\n\n"
|
||||
"Your job is to think, not to code:\n"
|
||||
"- Deep dive into the problem before suggesting anything\n"
|
||||
"- Research Qt docs, patterns, and best practices\n"
|
||||
"- Find multiple ways to solve it\n"
|
||||
"- Compare trade-offs: performance, complexity, maintainability\n"
|
||||
"- Look for relevant Qt APIs and modules\n"
|
||||
"- Think about architectural consequences\n\n"
|
||||
"How you work:\n"
|
||||
"1. **Problem Analysis** - what exactly needs solving\n"
|
||||
"2. **Research Findings** - what you learned about this problem space\n"
|
||||
"3. **Solution Options** - present 2-3 approaches with honest pros/cons\n"
|
||||
"4. **Recommendation** - which one fits best and why\n"
|
||||
"5. **Next Steps** - what to consider before implementing\n\n"
|
||||
"What you provide:\n"
|
||||
"- Clear comparison of different approaches\n"
|
||||
"- Code snippets as examples (not ready-to-use patches)\n"
|
||||
"- Links to docs, examples, similar implementations\n"
|
||||
"- Questions to clarify requirements\n"
|
||||
"- Warning about potential problems\n\n"
|
||||
"You DO NOT write implementation code. You explore options and let the developer choose.\n\n"
|
||||
"Think like a consultant: research thoroughly, present clearly, stay objective.",
|
||||
true};
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
|
||||
@ -76,6 +76,7 @@ public:
|
||||
private:
|
||||
static AgentRole getDefaultDeveloperRole();
|
||||
static AgentRole getDefaultReviewerRole();
|
||||
static AgentRole getDefaultResearcherRole();
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Settings
|
||||
|
||||
@ -43,6 +43,93 @@ ConfigurationManager &ConfigurationManager::instance()
|
||||
return instance;
|
||||
}
|
||||
|
||||
QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
|
||||
ConfigurationType type)
|
||||
{
|
||||
QVector<AIConfiguration> presets;
|
||||
|
||||
AIConfiguration claudeOpus;
|
||||
claudeOpus.id = "preset_claude_opus";
|
||||
claudeOpus.name = "Claude Opus 4.5";
|
||||
claudeOpus.provider = "Claude";
|
||||
claudeOpus.model = "claude-opus-4-5-20251101";
|
||||
claudeOpus.url = "https://api.anthropic.com";
|
||||
claudeOpus.endpointMode = "Auto";
|
||||
claudeOpus.customEndpoint = "";
|
||||
claudeOpus.templateName = "Claude";
|
||||
claudeOpus.type = type;
|
||||
claudeOpus.isPredefined = true;
|
||||
|
||||
AIConfiguration claudeSonnet;
|
||||
claudeSonnet.id = "preset_claude_sonnet";
|
||||
claudeSonnet.name = "Claude Sonnet 4.5";
|
||||
claudeSonnet.provider = "Claude";
|
||||
claudeSonnet.model = "claude-sonnet-4-5-20250929";
|
||||
claudeSonnet.url = "https://api.anthropic.com";
|
||||
claudeSonnet.endpointMode = "Auto";
|
||||
claudeSonnet.customEndpoint = "";
|
||||
claudeSonnet.templateName = "Claude";
|
||||
claudeSonnet.type = type;
|
||||
claudeSonnet.isPredefined = true;
|
||||
|
||||
AIConfiguration claudeHaiku;
|
||||
claudeHaiku.id = "preset_claude_haiku";
|
||||
claudeHaiku.name = "Claude Haiku 4.5";
|
||||
claudeHaiku.provider = "Claude";
|
||||
claudeHaiku.model = "claude-haiku-4-5-20251001";
|
||||
claudeHaiku.url = "https://api.anthropic.com";
|
||||
claudeHaiku.endpointMode = "Auto";
|
||||
claudeHaiku.customEndpoint = "";
|
||||
claudeHaiku.templateName = "Claude";
|
||||
claudeHaiku.type = type;
|
||||
claudeHaiku.isPredefined = true;
|
||||
|
||||
AIConfiguration mistralCodestral;
|
||||
mistralCodestral.id = "preset_mistral_codestral";
|
||||
mistralCodestral.name = "Mistral Codestral";
|
||||
mistralCodestral.provider = "Mistral AI";
|
||||
mistralCodestral.model = "codestral-2501";
|
||||
mistralCodestral.url = "https://api.mistral.ai";
|
||||
mistralCodestral.endpointMode = "Auto";
|
||||
mistralCodestral.customEndpoint = "";
|
||||
mistralCodestral.templateName = type == ConfigurationType::CodeCompletion ? "Mistral AI FIM" : "Mistral AI Chat";
|
||||
mistralCodestral.type = type;
|
||||
mistralCodestral.isPredefined = true;
|
||||
|
||||
AIConfiguration geminiFlash;
|
||||
geminiFlash.id = "preset_gemini_flash";
|
||||
geminiFlash.name = "Gemini 2.5 Flash";
|
||||
geminiFlash.provider = "Google AI";
|
||||
geminiFlash.model = "gemini-2.5-flash";
|
||||
geminiFlash.url = "https://generativelanguage.googleapis.com/v1beta";
|
||||
geminiFlash.endpointMode = "Auto";
|
||||
geminiFlash.customEndpoint = "";
|
||||
geminiFlash.templateName = "Google AI";
|
||||
geminiFlash.type = type;
|
||||
geminiFlash.isPredefined = true;
|
||||
|
||||
AIConfiguration gpt52codex;
|
||||
gpt52codex.id = "preset_gpt52codex";
|
||||
gpt52codex.name = "gpt-5.2-codex";
|
||||
gpt52codex.provider = "OpenAI Responses";
|
||||
gpt52codex.model = "gpt-5.2-codex";
|
||||
gpt52codex.url = "https://api.openai.com";
|
||||
gpt52codex.endpointMode = "Auto";
|
||||
gpt52codex.customEndpoint = "";
|
||||
gpt52codex.templateName = "OpenAI Responses";
|
||||
gpt52codex.type = type;
|
||||
gpt52codex.isPredefined = true;
|
||||
|
||||
presets.append(claudeSonnet);
|
||||
presets.append(claudeHaiku);
|
||||
presets.append(claudeOpus);
|
||||
presets.append(gpt52codex);
|
||||
presets.append(mistralCodestral);
|
||||
presets.append(geminiFlash);
|
||||
|
||||
return presets;
|
||||
}
|
||||
|
||||
QString ConfigurationManager::configurationTypeToString(ConfigurationType type) const
|
||||
{
|
||||
switch (type) {
|
||||
@ -94,6 +181,9 @@ bool ConfigurationManager::loadConfigurations(ConfigurationType type)
|
||||
|
||||
configs->clear();
|
||||
|
||||
QVector<AIConfiguration> predefinedConfigs = getPredefinedConfigurations(type);
|
||||
configs->append(predefinedConfigs);
|
||||
|
||||
if (!ensureDirectoryExists(type)) {
|
||||
LOG_MESSAGE("Failed to create configuration directory");
|
||||
return false;
|
||||
@ -131,6 +221,7 @@ bool ConfigurationManager::loadConfigurations(ConfigurationType type)
|
||||
config.customEndpoint = obj["customEndpoint"].toString();
|
||||
config.type = type;
|
||||
config.formatVersion = obj.value("formatVersion").toInt(1);
|
||||
config.isPredefined = false;
|
||||
|
||||
if (config.id.isEmpty() || config.name.isEmpty()) {
|
||||
LOG_MESSAGE(QString("Invalid configuration data in file: %1").arg(fileInfo.fileName()));
|
||||
@ -185,6 +276,12 @@ bool ConfigurationManager::saveConfiguration(const AIConfiguration &config)
|
||||
|
||||
bool ConfigurationManager::deleteConfiguration(const QString &id, ConfigurationType type)
|
||||
{
|
||||
AIConfiguration config = getConfigurationById(id, type);
|
||||
if (config.isPredefined) {
|
||||
LOG_MESSAGE(QString("Cannot delete predefined configuration: %1").arg(id));
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir dir(getConfigurationDirectory(type));
|
||||
QStringList filters;
|
||||
filters << QString("*_%1.json").arg(id);
|
||||
|
||||
@ -41,6 +41,7 @@ struct AIConfiguration
|
||||
QString customEndpoint;
|
||||
ConfigurationType type;
|
||||
int formatVersion = CONFIGURATION_FORMAT_VERSION;
|
||||
bool isPredefined = false;
|
||||
};
|
||||
|
||||
class ConfigurationManager : public QObject
|
||||
@ -58,6 +59,8 @@ public:
|
||||
AIConfiguration getConfigurationById(const QString &id, ConfigurationType type) const;
|
||||
|
||||
QString getConfigurationDirectory(ConfigurationType type) const;
|
||||
|
||||
static QVector<AIConfiguration> getPredefinedConfigurations(ConfigurationType type);
|
||||
|
||||
signals:
|
||||
void configurationsChanged(ConfigurationType type);
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/utilsicons.h>
|
||||
#include <QComboBox>
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QInputDialog>
|
||||
@ -88,6 +89,27 @@ GeneralSettings::GeneralSettings()
|
||||
|
||||
resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS;
|
||||
checkUpdate.m_buttonText = TrConstants::CHECK_UPDATE;
|
||||
|
||||
ccPresetConfig.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
ccPresetConfig.setLabelText(Tr::tr("Quick Setup"));
|
||||
loadPresetConfigurations(ccPresetConfig, ConfigurationType::CodeCompletion);
|
||||
|
||||
ccConfigureApiKey.m_buttonText = Tr::tr("Configure API Key");
|
||||
ccConfigureApiKey.m_tooltip = Tr::tr("Open Provider Settings to configure API keys");
|
||||
|
||||
caPresetConfig.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
caPresetConfig.setLabelText(Tr::tr("Quick Setup"));
|
||||
loadPresetConfigurations(caPresetConfig, ConfigurationType::Chat);
|
||||
|
||||
caConfigureApiKey.m_buttonText = Tr::tr("Configure API Key");
|
||||
caConfigureApiKey.m_tooltip = Tr::tr("Open Provider Settings to configure API keys");
|
||||
|
||||
qrPresetConfig.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
qrPresetConfig.setLabelText(Tr::tr("Quick Setup"));
|
||||
loadPresetConfigurations(qrPresetConfig, ConfigurationType::QuickRefactor);
|
||||
|
||||
qrConfigureApiKey.m_buttonText = Tr::tr("Configure API Key");
|
||||
qrConfigureApiKey.m_tooltip = Tr::tr("Open Provider Settings to configure API keys");
|
||||
|
||||
initStringAspect(ccProvider, Constants::CC_PROVIDER, TrConstants::PROVIDER, "Ollama");
|
||||
ccProvider.setReadOnly(true);
|
||||
@ -127,6 +149,7 @@ GeneralSettings::GeneralSettings()
|
||||
|
||||
ccSaveConfig.m_buttonText = TrConstants::SAVE_CONFIG;
|
||||
ccLoadConfig.m_buttonText = TrConstants::LOAD_CONFIG;
|
||||
ccLoadConfig.m_tooltip = Tr::tr("Load configuration (includes predefined cloud models)");
|
||||
ccOpenConfigFolder.m_buttonText = TrConstants::OPEN_CONFIG_FOLDER;
|
||||
ccOpenConfigFolder.m_icon = Utils::Icons::OPENFILE.icon();
|
||||
ccOpenConfigFolder.m_isCompact = true;
|
||||
@ -218,6 +241,7 @@ GeneralSettings::GeneralSettings()
|
||||
|
||||
caSaveConfig.m_buttonText = TrConstants::SAVE_CONFIG;
|
||||
caLoadConfig.m_buttonText = TrConstants::LOAD_CONFIG;
|
||||
caLoadConfig.m_tooltip = Tr::tr("Load configuration (includes predefined cloud models)");
|
||||
caOpenConfigFolder.m_buttonText = TrConstants::OPEN_CONFIG_FOLDER;
|
||||
caOpenConfigFolder.m_icon = Utils::Icons::OPENFILE.icon();
|
||||
caOpenConfigFolder.m_isCompact = true;
|
||||
@ -262,6 +286,7 @@ GeneralSettings::GeneralSettings()
|
||||
|
||||
qrSaveConfig.m_buttonText = TrConstants::SAVE_CONFIG;
|
||||
qrLoadConfig.m_buttonText = TrConstants::LOAD_CONFIG;
|
||||
qrLoadConfig.m_tooltip = Tr::tr("Load configuration (includes predefined cloud models)");
|
||||
qrOpenConfigFolder.m_buttonText = TrConstants::OPEN_CONFIG_FOLDER;
|
||||
qrOpenConfigFolder.m_icon = Utils::Icons::OPENFILE.icon();
|
||||
qrOpenConfigFolder.m_isCompact = true;
|
||||
@ -325,17 +350,24 @@ GeneralSettings::GeneralSettings()
|
||||
title(TrConstants::CODE_COMPLETION),
|
||||
Column{
|
||||
Row{ccSaveConfig, ccLoadConfig, ccOpenConfigFolder, Stretch{1}},
|
||||
Row{ccPresetConfig, ccConfigureApiKey, Stretch{1}},
|
||||
ccGrid,
|
||||
Row{specifyPreset1, preset1Language, Stretch{1}},
|
||||
ccPreset1Grid}};
|
||||
|
||||
auto caGroup = Group{
|
||||
title(TrConstants::CHAT_ASSISTANT),
|
||||
Column{Row{caSaveConfig, caLoadConfig, caOpenConfigFolder, Stretch{1}}, caGrid}};
|
||||
Column{
|
||||
Row{caSaveConfig, caLoadConfig, caOpenConfigFolder, Stretch{1}},
|
||||
Row{caPresetConfig, caConfigureApiKey, Stretch{1}},
|
||||
caGrid}};
|
||||
|
||||
auto qrGroup = Group{
|
||||
title(TrConstants::QUICK_REFACTOR),
|
||||
Column{Row{qrSaveConfig, qrLoadConfig, qrOpenConfigFolder, Stretch{1}}, qrGrid}};
|
||||
Column{
|
||||
Row{qrSaveConfig, qrLoadConfig, qrOpenConfigFolder, Stretch{1}},
|
||||
Row{qrPresetConfig, qrConfigureApiKey, Stretch{1}},
|
||||
qrGrid}};
|
||||
|
||||
auto rootLayout = Column{
|
||||
Row{enableQodeAssist, Stretch{1}, Row{checkUpdate, resetToDefaults}},
|
||||
@ -570,6 +602,33 @@ void GeneralSettings::setupConnections()
|
||||
connect(&checkUpdate, &ButtonAspect::clicked, this, [this]() {
|
||||
QodeAssist::UpdateDialog::checkForUpdatesAndShow(Core::ICore::dialogParent());
|
||||
});
|
||||
|
||||
connect(&ccPresetConfig, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
|
||||
applyPresetConfiguration(ccPresetConfig.volatileValue(), ConfigurationType::CodeCompletion);
|
||||
ccPresetConfig.setValue(0);
|
||||
});
|
||||
|
||||
connect(&ccConfigureApiKey, &ButtonAspect::clicked, this, []() {
|
||||
Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
||||
});
|
||||
|
||||
connect(&caPresetConfig, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
|
||||
applyPresetConfiguration(caPresetConfig.volatileValue(), ConfigurationType::Chat);
|
||||
caPresetConfig.setValue(0);
|
||||
});
|
||||
|
||||
connect(&caConfigureApiKey, &ButtonAspect::clicked, this, []() {
|
||||
Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
||||
});
|
||||
|
||||
connect(&qrPresetConfig, &Utils::SelectionAspect::volatileValueChanged, this, [this]() {
|
||||
applyPresetConfiguration(qrPresetConfig.volatileValue(), ConfigurationType::QuickRefactor);
|
||||
qrPresetConfig.setValue(0);
|
||||
});
|
||||
|
||||
connect(&qrConfigureApiKey, &ButtonAspect::clicked, this, []() {
|
||||
Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
||||
});
|
||||
|
||||
connect(&specifyPreset1, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
updatePreset1Visiblity(specifyPreset1.volatileValue());
|
||||
@ -776,11 +835,33 @@ void GeneralSettings::onLoadConfiguration(const QString &prefix)
|
||||
|
||||
SettingsDialog dialog(TrConstants::LOAD_CONFIGURATION);
|
||||
dialog.addLabel(TrConstants::SELECT_CONFIGURATION);
|
||||
|
||||
int predefinedCount = 0;
|
||||
for (const AIConfiguration &config : configs) {
|
||||
if (config.isPredefined) {
|
||||
predefinedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (predefinedCount > 0) {
|
||||
auto *hintLabel = dialog.addLabel(
|
||||
Tr::tr("[Preset] configurations are predefined cloud models ready to use."));
|
||||
QFont hintFont = hintLabel->font();
|
||||
hintFont.setItalic(true);
|
||||
hintFont.setPointSize(hintFont.pointSize() - 1);
|
||||
hintLabel->setFont(hintFont);
|
||||
hintLabel->setStyleSheet("color: gray;");
|
||||
}
|
||||
|
||||
dialog.addSpacing();
|
||||
|
||||
QStringList configNames;
|
||||
for (const AIConfiguration &config : configs) {
|
||||
configNames.append(config.name);
|
||||
QString displayName = config.name;
|
||||
if (config.isPredefined) {
|
||||
displayName = QString("[Preset] %1").arg(config.name);
|
||||
}
|
||||
configNames.append(displayName);
|
||||
}
|
||||
|
||||
auto configList = dialog.addComboBox(configNames, QString());
|
||||
@ -790,9 +871,31 @@ void GeneralSettings::onLoadConfiguration(const QString &prefix)
|
||||
auto *okButton = new QPushButton(TrConstants::OK);
|
||||
auto *cancelButton = new QPushButton(TrConstants::CANCEL);
|
||||
|
||||
auto updateDeleteButtonState = [&]() {
|
||||
int currentIndex = configList->currentIndex();
|
||||
if (currentIndex >= 0 && currentIndex < configs.size()) {
|
||||
deleteButton->setEnabled(!configs[currentIndex].isPredefined);
|
||||
}
|
||||
};
|
||||
|
||||
connect(configList,
|
||||
QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
updateDeleteButtonState);
|
||||
|
||||
updateDeleteButtonState();
|
||||
|
||||
connect(deleteButton, &QPushButton::clicked, &dialog, [&]() {
|
||||
int currentIndex = configList->currentIndex();
|
||||
if (currentIndex >= 0 && currentIndex < configs.size()) {
|
||||
const AIConfiguration &configToDelete = configs[currentIndex];
|
||||
if (configToDelete.isPredefined) {
|
||||
QMessageBox::information(
|
||||
&dialog,
|
||||
TrConstants::DELETE_CONFIGURATION,
|
||||
Tr::tr("Predefined configurations cannot be deleted."));
|
||||
return;
|
||||
}
|
||||
|
||||
QMessageBox::StandardButton reply = QMessageBox::question(
|
||||
&dialog,
|
||||
TrConstants::DELETE_CONFIGURATION,
|
||||
@ -800,7 +903,6 @@ void GeneralSettings::onLoadConfiguration(const QString &prefix)
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (reply == QMessageBox::Yes) {
|
||||
const AIConfiguration &configToDelete = configs[currentIndex];
|
||||
if (manager.deleteConfiguration(configToDelete.id, type)) {
|
||||
dialog.accept();
|
||||
onLoadConfiguration(prefix);
|
||||
@ -860,6 +962,73 @@ void GeneralSettings::onLoadConfiguration(const QString &prefix)
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void GeneralSettings::loadPresetConfigurations(Utils::SelectionAspect &aspect,
|
||||
ConfigurationType type)
|
||||
{
|
||||
QVector<AIConfiguration> presets = ConfigurationManager::getPredefinedConfigurations(type);
|
||||
|
||||
if (type == ConfigurationType::CodeCompletion) {
|
||||
m_ccPresets = presets;
|
||||
} else if (type == ConfigurationType::Chat) {
|
||||
m_caPresets = presets;
|
||||
} else if (type == ConfigurationType::QuickRefactor) {
|
||||
m_qrPresets = presets;
|
||||
}
|
||||
|
||||
aspect.addOption(Tr::tr("-- Select Preset --"));
|
||||
for (const AIConfiguration &config : presets) {
|
||||
aspect.addOption(config.name);
|
||||
}
|
||||
aspect.setDefaultValue(0);
|
||||
}
|
||||
|
||||
void GeneralSettings::applyPresetConfiguration(int index, ConfigurationType type)
|
||||
{
|
||||
if (index <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<AIConfiguration> *presets = nullptr;
|
||||
if (type == ConfigurationType::CodeCompletion) {
|
||||
presets = &m_ccPresets;
|
||||
} else if (type == ConfigurationType::Chat) {
|
||||
presets = &m_caPresets;
|
||||
} else if (type == ConfigurationType::QuickRefactor) {
|
||||
presets = &m_qrPresets;
|
||||
}
|
||||
|
||||
if (!presets || index - 1 >= presets->size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const AIConfiguration &config = presets->at(index - 1);
|
||||
|
||||
if (type == ConfigurationType::CodeCompletion) {
|
||||
ccProvider.setValue(config.provider);
|
||||
ccModel.setValue(config.model);
|
||||
ccTemplate.setValue(config.templateName);
|
||||
ccUrl.setValue(config.url);
|
||||
ccEndpointMode.setValue(ccEndpointMode.indexForDisplay(config.endpointMode));
|
||||
ccCustomEndpoint.setValue(config.customEndpoint);
|
||||
} else if (type == ConfigurationType::Chat) {
|
||||
caProvider.setValue(config.provider);
|
||||
caModel.setValue(config.model);
|
||||
caTemplate.setValue(config.templateName);
|
||||
caUrl.setValue(config.url);
|
||||
caEndpointMode.setValue(caEndpointMode.indexForDisplay(config.endpointMode));
|
||||
caCustomEndpoint.setValue(config.customEndpoint);
|
||||
} else if (type == ConfigurationType::QuickRefactor) {
|
||||
qrProvider.setValue(config.provider);
|
||||
qrModel.setValue(config.model);
|
||||
qrTemplate.setValue(config.templateName);
|
||||
qrUrl.setValue(config.url);
|
||||
qrEndpointMode.setValue(qrEndpointMode.indexForDisplay(config.endpointMode));
|
||||
qrCustomEndpoint.setValue(config.customEndpoint);
|
||||
}
|
||||
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
class GeneralSettingsPage : public Core::IOptionsPage
|
||||
{
|
||||
public:
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#include <QPointer>
|
||||
|
||||
#include "ButtonAspect.hpp"
|
||||
#include "ConfigurationManager.hpp"
|
||||
|
||||
namespace Utils {
|
||||
class DetailsWidget;
|
||||
@ -46,6 +47,9 @@ public:
|
||||
ButtonAspect resetToDefaults{this};
|
||||
|
||||
// code completion setttings
|
||||
Utils::SelectionAspect ccPresetConfig{this};
|
||||
ButtonAspect ccConfigureApiKey{this};
|
||||
|
||||
Utils::StringAspect ccProvider{this};
|
||||
ButtonAspect ccSelectProvider{this};
|
||||
|
||||
@ -91,6 +95,9 @@ public:
|
||||
ButtonAspect ccPreset1SelectTemplate{this};
|
||||
|
||||
// chat assistant settings
|
||||
Utils::SelectionAspect caPresetConfig{this};
|
||||
ButtonAspect caConfigureApiKey{this};
|
||||
|
||||
Utils::StringAspect caProvider{this};
|
||||
ButtonAspect caSelectProvider{this};
|
||||
|
||||
@ -116,6 +123,9 @@ public:
|
||||
ButtonAspect caOpenConfigFolder{this};
|
||||
|
||||
// quick refactor settings
|
||||
Utils::SelectionAspect qrPresetConfig{this};
|
||||
ButtonAspect qrConfigureApiKey{this};
|
||||
|
||||
Utils::StringAspect qrProvider{this};
|
||||
ButtonAspect qrSelectProvider{this};
|
||||
|
||||
@ -162,10 +172,17 @@ public:
|
||||
|
||||
void onSaveConfiguration(const QString &prefix);
|
||||
void onLoadConfiguration(const QString &prefix);
|
||||
|
||||
void loadPresetConfigurations(Utils::SelectionAspect &aspect, ConfigurationType type);
|
||||
void applyPresetConfiguration(int index, ConfigurationType type);
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
void resetPageToDefaults();
|
||||
|
||||
QVector<AIConfiguration> m_ccPresets;
|
||||
QVector<AIConfiguration> m_caPresets;
|
||||
QVector<AIConfiguration> m_qrPresets;
|
||||
};
|
||||
|
||||
GeneralSettings &generalSettings();
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDir>
|
||||
#include <QFontMetrics>
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
#include <QIcon>
|
||||
#include <QLabel>
|
||||
@ -72,15 +73,15 @@ static QIcon createThemedIcon(const QString &svgPath, const QColor &color)
|
||||
painter.end();
|
||||
|
||||
QImage image = pixmap.toImage().convertToFormat(QImage::Format_ARGB32);
|
||||
|
||||
|
||||
uchar *bits = image.bits();
|
||||
const int bytesPerPixel = 4;
|
||||
const int totalBytes = image.width() * image.height() * bytesPerPixel;
|
||||
|
||||
|
||||
const int newR = color.red();
|
||||
const int newG = color.green();
|
||||
const int newB = color.blue();
|
||||
|
||||
|
||||
for (int i = 0; i < totalBytes; i += bytesPerPixel) {
|
||||
int alpha = bits[i + 3];
|
||||
if (alpha > 0) {
|
||||
@ -100,11 +101,17 @@ QuickRefactorDialog::QuickRefactorDialog(QWidget *parent, const QString &lastIns
|
||||
setWindowTitle(Tr::tr("Quick Refactor"));
|
||||
setupUi();
|
||||
|
||||
if (!m_lastInstructions.isEmpty()) {
|
||||
m_instructionEdit->setPlainText(m_lastInstructions);
|
||||
m_instructionEdit->selectAll();
|
||||
}
|
||||
|
||||
QTimer::singleShot(0, this, &QuickRefactorDialog::updateDialogSize);
|
||||
m_textEdit->installEventFilter(this);
|
||||
m_instructionEdit->installEventFilter(this);
|
||||
m_commandsComboBox->installEventFilter(this);
|
||||
updateDialogSize();
|
||||
|
||||
m_commandsComboBox->setFocus();
|
||||
m_instructionEdit->setFocus();
|
||||
}
|
||||
|
||||
void QuickRefactorDialog::setupUi()
|
||||
@ -173,56 +180,65 @@ void QuickRefactorDialog::setupUi()
|
||||
|
||||
mainLayout->addLayout(actionsLayout);
|
||||
|
||||
QHBoxLayout *instructionsLayout = new QHBoxLayout();
|
||||
instructionsLayout->setSpacing(4);
|
||||
QLabel *instructionLabel = new QLabel(Tr::tr("Your Current Instruction:"), this);
|
||||
mainLayout->addWidget(instructionLabel);
|
||||
|
||||
QLabel *instructionsLabel = new QLabel(Tr::tr("Custom Instructions:"), this);
|
||||
instructionsLayout->addWidget(instructionsLabel);
|
||||
m_instructionEdit = new QPlainTextEdit(this);
|
||||
m_instructionEdit->setMinimumHeight(80);
|
||||
m_instructionEdit->setPlaceholderText(Tr::tr("Type or edit your instruction..."));
|
||||
mainLayout->addWidget(m_instructionEdit);
|
||||
|
||||
QHBoxLayout *savedInstructionsLayout = new QHBoxLayout();
|
||||
savedInstructionsLayout->setSpacing(4);
|
||||
|
||||
QLabel *savedLabel = new QLabel(Tr::tr("Or Load saved:"), this);
|
||||
savedInstructionsLayout->addWidget(savedLabel);
|
||||
|
||||
m_commandsComboBox = new QComboBox(this);
|
||||
m_commandsComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
m_commandsComboBox->setEditable(true);
|
||||
m_commandsComboBox->setInsertPolicy(QComboBox::NoInsert);
|
||||
m_commandsComboBox->lineEdit()->setPlaceholderText("Search or select instruction...");
|
||||
|
||||
m_commandsComboBox->lineEdit()->setPlaceholderText(Tr::tr("Search saved instructions..."));
|
||||
|
||||
QCompleter *completer = new QCompleter(this);
|
||||
completer->setCompletionMode(QCompleter::PopupCompletion);
|
||||
completer->setCaseSensitivity(Qt::CaseInsensitive);
|
||||
completer->setFilterMode(Qt::MatchContains);
|
||||
m_commandsComboBox->setCompleter(completer);
|
||||
|
||||
instructionsLayout->addWidget(m_commandsComboBox);
|
||||
|
||||
savedInstructionsLayout->addWidget(m_commandsComboBox);
|
||||
|
||||
m_addCommandButton = new QToolButton(this);
|
||||
m_addCommandButton->setText("+");
|
||||
m_addCommandButton->setToolTip(Tr::tr("Add Custom Instruction"));
|
||||
instructionsLayout->addWidget(m_addCommandButton);
|
||||
m_addCommandButton->setFocusPolicy(Qt::NoFocus);
|
||||
savedInstructionsLayout->addWidget(m_addCommandButton);
|
||||
|
||||
m_editCommandButton = new QToolButton(this);
|
||||
m_editCommandButton->setText("✎");
|
||||
m_editCommandButton->setToolTip(Tr::tr("Edit Custom Instruction"));
|
||||
instructionsLayout->addWidget(m_editCommandButton);
|
||||
m_editCommandButton->setFocusPolicy(Qt::NoFocus);
|
||||
savedInstructionsLayout->addWidget(m_editCommandButton);
|
||||
|
||||
m_deleteCommandButton = new QToolButton(this);
|
||||
m_deleteCommandButton->setText("−");
|
||||
m_deleteCommandButton->setToolTip(Tr::tr("Delete Custom Instruction"));
|
||||
instructionsLayout->addWidget(m_deleteCommandButton);
|
||||
m_deleteCommandButton->setFocusPolicy(Qt::NoFocus);
|
||||
savedInstructionsLayout->addWidget(m_deleteCommandButton);
|
||||
|
||||
m_openFolderButton = new QToolButton(this);
|
||||
m_openFolderButton->setText("📁");
|
||||
m_openFolderButton->setToolTip(Tr::tr("Open Instructions Folder"));
|
||||
instructionsLayout->addWidget(m_openFolderButton);
|
||||
m_openFolderButton->setFocusPolicy(Qt::NoFocus);
|
||||
savedInstructionsLayout->addWidget(m_openFolderButton);
|
||||
|
||||
mainLayout->addLayout(instructionsLayout);
|
||||
mainLayout->addLayout(savedInstructionsLayout);
|
||||
|
||||
m_instructionsLabel = new QLabel(Tr::tr("Additional instructions (optional):"), this);
|
||||
mainLayout->addWidget(m_instructionsLabel);
|
||||
|
||||
m_textEdit = new QPlainTextEdit(this);
|
||||
m_textEdit->setMinimumHeight(100);
|
||||
m_textEdit->setPlaceholderText(Tr::tr("Add extra details or modifications to the selected instruction..."));
|
||||
|
||||
connect(m_textEdit, &QPlainTextEdit::textChanged, this, &QuickRefactorDialog::updateDialogSize);
|
||||
connect(
|
||||
m_instructionEdit,
|
||||
&QPlainTextEdit::textChanged,
|
||||
this,
|
||||
&QuickRefactorDialog::updateDialogSize);
|
||||
connect(
|
||||
m_commandsComboBox,
|
||||
QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
@ -242,8 +258,6 @@ void QuickRefactorDialog::setupUi()
|
||||
this,
|
||||
&QuickRefactorDialog::onOpenInstructionsFolder);
|
||||
|
||||
mainLayout->addWidget(m_textEdit);
|
||||
|
||||
loadCustomCommands();
|
||||
loadAvailableConfigurations();
|
||||
|
||||
@ -255,12 +269,23 @@ void QuickRefactorDialog::setupUi()
|
||||
|
||||
QDialogButtonBox *buttonBox
|
||||
= new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &QuickRefactorDialog::validateAndAccept);
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
mainLayout->addWidget(buttonBox);
|
||||
|
||||
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
|
||||
QPushButton *cancelButton = buttonBox->button(QDialogButtonBox::Cancel);
|
||||
|
||||
setTabOrder(m_commandsComboBox, m_textEdit);
|
||||
setTabOrder(m_textEdit, buttonBox);
|
||||
if (okButton) {
|
||||
okButton->installEventFilter(this);
|
||||
}
|
||||
if (cancelButton) {
|
||||
cancelButton->installEventFilter(this);
|
||||
}
|
||||
|
||||
setTabOrder(m_instructionEdit, m_commandsComboBox);
|
||||
setTabOrder(m_commandsComboBox, okButton);
|
||||
setTabOrder(okButton, cancelButton);
|
||||
}
|
||||
|
||||
void QuickRefactorDialog::createActionButtons()
|
||||
@ -295,27 +320,12 @@ void QuickRefactorDialog::createActionButtons()
|
||||
|
||||
QString QuickRefactorDialog::instructions() const
|
||||
{
|
||||
QString result;
|
||||
|
||||
CustomInstruction instruction = findCurrentInstruction();
|
||||
if (!instruction.id.isEmpty()) {
|
||||
result = instruction.body;
|
||||
}
|
||||
|
||||
QString additionalText = m_textEdit->toPlainText().trimmed();
|
||||
if (!additionalText.isEmpty()) {
|
||||
if (!result.isEmpty()) {
|
||||
result += "\n\n";
|
||||
}
|
||||
result += additionalText;
|
||||
}
|
||||
|
||||
return result;
|
||||
return m_instructionEdit->toPlainText().trimmed();
|
||||
}
|
||||
|
||||
void QuickRefactorDialog::setInstructions(const QString &instructions)
|
||||
{
|
||||
m_textEdit->setPlainText(instructions);
|
||||
m_instructionEdit->setPlainText(instructions);
|
||||
}
|
||||
|
||||
QuickRefactorDialog::Action QuickRefactorDialog::selectedAction() const
|
||||
@ -323,17 +333,33 @@ QuickRefactorDialog::Action QuickRefactorDialog::selectedAction() const
|
||||
return m_selectedAction;
|
||||
}
|
||||
|
||||
void QuickRefactorDialog::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
QDialog::keyPressEvent(event);
|
||||
}
|
||||
|
||||
bool QuickRefactorDialog::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (watched == m_textEdit && event->type() == QEvent::KeyPress) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
|
||||
if (keyEvent->modifiers() & Qt::ShiftModifier) {
|
||||
return false;
|
||||
}
|
||||
|
||||
accept();
|
||||
return true;
|
||||
if (watched == m_instructionEdit) {
|
||||
if (keyEvent->key() == Qt::Key_Tab) {
|
||||
m_commandsComboBox->setFocus();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (watched == m_commandsComboBox || watched == m_commandsComboBox->lineEdit()) {
|
||||
if (keyEvent->key() == Qt::Key_Tab) {
|
||||
QPushButton *okButton = findChild<QPushButton *>();
|
||||
if (okButton && okButton->text() == "OK") {
|
||||
okButton->setFocus();
|
||||
} else {
|
||||
focusNextChild();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return QDialog::eventFilter(watched, event);
|
||||
@ -343,8 +369,7 @@ void QuickRefactorDialog::useLastInstructions()
|
||||
{
|
||||
if (!m_lastInstructions.isEmpty()) {
|
||||
m_commandsComboBox->setCurrentIndex(0);
|
||||
m_commandsComboBox->clearEditText(); // Clear search text
|
||||
m_textEdit->setPlainText(m_lastInstructions);
|
||||
m_instructionEdit->setPlainText(m_lastInstructions);
|
||||
m_selectedAction = Action::RepeatLast;
|
||||
}
|
||||
accept();
|
||||
@ -353,10 +378,10 @@ void QuickRefactorDialog::useLastInstructions()
|
||||
void QuickRefactorDialog::useImproveCodeTemplate()
|
||||
{
|
||||
m_commandsComboBox->setCurrentIndex(0);
|
||||
m_commandsComboBox->clearEditText(); // Clear search text
|
||||
m_textEdit->setPlainText(Tr::tr(
|
||||
"Improve the selected code by enhancing readability, efficiency, and maintainability. "
|
||||
"Follow best practices for C++/Qt and fix any potential issues."));
|
||||
m_instructionEdit->setPlainText(
|
||||
Tr::tr(
|
||||
"Improve the selected code by enhancing readability, efficiency, and maintainability. "
|
||||
"Follow best practices for C++/Qt and fix any potential issues."));
|
||||
m_selectedAction = Action::ImproveCode;
|
||||
accept();
|
||||
}
|
||||
@ -364,36 +389,29 @@ void QuickRefactorDialog::useImproveCodeTemplate()
|
||||
void QuickRefactorDialog::useAlternativeSolutionTemplate()
|
||||
{
|
||||
m_commandsComboBox->setCurrentIndex(0);
|
||||
m_commandsComboBox->clearEditText(); // Clear search text
|
||||
m_textEdit->setPlainText(
|
||||
Tr::tr("Suggest an alternative implementation approach for the selected code. "
|
||||
"Provide a different solution that might be cleaner, more efficient, "
|
||||
"or uses different Qt/C++ patterns or idioms."));
|
||||
m_instructionEdit->setPlainText(
|
||||
Tr::tr(
|
||||
"Suggest an alternative implementation approach for the selected code. "
|
||||
"Provide a different solution that might be cleaner, more efficient, "
|
||||
"or uses different Qt/C++ patterns or idioms."));
|
||||
m_selectedAction = Action::AlternativeSolution;
|
||||
accept();
|
||||
}
|
||||
|
||||
void QuickRefactorDialog::updateDialogSize()
|
||||
{
|
||||
QString text = m_textEdit->toPlainText();
|
||||
QString text = m_instructionEdit->toPlainText();
|
||||
|
||||
QFontMetrics fm(m_textEdit->font());
|
||||
QFontMetrics fm(m_instructionEdit->font());
|
||||
|
||||
QStringList lines = text.split('\n');
|
||||
int lineCount = lines.size();
|
||||
int lineCount = qMax(lines.size(), 3);
|
||||
|
||||
if (lineCount <= 1) {
|
||||
int singleLineHeight = fm.height() + 10;
|
||||
m_textEdit->setMinimumHeight(singleLineHeight);
|
||||
m_textEdit->setMaximumHeight(singleLineHeight);
|
||||
} else {
|
||||
m_textEdit->setMaximumHeight(QWIDGETSIZE_MAX);
|
||||
m_instructionEdit->setMaximumHeight(QWIDGETSIZE_MAX);
|
||||
|
||||
int lineHeight = fm.height() + 2;
|
||||
|
||||
int textEditHeight = qMin(qMax(lineCount, 2) * lineHeight, 20 * lineHeight);
|
||||
m_textEdit->setMinimumHeight(textEditHeight);
|
||||
}
|
||||
int lineHeight = fm.height() + 2;
|
||||
int textEditHeight = qMin(qMax(lineCount, 3) * lineHeight, 15 * lineHeight);
|
||||
m_instructionEdit->setMinimumHeight(textEditHeight);
|
||||
|
||||
int maxWidth = 500;
|
||||
for (const QString &line : lines) {
|
||||
@ -405,14 +423,7 @@ void QuickRefactorDialog::updateDialogSize()
|
||||
QRect screenGeometry = screen->availableGeometry();
|
||||
|
||||
int newWidth = qMin(maxWidth + 40, screenGeometry.width() * 3 / 4);
|
||||
|
||||
int newHeight;
|
||||
if (lineCount <= 1) {
|
||||
newHeight = 150;
|
||||
} else {
|
||||
newHeight = m_textEdit->minimumHeight() + 150;
|
||||
}
|
||||
newHeight = qMin(newHeight, screenGeometry.height() * 3 / 4);
|
||||
int newHeight = qMin(m_instructionEdit->minimumHeight() + 200, screenGeometry.height() * 3 / 4);
|
||||
|
||||
resize(newWidth, newHeight);
|
||||
}
|
||||
@ -420,22 +431,16 @@ void QuickRefactorDialog::updateDialogSize()
|
||||
void QuickRefactorDialog::loadCustomCommands()
|
||||
{
|
||||
m_commandsComboBox->clear();
|
||||
m_commandsComboBox->addItem("", QString()); // Empty item for no selection
|
||||
m_commandsComboBox->addItem("", QString());
|
||||
|
||||
auto &manager = CustomInstructionsManager::instance();
|
||||
const QVector<CustomInstruction> &instructions = manager.instructions();
|
||||
|
||||
QStringList instructionNames;
|
||||
int defaultInstructionIndex = -1;
|
||||
|
||||
for (int i = 0; i < instructions.size(); ++i) {
|
||||
const CustomInstruction &instruction = instructions[i];
|
||||
|
||||
for (const CustomInstruction &instruction : instructions) {
|
||||
m_commandsComboBox->addItem(instruction.name, instruction.id);
|
||||
instructionNames.append(instruction.name);
|
||||
|
||||
if (instruction.isDefault) {
|
||||
defaultInstructionIndex = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_commandsComboBox->completer()) {
|
||||
@ -443,10 +448,6 @@ void QuickRefactorDialog::loadCustomCommands()
|
||||
m_commandsComboBox->completer()->setModel(model);
|
||||
}
|
||||
|
||||
if (defaultInstructionIndex > 0) {
|
||||
m_commandsComboBox->setCurrentIndex(defaultInstructionIndex);
|
||||
}
|
||||
|
||||
bool hasInstructions = !instructions.isEmpty();
|
||||
m_editCommandButton->setEnabled(hasInstructions);
|
||||
m_deleteCommandButton->setEnabled(hasInstructions);
|
||||
@ -461,13 +462,13 @@ CustomInstruction QuickRefactorDialog::findCurrentInstruction() const
|
||||
|
||||
auto &manager = CustomInstructionsManager::instance();
|
||||
const QVector<CustomInstruction> &instructions = manager.instructions();
|
||||
|
||||
|
||||
for (const CustomInstruction &instruction : instructions) {
|
||||
if (instruction.name == currentText) {
|
||||
return instruction;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int currentIndex = m_commandsComboBox->currentIndex();
|
||||
if (currentIndex > 0) {
|
||||
QString instructionId = m_commandsComboBox->itemData(currentIndex).toString();
|
||||
@ -475,13 +476,20 @@ CustomInstruction QuickRefactorDialog::findCurrentInstruction() const
|
||||
return manager.getInstructionById(instructionId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return CustomInstruction();
|
||||
}
|
||||
|
||||
void QuickRefactorDialog::onCommandSelected(int index)
|
||||
{
|
||||
Q_UNUSED(index);
|
||||
if (index <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
CustomInstruction instruction = findCurrentInstruction();
|
||||
if (!instruction.id.isEmpty()) {
|
||||
m_instructionEdit->setPlainText(instruction.body);
|
||||
}
|
||||
}
|
||||
|
||||
void QuickRefactorDialog::onAddCustomCommand()
|
||||
@ -493,10 +501,7 @@ void QuickRefactorDialog::onAddCustomCommand()
|
||||
|
||||
if (manager.saveInstruction(instruction)) {
|
||||
loadCustomCommands();
|
||||
|
||||
m_commandsComboBox->setCurrentText(instruction.name);
|
||||
|
||||
m_textEdit->clear();
|
||||
} else {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
@ -509,10 +514,12 @@ void QuickRefactorDialog::onAddCustomCommand()
|
||||
void QuickRefactorDialog::onEditCustomCommand()
|
||||
{
|
||||
CustomInstruction instruction = findCurrentInstruction();
|
||||
|
||||
|
||||
if (instruction.id.isEmpty()) {
|
||||
QMessageBox::information(
|
||||
this, Tr::tr("No Instruction Selected"), Tr::tr("Please select an instruction to edit."));
|
||||
this,
|
||||
Tr::tr("No Instruction Selected"),
|
||||
Tr::tr("Please select an instruction to edit."));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -524,7 +531,6 @@ void QuickRefactorDialog::onEditCustomCommand()
|
||||
if (manager.saveInstruction(updatedInstruction)) {
|
||||
loadCustomCommands();
|
||||
m_commandsComboBox->setCurrentText(updatedInstruction.name);
|
||||
m_textEdit->clear();
|
||||
} else {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
@ -537,10 +543,12 @@ void QuickRefactorDialog::onEditCustomCommand()
|
||||
void QuickRefactorDialog::onDeleteCustomCommand()
|
||||
{
|
||||
CustomInstruction instruction = findCurrentInstruction();
|
||||
|
||||
|
||||
if (instruction.id.isEmpty()) {
|
||||
QMessageBox::information(
|
||||
this, Tr::tr("No Instruction Selected"), Tr::tr("Please select an instruction to delete."));
|
||||
this,
|
||||
Tr::tr("No Instruction Selected"),
|
||||
Tr::tr("Please select an instruction to delete."));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -569,12 +577,12 @@ void QuickRefactorDialog::onOpenInstructionsFolder()
|
||||
{
|
||||
QString path = QString("%1/qodeassist/quick_refactor/instructions")
|
||||
.arg(Core::ICore::userResourcePath().toFSPathString());
|
||||
|
||||
|
||||
QDir dir(path);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(".");
|
||||
}
|
||||
|
||||
|
||||
QUrl url = QUrl::fromLocalFile(dir.absolutePath());
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
@ -594,8 +602,8 @@ void QuickRefactorDialog::loadAvailableConfigurations()
|
||||
auto &manager = Settings::ConfigurationManager::instance();
|
||||
manager.loadConfigurations(Settings::ConfigurationType::QuickRefactor);
|
||||
|
||||
QVector<Settings::AIConfiguration> configs
|
||||
= manager.configurations(Settings::ConfigurationType::QuickRefactor);
|
||||
QVector<Settings::AIConfiguration> configs = manager.configurations(
|
||||
Settings::ConfigurationType::QuickRefactor);
|
||||
|
||||
m_configComboBox->clear();
|
||||
m_configComboBox->addItem(Tr::tr("Current"), QString());
|
||||
@ -640,4 +648,20 @@ void QuickRefactorDialog::onConfigurationChanged(int index)
|
||||
}
|
||||
}
|
||||
|
||||
void QuickRefactorDialog::validateAndAccept()
|
||||
{
|
||||
QString instruction = m_instructionEdit->toPlainText().trimmed();
|
||||
|
||||
if (instruction.isEmpty()) {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
Tr::tr("No Instruction"),
|
||||
Tr::tr("Please type an instruction or select a saved one."));
|
||||
m_instructionEdit->setFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@ -27,6 +27,8 @@ class QPlainTextEdit;
|
||||
class QToolButton;
|
||||
class QLabel;
|
||||
class QComboBox;
|
||||
class QLineEdit;
|
||||
class QFrame;
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
@ -49,6 +51,7 @@ public:
|
||||
QString selectedConfiguration() const;
|
||||
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void useLastInstructions();
|
||||
@ -64,13 +67,14 @@ private slots:
|
||||
void loadCustomCommands();
|
||||
void loadAvailableConfigurations();
|
||||
void onConfigurationChanged(int index);
|
||||
void validateAndAccept();
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
void createActionButtons();
|
||||
CustomInstruction findCurrentInstruction() const;
|
||||
|
||||
QPlainTextEdit *m_textEdit;
|
||||
QPlainTextEdit *m_instructionEdit;
|
||||
QToolButton *m_repeatButton;
|
||||
QToolButton *m_improveButton;
|
||||
QToolButton *m_alternativeButton;
|
||||
@ -83,7 +87,6 @@ private:
|
||||
QToolButton *m_thinkingButton;
|
||||
QComboBox *m_commandsComboBox;
|
||||
QComboBox *m_configComboBox;
|
||||
QLabel *m_instructionsLabel;
|
||||
|
||||
Action m_selectedAction = Action::Custom;
|
||||
QString m_lastInstructions;
|
||||
|
||||
Reference in New Issue
Block a user