`), including all dependencies.
-2. **User Discussion & Review:** The generated plan is wrapped in a `Discussable` agent. This presents the plan to the
- user in several formats:
- * **Text:** The raw textual plan from the AI.
- * **JSON:** The structured plan data.
- * **Diagram:** A Mermaid.js graph visually representing the tasks and their dependencies.
- The user can then chat with the AI to revise and refine the plan until they are satisfied.
-3. **Execution:** Once the user approves the plan (or if `autoFix` is enabled), the `TaskOrchestrator`'s `executePlan`
- method is called. The orchestrator then executes the entire graph of tasks, respecting all dependencies, until the
- plan is complete or a task fails.
-
-* **Key Characteristics:**
-* **Plan-First:** All planning is done upfront.
-* **Transparent:** The user sees and approves the entire workflow before execution.
-* **Predictable:** The execution path is fixed once approved.
-* **Inflexible Execution:** The plan is not modified during the execution phase.
-
-* **When to Use It:**
-* Ideal for well-defined problems where the sequence of steps is clear from the outset.
-* Excellent for projects that require formal review or approval before work begins.
-* Suitable for batch processes or automated workflows that need to be reliable and repeatable.
-
-* **Strengths:**
-* Provides maximum user control over the plan.
-* Reduces the risk of unexpected actions.
-* The visual diagram makes complex dependencies easy to understand.
-
-* **Weaknesses:**
-* Poorly suited for ambiguous or exploratory problems where the next step is unknown.
-* Cannot adapt to unexpected results or changes in the environment once execution has started.
-
-### 2. Conversational Mode (Chat Mode)
-
-The `ConversationalMode` is an interactive, step-by-step execution model that behaves like a powerful chatbot with
-access to tools.
-
-* **High-Level Concept:** Instead of creating a large plan, analyze the user's immediate request, select the single most
- appropriate task to perform right now, execute it, and wait for the next instruction.
-
-* **How It Works (Internal Logic):**
-
-1. **Message Handling:** User messages are added to a queue. A background process pulls messages one by one for
- execution.
-2. **Expansion Syntax Processing:** The core logic resides in `processMsgRecursive`. Before any AI is involved, this
- function scans the message for special expansion syntax:
- * **Alternatives `@[a|b|c]`:** Splits the command into multiple parallel tasks.
- * **Sequence `@{a -> b -> c}`:** Chains commands to be executed sequentially.
- * **Range `@(1..5)`:** Expands a numerical range into a sequence.
- * **Topic Reference `@{TopicName}`:** Substitutes a placeholder with a list of items (e.g., file names) aggregated
- from previous turns.
-3. **Single Task Selection:** If no expansion syntax is found, the `executeTask` method is called. It uses a
- `ParsedAgent` named "TaskChooser" which analyzes the current message and the conversation history to select and
- configure *one single task*.
-4. **Execution & History:** The chosen task is executed immediately. The original user message and the task's result are
- then appended to the conversation history, providing context for the next turn.
-
-* **Key Characteristics:**
-* **Interactive & Reactive:** Acts on one command at a time, providing immediate feedback.
-* **Stateful:** Maintains a conversation history to inform future actions.
-* **Powerful Syntax:** The expansion syntax allows for complex parallel and sequential operations with a concise
- notation.
-
-* **When to Use It:**
-* Perfect for exploratory work, debugging, or iterative development.
-* When you want to guide the AI step-by-step.
-* Ideal for running variations of a task on different inputs (e.g., "Analyze these
- files: @[file1.txt|file2.txt|file3.txt]").
-
-* **Strengths:**
-* Extremely flexible and allows for rapid changes in direction.
-* Immediate feedback loop is great for tasks where results need to be inspected before proceeding.
-* The expansion syntax is a powerful force multiplier for repetitive tasks.
-
-* **Weaknesses:**
-* Can lose sight of the high-level goal without continuous user guidance.
-* Not suitable for complex, long-term planning on its own.
-
-### 3. Adaptive Planning Mode
-
-The `AdaptivePlanningMode` is an autonomous agent that operates in a cyclical "think, act, reflect" loop. It maintains a
-complex internal "state of mind" to solve problems iteratively.
-
-* **High-Level Concept:** Start with an initial understanding of the goal. In a loop, choose the best next actions based
- on the current state, execute them, and then update the internal state based on the results. Repeat until the goal is
- achieved.
-
-* **How It Works (Internal Logic):**
-
-1. **Initialization:** Upon receiving a user message, it initializes its internal state using a specific **Cognitive Strategy**.
- The default strategy (`ProjectManagerStrategy`) creates a `ReasoningState` containing goals, knowledge, and execution context.
- However, other strategies can be used to define different mental models (e.g., Scientific Method, Agile Development).
-2. **The Main Loop (Think-Act-Reflect):**
- * **Think (`getNextTask`):** At the start of each iteration, the agent analyzes its current `ReasoningState` and
- the history of past actions to decide on a small batch of tasks to execute next.
- * **Act (`runTask`):** The selected tasks are executed, often in parallel. Their results are captured.
- * **Reflect (`updateThinking`):** After the tasks complete, the agent analyzes their results. It uses a
- `ParsedAgent` to update its `ReasoningState`—revising goals, confirming hypotheses, answering open questions, and
- planning the next set of actions.
-3. **Termination:** The loop continues until no more tasks are generated, a maximum number of iterations is reached, or
- the agent concludes the goal is complete.
-
-* **Key Characteristics:**
-* **Autonomous:** Can work towards a goal for multiple iterations without user intervention.
-* **Iterative:** Refines its understanding and plan over time.
-* **Stateful & Reflective:** The `ReasoningState` acts as its memory and consciousness, allowing it to learn from its
- actions.
-* **Cognitive Strategies:** The mode's behavior is defined by its strategy. Available strategies include:
- * **Project Manager:** Standard goal-oriented planning.
- * **Scientific Researcher:** Hypothesis-driven investigation.
- * **Agile Developer:** Iterative Test-Driven Development.
- * **Critical Auditor:** Security and logic validation.
- * **Creative Writer:** Narrative and content generation.
-
-* **When to Use It:**
-* Complex, ambiguous, or poorly defined problems that require research, experimentation, and adaptation.
-* Long-running tasks where you want the agent to work autonomously.
-* Problems where the optimal path is not known in advance.
-
-* **Strengths:**
-* Can tackle complex problems that are too difficult for single-shot planning.
-* Adapts its strategy based on new information and task outcomes.
-* Maintains a coherent, long-term focus on the overall goal.
-
-* **Weaknesses:**
-* Can be slower and more resource-intensive due to the multiple LLM calls per iteration.
-* As an autonomous agent, it may occasionally pursue an incorrect path before self-correcting.
-* Offers less direct user control during its execution loop.
-
-### 4. Hierarchical Planning Mode
-
-The `HierarchicalPlanningMode` employs a "divide and conquer" strategy. It breaks down large, complex goals into a tree
-of smaller, manageable sub-goals and tasks, and then orchestrates their execution based on dependencies.
-
-* **High-Level Concept:** Decompose a primary goal into a hierarchy of sub-goals. Break down the lowest-level goals into
- concrete, executable tasks. Manage the dependencies between all goals and tasks, and execute them in parallel whenever
- possible.
-
-* **How It Works (Internal Logic):**
-
-1. **Initial Decomposition:** The initial user message is parsed into one or more high-level root goals, forming the top
- of the `goalTree`.
-2. **The Main Loop (Decompose-Execute-Update):**
- * **Decompose (`expandGoal`):** The agent finds active goals that have not yet been broken down. It uses a "
- GoalDecomposer" agent to decompose each goal into a set of smaller sub-goals and/or a list of executable tasks.
- * **Execute (`executeTask`):** The agent identifies all tasks whose dependencies have been met (i.e., are in
- `PENDING` status). It submits these tasks to a concurrent processor for parallel execution.
- * **Update Status (`updateAllStatuses`):** This is a critical, continuous process. The agent re-evaluates the
- status of every goal and task in the tree. A task becomes `PENDING` when its dependencies are complete. A goal
- becomes `COMPLETED` when all its children (sub-goals and tasks) are complete. This propagation of status updates
- drives the entire execution forward. The system also detects and attempts to break circular dependencies.
-3. **Termination:** The process continues until all goals are either `COMPLETED` or `BLOCKED`, and no tasks are left
- pending or running.
-
-* **Key Characteristics:**
-* **Structured & Hierarchical:** Organizes work into a clear, nested structure.
-* **Dependency-Aware:** Explicitly models and manages dependencies between tasks and goals.
-* **Highly Parallel:** Designed to maximize parallel execution of independent tasks.
-
-* **When to Use It:**
-* Large, complex projects that can be logically broken down into smaller parts (e.g., "build a web application").
-* Problems requiring sophisticated project management with clear dependencies.
-* When you want a transparent, real-time view of a complex plan's progress.
-
-* **Strengths:**
-* Brings structure and organization to massive tasks.
-* Excellent at managing complex dependencies automatically.
-* Enables a high degree of parallelism, potentially speeding up execution significantly.
-* The goal tree provides a clear and intuitive visualization of the project's status.
-
-* **Weaknesses:**
-* Incurs significant overhead from the constant planning, decomposition, and status updates.
-* The success of the entire plan is highly dependent on the quality of the AI's decomposition logic.
-
-### 5. Parallel Mode
-
-The `ParallelMode` is a batch-processing engine designed to execute a specific task across multiple inputs simultaneously.
-* **High-Level Concept:** Analyze the user's request to identify a template task and a set of variables (e.g., a list of files). Generate all combinations of these variables, render the template for each, and execute the resulting tasks in parallel.
-* **How It Works (Internal Logic):**
-1. **Configuration Parsing:** The user's message is analyzed by a `ParsedAgent` to extract a `Config` object. This includes:
- * **Variables:** Lists of items to process (e.g., file paths, input strings). Supports glob patterns (e.g., `src/**/*.kt`).
- * **Template:** A string with placeholders (e.g., "Review the code in {{file}}").
- * **Concurrency:** How many tasks to run at once.
- * **Mode:** How to combine variables (`CrossJoin` for all combinations, `Zip` for pairing).
-2. **Expansion & Combination:** Variable values are expanded (e.g., resolving file globs). The system then generates a list of task configurations based on the selected mode.
-3. **Parallel Execution:** A `FixedConcurrencyProcessor` manages the execution. For each combination:
- * The template is rendered with the specific values.
- * The system determines the appropriate task implementation (using logic similar to Conversational Mode).
- * The task is executed, and results are displayed in a tabbed interface.
-* **Key Characteristics:**
-* **High Throughput:** Optimized for running many independent tasks at once.
-* **Template-Driven:** Uses a single instruction template applied to many contexts.
-* **Flexible Inputs:** Supports file globs and variable lists.
-* **When to Use It:**
-* Batch operations on files (e.g., "Refactor all Java files in src/").
-* Running the same analysis on multiple datasets.
-* Testing a prompt against a variety of inputs.
-* **Strengths:**
-* Drastically reduces time for repetitive tasks.
-* Automates the creation of many similar tasks.
-* Visualizes progress across multiple streams via tabs.
-* **Weaknesses:**
-* Not suitable for tasks with dependencies between steps.
-* Can consume significant API resources quickly due to parallelism.
-
-### 6. Protocol Mode (Experimental)
-
-The `ProtocolMode` is a rigorous, state-machine-driven strategy designed to enforce specific methodologies and ensure high-quality output through validation.
-* **High-Level Concept:** Define a strict protocol (a set of states with instructions and validation criteria) to achieve the user's request. The system moves through these states, executing actions and validating them with a "Referee" agent before proceeding.
-* **How It Works (Internal Logic):**
-1. **Protocol Definition:** The agent analyzes the request and defines a `ProtocolDefinition`. This is a state machine containing a list of states (e.g., "Red", "Green", "Refactor" for TDD), an initial state, and transitions. Each state has specific instructions and validation criteria.
-2. **State Execution Loop:**
- * **Action:** The system enters the current state and uses a "StateExecutor" agent to perform the required task based on the state's instructions.
- * **Validation:** A "Referee" agent reviews the result of the action against the state's `validationCriteria`.
- * **Retry/Transition:** If the validation passes, the system transitions to the defined `nextState`. If it fails, the system retries the action (up to a limit) with feedback from the Referee.
-3. **Termination:** The process continues until a terminal state (no next state) is reached or a safety limit is hit.
-* **Key Characteristics:**
-* **Methodical:** Enforces structured workflows like TDD or Read-Draft-Verify.
-* **Self-Correcting:** The Referee loop ensures that each step meets quality standards before moving on.
-* **Transparent:** The protocol and state transitions are clearly visible.
-* **When to Use It:**
-* Tasks requiring strict adherence to a process (e.g., Test-Driven Development).
-* Generating high-stakes documentation or code where verification is crucial.
-* Complex workflows that can be modeled as a state machine.
-* **Strengths:**
-* High reliability due to the validation step.
-* Enforces best practices (like writing tests before code).
-* Clear separation of concerns between execution and validation.
-* **Weaknesses:**
-* Can be slow due to the overhead of validation and potential retries.
-* Rigid compared to conversational modes.
-
-### 7. Session Mode (Experimental)
-
-The `SessionMode` focuses on deep interaction with a single tool. It assigns an AI "Operator" to drive a specific tool continuously until a goal is achieved.
-* **High-Level Concept:** Select the most appropriate tool for the user's request, then enter a loop where an AI operator issues commands to that tool, interprets the output, and issues new commands until the task is done.
-* **How It Works (Internal Logic):**
-1. **Tool Selection:** The system analyzes the user's message to select a single, persistent tool (e.g., a specific CLI wrapper or coding agent).
-2. **Session Loop:**
- * **Plan:** A "SessionOperator" agent reviews the conversation history and the current goal. It decides whether the goal is complete or what the next command should be.
- * **Execute:** The command is executed by the selected tool.
- * **Update:** The command and its result are added to the session history.
-3. **Termination:** The loop ends when the Operator deems the goal complete or a limit is reached.
-* **Key Characteristics:**
-* **Tool-Centric:** Locks onto one tool and uses it extensively.
-* **Autonomous Operator:** The AI acts as a user of the tool, navigating its interface or command set.
-* **Stateful:** Maintains the context of the tool's session.
-* **When to Use It:**
-* Tasks that require multiple interactions with the same utility (e.g., "Debug this issue using the terminal").
-* Exploratory tasks where the AI needs to "poke around" using a specific instrument.
-* **Strengths:**
-* Allows for complex, multi-step operations within a specific domain.
-* Reduces context switching by focusing on one tool.
-* **Weaknesses:**
-* Limited to the capabilities of the selected tool.
-* Can get stuck in loops if the tool provides confusing feedback.
-
-### 8. Council Mode
-
-The `CouncilMode` implements a democratic, multi-agent decision-making process. Instead of a single agent driving the process, a "council" of distinct personas collaborates to nominate and vote on tasks.
-* **High-Level Concept:** A group of specialized agents (e.g., CEO, CTO, QA) independently analyze the situation and nominate tasks. They then vote on the best course of action. The winning tasks are executed, and all agents update their internal states based on the results.
-* **How It Works (Internal Logic):**
-1. **Council Initialization:** The mode initializes a list of `CognitiveSchemaStrategy` instances, representing the council members (default: CEO, CTO, QA). Each member maintains its own private state.
-2. **The Main Loop:**
- * **Nomination:** Each council member analyzes the current situation and nominates tasks.
- * **Voting:** If there are conflicting nominations, the council members vote on the proposed tasks.
- * **Execution:** The tasks with the most votes are executed.
- * **State Update:** Every council member observes the results of the executed tasks and updates their own internal state/perspective accordingly.
-* **Key Characteristics:**
-* **Multi-Perspective:** Balances different viewpoints (e.g., business value vs. technical feasibility vs. quality).
-* **Democratic:** Decisions are made via voting, preventing one narrow perspective from dominating.
-* **When to Use It:**
-* High-stakes projects requiring balanced decision-making.
-* Complex architectural design where trade-offs need to be weighed.
-* Situations where a single agent might be prone to bias or tunnel vision.
-
-
+# Cognotik Cognitive Modes: User Guide
+
+Welcome to the Cognotik User Guide. This document explains the various **Cognitive Modes** available in the system. Each mode represents a different strategy for how the AI thinks, plans, and executes tasks to solve your requests.
+
+---
+
+## Table of Contents
+1. [Core Concepts](#core-concepts)
+2. [Conversational Modes](#1-conversational-modes)
+ * [Conversational Mode](#conversational-mode)
+ * [Persona Chat Mode](#persona-chat-mode)
+3. [Planning & Execution Modes](#2-planning--execution-modes)
+ * [Waterfall Mode](#waterfall-mode)
+ * [Adaptive Planning Mode](#adaptive-planning-mode)
+ * [Hierarchical Planning Mode](#hierarchical-planning-mode)
+4. [Advanced Orchestration Modes](#3-advanced-orchestration-modes)
+ * [Council Mode](#council-mode)
+ * [Protocol Mode](#protocol-mode)
+ * [Parallel Mode](#parallel-mode)
+5. [Advanced Syntax & Features](#advanced-syntax--features)
+
+---
+
+## Core Concepts
+
+Before choosing a mode, understand these three elements:
+* **Tasks:** Atomic actions the AI can perform (e.g., writing code, reading files, searching the web).
+* **Orchestrator:** The engine that manages the working directory and executes the tasks chosen by the cognitive mode.
+* **Working Directory:** The file system area where the AI reads and writes data.
+
+---
+
+## 1. Conversational Modes
+
+### Conversational Mode
+**Best for:** General assistance, quick questions, and interactive debugging.
+* **How it works:** This is a standard chat interface. For every message you send, the AI analyzes the history and chooses **one** specific task to execute.
+* **Key Feature:** It maintains a continuous history, allowing you to build on previous results.
+
+### Persona Chat Mode
+**Best for:** Specialized consulting (e.g., talking to a "Security Auditor" or a "Scientific Researcher").
+* **How it works:** Similar to Conversational Mode, but the AI adopts a specific **Cognitive Strategy**. It maintains an internal "Persona State" that evolves as the conversation progresses.
+* **Strategies available:** Project Manager, Scientist, Agile Developer, Critical Auditor, Creative Writer.
+
+---
+
+## 2. Planning & Execution Modes
+
+### Waterfall Mode
+**Best for:** Well-defined, linear projects where you want to see the full plan before any work starts.
+* **How it works:**
+ 1. The AI analyzes your request and generates a complete **JSON Plan** containing all necessary steps.
+ 2. You can review and discuss the plan.
+ 3. Once finalized, the system executes the steps sequentially.
+* **Pro Tip:** It saves a `plan.json` in your directory. You can "resume" or "re-run" specific plans by referencing this file.
+
+### Adaptive Planning Mode
+**Best for:** Complex, "fuzzy" problems where the next step depends on the result of the previous one.
+* **How it works:** This mode uses an iterative "Think-Act-Reflect" loop.
+ 1. **Think:** Updates its "Reasoning State" (Goals, Facts, Hypotheses).
+ 2. **Act:** Nominates and executes up to 5 tasks in parallel.
+ 3. **Reflect:** Analyzes the results and updates the plan for the next iteration.
+* **Visuals:** You can track the AI's "Thinking Status" in a dedicated tab to see what it currently believes to be true.
+
+### Hierarchical Planning Mode
+**Best for:** Massive projects with many dependencies (e.g., building a full software application).
+* **How it works:** It builds a **Goal Tree**.
+ 1. It breaks your high-level request into **Goals**.
+ 2. It decomposes Goals into **Sub-goals** or **Tasks**.
+ 3. It manages dependencies (e.g., "Don't start Task B until Goal A is complete").
+* **Key Feature:** Provides a real-time visual tree of your project's progress.
+
+---
+
+## 3. Advanced Orchestration Modes
+
+### Council Mode
+**Best for:** High-stakes decisions or tasks requiring multiple perspectives.
+* **How it works:** It simulates a meeting between different AI personas (e.g., a CEO, a CTO, and a QA Engineer).
+ 1. **Nomination:** Each council member suggests tasks based on their specialty.
+ 2. **Voting:** The council votes on which tasks are most important.
+ 3. **Execution:** The winning tasks are executed.
+* **Benefit:** Reduces "AI hallucinations" and ensures technical feasibility and quality.
+
+### Protocol Mode
+**Best for:** Strict, multi-stage workflows or "State Machine" logic.
+* **How it works:** The AI defines a "Protocol" (a set of states).
+ 1. **Action:** The AI performs the work required for the current state.
+ 2. **Referee:** A separate "Referee" agent validates the output against success criteria.
+ 3. **Transition:** If passed, the Referee moves the session to the next state; if failed, it triggers a retry.
+
+### Parallel Mode
+**Best for:** Batch processing and automation (e.g., "Run a security scan on every `.kt` file in this folder").
+* **How it works:** You provide a template and a list of variables.
+* **Combination Modes:**
+ * **CrossJoin:** Runs every combination of variables (e.g., 2 files x 2 tests = 4 tasks).
+ * **Zip:** Pairs items together (e.g., File A with Test A, File B with Test B).
+* **Key Feature:** Uses a `FixedConcurrencyProcessor` to run many tasks simultaneously without crashing your system.
+
+---
+
+## Advanced Syntax & Features
+
+### Expansion Syntax
+In most modes (especially Conversational and Parallel), you can use special syntax to trigger multiple actions at once:
+
+1. **Alternatives:** `@[option1|option2]`
+ * *Example:* "Write a unit test for @[AuthService|DataService]" will trigger two parallel tasks.
+2. **Sequences:** `@{Step 1 -> Step 2 -> Step 3}`
+ * *Example:* "Process the data @{Clean -> Analyze -> Summarize}" will run these in order.
+3. **Ranges:** `@(1..5)` or `@(1 to 10 by 2)`
+ * *Example:* "Generate @(1..3) variations of the logo."
+4. **Topic References:** `@Files` or `@{Source Code}`
+ * If the AI has previously identified "Topics" in the chat, you can refer to them as a group using the `@` symbol.
+
+### Transcripts
+Every session generates a detailed **Markdown Transcript**.
+* Look for the "Writing transcript to..." link at the start of your session.
+* Transcripts include the full reasoning process, task inputs, and raw outputs.
+* In many modes, you can also view these as HTML or PDF.
+
+---
+
+## Choosing the Right Mode: Quick Reference
+
+| If you want to... | Use this Mode |
+| :--- | :--- |
+| Just chat and do one thing at a time | **Conversational** |
+| Build a complex app with a strict plan | **Waterfall** |
+| Solve a hard problem step-by-step | **Adaptive Planning** |
+| Manage a project with many sub-parts | **Hierarchical Planning** |
+| Get a second and third opinion | **Council** |
+| Automate a task across 50 files | **Parallel** |
+| Follow a strict "Checklist" workflow | **Protocol** |
diff --git a/docs/cognitive_schema.md b/docs/cognitive_schema.md
new file mode 100644
index 000000000..1f1ec7d88
--- /dev/null
+++ b/docs/cognitive_schema.md
@@ -0,0 +1,111 @@
+Here is a detailed user guide for the **Cognitive Schema Strategies** system defined in the provided code.
+
+---
+
+# Cognotik Cognitive Schema Strategies: User Guide
+
+## 1. Overview
+The `CognitiveSchemaStrategy` system defines the "mindset" or "persona" an AI agent adopts when solving a problem. Instead of a generic "answer the prompt" approach, this system allows the AI to structure its memory, planning, and decision-making processes according to specific methodologies (e.g., Scientific Method, Agile Development, Auditing).
+
+Each strategy dictates:
+1. **Initialization:** How the problem is broken down initially.
+2. **State Management:** What specific data is tracked (e.g., hypotheses, TODO lists, risk assessments).
+3. **Update Logic:** How the AI reflects on completed tasks to update its plan.
+4. **Task Guidance:** How the AI decides what to do next.
+
+## 2. Available Strategies
+
+The system comes with five built-in strategies. Choose the one that best fits your current objective.
+
+### A. Project Manager (The Generalist)
+* **Best for:** General complex tasks, multi-step workflows, and goals that require breaking down into sub-tasks.
+* **How it thinks:** It acts like a standard project manager. It maintains a list of short-term and long-term goals, tracks facts, and identifies open questions.
+* **Internal State:** `ReasoningState`
+ * **Goals:** Short-term vs. Long-term.
+ * **Knowledge:** Facts, Hypotheses, Open Questions.
+ * **Execution Context:** Next steps, potential challenges.
+* **Behavior:** It prioritizes completing the user's request by managing a queue of tasks and ensuring alignment with the overall goal.
+
+### B. Scientific Method (The Debugger)
+* **Best for:** Debugging, root cause analysis, exploring unknown systems, or answering "Why?" questions.
+* **How it thinks:** It treats the problem as a scientific experiment. It refuses to guess; instead, it formulates hypotheses and creates tasks specifically to prove or disprove them.
+* **Internal State:** `ScientificState`
+ * **Research Question:** The core problem.
+ * **Hypotheses:** Potential explanations with confidence scores.
+ * **Established Facts:** Things proven true.
+ * **Refuted Theories:** Things proven false.
+ * **Experiment Log:** History of investigations.
+* **Guidance:** "Select tasks specifically designed to falsify or validate the top hypothesis."
+
+### C. Agile Developer (The Coder)
+* **Best for:** Writing code, implementing features, and fixing specific bugs.
+* **How it thinks:** It follows a strict Test-Driven Development (TDD) loop.
+* **Internal State:** `AgileState`
+ * **User Story & Acceptance Criteria:** What needs to be built.
+ * **Current Phase:** Cycles through `TEST_FAILING` -> `IMPLEMENTING` -> `REFACTORING`.
+ * **TODO List:** Incremental coding steps.
+* **Guidance:**
+ * *If in Test Failing:* Write a test that fails.
+ * *If in Implementing:* Write code to pass the test.
+ * *If in Refactoring:* Clean up the code without changing behavior.
+
+### D. Critical Auditor (The Security Expert)
+* **Best for:** Code reviews, security audits, compliance checks, and validating logic.
+* **How it thinks:** It adopts an adversarial mindset. It does not try to "fix" things; it tries to break them or find flaws.
+* **Internal State:** `AuditState`
+ * **Target Scope:** What is being audited.
+ * **Risk Assessment:** List of risks with severity (High/Med/Low).
+ * **Compliance Checklist:** Regulatory or logical requirements.
+ * **Vulnerabilities:** Confirmed issues.
+* **Guidance:** "Choose tasks that stress-test the system. Try to break the implementation. Do not fix issues, only report them."
+
+### E. Creative Writer (The Author)
+* **Best for:** Writing documentation, stories, marketing copy, or long-form content.
+* **How it thinks:** It focuses on narrative flow, tone, and structure rather than technical correctness or logic.
+* **Internal State:** `NarrativeState`
+ * **Theme & Audience:** The stylistic guardrails.
+ * **Outline:** Chapters or sections with status (Draft/Reviewed/Done).
+ * **Tone Check:** Feedback on the writing style.
+* **Guidance:** "Focus on generating content. If the tone is off, select a task to rewrite or edit."
+
+---
+
+## 3. How It Works (The Lifecycle)
+
+When you assign a strategy to an agent, it follows this lifecycle:
+
+### Step 1: Initialize
+When the user sends the first prompt, the strategy's `initialize` method is called.
+* **Input:** User prompt + Context.
+* **Action:** The AI uses a specific LLM prompt (defined in the strategy) to parse the request into the strategy's specific **State Object**.
+* **Example:** If using *Agile Developer*, the AI converts "Make a login page" into a `User Story`, `Acceptance Criteria`, and sets the phase to `TEST_FAILING`.
+
+### Step 2: Task Selection
+The system asks the strategy for `getTaskSelectionGuidance`.
+* **Action:** The strategy looks at its current state and tells the orchestration engine what kind of tool or task to run next.
+* **Example:** The *Scientific Method* strategy sees a hypothesis "Database is down" and guides the agent to run a "Check Database Connection" tool.
+
+### Step 3: Execution & Update
+After a tool or task is executed, the strategy's `update` method is called.
+* **Input:** The previous State + The Result of the task just performed.
+* **Action:** The AI reflects on the result and modifies the State Object.
+* **Example:**
+ * *Agile Strategy:* "The test passed." -> Change state from `IMPLEMENTING` to `REFACTORING`.
+ * *Scientific Strategy:* "Database connection failed." -> Move "Database is down" from `Hypothesis` to `Established Facts`.
+
+---
+
+## 4. JSON Serialization
+These strategies are designed to be paused and resumed. The class uses Jackson annotations (`@JsonSerialize`, `@JsonDeserialize`) to save the strategy type and its internal state to JSON.
+
+* **Persistence:** You can save the entire agent session to a database or file. When reloaded, the agent remembers exactly where it was in the process (e.g., it remembers it was in the "Refactoring" phase of the Agile strategy).
+
+## 5. Extending the System
+To create a custom strategy (e.g., "Legal Analyst" or "Teacher"):
+
+1. Extend `CognitiveSchemaStrategy`.
+2. Define a data class for your state (e.g., `LegalState`).
+3. Implement `initialize`: Write a prompt that converts user input into `LegalState`.
+4. Implement `update`: Write a prompt that updates `LegalState` based on new findings.
+5. Implement `getTaskSelectionGuidance`: Define the logic for the next step.
+6. Add your new strategy to the `companion object` values list if you want it discoverable.
\ No newline at end of file
diff --git a/docs/GeneticOptimizationTask.md b/docs/etc/GeneticOptimizationTask.md
similarity index 100%
rename from docs/GeneticOptimizationTask.md
rename to docs/etc/GeneticOptimizationTask.md
diff --git a/docs/LLMExperimentTask.md b/docs/etc/LLMExperimentTask.md
similarity index 100%
rename from docs/LLMExperimentTask.md
rename to docs/etc/LLMExperimentTask.md
diff --git a/docs/LLMPollSimulationTask.md b/docs/etc/LLMPollSimulationTask.md
similarity index 100%
rename from docs/LLMPollSimulationTask.md
rename to docs/etc/LLMPollSimulationTask.md
diff --git a/docs/competitive_analysis.md b/docs/etc/competitive_analysis.md
similarity index 100%
rename from docs/competitive_analysis.md
rename to docs/etc/competitive_analysis.md
diff --git a/docs/image_agents.md b/docs/etc/image_agents.md
similarity index 100%
rename from docs/image_agents.md
rename to docs/etc/image_agents.md
diff --git a/docs/philosophy.md b/docs/etc/philosophy.md
similarity index 100%
rename from docs/philosophy.md
rename to docs/etc/philosophy.md
diff --git a/docs/reasoning_task_chooser.md b/docs/etc/reasoning_task_chooser.md
similarity index 100%
rename from docs/reasoning_task_chooser.md
rename to docs/etc/reasoning_task_chooser.md
diff --git a/docs/reasoning_task_types.md b/docs/etc/reasoning_task_types.md
similarity index 100%
rename from docs/reasoning_task_types.md
rename to docs/etc/reasoning_task_types.md
diff --git a/docs/social_reasoning.md b/docs/etc/social_reasoning.md
similarity index 99%
rename from docs/social_reasoning.md
rename to docs/etc/social_reasoning.md
index 20322f702..80d0541f2 100644
--- a/docs/social_reasoning.md
+++ b/docs/etc/social_reasoning.md
@@ -2358,7 +2358,7 @@ All files are accessible via links in the task completion message.
### See Also
-- [LLMExperimentTask](./LLMExperimentTask.md) - Run controlled LLM experiments
+- [LLMExperimentTask](LLMExperimentTask.md) - Run controlled LLM experiments
- [GameTheoryTask](./GameTheoryTask.md) - Simulate strategic interactions
- [MultiPerspectiveAnalysisTask](./MultiPerspectiveAnalysisTask.md) - Analyze from multiple viewpoints
diff --git a/docs/social_reasoning/present.md b/docs/etc/social_reasoning/present.md
similarity index 100%
rename from docs/social_reasoning/present.md
rename to docs/etc/social_reasoning/present.md
diff --git a/docs/social_reasoning_pkg.md b/docs/etc/social_reasoning_pkg.md
similarity index 100%
rename from docs/social_reasoning_pkg.md
rename to docs/etc/social_reasoning_pkg.md
diff --git a/docs/strategies/ai-collaboration-concept.png b/docs/etc/strategies/ai-collaboration-concept.png
similarity index 100%
rename from docs/strategies/ai-collaboration-concept.png
rename to docs/etc/strategies/ai-collaboration-concept.png
diff --git a/docs/strategies/emergent-capabilities-network.png b/docs/etc/strategies/emergent-capabilities-network.png
similarity index 100%
rename from docs/strategies/emergent-capabilities-network.png
rename to docs/etc/strategies/emergent-capabilities-network.png
diff --git a/docs/strategies/historical-timeline-illustration.png b/docs/etc/strategies/historical-timeline-illustration.png
similarity index 100%
rename from docs/strategies/historical-timeline-illustration.png
rename to docs/etc/strategies/historical-timeline-illustration.png
diff --git a/docs/strategies/multiplicative-growth-visualization.png b/docs/etc/strategies/multiplicative-growth-visualization.png
similarity index 100%
rename from docs/strategies/multiplicative-growth-visualization.png
rename to docs/etc/strategies/multiplicative-growth-visualization.png
diff --git a/docs/strategies/orthogonal-strategies-diagram.png b/docs/etc/strategies/orthogonal-strategies-diagram.png
similarity index 100%
rename from docs/strategies/orthogonal-strategies-diagram.png
rename to docs/etc/strategies/orthogonal-strategies-diagram.png
diff --git a/docs/strategies/presentation_3/ai_capabilities_neural_space.png b/docs/etc/strategies/presentation_3/ai_capabilities_neural_space.png
similarity index 100%
rename from docs/strategies/presentation_3/ai_capabilities_neural_space.png
rename to docs/etc/strategies/presentation_3/ai_capabilities_neural_space.png
diff --git a/docs/strategies/presentation_3/ai_human_collaboration_flow.png b/docs/etc/strategies/presentation_3/ai_human_collaboration_flow.png
similarity index 100%
rename from docs/strategies/presentation_3/ai_human_collaboration_flow.png
rename to docs/etc/strategies/presentation_3/ai_human_collaboration_flow.png
diff --git a/docs/strategies/presentation_3/architecture_comparison_diagram.png b/docs/etc/strategies/presentation_3/architecture_comparison_diagram.png
similarity index 100%
rename from docs/strategies/presentation_3/architecture_comparison_diagram.png
rename to docs/etc/strategies/presentation_3/architecture_comparison_diagram.png
diff --git a/docs/strategies/presentation_3/cognotik_strategy_ecosystem.png b/docs/etc/strategies/presentation_3/cognotik_strategy_ecosystem.png
similarity index 100%
rename from docs/strategies/presentation_3/cognotik_strategy_ecosystem.png
rename to docs/etc/strategies/presentation_3/cognotik_strategy_ecosystem.png
diff --git a/docs/strategies/presentation_3/complexity_comparison_background.png b/docs/etc/strategies/presentation_3/complexity_comparison_background.png
similarity index 100%
rename from docs/strategies/presentation_3/complexity_comparison_background.png
rename to docs/etc/strategies/presentation_3/complexity_comparison_background.png
diff --git a/docs/strategies/presentation_3/component_benefits_layers.png b/docs/etc/strategies/presentation_3/component_benefits_layers.png
similarity index 100%
rename from docs/strategies/presentation_3/component_benefits_layers.png
rename to docs/etc/strategies/presentation_3/component_benefits_layers.png
diff --git a/docs/strategies/presentation_3/intro.md b/docs/etc/strategies/presentation_3/intro.md
similarity index 100%
rename from docs/strategies/presentation_3/intro.md
rename to docs/etc/strategies/presentation_3/intro.md
diff --git a/docs/strategies/presentation_3/intro_presentation.html b/docs/etc/strategies/presentation_3/intro_presentation.html
similarity index 100%
rename from docs/strategies/presentation_3/intro_presentation.html
rename to docs/etc/strategies/presentation_3/intro_presentation.html
diff --git a/docs/strategies/presentation_3/presentation.css b/docs/etc/strategies/presentation_3/presentation.css
similarity index 100%
rename from docs/strategies/presentation_3/presentation.css
rename to docs/etc/strategies/presentation_3/presentation.css
diff --git a/docs/strategies/presentation_3/presentation.js b/docs/etc/strategies/presentation_3/presentation.js
similarity index 100%
rename from docs/strategies/presentation_3/presentation.js
rename to docs/etc/strategies/presentation_3/presentation.js
diff --git a/docs/strategies/presentation_3/reveal_init.js b/docs/etc/strategies/presentation_3/reveal_init.js
similarity index 100%
rename from docs/strategies/presentation_3/reveal_init.js
rename to docs/etc/strategies/presentation_3/reveal_init.js
diff --git a/docs/strategies/presentation_3/strategy_pattern_interchange.png b/docs/etc/strategies/presentation_3/strategy_pattern_interchange.png
similarity index 100%
rename from docs/strategies/presentation_3/strategy_pattern_interchange.png
rename to docs/etc/strategies/presentation_3/strategy_pattern_interchange.png
diff --git a/docs/strategies/presentation_3/strategy_selection_decision_tree.png b/docs/etc/strategies/presentation_3/strategy_selection_decision_tree.png
similarity index 100%
rename from docs/strategies/presentation_3/strategy_selection_decision_tree.png
rename to docs/etc/strategies/presentation_3/strategy_selection_decision_tree.png
diff --git a/docs/strategies/presentation_3/title_slide_architecture_network.png b/docs/etc/strategies/presentation_3/title_slide_architecture_network.png
similarity index 100%
rename from docs/strategies/presentation_3/title_slide_architecture_network.png
rename to docs/etc/strategies/presentation_3/title_slide_architecture_network.png
diff --git a/docs/strategies/presentation_3/transcript.md b/docs/etc/strategies/presentation_3/transcript.md
similarity index 100%
rename from docs/strategies/presentation_3/transcript.md
rename to docs/etc/strategies/presentation_3/transcript.md
diff --git a/docs/strategies/scaling-calculator-interface.png b/docs/etc/strategies/scaling-calculator-interface.png
similarity index 100%
rename from docs/strategies/scaling-calculator-interface.png
rename to docs/etc/strategies/scaling-calculator-interface.png
diff --git a/docs/strategies/strategies.html b/docs/etc/strategies/strategies.html
similarity index 100%
rename from docs/strategies/strategies.html
rename to docs/etc/strategies/strategies.html
diff --git a/docs/strategies/strategy-pattern-architecture.png b/docs/etc/strategies/strategy-pattern-architecture.png
similarity index 100%
rename from docs/strategies/strategy-pattern-architecture.png
rename to docs/etc/strategies/strategy-pattern-architecture.png
diff --git a/docs/strategies_intro.md b/docs/etc/strategies_intro.md
similarity index 100%
rename from docs/strategies_intro.md
rename to docs/etc/strategies_intro.md
diff --git a/docs/usage_taxonomy/basic_generation_flow_diagram_png.png b/docs/etc/usage_taxonomy/basic_generation_flow_diagram_png.png
similarity index 100%
rename from docs/usage_taxonomy/basic_generation_flow_diagram_png.png
rename to docs/etc/usage_taxonomy/basic_generation_flow_diagram_png.png
diff --git a/docs/usage_taxonomy/header_llm_network_abstract.png b/docs/etc/usage_taxonomy/header_llm_network_abstract.png
similarity index 100%
rename from docs/usage_taxonomy/header_llm_network_abstract.png
rename to docs/etc/usage_taxonomy/header_llm_network_abstract.png
diff --git a/docs/usage_taxonomy/llm_neural_ecosystem_hero_png.png b/docs/etc/usage_taxonomy/llm_neural_ecosystem_hero_png.png
similarity index 100%
rename from docs/usage_taxonomy/llm_neural_ecosystem_hero_png.png
rename to docs/etc/usage_taxonomy/llm_neural_ecosystem_hero_png.png
diff --git a/docs/usage_taxonomy/llm_pattern_taxonomy_map_png.png b/docs/etc/usage_taxonomy/llm_pattern_taxonomy_map_png.png
similarity index 100%
rename from docs/usage_taxonomy/llm_pattern_taxonomy_map_png.png
rename to docs/etc/usage_taxonomy/llm_pattern_taxonomy_map_png.png
diff --git a/docs/usage_taxonomy/pattern_agent_tool_orchestration.png b/docs/etc/usage_taxonomy/pattern_agent_tool_orchestration.png
similarity index 100%
rename from docs/usage_taxonomy/pattern_agent_tool_orchestration.png
rename to docs/etc/usage_taxonomy/pattern_agent_tool_orchestration.png
diff --git a/docs/usage_taxonomy/pattern_classification_sorting.png b/docs/etc/usage_taxonomy/pattern_classification_sorting.png
similarity index 100%
rename from docs/usage_taxonomy/pattern_classification_sorting.png
rename to docs/etc/usage_taxonomy/pattern_classification_sorting.png
diff --git a/docs/usage_taxonomy/pattern_code_generation_assist.png b/docs/etc/usage_taxonomy/pattern_code_generation_assist.png
similarity index 100%
rename from docs/usage_taxonomy/pattern_code_generation_assist.png
rename to docs/etc/usage_taxonomy/pattern_code_generation_assist.png
diff --git a/docs/usage_taxonomy/pattern_data_extraction_flow.png b/docs/etc/usage_taxonomy/pattern_data_extraction_flow.png
similarity index 100%
rename from docs/usage_taxonomy/pattern_data_extraction_flow.png
rename to docs/etc/usage_taxonomy/pattern_data_extraction_flow.png
diff --git a/docs/usage_taxonomy/pattern_evaluation_feedback_loop.png b/docs/etc/usage_taxonomy/pattern_evaluation_feedback_loop.png
similarity index 100%
rename from docs/usage_taxonomy/pattern_evaluation_feedback_loop.png
rename to docs/etc/usage_taxonomy/pattern_evaluation_feedback_loop.png
diff --git a/docs/usage_taxonomy/pattern_finetuning_learning.png b/docs/etc/usage_taxonomy/pattern_finetuning_learning.png
similarity index 100%
rename from docs/usage_taxonomy/pattern_finetuning_learning.png
rename to docs/etc/usage_taxonomy/pattern_finetuning_learning.png
diff --git a/docs/usage_taxonomy/pattern_rag_architecture_diagram.png b/docs/etc/usage_taxonomy/pattern_rag_architecture_diagram.png
similarity index 100%
rename from docs/usage_taxonomy/pattern_rag_architecture_diagram.png
rename to docs/etc/usage_taxonomy/pattern_rag_architecture_diagram.png
diff --git a/docs/usage_taxonomy/pattern_summarization_funnel.png b/docs/etc/usage_taxonomy/pattern_summarization_funnel.png
similarity index 100%
rename from docs/usage_taxonomy/pattern_summarization_funnel.png
rename to docs/etc/usage_taxonomy/pattern_summarization_funnel.png
diff --git a/docs/usage_taxonomy/rag_architecture_workflow_png.png b/docs/etc/usage_taxonomy/rag_architecture_workflow_png.png
similarity index 100%
rename from docs/usage_taxonomy/rag_architecture_workflow_png.png
rename to docs/etc/usage_taxonomy/rag_architecture_workflow_png.png
diff --git a/docs/usage_taxonomy/use_case_categories.html b/docs/etc/usage_taxonomy/use_case_categories.html
similarity index 100%
rename from docs/usage_taxonomy/use_case_categories.html
rename to docs/etc/usage_taxonomy/use_case_categories.html
diff --git a/docs/writing_task_types.md b/docs/etc/writing_task_types.md
similarity index 100%
rename from docs/writing_task_types.md
rename to docs/etc/writing_task_types.md
diff --git a/docs/interpreter.md b/docs/interpreter.md
index ad824f872..52e50849b 100644
--- a/docs/interpreter.md
+++ b/docs/interpreter.md
@@ -11,7 +11,7 @@ assistants.
### Core Components
```
-cognotik-interpreter/
+Cognotik/
├── core/ # Core interpreter interface and utilities
├── kotlin/ # Kotlin interpreter implementation
├── groovy/ # Groovy interpreter implementation
diff --git a/docs/session_task_ui.md b/docs/session_task_ui.md
new file mode 100644
index 000000000..750ae6c1f
--- /dev/null
+++ b/docs/session_task_ui.md
@@ -0,0 +1,400 @@
+This guide provides a comprehensive overview of how to manage User Interfaces within the Cognotik framework. The UI system is **server-driven**, meaning you write Kotlin code to generate HTML, which is pushed to the client via WebSockets.
+
+The core philosophy is **Task-Based UI**: The interface is built around `SessionTask` objects, which represent a specific, addressable block in the UI (usually a `div`) where content is appended or updated.
+
+---
+
+## 1. Core Concepts
+
+### SessionTask
+The `SessionTask` is your primary canvas. It manages a buffer of HTML content and a unique `messageID`.
+* **Lifecycle:** A task starts with a "Spinner" (loading state). When you finish processing, you call `complete()` to remove the spinner.
+* **Output:** You can stream text, HTML, or specific UI components into a task. Content is appended sequentially.
+* **Nesting:** Tasks can contain other tasks (via `newTask`), allowing for complex, nested layouts.
+
+### SocketManager
+The `SocketManager` handles the connection between the server and the browser. You rarely instantiate this directly; instead, you access it via `task.ui`. It is used to create new tasks, handle file paths, and manage interactivity.
+
+### Creating Sub-Tasks
+
+You can create nested tasks to organize output. There are two main ways to do this:
+
+1. **Inline Sub-Task (`task.newTask()`):**
+ Calling `task.newTask()` creates a new task and immediately appends its placeholder to the current task's output.
+ **Crucially, this reserves the display order.** You can continue adding content to the parent `task`, but any content added to the sub-task will appear in the reserved spot (above the subsequent parent content). This is useful for parallel processing or updating a specific section of the UI while the main thread continues.
+
+2. **Manual Placement (Inner Tasks):**
+ Calling `task.ui.newTask(false)` creates a "detached" task. It is **not** rendered automatically. Instead, you must
+ use the task's `placeholder` property (a string containing a `div` with the specific `messageID`) to place it within
+ the UI.
+ This is essential for complex layouts, such as putting a streaming task inside a table cell or a specific HTML
+ structure.
+
+```kotlin
+// 1. Inline: Reserves a spot in the current output stream
+val subTask = task.newTask()
+// We can add to the parent task immediately
+task.add("This appears BELOW the subTask")
+
+// Later, we can update the subTask, and it appears ABOVE the text we just added
+subTask.add("I am a sub-task, appearing in my reserved spot")
+subTask.complete()
+
+// 2. Manual: Must be placed explicitly
+val innerTask = task.ui.newTask(false)
+// Inject the placeholder into a custom layout
+task.add(innerTask.placeholder)
+// Now content added to innerTask appears inside the .custom-box div
+innerTask.add("I am inside the box")
+```
+
+**Other Options:**
+
+```kotlin
+
+// Create a cancelable task (renders with a close button)
+// If the user clicks 'X', the task element is removed from the DOM.
+val closableTask = task.ui.newTask(root = true, cancelable = true)
+```
+
+---
+
+## 2. Basic Content Rendering
+
+All rendering methods belong to the `SessionTask` class.
+
+### Text and HTML
+```kotlin
+fun myTask(task: SessionTask) {
+ // Add a simple message (wrapped in a div)
+ task.add("Hello, World!")
+
+ // Add a header (H1 - H6)
+ task.header("Analysis Results", level = 2)
+
+ // Add raw HTML with custom classes
+ task.add("Bold Text", additionalClasses = "text-primary")
+
+ // Echo a user-style message (right-aligned usually)
+ task.echo("This looks like a user prompt")
+
+ // Mark the task as finished (removes the loading spinner)
+ task.complete()
+}
+// Add a dismissible message (has a close button)
+task.hideable("Note: Click the X to remove me.")
+
+// Add verbose output (rendered in a tag, often used for debug info)
+// This is hidden by default via CSS but visible if inspected or styled otherwise.
+task.verbose("Detailed debug info...")
+```
+### Raw HTML
+If you need to append raw HTML without wrapping it in a specific tag (like `div` or `pre`), use `append`.
+```kotlin
+task.append("Raw Content", showSpinner = true)
+```
+### CSS Styling
+Most rendering methods (like `add`, `header`, `hideable`) accept an `additionalClasses` parameter. This allows you to inject CSS class names into the container element to style the output.
+```kotlin
+// Renders: Warning!
+task.add("Warning!", additionalClasses = "alert alert-warning")
+```
+### Dynamic Updates (Updatable Buffers)
+Methods like `add()`, `append()`, and `hideable()` return a `StringBuilder` object. This object is a direct reference to the content stored in the task's memory. You can modify this buffer and call `task.update()` to refresh the UI in real-time without appending new elements.
+```kotlin
+// 1. Add a message and keep the reference to the buffer
+val statusBuffer = task.add("Starting process...")
+// 2. Modify the buffer in a loop
+for (i in 1..5) {
+ Thread.sleep(500)
+ // Clear and update text
+ statusBuffer?.setLength(0)
+ statusBuffer?.append("Processing step $i/5...")
+ // 3. Push changes to the client
+ task.update()
+}
+// Finalize
+statusBuffer?.setLength(0)
+statusBuffer?.append("Done!")
+task.update()
+task.complete()
+```
+
+
+### Expandable Content
+Useful for hiding verbose logs or large context data.
+```kotlin
+// Collapsed by default
+task.expandable("Debug Logs", "Log content...
")
+
+// Expanded by default
+task.expanded("Executive Summary", "The result is positive.
")
+```
+
+### Error Handling
+Display stack traces or error messages gracefully.
+```kotlin
+try {
+ // risky code
+} catch (e: Exception) {
+ task.error(e) // Renders a red error box with expandable stack trace
+}
+```
+
+### Images
+You can render `BufferedImage` objects directly. The system handles saving the file and generating the `
` tag.
+```kotlin
+val myImage: BufferedImage = ...
+task.image(myImage)
+```
+### Markdown and Mermaid
+Cognotik includes utilities to render Markdown and Mermaid diagrams automatically.
+```kotlin
+import com.simiacryptus.cognotik.util.MarkdownUtil
+val rawMarkdown = """
+# Title
+* List item
+"""
+// Renders Markdown to HTML. If Mermaid code blocks are found,
+// they are rendered to SVG (requires Mermaid CLI installed).
+val html = MarkdownUtil.renderMarkdown(rawMarkdown, ui = task.ui)
+task.add(html)
+```
+
+---
+
+## 3. Interactivity
+
+Cognotik allows you to bind Kotlin closures to HTML interactions.
+
+### Buttons and Links (`hrefLink`)
+Instead of navigating to a URL, links trigger server-side code.
+
+```kotlin
+// Creates an tag. When clicked, the lambda executes.
+// You can optionally specify a CSS class and an ID.
+val linkHtml = task.ui.hrefLink("Click Me") {
+ log.info("Link was clicked!")
+ // You can trigger new UI updates here
+}
+task.add("Please $linkHtml to continue.")
+```
+
+### Text Input
+To get text from the user, use the `SocketManager`.
+
+```kotlin
+val inputHtml = task.ui.textInput { userResponse: String ->
+ task.add("You typed: $userResponse")
+}
+task.add(inputHtml)
+```
+**Note:** The `SocketManager` handles the routing of these events. When a button is clicked, the ID is sent back to the server, which looks up the registered lambda in `linkTriggers` or `txtTriggers` and executes it.
+
+---
+
+## 4. Layout Components: TabbedDisplay
+
+The `TabbedDisplay` class allows you to organize content into switchable tabs. This is dynamic; you can add or update tabs programmatically after rendering.
+
+### Basic Usage
+```kotlin
+val tabs = TabbedDisplay(task)
+
+// Add a tab
+tabs["Summary"] = "This is the summary content."
+
+// Add another tab
+tabs["Details"] = ""
+
+// Initialize with options
+val tabs = TabbedDisplay(
+ task = task,
+ closable = false, // Disable close buttons
+ additionalClasses = "my-custom-tabs"
+)
+
+// You must call update() to refresh the UI after modifying tabs manually
+// (Note: the operator set[] calls update() automatically)
+```
+
+### Dynamic Updates
+You can update the content of an existing tab by assigning to the same key.
+```kotlin
+// Overwrites the "Summary" tab
+tabs["Summary"] = "Updated summary content."
+```
+
+### Deleting Tabs
+```kotlin
+tabs.delete("Details")
+// Clearing all tabs
+tabs.clear()
+```
+
+### Streaming Content into Tabs
+
+A powerful pattern in Cognotik is embedding a live, streaming `SessionTask` inside a tab.
+
+While you can manually create a detached task (`newTask(false)`) and assign its `placeholder` to a tab, the
+`TabbedDisplay` class provides a helper method `newTask(label)` to do this automatically. **This is the preferred
+method.**
+
+```kotlin
+val tabs = TabbedDisplay(task)
+
+// Creates a new task, adds a tab named "Live Progress",
+// and places the task's placeholder inside it.
+val workerTask = tabs.newTask("Live Progress")
+
+// Writing to workerTask updates the content *inside* the tab
+workerTask.add("Step 1 complete...")
+workerTask.add("Step 2 complete...")
+workerTask.complete()
+```
+
+---
+
+## 5. Advanced Workflows
+
+Cognotik provides specialized classes for AI-driven workflows.
+
+### Retryable
+`Retryable` extends `TabbedDisplay`. It is designed for operations that might fail or produce poor results (like LLM generation) and need to be re-run. It automatically adds a "Recycle" (♻) button.
+
+```kotlin
+Retryable.retryable(task.ui) { subTask ->
+ // This code runs in a thread pool.
+ // If the user clicks the recycle button, this block runs again
+ // in a new tab.
+ val result = performExpensiveOperation()
+ subTask.add(result)
+ subTask.complete()
+}
+```
+
+### Discussable (Human-in-the-Loop)
+`Discussable` is a powerful component for the **Generate -> Review -> Revise** loop. It blocks execution until the user accepts the result.
+
+**Flow:**
+1. **Initial Response:** Generates content based on input.
+2. **Feedback Form:** Displays the content with a chat box and an "Accept" button.
+3. **Revision:** If the user types in the chat box, the `reviseResponse` function is called to generate a new version in a new tab.
+4. **Acceptance:** When "Accept" is clicked, the function returns the final object.
+
+```kotlin
+import com.simiacryptus.cognotik.models.ModelSchema.Role
+
+val finalResult = Discussable(
+ task = task,
+ heading = "Drafting Email",
+ userMessage = { "Draft an email to the team" },
+ initialResponse = { prompt ->
+ MyObject(llm.generate(prompt))
+ },
+ outputFn = { design ->
+ // Renders the object to HTML for the user to see
+ design.toHtml()
+ },
+ reviseResponse = { history ->
+ // history is List> (User feedback + Assistant responses)
+ llm.chat(history)
+ }
+).call() // Blocks here until user clicks "Accept"
+
+task.add("Final accepted email: $finalResult")
+```
+
+---
+
+## 6. File Management
+
+You can save files to the session directory and generate links to them.
+
+### Saving Files
+```kotlin
+val data = "some content".toByteArray()
+// Saves to session_dir/reports/data.txt and returns a relative URL
+val fileUrl = task.saveFile("reports/data.txt", data)
+
+task.add("Download report: Click Here")
+```
+
+### Creating Log Streams
+For debugging, you can create a live-updating log file that is linked in the UI.
+```kotlin
+val logStream = task.newLogStream("API Debug Log")
+logStream.write("Starting process...\n".toByteArray())
+// This creates a link in the UI to a .html file viewing the logs
+```
+
+---
+
+## 7. Utilities & Patterns
+
+### Display Map in Tabs
+Quickly render a `Map` as a tabbed view.
+
+```kotlin
+val data = mapOf("File A" to "Content A", "File B" to "Content B")
+val html = AgentPatterns.displayMapInTabs(data)
+task.add(html)
+```
+
+### File Patching & Diffing
+If your agent generates code blocks or diffs, you can instrument the response to include "Save" or "Apply Diff" buttons using `AddApplyFileDiffLinks`. This parses the text for headers (e.g., `### path/to/file.kt`) and code blocks.
+
+```kotlin
+val response = llm.generate(prompt)
+// Automatically adds "Save" or "Apply" buttons to code blocks with file headers
+val instrumentedHtml = AddApplyFileDiffLinks.instrumentFileDiffs(
+ self = task.ui,
+ root = workingDirectory, // Path object
+ response = response,
+ processor = patchProcessor, // PatchProcessor instance
+ shouldAutoApply = { path -> false } // Optional: Predicate to auto-apply changes
+)
+task.add(instrumentedHtml)
+```
+
+### Sub-Sessions
+You can spawn independent sessions (e.g., for sub-agents) and link to them.
+
+```kotlin
+// Creates a new session and returns a task for it.
+// Adds a link to the new session in the current task.
+val subTask = task.linkedTask("Open Analysis Agent")
+
+// Content added to subTask appears in the new session/window
+subTask.add("Welcome to the sub-agent.")
+```
+
+### Linking to Sessions
+You can generate a link to the current session using `linkToSession`.
+```kotlin
+val link = task.ui.linkToSession("Open this session in new tab")
+task.add(link)
+```
+
+---
+
+## 8. Best Practices
+
+1. **Thread Safety:**
+ * `SessionTask` methods are generally safe to call from background threads.
+ * When using `TabbedDisplay`, the `container` updates are synchronized, but if you are doing complex logic involving multiple UI updates, ensure you aren't blocking the main UI thread (though Cognotik handles most of this via `pool`).
+
+2. **Completing Tasks:**
+ * Always call `task.complete()` when a unit of work is done. If you don't, the spinner will spin forever, making the UI look unresponsive.
+
+3. **IDs and State:**
+ * The system relies on `UUID`s and `messageID`s to find DOM elements. Avoid manipulating the DOM manually via raw JavaScript injection unless necessary; rely on `task.add` and `TabbedDisplay.update`.
+ * `SocketManager` maintains a version history of messages to optimize bandwidth, sending only updates when content changes.
+
+4. **Blocking vs Non-Blocking:**
+ * `Discussable` is **blocking**. Do not call it on the main server thread if you are handling high throughput synchronously (though usually, you are running inside a `SessionTask` thread pool).
+ * `Retryable` submits work to a thread pool automatically.
+ * You can access the session's thread pool via `task.ui.pool` to offload heavy computations.
+ * For delayed or periodic execution, use `task.ui.scheduledThreadPoolExecutor`.
+5. **Security:**
+ * `SocketManager` checks `ApplicationServices.authorizationManager` before allowing writes or reads. Ensure your `AuthorizationInterface` is configured correctly.
\ No newline at end of file
diff --git a/docs/task_best_practices.md b/docs/task_best_practices.md
index 81baf91db..9f0c85070 100644
--- a/docs/task_best_practices.md
+++ b/docs/task_best_practices.md
@@ -170,4 +170,101 @@ class ExampleTask(
)
}
}
-```
\ No newline at end of file
+```
+
+---
+
+# Best Practices: Handling `autoFix` and User Oversight
+
+In the Cognotik environment, `autoFix` is the toggle between **Autonomous Mode** (agent-driven) and **Interactive Mode** (human-in-the-loop). Proper implementation ensures that side effects (file writes, code execution) are safely guarded while providing a seamless UI for manual review.
+
+## 1. The Core Conditional Pattern
+Every task that performs a side effect should follow this structural template:
+
+```kotlin
+if (orchestrationConfig.autoFix) {
+ // 1. Perform side effect immediately
+ // 2. Log to transcript
+ // 3. Release semaphore/call resultFn
+} else {
+ // 1. Display proposed changes/logic to UI
+ // 2. Provide interactive controls (Discussable, hrefLink)
+ // 3. Wait for user to trigger completion (acceptButtonFooter)
+}
+```
+
+## 2. Guarding Logic with `Discussable`
+Use `Discussable` when the output of a task is a "thought product" (like a plan, a report, or a design) that the user might want to refine before it becomes the "official" result of the task.
+
+* **When to use:** `DiscussionTask`, Planning phases, or complex architectural decisions.
+* **Best Practice:** In interactive mode, wrap the AI's response logic in a `Discussable` block. This allows the user to provide feedback, which triggers a `reviseResponse` call to the LLM.
+
+```kotlin
+// Example from DiscussionTask.kt
+if (orchestrationConfig.autoFix) {
+ insightActor.answer(input) // Direct execution
+} else {
+ Discussable(
+ task = task,
+ initialResponse = { input -> insightActor.answer(input) },
+ reviseResponse = { messages -> insightActor.respond(messages) },
+ // ...
+ ).call() // Interactive loop
+}
+```
+
+## 3. Guarding Side Effects with `hrefLink`
+Side effects like running code or applying specific patches should be bound to UI triggers when `autoFix` is disabled.
+
+* **When to use:** Executing shell commands, running scripts, or applying specific file diffs.
+* **Best Practice:** Use `ui.hrefLink` to create buttons that perform the action only upon a click.
+
+```kotlin
+// Example from RunCodeTask.kt
+if (!orchestrationConfig.autoFix) {
+ task.add(ui.hrefLink("▶ Run", "play-button") {
+ execute(task, response) // Side effect happens ONLY on click
+ })
+}
+```
+
+## 4. Finalizing Tasks with `acceptButtonFooter`
+When a task involves multiple potential changes (like `FileModificationTask`), the user needs a way to signal that they are satisfied with the state of the workspace and ready to move to the next task in the plan.
+
+* **When to use:** At the end of any task where `autoFix` is false and a `Semaphore` is blocking the orchestrator.
+* **Best Practice:** Append the `acceptButtonFooter` to the final markdown output. This button should release the semaphore or call the `resultFn`.
+
+```kotlin
+// Example from FileModificationTask.kt
+val footer = acceptButtonFooter(task.ui) {
+ task.complete()
+ semaphore.release() // Unblocks the TaskOrchestrator
+}
+task.complete(renderMarkdown(codeResult) + footer)
+```
+
+## 5. Transcript Logging
+Regardless of whether `autoFix` is enabled, all actions—both AI-generated and user-triggered—must be written to the `transcript`.
+
+* **Auto Mode:** Log "Auto-applying changes..."
+* **Manual Mode:** Log "User Action: [Button Name]" or "User Feedback: [Text]".
+
+This ensures that the final log of the session is a complete record of how the current state was reached.
+
+## 6. Summary Table
+
+| Feature | `autoFix == true` | `autoFix == false` |
+| :--- | :--- | :--- |
+| **Execution** | Immediate | Guarded by `hrefLink` or `Discussable` |
+| **User Feedback** | Skipped | Enabled via `ui.textInput` or `Discussable` |
+| **Completion** | Automatic `semaphore.release()` | Manual via `acceptButtonFooter` |
+| **File Diffs** | `shouldAutoApply = true` | Manual "Apply" links |
+| **Transcript** | Logs AI intent + result | Logs AI intent + User actions |
+
+## 7. Checklist for New Tasks
+1. [ ] Does the task modify files or run code?
+2. [ ] If `autoFix` is false, is there a `Semaphore` or blocking mechanism to wait for the user?
+3. [ ] Are side effects wrapped in a `hrefLink` handler for manual mode?
+4. [ ] Is there a `textInput` or `Discussable` to allow the user to correct the AI?
+5. [ ] Does the manual path end with a clear "Continue" or "Accept" button?
+6. [ ] Are all paths (Auto and Manual) logging to the `transcript`?
\ No newline at end of file
diff --git a/docs/task_planning_launch_api.md b/docs/task_planning_launch_api.md
new file mode 100644
index 000000000..8ba3cbb3d
--- /dev/null
+++ b/docs/task_planning_launch_api.md
@@ -0,0 +1,401 @@
+### Overview
+
+A **Task Planning Session** in Cognotik is an orchestrated interaction where an AI Agent breaks down a high-level user
+request into smaller, executable steps. The configuration determines the "brain" (Cognitive Mode), the "tools" (Task
+Types), and the "environment" (Models/Limits) the AI uses to solve the problem.
+
+---
+
+### 1. Accessing the Configuration
+
+Depending on your interface, the entry point differs slightly:
+
+* **Web UI:** You navigate through a 4-step wizard starting at the "Welcome" screen (`welcome.html`).
+* **IntelliJ Plugin:** You open the **Plan Config Dialog** (`PlanConfigDialog.kt`), usually accessible via the plugin's
+ action menu when starting a new task.
+
+---
+
+### 2. Global Orchestration Settings
+
+Before defining specific tasks, you must configure the environment in which the AI operates.
+
+#### A. Model Selection
+
+You must define the AI models used for different aspects of the session:
+
+* **Smart Model (Default):** The "Brain." Used for high-level reasoning, planning, and complex code generation (e.g.,
+ GPT-4o).
+* **Parsing Model (Fast):** A cheaper, faster model used for structuring data, parsing outputs, and simple logic (e.g.,
+ GPT-3.5 Turbo or GPT-4o Mini).
+* **Image Model:** A multimodal model used specifically for tasks involving image analysis or generation.
+
+#### B. Execution Limits & Safety
+
+To prevent infinite loops or excessive API costs, you configure the following limits (found in `PlanConfigDialog.kt` and
+`OrchestrationConfig.kt`):
+
+* **Max Iterations:** The maximum number of planning rounds the AI can perform (Default: 10).
+* **Max Tasks Per Iteration:** How many parallel tasks the AI can schedule in a single round (Default: 1).
+* **Max Task History Chars:** The size of the context window retained from previous steps (Default: 10,000 chars).
+* **Budget ($):** A hard limit on API spending for the session.
+* **Temperature:** Controls creativity (0.0 = Deterministic/Code, 1.0 = Creative/Writing).
+* **Auto-Fix:** If checked, the system will automatically attempt to fix errors without asking for user confirmation.
+
+---
+
+### 3. Cognitive Mode Configuration
+
+The **Cognitive Mode** determines the strategy the AI uses to approach the problem. You select this from a dropdown (
+e.g., "Auto Plan", "Waterfall", "Chat").
+
+* **Configuration:** Clicking "Configure" next to the mode selection opens the `CognitiveConfigDialog`.
+* **Customization:** Depending on the mode, you can tweak internal prompts, descriptions, or specific boolean flags (
+ e.g., enabling specific reasoning steps) via reflection-based forms.
+
+---
+
+### 4. Task Configuration (The "Tools")
+
+This is the most critical part of the setup. You define which capabilities are available to the AI.
+
+#### A. Adding and Enabling Tasks
+
+You select from a list of available **Task Types** (e.g., `FileModificationTask`, `CrawlerAgentTask`, `PdfFormTask`).
+
+* **Single Selection:** Adds a specific tool to the AI's toolkit.
+* **Multiple Selection:** You can add multiple instances of the same task type with different configurations (e.g.,
+ two "Crawler" tasks configured for different domains).
+
+#### B. Configuring Specific Tasks
+
+Double-clicking a task in the list opens the **Task Config Dialog**. The settings available depend on the specific
+Kotlin class of the task:
+
+1. **General Settings:**
+ * **Name:** A unique identifier for this configuration (e.g., "Documentation_Crawler").
+ * **Model Override:** You can force a specific task to use a different model than the global default (e.g., use a
+ specialized coding model just for the `FileModificationTask`).
+
+2. **Task-Specific Fields:**
+ The UI dynamically generates fields based on the underlying code. Examples include:
+ * **Crawler Task:** `max_pages`, `allowed_domains`, `concurrent_processing`.
+ * **Persuasive Essay Task:** `thesis`, `target_word_count`, `revision_passes`.
+ * **MCP Tool Task:** `timeout`, `max_retries`.
+
+#### C. Sub-Planning (Recursive Planning)
+
+The `SubPlanTask` is a special task type that allows the AI to spawn a child session.
+
+* **Purpose:** You define a specific purpose for this sub-planner.
+* **Cognitive Mode:** The sub-plan can use a different strategy (e.g., "Waterfall") than the parent session.
+* **Sub-Task List:** You must explicitly add which tools (Tasks) are available to the sub-planner. This allows for
+ granular control (e.g., the main planner can browse the web, but the sub-planner can only write files).
+
+---
+
+### 5. Managing Configurations (Save/Load)
+
+To avoid re-configuring complex setups every time:
+
+* **Save:** In the IntelliJ dialog, you can name and save your current configuration (stored in `AppSettingsState`).
+* **Load:** Select a previously saved configuration from the dropdown to instantly restore all settings, models, and
+ task lists.
+* **Export/Import:** You can copy the entire configuration as a JSON string to your clipboard to share with other users
+ or move between machines.
+
+---
+
+### 6. Launching the Session
+
+Once configured:
+
+1. **Review:** Ensure the correct models are selected and the budget is set.
+2. **Launch:**
+ * **Web UI:** Click "Launch AI Session" in Step 4.
+ * **IntelliJ:** Click "OK" on the Plan Config Dialog.
+3. **Execution:** The system initializes the `OrchestrationConfig` object, instantiates the `PatchProcessor`, and begins
+ the planning loop based on your user prompt.
+
+### Summary Checklist for a Robust Session
+
+1. **Model:** Ensure you have a "Smart" model (GPT-4 class) selected for the default.
+2. **Context:** Increase `Max Task History Chars` if you are dealing with large codebases.
+3. **Tools:** Only enable the tasks strictly necessary for the job to reduce AI confusion.
+4. **Safety:** Set a reasonable `Budget` and `Max Iterations` to prevent runaway processes.
+
+---
+
+### 7. HTTP API Reference
+
+To implement a custom client (CLI, Web, or IDE Plugin) that launches tasks via HTTP, you need to interact with the
+configuration servlets to discover capabilities, configure the environment, and launch a session.
+
+#### A. Discovery Endpoints (Metadata)
+
+Clients should fetch available configurations to render forms or validate input.
+
+**1. API Providers & Models**
+* **Endpoint:** `/apiProviders/` (GET)
+* **Response:** JSON Object containing configured and available providers.
+* **Structure:**
+ ```json
+ {
+ "configuredProviders": [
+ {
+ "name": "OpenAI",
+ "baseUrl": "...",
+ "models": [
+ { "name": "gpt-4o", "maxTokens": 128000 },
+ { "name": "gpt-3.5-turbo", "maxTokens": 16000 }
+ ]
+ }
+ ],
+ "availableProviders": ["OpenAI", "Anthropic", "Ollama"]
+ }
+ ```
+* **Usage:** Use the model names (e.g., "gpt-4o") to populate model selection dropdowns.
+
+**2. Task Types Metadata**
+
+* **Endpoint:** `/taskConfig` (GET)
+* **Response:** JSON Array of Task Definitions.
+* **Structure:**
+ ```json
+ [
+ {
+ "id": "CrawlerAgent",
+ "name": "Crawler Agent Task",
+ "description": "Crawls websites...",
+ "category": "Online",
+ "configFields": [
+ {
+ "id": "max_pages_per_task",
+ "label": "Max Pages Per Task",
+ "type": "number",
+ "default": 10
+ },
+ {
+ "id": "allowed_domains",
+ "label": "Allowed Domains",
+ "type": "text",
+ "tooltip": "Comma separated list..."
+ }
+ ]
+ }
+ ]
+ ```
+* **Field Types:**
+ * `text`, `number`, `textarea`, `checkbox`: Standard inputs.
+ * `select`: Dropdown (includes `options` array of strings).
+ * `subtasks`: Special type for recursive planning. Requires a nested configuration map (see Payload).
+
+**3. Cognitive Modes Metadata**
+* **Endpoint:** `/cognitiveConfig` (GET)
+* **Response:** JSON Array of Cognitive Mode Definitions.
+* **Structure:** Similar to Task Types, but defines strategies (e.g., "Waterfall", "Auto Plan").
+
+#### B. User Settings (API Keys)
+
+Before launching, ensure API keys are configured.
+
+* **Endpoint:** `/userSettings/`
+* **GET:** Returns current settings (keys are masked or present).
+* **POST:** Save settings.
+ * **Content-Type:** `application/x-www-form-urlencoded`
+ * **Body:** `action=save&settings={JSON_STRING}`
+ * **JSON Structure:**
+ ```json
+ {
+ "apis": [
+ { "provider": "OpenAI", "key": "sk-...", "baseUrl": "" }
+ ],
+ "tools": ["/path/to/local/tool"]
+ }
+ ```
+
+#### C. Session Configuration & Launch
+
+To start a session, the client typically saves the session configuration to the server. The server then initializes the session state.
+
+* **Endpoint:** `/taskChat/settings` (POST)
+* **Content-Type:** `application/x-www-form-urlencoded`
+* **Body Parameters:**
+ * `sessionId`: String (Unique ID, e.g., `session_123456789`).
+ * `action`: `save`
+ * `settings`: JSON String of the `OrchestrationConfig`.
+
+**`settings` JSON Structure:**
+
+| Field | Type | Description |
+|:--------------------|:-------|:------------------------------------------------|
+| `sessionId` | String | Unique identifier for the session. |
+| `defaultSmartModel` | String | Model ID (e.g., "gpt-4o") from `/apiProviders`. |
+| `defaultFastModel` | String | Model ID (e.g., "gpt-4o-mini"). |
+| `imageChatModel` | String | Model ID for image generation (optional). |
+| `budget` | Number | Max cost in USD. |
+| `temperature` | Number | 0.0 to 1.0. |
+| `maxIterations` | Number | Loop limit. |
+| `workingDir` | String | Path to working directory. |
+| `cognitiveSettings` | Object | Strategy configuration. |
+| `taskSettings` | Map | Map of `ConfigName -> Configuration`. |
+
+**1. Cognitive Settings Object**
+```json
+{
+ "type": "Waterfall",
+ "feedback_rounds": 2
+}
+```
+
+**2. Task Settings Map (Polymorphic)**
+The `taskSettings` map keys are unique identifiers (strings). The values are polymorphic objects. You **must** include
+the `task_type` field (discriminator) matching the `id` from the metadata endpoint.
+
+```json
+"taskSettings": {
+ "MyCrawler_1": {
+ "task_type": "CrawlerAgent", <-- REQUIRED DISCRIMINATOR
+ "name": "MyCrawler_1",
+ "max_pages_per_task": 50,
+ "allowed_domains": "example.com"
+ },
+ "CodeRunner": {
+ "task_type": "RunCode",
+ "name": "CodeRunner"
+ }
+}
+```
+
+
+
+**3. Complete Example Payload (inside `settings` parameter)**
+
+```json
+{
+ "sessionId": "custom-client-session-123",
+ "budget": 5.0,
+ "temperature": 0.2,
+ "maxIterations": 15,
+ "autoFix": true,
+ "defaultSmartModel": "gpt-4o",
+ "defaultFastModel": "gpt-3.5-turbo",
+ "cognitiveSettings": {
+ "type": "Waterfall"
+ },
+ "taskSettings": {
+ "WebSearch": {
+ "task_type": "CrawlerAgent",
+ "name": "WebSearch",
+ "max_pages_per_task": 5
+ },
+ "FileEdit": {
+ "task_type": "FileModification",
+ "name": "FileEdit"
+ }
+ }
+}
+```
+
+---
+
+### 8. Embedding and Testing (Programmatic Access)
+
+Beyond the standard Web UI and IDE plugins, Cognotik provides robust Test Harnesses for embedding agent capabilities directly into code or running integration tests. These harnesses wrap the complex server infrastructure (Jetty, Websockets, Session Management) into a simple, synchronous or asynchronous API.
+
+#### A. The Unified Harness Architecture
+
+Both `PlanHarness` and `TaskHarness` delegate to a `UnifiedHarness` designed for ephemeral execution:
+1. **Ephemeral Workspace:** Automatically creates a timestamped temporary directory for the session (e.g., `workspaces/TaskName/test-20231027_120000`).
+2. **Server Modes:**
+ * **Serverless (Default for Tasks):** Runs the logic directly in the current thread/process without starting Jetty. Ideal for unit tests.
+ * **Server (Default for Plans):** Starts a local Jetty server (default port 8082) to allow Web UI interaction.
+3. **Session Management:** Initializes a `Session`, `User`, and `OrchestrationConfig` automatically.
+4. **Lifecycle Management:** Blocks execution until the task completes, fails, or times out.
+
+#### B. PlanHarness (Full Agent Workflow)
+
+Use `PlanHarness` when you want to execute a high-level user prompt using a specific Cognitive Mode (e.g., "Waterfall" or "Auto Plan"). This simulates a full user session programmatically.
+
+**Key Parameters:**
+* `prompt`: The string instruction to the agent.
+* `cognitiveSettings`: Configuration for the planning strategy.
+* `openBrowser`: If `true`, opens the UI to visualize the plan (requires `serverless=false`).
+* `serverless`: Defaults to `false`. Set to `true` to skip Jetty startup.
+* `modelInstanceFn`: A factory function to inject API keys and model instances.
+
+**Example Usage:**
+
+```kotlin
+val harness = PlanHarness(
+ prompt = "Research the history of the transistor and write a summary to summary.md",
+ cognitiveSettings = CognitiveModeConfig(
+ type = CognitiveModeType.Waterfall, // or Auto_Plan
+ name = "ResearchAgent"
+ ),
+ // Inject your API keys here
+ modelInstanceFn = { model ->
+ val apiKey = System.getenv("OPENAI_API_KEY")
+ model.model!!.instance(key = apiKey)
+ },
+ openBrowser = true, // Watch it run in the browser
+ timeoutMinutes = 15
+)
+
+harness.run() // Blocks until completion
+```
+
+**What happens:**
+1. The harness boots the server (unless `serverless=true`).
+2. It injects the `prompt` as if a user typed it into the chat.
+3. The agent plans, executes tools, and writes files to the temp workspace.
+4. On completion, `results.md` is written, and the harness shuts down.
+
+#### C. TaskHarness (Unit Testing Tools)
+
+Use `TaskHarness` to test a specific **Task Type** in isolation without the overhead of a planning agent. This is useful for debugging custom tools (e.g., a specific Crawler configuration or a custom API integration).
+
+**Key Parameters:**
+* `taskType`: The definition of the tool (e.g., `FileModificationTask`).
+* `typeConfig`: The static configuration for the tool (e.g., allowed domains for a crawler).
+* `executionConfig`: The runtime input for the tool (e.g., the specific URL to crawl).
+* `serverless`: Defaults to `true` for faster execution.
+
+**Example Usage:**
+
+```kotlin
+// 1. Define the Task Type and Configuration
+val myTaskType = TaskType.FileModification
+val myConfig = FileModificationConfig(name = "FileEditor")
+
+// 2. Define the specific job
+val executionInput = FileModificationExecutionConfig(
+ instructions = "Create a Hello World python script",
+ files = listOf()
+)
+
+// 3. Run the Harness
+val harness = TaskHarness(
+ taskType = myTaskType,
+ typeConfig = myConfig,
+ executionConfig = executionInput,
+ modelInstanceFn = { /* inject keys */ },
+ openBrowser = false // Usually false for unit tests
+)
+
+harness.run()
+```
+
+#### D. Platform Configuration
+
+When embedding these harnesses in a standalone application (outside the standard plugin environment), you may need to initialize the platform services (Authentication, Authorization, Tool Providers) before running a harness.
+
+```kotlin
+// Call this once at application startup
+PlanHarness.configurePlatform()
+```
+
+This static helper ensures that:
+1. `TaskType` and `ToolProvider` enumerations are loaded.
+2. A default "No-Op" Authentication/Authorization manager is installed (allowing local execution without login).
+3. Global orchestration settings are initialized.
diff --git a/docs/type_describers.md b/docs/type_describers.md
new file mode 100644
index 000000000..ab7159572
--- /dev/null
+++ b/docs/type_describers.md
@@ -0,0 +1,117 @@
+# Type Describers
+
+**Cognotik** relies heavily on reflection to describe code structures to Large Language Models (LLMs). The `TypeDescriber` system is responsible for converting Java/Kotlin classes, methods, and interfaces into text-based schemas (YAML, JSON, or TypeScript) that LLMs can understand and utilize.
+
+This is the engine behind `ParsedAgent` (for output formatting) and `CodeAgent`/`ProxyAgent` (for tool/API definition).
+
+---
+
+## 1. The `TypeDescriber` Abstraction
+**File:** `TypeDescriber.kt`
+
+The abstract base class for all describers. It handles:
+* **Recursion Detection:** Prevents infinite loops when describing self-referencing types.
+* **Generics Resolution:** Unwraps `List`, `Map`, and other parameterized types.
+* **Method Filtering:** Excludes common methods like `toString`, `hashCode`, `equals`.
+* **Polymorphism:** Allows registering subtypes so abstract types in a schema include their concrete implementations.
+
+### Key Configuration
+* **`coverMethods` (Boolean):** Whether to include method signatures in the description. Default is `true`.
+* **`stackMax` (Int):** Depth limit for recursion. Default is usually 10.
+
+---
+
+## 2. Implementations
+
+### `YamlDescriber`
+**File:** `YamlDescriber.kt`
+**Format:** YAML
+
+The default describer for most agents. YAML is preferred because it is token-efficient and highly readable for LLMs.
+
+**Example Output:**
+```yaml
+type: object
+class: com.example.UserProfile
+properties:
+ username:
+ type: string
+ description: "Unique identifier"
+ roles:
+ type: array
+ items:
+ type: enum
+ values:
+ - ADMIN
+ - USER
+methods:
+ updateEmail:
+ operationId: updateEmail
+ parameters:
+ - name: newEmail
+ type: string
+```
+
+### `JsonDescriber`
+**File:** `JsonDescriber.kt`
+**Format:** JSON
+
+Produces a JSON Schema-like structure. Useful for models that are fine-tuned on JSON or when strict JSON output is required.
+
+**Features:**
+* **Whitelist:** Accepts a set of allowed class names. Types outside this whitelist are described as `{"allowed": false}` to prevent leaking internal API details.
+
+### `TypeScriptDescriber`
+**File:** `TypeScriptDescriber.kt`
+**Format:** TypeScript Interfaces
+
+Generates TypeScript interface definitions. This is particularly effective for `CodeAgent` or coding-specialized LLMs, as they are often heavily trained on TypeScript definitions.
+
+**Example Output:**
+```typescript
+interface UserProfile {
+ username: string; /* Unique identifier */
+ roles: string[];
+ updateEmail(newEmail: string): void;
+}
+```
+
+---
+
+## 3. Advanced Usage
+
+### Abbreviation & Whitelisting
+When describing complex object graphs, you often want to hide implementation details of third-party libraries or deep internal structures.
+
+* **`AbbrevWhitelistYamlDescriber`** & **`AbbrevWhitelistTSDescriber`**
+ * These classes take a list of package prefixes in their constructor.
+ * **Logic:** If a type's name does *not* start with one of the provided prefixes, it is "abbreviated" (shown only as `type: object, class: ClassName` without properties/methods).
+ * This focuses the LLM's attention on your specific domain objects.
+
+### Polymorphism (Subtypes)
+If your API returns an interface (e.g., `Shape`), the LLM needs to know about `Circle` and `Square` to construct or interpret them correctly.
+
+```kotlin
+val describer = YamlDescriber()
+// Register concrete implementations
+describer.registerSubTypes(Shape::class.java, Circle::class.java, Square::class.java)
+```
+
+The generated schema will now include a `subtypes` section listing the structures of `Circle` and `Square`.
+
+### Annotations
+The system recognizes the `@Description` annotation to inject semantic meaning into the schema.
+
+* **On Properties:** Describes the field.
+* **On Methods:** Describes the operation.
+* **On Parameters:** Describes the argument.
+
+```kotlin
+data class Request(
+ @Description("The target URL to fetch")
+ val url: String
+)
+```
+
+### Dynamic Enums
+Support for `DynamicEnum` allows describing extensible enumerations that aren't standard Java enums, filtering values based on `EnabledStrategy`.
diff --git a/docs/user_settings.md b/docs/user_settings.md
new file mode 100644
index 000000000..15215fd8e
--- /dev/null
+++ b/docs/user_settings.md
@@ -0,0 +1,125 @@
+# User Settings Subsystem Developer Guide
+
+## 1. Overview
+The User Settings subsystem is responsible for managing user-specific configurations, primarily focusing on **API credentials** (for LLM providers like OpenAI, Anthropic, etc.) and **local tool configurations** (paths to executables like Git, Python, Docker).
+
+It provides a persistent, file-based storage mechanism, a JSON serialization layer that handles backward compatibility, and a web servlet for UI interaction with security masking for sensitive keys.
+
+## 2. Architecture
+
+The subsystem is built around a central interface and a file-based implementation, integrated into the web server via a Servlet.
+
+### Core Components
+* **`UserSettingsInterface`**: Defines the contract for retrieving and updating settings.
+* **`UserSettingsManager`**: The concrete implementation that manages file I/O and in-memory caching.
+* **`UserSettings`**: The data transfer object (DTO) representing the configuration state.
+* **`UserSettingsServlet`**: Handles HTTP GET/POST requests to view and modify settings via the Web UI.
+
+## 3. Data Model
+
+The `UserSettings` class is the root container. It has evolved from a map-based structure to a list-based structure to support multiple configurations per provider.
+
+### Structure (`UserSettings`)
+| Property | Type | Description |
+| :--- | :--- | :--- |
+| **`apis`** | `MutableList` | **Primary.** Contains configurations for API providers (Key, Base URL, Provider Type). |
+| **`tools`** | `MutableList` | **Primary.** Contains paths to local executables (e.g., path to `python.exe`). |
+| **`etc`** | `MutableMap` | A generic map for miscellaneous configuration flags. |
+| *`apiBase`* | `Map` | *Deprecated.* Read-only property for backward compatibility. |
+
+### Sub-Models
+* **`ApiData`**: Represents a connection to an AI provider.
+ * `provider`: Enum (`APIProvider`) e.g., OpenAI, Google.
+ * `key`: The API Key (String).
+ * `baseUrl`: Optional override for the API endpoint.
+ * `name`: Optional display name.
+* **`ToolData`**: Represents a local tool.
+ * `provider`: Enum (`ToolProvider`) e.g., Git, Python.
+ * `path`: Absolute path to the executable.
+
+## 4. Persistence & Storage
+
+### File System
+Settings are stored as JSON files on the local disk.
+* **Location**: Defined by the `root` parameter passed to `UserSettingsManager`.
+* **Naming Convention**: `.json` (e.g., `user@localhost.json`).
+
+### Caching
+The `UserSettingsManager` maintains an in-memory `HashMap` cache.
+* **Read**: Checks cache first. If missing, reads from disk. If file is missing, creates a new default instance.
+* **Write**: Updates cache immediately and writes to disk synchronously.
+
+## 5. Serialization & Migration
+
+The subsystem uses custom Jackson serializers/deserializers to handle schema evolution seamlessly.
+
+### Deserialization (`UserSettingsDeserializer`)
+This class contains logic to migrate legacy configuration files to the new format automatically.
+
+1. **New Format Detection**: Checks if the JSON contains `apis` or `tools`. If so, it loads them directly.
+2. **Legacy Fallback**: If new fields are missing, it looks for `apiKeys` and `apiBase` (old maps). It converts these maps into the `List` format via the `toApiList` helper function.
+3. **Tool Discovery**: If the `tools` list is empty after loading, it calls `discoverAllToolsFromPath()`. This scans the system `PATH` environment variable to automatically populate available tools (Git, Java, Python, etc.).
+
+### Serialization (`UserSettingsSerializer`)
+Always writes the data in the **new** format (`apis`, `tools`, `etc`), effectively migrating old files to the new structure upon the first save.
+
+## 6. Web API & Security
+
+The `UserSettingsServlet` exposes settings at `/userSettings`. It implements specific security logic to prevent API keys from being exposed to the client browser.
+
+### GET Request (Retrieval)
+When the UI requests settings:
+1. Loads the actual `UserSettings` object.
+2. Creates a **Visible Copy**:
+ * Iterates through `apis`.
+ * **Masking**: Replaces the actual API key with the string `"********"`.
+ * Preserves `baseUrl` and `provider`.
+3. Returns the masked JSON to the client.
+
+### POST Request (Update)
+When the UI saves settings:
+1. Receives the JSON (containing masked keys).
+2. Loads the **Previous Settings** from the manager.
+3. **Reconstruction Logic**:
+ * Iterates through the submitted `apis`.
+ * Checks the `key` field.
+ * **If `key == "********"`**: It assumes the user did not change the key. It retrieves the *actual* key from the `Previous Settings`.
+ * **If `key != "********"`**: It assumes the user entered a new key. It uses the new value.
+4. Merges new tools with existing tools.
+5. Saves the reconstructed object to disk.
+
+## 7. Integration Guide
+
+### Retrieving Settings in Code
+To access the current user's settings from anywhere in the platform:
+
+```kotlin
+val user = ApplicationServices.authenticationManager.getUser(requestCookie)
+val settings = ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings(user)
+
+// Access an API Key
+val openAiKey = settings.apis.find { it.provider == APIProvider.OpenAI }?.key
+
+// Access a Tool Path
+val pythonPath = settings.tools.find { it.provider == ToolProvider.Python }?.path
+```
+
+### Adding a New API Provider
+1. Modify `com.simiacryptus.cognotik.models.APIProvider`.
+2. Add a new object extending `APIProvider`.
+3. Register it in the `init` block of the companion object.
+4. The `UserSettings` system will automatically handle serialization for the new enum value due to `DynamicEnum`.
+
+### Adding a New Tool
+1. Modify `com.simiacryptus.cognotik.models.ToolProvider`.
+2. Add a new object extending `ToolProvider`.
+3. Implement `getExecutables()` (list of binary names, e.g., `["node", "npm"]`).
+4. Implement `getVersion(path)` for validation.
+5. Register it in the `init` block.
+6. The auto-discovery logic in `UserSettingsDeserializer` will now automatically find this tool on user systems.
+
+## 8. Common Pitfalls
+
+1. **Masking Collision**: If a user actually sets their API key to the literal string `"********"`, the system will overwrite it with the previous key on the next save.
+2. **Manual File Editing**: If editing the JSON file manually while the server is running, changes might be overwritten because the `UserSettingsManager` caches the object in memory. Restart the server after manual edits.
+3. **Validation**: `ApiData.validate()` throws an `IllegalStateException` if a provider is set but the key is missing. This validation is enforced during the reconstruction phase in the Servlet.
diff --git a/docs/websocket_protocol.md b/docs/websocket_protocol.md
new file mode 100644
index 000000000..a2eaa08d9
--- /dev/null
+++ b/docs/websocket_protocol.md
@@ -0,0 +1,94 @@
+ # WebSocket Protocol Guide
+
+ This document outlines the WebSocket protocol used by the Cognotik web UI framework to enable real-time, server-driven user interfaces.
+
+ ## 1. Connection Lifecycle
+
+ The application uses a standard Jetty-based WebSocket implementation. The connection is persistent and stateful, tied to a specific `Session` ID.
+
+ ### Endpoint
+ ```
+ ws://:/ws
+ ```
+
+ ### Handshake Parameters
+ The connection request **must** include the following query parameters:
+
+| Parameter | Required | Type | Description |
+|:------------------|:---------|:------------|:-------------------------------------------------------------------------------------------------------|
+| `sessionId` | Yes | UUID String | The unique identifier for the user's session. If missing, the connection is rejected with a 500 error. |
+| `lastMessageTime` | No | Long | Used for message replay/synchronization (see below). Defaults to `0`. |
+
+ ### Authentication
+ Authentication is handled via HTTP Cookies during the initial WebSocket upgrade request.
+ * **Cookie Name:** Defined by `AuthenticationInterface.AUTH_COOKIE`.
+ * **Behavior:** The server extracts the cookie to resolve the `User` object. If no cookie is present or valid, the user is treated as "anonymous" (or `defaultUser`).
+
+ ### Configuration (Server-Side)
+ Defined in `ChatServer.kt`:
+ * **Idle Timeout:** 10 minutes.
+ * **Max Message Size:** 1MB (Text/Binary).
+ * **Auto Fragment:** False.
+
+ ---
+
+ ## 2. Message Replay & Synchronization
+
+ To support page refreshes and network reconnections without losing UI state, the protocol implements a replay mechanism based on timestamps.
+
+ ### The `lastMessageTime` Parameter
+ When a client connects, it sends the timestamp of the last message it successfully processed via the `lastMessageTime` query parameter.
+
+ * **Value Handling:**
+ * `"null"`, `""`, or missing: Defaults to `0` (Replay all history).
+ * `"-Infinity"`: `Long.MIN_VALUE`.
+ * `"Infinity"`: `Long.MAX_VALUE` (No replay).
+ * Numeric String: Parsed as Long.
+
+ ### Server Behavior
+ Upon connection (`onWebSocketConnect`):
+ 1. The socket is added to the `SocketManager`.
+ 2. The server retrieves the message history for the session.
+ 3. It filters messages that occurred *after* `lastMessageTime`.
+ 4. It immediately sends these messages to the client in chronological order.
+
+ ---
+
+ ## 3. Data Transport
+
+ ### Server-to-Client
+ The server sends text frames. While the exact wire format is managed by `SocketManager`, logically these messages contain:
+ 1. **Target ID:** The `messageID` of the `SessionTask` (UI element) to update.
+ 2. **Content:** The HTML payload to append or replace.
+ 3. **State:** Indicators for task completion (removing spinners) or errors.
+
+ The client is expected to be a "dumb terminal" that parses these frames and updates the DOM elements matching the Target IDs.
+
+ ### Client-to-Server
+ The client sends text frames to the server. These are routed to `SocketManager.onWebSocketText`.
+
+ **Types of Messages:**
+ 1. **Chat Input:** Raw text strings entered by the user in the main chat interface. These trigger `ChatSocketManager.onRun`.
+ 2. **UI Events:** JSON-structured commands triggered by interactive elements (buttons, inputs) generated by `SessionTask`.
+ * *Example:* Clicking a link generated by `task.ui.hrefLink` sends the link's ID back to the server to execute the associated Kotlin lambda.
+
+ ---
+
+ ## 4. Error Handling
+
+ ### Connection Errors
+ * If the WebSocket configuration fails (e.g., missing `sessionId`), the server sends an HTTP 500 error during the handshake.
+ * Exceptions during `onWebSocketConnect` result in the socket being closed with status code `1011` (Server Error).
+
+ ### Runtime Errors
+ * **Server-Side:** Exceptions within `ChatSocketManager` (e.g., during LLM generation) are caught and rendered as HTML error blocks within the `SessionTask` output (using `task.error(e)`).
+ * **Transport:** If a message fails to send (e.g., broken pipe), the error is logged, but the session remains active for other sockets.
+
+ ---
+
+ ## 5. Architecture Summary
+
+ 1. **`ChatServer`**: The Jetty entry point. Configures the WebSocket factory and handles the initial HTTP upgrade.
+ 2. **`ChatSocket`**: The raw WebSocket adapter. Handles low-level events (`Connect`, `Close`, `Text`) and delegates logic to the `SocketManager`. It is responsible for the Replay loop.
+ 3. **`SocketManager`**: The session controller. It maintains the state of the UI, manages the list of active sockets (tabs/windows) for a single session, and routes incoming messages to the appropriate `SessionTask` or event handler.
+ 4. **`ChatSocketManager`**: A specialized implementation for Chat applications. It handles the "User types -> LLM responds" loop, query expansion, and transcript generation.
\ No newline at end of file
diff --git a/game.js b/game.js
deleted file mode 100644
index 08dbbd3c9..000000000
--- a/game.js
+++ /dev/null
@@ -1,269 +0,0 @@
-// Game constants
-const GRID_SIZE = 20;
-const CELL_SIZE = 20;
-const INITIAL_SPEED = 150;
-const SPEED_INCREMENT = 5;
-
-// Game state
-let canvas, ctx;
-let snake = [];
-let food = {};
-let direction = 'right';
-let nextDirection = 'right';
-let score = 0;
-let highScore = 0;
-let gameLoop = null;
-let isPaused = false;
-let gameSpeed = INITIAL_SPEED;
-
-// DOM elements
-const scoreElement = document.getElementById('score');
-const highScoreElement = document.getElementById('highScore');
-const startBtn = document.getElementById('startBtn');
-const pauseBtn = document.getElementById('pauseBtn');
-const restartBtn = document.getElementById('restartBtn');
-const gameOverElement = document.getElementById('gameOver');
-const finalScoreElement = document.getElementById('finalScore');
-
-// Initialize game
-function init() {
- canvas = document.getElementById('gameCanvas');
- ctx = canvas.getContext('2d');
-
- // Load high score from localStorage
- highScore = localStorage.getItem('snakeHighScore') || 0;
- highScoreElement.textContent = highScore;
-
- // Event listeners
- startBtn.addEventListener('click', startGame);
- pauseBtn.addEventListener('click', togglePause);
- restartBtn.addEventListener('click', restartGame);
- document.addEventListener('keydown', handleKeyPress);
-}
-
-// Start new game
-function startGame() {
- // Reset game state
- snake = [
- { x: 10, y: 10 },
- { x: 9, y: 10 },
- { x: 8, y: 10 }
- ];
- direction = 'right';
- nextDirection = 'right';
- score = 0;
- gameSpeed = INITIAL_SPEED;
- isPaused = false;
-
- updateScore();
- spawnFood();
-
- // Update UI
- startBtn.disabled = true;
- pauseBtn.disabled = false;
- gameOverElement.classList.add('hidden');
-
- // Start game loop
- if (gameLoop) clearInterval(gameLoop);
- gameLoop = setInterval(update, gameSpeed);
-}
-
-// Game update loop
-function update() {
- if (isPaused) return;
-
- // Update direction
- direction = nextDirection;
-
- // Calculate new head position
- const head = { ...snake[0] };
-
- switch (direction) {
- case 'up':
- head.y--;
- break;
- case 'down':
- head.y++;
- break;
- case 'left':
- head.x--;
- break;
- case 'right':
- head.x++;
- break;
- }
-
- // Check collisions
- if (checkCollision(head)) {
- gameOver();
- return;
- }
-
- // Add new head
- snake.unshift(head);
-
- // Check if food eaten
- if (head.x === food.x && head.y === food.y) {
- score += 10;
- updateScore();
- spawnFood();
-
- // Increase speed slightly
- if (score % 50 === 0 && gameSpeed > 50) {
- gameSpeed -= SPEED_INCREMENT;
- clearInterval(gameLoop);
- gameLoop = setInterval(update, gameSpeed);
- }
- } else {
- // Remove tail if no food eaten
- snake.pop();
- }
-
- draw();
-}
-
-// Draw game
-function draw() {
- // Clear canvas
- ctx.fillStyle = '#000';
- ctx.fillRect(0, 0, canvas.width, canvas.height);
-
- // Draw grid (optional)
- ctx.strokeStyle = '#111';
- for (let i = 0; i <= GRID_SIZE; i++) {
- ctx.beginPath();
- ctx.moveTo(i * CELL_SIZE, 0);
- ctx.lineTo(i * CELL_SIZE, canvas.height);
- ctx.stroke();
-
- ctx.beginPath();
- ctx.moveTo(0, i * CELL_SIZE);
- ctx.lineTo(canvas.width, i * CELL_SIZE);
- ctx.stroke();
- }
-
- // Draw snake
- snake.forEach((segment, index) => {
- ctx.fillStyle = index === 0 ? '#4ade80' : '#22c55e';
- ctx.fillRect(
- segment.x * CELL_SIZE,
- segment.y * CELL_SIZE,
- CELL_SIZE - 2,
- CELL_SIZE - 2
- );
-
- // Add eye to head
- if (index === 0) {
- ctx.fillStyle = '#000';
- const eyeSize = 3;
- if (direction === 'right') {
- ctx.fillRect(segment.x * CELL_SIZE + 12, segment.y * CELL_SIZE + 5, eyeSize, eyeSize);
- ctx.fillRect(segment.x * CELL_SIZE + 12, segment.y * CELL_SIZE + 12, eyeSize, eyeSize);
- } else if (direction === 'left') {
- ctx.fillRect(segment.x * CELL_SIZE + 5, segment.y * CELL_SIZE + 5, eyeSize, eyeSize);
- ctx.fillRect(segment.x * CELL_SIZE + 5, segment.y * CELL_SIZE + 12, eyeSize, eyeSize);
- } else if (direction === 'up') {
- ctx.fillRect(segment.x * CELL_SIZE + 5, segment.y * CELL_SIZE + 5, eyeSize, eyeSize);
- ctx.fillRect(segment.x * CELL_SIZE + 12, segment.y * CELL_SIZE + 5, eyeSize, eyeSize);
- } else {
- ctx.fillRect(segment.x * CELL_SIZE + 5, segment.y * CELL_SIZE + 12, eyeSize, eyeSize);
- ctx.fillRect(segment.x * CELL_SIZE + 12, segment.y * CELL_SIZE + 12, eyeSize, eyeSize);
- }
- }
- });
-
- // Draw food
- ctx.fillStyle = '#ef4444';
- ctx.beginPath();
- ctx.arc(
- food.x * CELL_SIZE + CELL_SIZE / 2,
- food.y * CELL_SIZE + CELL_SIZE / 2,
- CELL_SIZE / 2 - 2,
- 0,
- Math.PI * 2
- );
- ctx.fill();
-}
-
-// Spawn food at random position
-function spawnFood() {
- do {
- food = {
- x: Math.floor(Math.random() * GRID_SIZE),
- y: Math.floor(Math.random() * GRID_SIZE)
- };
- } while (snake.some(segment => segment.x === food.x && segment.y === food.y));
-}
-
-// Check collision with walls or self
-function checkCollision(head) {
- // Wall collision
- if (head.x < 0 || head.x >= GRID_SIZE || head.y < 0 || head.y >= GRID_SIZE) {
- return true;
- }
-
- // Self collision
- return snake.some(segment => segment.x === head.x && segment.y === head.y);
-}
-
-// Handle keyboard input
-function handleKeyPress(e) {
- const key = e.key.toLowerCase();
-
- // Prevent default arrow key behavior
- if (['arrowup', 'arrowdown', 'arrowleft', 'arrowright'].includes(key)) {
- e.preventDefault();
- }
-
- // Change direction (prevent 180-degree turns)
- if ((key === 'arrowup' || key === 'w') && direction !== 'down') {
- nextDirection = 'up';
- } else if ((key === 'arrowdown' || key === 's') && direction !== 'up') {
- nextDirection = 'down';
- } else if ((key === 'arrowleft' || key === 'a') && direction !== 'right') {
- nextDirection = 'left';
- } else if ((key === 'arrowright' || key === 'd') && direction !== 'left') {
- nextDirection = 'right';
- } else if (key === ' ' || key === 'p') {
- togglePause();
- }
-}
-
-// Toggle pause
-function togglePause() {
- if (!gameLoop) return;
-
- isPaused = !isPaused;
- pauseBtn.textContent = isPaused ? 'Resume' : 'Pause';
-}
-
-// Update score display
-function updateScore() {
- scoreElement.textContent = score;
-
- if (score > highScore) {
- highScore = score;
- highScoreElement.textContent = highScore;
- localStorage.setItem('snakeHighScore', highScore);
- }
-}
-
-// Game over
-function gameOver() {
- clearInterval(gameLoop);
- gameLoop = null;
-
- finalScoreElement.textContent = score;
- gameOverElement.classList.remove('hidden');
-
- startBtn.disabled = false;
- pauseBtn.disabled = true;
-}
-
-// Restart game
-function restartGame() {
- startGame();
-}
-
-// Initialize when page loads
-window.addEventListener('load', init);
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index fb68b8023..1592485f6 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,7 +1,7 @@
pluginName=Cognotik - Open Source Agentic Power Tools
pluginRepositoryUrl=https://github.com/SimiaCryptus/Cognotik
libraryGroup=com.cognotik
-libraryVersion=2.0.38
+libraryVersion=2.0.39
gradleVersion=8.13
org.gradle.caching=true
org.gradle.configureondemand=false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index da488a326..9d6571549 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -62,6 +62,7 @@ androidx-emoji2 = "1.6.0"
slf4jandroid = "1.7.36"
androidx-test-ext = "1.3.0"
androidx-test-espresso = "3.7.0"
+intellij = "2025.12.01"
# plugins
changelog = "2.2.1"
@@ -175,6 +176,7 @@ junit-junit = { group = "junit", name = "junit", version.ref = "junit4" }
kotlin-test-junit5 = { group = "org.jetbrains.kotlin", name = "kotlin-test-junit5", version.ref = "kotlin" }
kotlin-script-runtime = { group = "org.jetbrains.kotlin", name = "kotlin-script-runtime", version.ref = "kotlin" }
aws-bom = { group = "software.amazon.awssdk", name = "bom", version.ref = "aws" }
+intellij-core = { group = "com.jetbrains.intellij.idea", name = "intellij-core", version.ref = "intellij" }
[plugins]
changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
diff --git a/intellij/build.gradle.kts b/intellij/build.gradle.kts
index 50673a359..30eecf371 100644
--- a/intellij/build.gradle.kts
+++ b/intellij/build.gradle.kts
@@ -46,6 +46,16 @@ dependencies {
exclude(group = "com.google.oauth-client")
}
+ // Needed for PsiModifier and other PSI classes
+/*
+ implementation(libs.intellij.core) {
+ exclude(group = "org.jetbrains.kotlin")
+ exclude(group = "org.slf4j")
+ }
+*/
+ implementation(kotlin("compiler-embeddable"))
+
+
implementation(libs.aws.bedrockruntime)
implementation(libs.aws.s3)
implementation(libs.aws.kms)
@@ -65,6 +75,7 @@ dependencies {
implementation(libs.jetty.websocket.client)
implementation(libs.slf4j.api)
implementation(libs.logback.classic)
+ implementation(libs.tinkerpop)
implementation("com.github.jai-imageio:jai-imageio-core:1.4.0")
implementation("com.github.jai-imageio:jai-imageio-jpeg2000:1.4.0")
diff --git a/intellij/src/main/kotlin/cognotik/actions/BaseAction.kt b/intellij/src/main/kotlin/cognotik/actions/BaseAction.kt
index 19073a66d..9df85781a 100644
--- a/intellij/src/main/kotlin/cognotik/actions/BaseAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/BaseAction.kt
@@ -63,9 +63,9 @@ abstract class BaseAction(
}
open fun isEnabled(event: AnActionEvent): Boolean {
- if(AppSettingsState.instance.smartModel == null) return false
- if(AppSettingsState.instance.fastModel == null) return false
- return true
+ if (AppSettingsState.instance.smartModel == null) return false
+ if (AppSettingsState.instance.fastModel == null) return false
+ return true
}
companion object {
diff --git a/intellij/src/main/kotlin/cognotik/actions/FileContextAction.kt b/intellij/src/main/kotlin/cognotik/actions/FileContextAction.kt
index ff082ef14..8b543e033 100644
--- a/intellij/src/main/kotlin/cognotik/actions/FileContextAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/FileContextAction.kt
@@ -9,7 +9,6 @@ import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.simiacryptus.cognotik.config.AppSettingsState
import com.simiacryptus.cognotik.util.*
-import com.simiacryptus.cognotik.util.LoggerFactory
import java.io.File
import java.nio.file.Path
import java.util.concurrent.TimeUnit
diff --git a/intellij/src/main/kotlin/cognotik/actions/agent/CommandAutofixAction.kt b/intellij/src/main/kotlin/cognotik/actions/agent/CommandAutofixAction.kt
index c6a400fc4..6b6a97d38 100644
--- a/intellij/src/main/kotlin/cognotik/actions/agent/CommandAutofixAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/agent/CommandAutofixAction.kt
@@ -36,558 +36,569 @@ import javax.swing.*
class CommandAutofixAction : BaseAction() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
-
- /**
- * Handles the action execution.
- * Shows settings dialog, creates patch app session and opens browser interface.
- */
- override fun handle(e: AnActionEvent) {
- try {
- UITools.runAsync(e.project, "Initializing Command Autofix", true) { progress ->
- progress.isIndeterminate = true
- progress.text = "Getting settings..."
- val files = e.getSelectedFiles()
- val folders = e.getSelectedFolders().map { it.toFile.toPath() }
- val root = (folders + files.map { it.toFile.toPath() }).filterNotNull().toTypedArray().commonRoot()
- lateinit var settingsUI: SettingsUI
- val settings = run {
- var settings1: PatchApp.Settings? = null
- SwingUtilities.invokeAndWait {
- settingsUI = SettingsUI(workingDirectory = root.toFile(), folders)
-
- if (files.size == 1) {
- val defaultFile = files[0]
- val whitelist = listOf("sh", "py", "bat", "ps", "ps1", "cmd", "exe", "jar")
- val matchesWhitelist = whitelist.any { defaultFile.name.endsWith(".$it", ignoreCase = true) }
- if (defaultFile.isFile && (defaultFile.toFile.canExecute() || matchesWhitelist)) {
-
- val first = settingsUI.commandsList.firstOrNull()
- if (first != null) {
- first.commandField.selectedItem = defaultFile.toFile.name
- first.workingDirectoryField.selectedItem = defaultFile.parent.toFile.absolutePath
- first.argumentsField.selectedItem = ""
- }
- }
- }
- val dialog = CommandSettingsDialog(e.project, settingsUI)
- dialog.show()
- settings1 = if (dialog.isOK) {
- val commands = settingsUI.commandsList.map { cmdPanel ->
- val executable = File(
- cmdPanel.commandField.selectedItem?.toString() ?: throw IllegalArgumentException("No executable selected")
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+
+ /**
+ * Handles the action execution.
+ * Shows settings dialog, creates patch app session and opens browser interface.
+ */
+ override fun handle(e: AnActionEvent) {
+ try {
+ UITools.runAsync(e.project, "Initializing Command Autofix", true) { progress ->
+ progress.isIndeterminate = true
+ progress.text = "Getting settings..."
+ val files = e.getSelectedFiles()
+ val folders = e.getSelectedFolders().map { it.toFile.toPath() }
+ val root = (folders + files.map { it.toFile.toPath() }).filterNotNull().toTypedArray().commonRoot()
+ lateinit var settingsUI: SettingsUI
+ val settings = run {
+ var settings1: PatchApp.Settings? = null
+ SwingUtilities.invokeAndWait {
+ settingsUI = SettingsUI(workingDirectory = root.toFile(), folders)
+
+ if (files.size == 1) {
+ val defaultFile = files[0]
+ val whitelist = listOf("sh", "py", "bat", "ps", "ps1", "cmd", "exe", "jar")
+ val matchesWhitelist =
+ whitelist.any { defaultFile.name.endsWith(".$it", ignoreCase = true) }
+ if (defaultFile.isFile && (defaultFile.toFile.canExecute() || matchesWhitelist)) {
+
+ val first = settingsUI.commandsList.firstOrNull()
+ if (first != null) {
+ first.commandField.selectedItem = defaultFile.toFile.name
+ first.workingDirectoryField.selectedItem = defaultFile.parent.toFile.absolutePath
+ first.argumentsField.selectedItem = ""
+ }
+ }
+ }
+ val dialog = CommandSettingsDialog(e.project, settingsUI)
+ dialog.show()
+ settings1 = if (dialog.isOK) {
+ val commands = settingsUI.commandsList.map { cmdPanel ->
+ val executable = File(
+ cmdPanel.commandField.selectedItem?.toString()
+ ?: throw IllegalArgumentException("No executable selected")
+ )
+ val tools =
+ ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().tools
+ tools.addAll(ToolProvider.scanRecursive(File(executable.absolutePath)))
+ val argument = cmdPanel.argumentsField.selectedItem?.toString() ?: ""
+ AppSettingsState.instance.recentArguments?.remove(argument)
+ AppSettingsState.instance.recentArguments?.add(0, argument)
+ AppSettingsState.instance.recentArguments?.apply {
+ if (size > MAX_RECENT_ARGUMENTS) dropLast(size - MAX_RECENT_ARGUMENTS)
+ }
+ val workingDir = cmdPanel.workingDirectoryField.selectedItem?.toString() ?: ""
+ AppSettingsState.instance.recentWorkingDirs?.remove(workingDir)
+ AppSettingsState.instance.recentWorkingDirs?.add(0, workingDir)
+ AppSettingsState.instance.recentWorkingDirs?.apply {
+ if (size > MAX_RECENT_ARGUMENTS) dropLast(size - MAX_RECENT_DIRS)
+ }
+ //require(executable.exists()) { "Executable file does not exist: $executable" }
+ val workingDirectory = File(workingDir)
+ PatchApp.CommandSettings(
+ executable = executable,
+ arguments = argument,
+ workingDirectory = workingDirectory,
+ additionalInstructions = settingsUI.additionalInstructionsField.text
+ )
+ }.toList()
+ PatchApp.Settings(
+ commands = commands,
+ autoFix = settingsUI.autoFixCheckBox.isSelected,
+ maxRetries = settingsUI.maxRetriesSlider.value,
+ includeLineNumbers = settingsUI.includeLineNumbersCheckBox.isSelected,
+ includeGitDiffs = settingsUI.includeGitDiffsCheckBox.isSelected
+ )
+ } else {
+ null
+ }
+ }
+ settings1
+ } ?: return@runAsync
+ val patchApp = CmdPatchApp(
+ root = root,
+ settings = settings,
+ files = files.map { it.toFile }.toTypedArray(),
+ model = AppSettingsState.instance.smartChatClient.getChildClient(),
+ parsingModel = AppSettingsState.instance.fastChatClient.getChildClient(),
+ processor = AppSettingsState.instance.processor
)
- val tools = ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().tools
- tools.addAll(ToolProvider.scanRecursive(File(executable.absolutePath)))
- val argument = cmdPanel.argumentsField.selectedItem?.toString() ?: ""
- AppSettingsState.instance.recentArguments?.remove(argument)
- AppSettingsState.instance.recentArguments?.add(0, argument)
- AppSettingsState.instance.recentArguments?.apply {
- if (size > MAX_RECENT_ARGUMENTS) dropLast(size - MAX_RECENT_ARGUMENTS)
- }
- val workingDir = cmdPanel.workingDirectoryField.selectedItem?.toString() ?: ""
- AppSettingsState.instance.recentWorkingDirs?.remove(workingDir)
- AppSettingsState.instance.recentWorkingDirs?.add(0, workingDir)
- AppSettingsState.instance.recentWorkingDirs?.apply {
- if (size > MAX_RECENT_ARGUMENTS) dropLast(size - MAX_RECENT_DIRS)
- }
- //require(executable.exists()) { "Executable file does not exist: $executable" }
- val workingDirectory = File(workingDir)
- PatchApp.CommandSettings(
- executable = executable,
- arguments = argument,
- workingDirectory = workingDirectory,
- additionalInstructions = settingsUI.additionalInstructionsField.text
+ val session = Session.newGlobalID()
+ SessionProxyServer.chats[session] = patchApp
+ ApplicationServer.appInfoMap[session] = AppInfoData(
+ applicationName = "Code Chat",
+ inputCnt = 1,
+ stickyInput = false,
+ loadImages = false,
+ showMenubar = false
)
- }.toList()
- PatchApp.Settings(
- commands = commands,
- autoFix = settingsUI.autoFixCheckBox.isSelected,
- maxRetries = settingsUI.maxRetriesSlider.value,
- includeLineNumbers = settingsUI.includeLineNumbersCheckBox.isSelected,
- includeGitDiffs = settingsUI.includeGitDiffsCheckBox.isSelected
- )
- } else {
- null
+ val dateFormat = SimpleDateFormat("HH:mm:ss")
+ val sessionName = "${javaClass.simpleName} @ ${dateFormat.format(System.currentTimeMillis())}"
+ SessionProxyServer.metadataStorage.setSessionName(null, session, sessionName)
+ Thread {
+ Thread.sleep(500)
+ try {
+ val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
+ BaseAction.log.info("Opening browser to $uri")
+ browse(uri)
+ } catch (e: Throwable) {
+ log.warn("Error opening browser", e)
+ }
+ }.start()
}
- }
- settings1
- } ?: return@runAsync
- val patchApp = CmdPatchApp(
- root = root,
- settings = settings,
- files = files.map { it.toFile }.toTypedArray(),
- model = AppSettingsState.instance.smartChatClient.getChildClient(),
- parsingModel = AppSettingsState.instance.fastChatClient.getChildClient(),
- processor = AppSettingsState.instance.processor
- )
- val session = Session.newGlobalID()
- SessionProxyServer.chats[session] = patchApp
- ApplicationServer.appInfoMap[session] = AppInfoData(
- applicationName = "Code Chat", inputCnt = 1, stickyInput = false, loadImages = false, showMenubar = false
- )
- val dateFormat = SimpleDateFormat("HH:mm:ss")
- val sessionName = "${javaClass.simpleName} @ ${dateFormat.format(System.currentTimeMillis())}"
- SessionProxyServer.metadataStorage.setSessionName(null, session, sessionName)
- Thread {
- Thread.sleep(500)
- try {
- val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
- BaseAction.log.info("Opening browser to $uri")
- browse(uri)
- } catch (e: Throwable) {
- log.warn("Error opening browser", e)
- }
- }.start()
- }
- } catch (e: Throwable) {
- log.error("Failed to execute command autofix", e)
- UITools.showErrorDialog("Failed to execute command autofix: ${e.message}", "Error")
+ } catch (e: Throwable) {
+ log.error("Failed to execute command autofix", e)
+ UITools.showErrorDialog("Failed to execute command autofix: ${e.message}", "Error")
+ }
}
- }
-
- /**
- * Checks if the action should be enabled
- */
- override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
- if (event.project == null) return false
- val folder = event.getSelectedFolder()
- val hasBasePath = event.project?.basePath != null
- return folder != null || hasBasePath
- }
-
- companion object {
- private val log = LoggerFactory.getLogger(CommandAutofixAction::class.java)
- private const val MAX_RECENT_ARGUMENTS = 10
- private const val MAX_RECENT_DIRS = 10
- private const val TEXT_AREA_ROWS = 6
/**
- * Dialog for command settings configuration
+ * Checks if the action should be enabled
*/
- class CommandSettingsDialog(
- project: com.intellij.openapi.project.Project?, private val settingsUI: SettingsUI
- ) : DialogWrapper(project, true) {
- init {
- title = "Command Autofix Settings"
- init()
- }
-
- override fun createCenterPanel(): JComponent {
- val panel = JPanel(BorderLayout())
- panel.preferredSize = Dimension(800, 600)
-
- val contentPanel = JPanel(BorderLayout()).apply {
- border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
- }
+ override fun isEnabled(event: AnActionEvent): Boolean {
+ if (!super.isEnabled(event)) return false
+ if (event.project == null) return false
+ val folder = event.getSelectedFolder()
+ val hasBasePath = event.project?.basePath != null
+ return folder != null || hasBasePath
+ }
- val optionsPanel = panel {
- row { cell(settingsUI.commandsContainerPanel) }
- group("Saved Configurations") {
- row {
- cell(settingsUI.savedConfigsCombo)
- button("Load") {
- val configName = settingsUI.savedConfigsCombo.selectedItem as? String
- if (!configName.isNullOrBlank()) {
- settingsUI.loadConfig(configName)
- }
- }
- button("Save") {
- settingsUI.saveCurrentConfig()
- }
+ companion object {
+ private val log = LoggerFactory.getLogger(CommandAutofixAction::class.java)
+ private const val MAX_RECENT_ARGUMENTS = 10
+ private const val MAX_RECENT_DIRS = 10
+ private const val TEXT_AREA_ROWS = 6
+
+ /**
+ * Dialog for command settings configuration
+ */
+ class CommandSettingsDialog(
+ project: com.intellij.openapi.project.Project?, private val settingsUI: SettingsUI
+ ) : DialogWrapper(project, true) {
+ init {
+ title = "Command Autofix Settings"
+ init()
}
- }
- group("Code Analysis Options") {
- row {
- cell(settingsUI.autoFixCheckBox)
- cell(settingsUI.includeLineNumbersCheckBox)
- cell(settingsUI.includeGitDiffsCheckBox)
+
+ override fun createCenterPanel(): JComponent {
+ val panel = JPanel(BorderLayout())
+ panel.preferredSize = Dimension(800, 600)
+
+ val contentPanel = JPanel(BorderLayout()).apply {
+ border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
+ }
+
+ val optionsPanel = panel {
+ row { cell(settingsUI.commandsContainerPanel) }
+ group("Saved Configurations") {
+ row {
+ cell(settingsUI.savedConfigsCombo)
+ button("Load") {
+ val configName = settingsUI.savedConfigsCombo.selectedItem as? String
+ if (!configName.isNullOrBlank()) {
+ settingsUI.loadConfig(configName)
+ }
+ }
+ button("Save") {
+ settingsUI.saveCurrentConfig()
+ }
+ }
+ }
+ group("Code Analysis Options") {
+ row {
+ cell(settingsUI.autoFixCheckBox)
+ cell(settingsUI.includeLineNumbersCheckBox)
+ cell(settingsUI.includeGitDiffsCheckBox)
+ }
+ }
+ group("Execution Options") {
+ row("Max Retries:") {
+ cell(settingsUI.maxRetriesSlider)
+ cell(settingsUI.maxRetriesField)
+ }
+ }
+ group("Autofix On Exit Code:") {
+ buttonsGroup {
+ row {
+ settingsUI.exitCodeNonZero = radioButton(
+ "Non-zero (Error)", SettingsUI.ExitCodeOption.NONZERO
+ )
+ settingsUI.exitCodeZero = radioButton(
+ "Zero (Success)", SettingsUI.ExitCodeOption.ZERO
+ )
+ settingsUI.exitCodeAny = radioButton(
+ "Any (Always Run)", SettingsUI.ExitCodeOption.ANY
+ )
+ }
+ }.apply {
+ bind({ settingsUI.exitCodeOption }, { settingsUI.exitCodeOption = it })
+ }
+ }
+ group("Additional Instructions") {
+ row {
+ cell(settingsUI.additionalInstructionsField)
+ }
+ }
+ }
+ contentPanel.add(optionsPanel, BorderLayout.CENTER)
+ panel.add(contentPanel, BorderLayout.CENTER)
+ return panel
}
- }
- group("Execution Options") {
- row("Max Retries:") {
- cell(settingsUI.maxRetriesSlider)
- cell(settingsUI.maxRetriesField)
+ }
+
+ class SettingsUI(val workingDirectory: File, val folders: List) {
+ val commandsPanel = JPanel().apply {
+ layout = BoxLayout(this, BoxLayout.Y_AXIS)
}
- }
- group("Autofix On Exit Code:") {
- buttonsGroup {
- row {
- settingsUI.exitCodeNonZero = radioButton(
- "Non-zero (Error)", SettingsUI.ExitCodeOption.NONZERO
- )
- settingsUI.exitCodeZero = radioButton(
- "Zero (Success)", SettingsUI.ExitCodeOption.ZERO
- )
- settingsUI.exitCodeAny = radioButton(
- "Any (Always Run)", SettingsUI.ExitCodeOption.ANY
- )
- }
- }.apply {
- bind({ settingsUI.exitCodeOption }, { settingsUI.exitCodeOption = it })
+ val commandsScrollPane = JBScrollPane(commandsPanel).apply {
+ border = BorderFactory.createLoweredBevelBorder()
+ verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
+ horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
+ preferredSize = Dimension(750, 250)
+
+ verticalScrollBar.unitIncrement = 16
+
+ minimumSize = Dimension(600, 150)
}
- }
- group("Additional Instructions") {
- row {
- cell(settingsUI.additionalInstructionsField)
+ val commandsList = mutableListOf()
+ val savedConfigsCombo = ComboBox().apply {
+ preferredSize = Dimension(200, 30)
+ AppSettingsState.instance.savedCommandConfigsJson?.keys?.sorted()?.forEach { addItem(it) }
}
- }
- }
- contentPanel.add(optionsPanel, BorderLayout.CENTER)
- panel.add(contentPanel, BorderLayout.CENTER)
- return panel
- }
- }
-
- class SettingsUI(val workingDirectory: File, val folders: List) {
- val commandsPanel = JPanel().apply {
- layout = BoxLayout(this, BoxLayout.Y_AXIS)
- }
- val commandsScrollPane = JBScrollPane(commandsPanel).apply {
- border = BorderFactory.createLoweredBevelBorder()
- verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
- horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
- preferredSize = Dimension(750, 250)
-
- verticalScrollBar.unitIncrement = 16
-
- minimumSize = Dimension(600, 150)
- }
- val commandsList = mutableListOf()
- val savedConfigsCombo = ComboBox().apply {
- preferredSize = Dimension(200, 30)
- AppSettingsState.instance.savedCommandConfigsJson?.keys?.sorted()?.forEach { addItem(it) }
- }
-
- enum class ExitCodeOption { NONZERO, ZERO, ANY }
-
- var exitCodeOption = ExitCodeOption.NONZERO
- var exitCodeNonZero: Cell? = null
- var exitCodeZero: Cell? = null
- var exitCodeAny: Cell? = null
- val includeGitDiffsCheckBox = JCheckBox("Include Git Working Copy Diffs").apply {
- isSelected = false
- toolTipText = "Include git diffs between working copy and HEAD when analyzing code"
- }
- val includeLineNumbersCheckBox = JCheckBox("Include Line Numbers").apply {
- isSelected = true
- toolTipText = "Show line numbers in code snippets for better context"
- }
-
- val commandsContainerPanel = JPanel(BorderLayout()).apply {
- border = BorderFactory.createTitledBorder("Commands")
- preferredSize = Dimension(750, 300)
- }
-
- val addCommandButton = JButton("Add Command").apply {
- addActionListener {
- addCommandPanel()
- }
- }
- init {
+ enum class ExitCodeOption { NONZERO, ZERO, ANY }
- commandsContainerPanel.add(commandsScrollPane, BorderLayout.CENTER)
- val buttonPanel = JPanel(BorderLayout()).apply {
- border = BorderFactory.createEmptyBorder(5, 0, 0, 0)
- add(addCommandButton, BorderLayout.EAST)
+ var exitCodeOption = ExitCodeOption.NONZERO
+ var exitCodeNonZero: Cell? = null
+ var exitCodeZero: Cell? = null
+ var exitCodeAny: Cell? = null
+ val includeGitDiffsCheckBox = JCheckBox("Include Git Working Copy Diffs").apply {
+ isSelected = false
+ toolTipText = "Include git diffs between working copy and HEAD when analyzing code"
+ }
+ val includeLineNumbersCheckBox = JCheckBox("Include Line Numbers").apply {
+ isSelected = true
+ toolTipText = "Show line numbers in code snippets for better context"
+ }
- add(JButton("Clear All").apply {
- addActionListener {
- if (commandsList.size > 0) {
+ val commandsContainerPanel = JPanel(BorderLayout()).apply {
+ border = BorderFactory.createTitledBorder("Commands")
+ preferredSize = Dimension(750, 300)
+ }
- val firstPanel = commandsList.firstOrNull()
- commandsList.clear()
- commandsPanel.removeAll()
- if (firstPanel != null) {
- commandsList.add(firstPanel)
- commandsPanel.add(firstPanel)
- commandsPanel.add(Box.createVerticalStrut(5))
- } else {
- addCommandPanel()
+ val addCommandButton = JButton("Add Command").apply {
+ addActionListener {
+ addCommandPanel()
}
- commandsPanel.revalidate()
- commandsPanel.repaint()
- }
}
- }, BorderLayout.WEST)
- }
- commandsContainerPanel.add(buttonPanel, BorderLayout.SOUTH)
- addCommandPanel()
- }
+ init {
+
+ commandsContainerPanel.add(commandsScrollPane, BorderLayout.CENTER)
+ val buttonPanel = JPanel(BorderLayout()).apply {
+ border = BorderFactory.createEmptyBorder(5, 0, 0, 0)
+ add(addCommandButton, BorderLayout.EAST)
+
+ add(JButton("Clear All").apply {
+ addActionListener {
+ if (commandsList.size > 0) {
+
+ val firstPanel = commandsList.firstOrNull()
+ commandsList.clear()
+ commandsPanel.removeAll()
+ if (firstPanel != null) {
+ commandsList.add(firstPanel)
+ commandsPanel.add(firstPanel)
+ commandsPanel.add(Box.createVerticalStrut(5))
+ } else {
+ addCommandPanel()
+ }
+ commandsPanel.revalidate()
+ commandsPanel.repaint()
+ }
+ }
+ }, BorderLayout.WEST)
+ }
+ commandsContainerPanel.add(buttonPanel, BorderLayout.SOUTH)
- fun addCommandPanel() {
- val cmdPanel = CommandPanel(workingDirectory, folders)
- commandsList.add(cmdPanel)
- commandsPanel.add(cmdPanel)
- commandsPanel.add(Box.createVerticalStrut(5))
- commandsPanel.revalidate()
- commandsPanel.repaint()
+ addCommandPanel()
+ }
- SwingUtilities.invokeLater {
+ fun addCommandPanel() {
+ val cmdPanel = CommandPanel(workingDirectory, folders)
+ commandsList.add(cmdPanel)
+ commandsPanel.add(cmdPanel)
+ commandsPanel.add(Box.createVerticalStrut(5))
+ commandsPanel.revalidate()
+ commandsPanel.repaint()
- commandsScrollPane.revalidate()
- commandsScrollPane.repaint()
+ SwingUtilities.invokeLater {
- SwingUtilities.invokeLater {
- commandsScrollPane.viewport.viewPosition = java.awt.Point(0, commandsPanel.height - commandsScrollPane.viewport.height)
- }
- }
- }
+ commandsScrollPane.revalidate()
+ commandsScrollPane.repaint()
- fun removeCommandPanel(panel: CommandPanel) {
+ SwingUtilities.invokeLater {
+ commandsScrollPane.viewport.viewPosition =
+ java.awt.Point(0, commandsPanel.height - commandsScrollPane.viewport.height)
+ }
+ }
+ }
- if (commandsList.size <= 1) {
- return
- }
+ fun removeCommandPanel(panel: CommandPanel) {
- commandsList.remove(panel)
- commandsPanel.remove(panel)
+ if (commandsList.size <= 1) {
+ return
+ }
- if (commandsPanel.componentCount > commandsList.size * 2) {
- commandsPanel.remove(commandsPanel.getComponentZOrder(panel) + 1)
- }
- commandsPanel.revalidate()
- commandsPanel.repaint()
+ commandsList.remove(panel)
+ commandsPanel.remove(panel)
- SwingUtilities.invokeLater {
- SwingUtilities.getWindowAncestor(commandsPanel)?.pack()
- }
- }
+ if (commandsPanel.componentCount > commandsList.size * 2) {
+ commandsPanel.remove(commandsPanel.getComponentZOrder(panel) + 1)
+ }
+ commandsPanel.revalidate()
+ commandsPanel.repaint()
+
+ SwingUtilities.invokeLater {
+ SwingUtilities.getWindowAncestor(commandsPanel)?.pack()
+ }
+ }
- val maxRetriesField: JSpinner = JSpinner(SpinnerNumberModel(3, 0, 10, 1)).apply {
- toolTipText = "Maximum number of auto-retry attempts (0-10)"
- addChangeListener {
+ val maxRetriesField: JSpinner = JSpinner(SpinnerNumberModel(3, 0, 10, 1)).apply {
+ toolTipText = "Maximum number of auto-retry attempts (0-10)"
+ addChangeListener {
- maxRetriesSlider.value = value as Int
- }
- }
- val maxRetriesSlider = JSlider(JSlider.HORIZONTAL, 0, 10, 3).apply {
- majorTickSpacing = 2
- minorTickSpacing = 1
- paintTicks = true
- paintLabels = true
- toolTipText = "Maximum number of auto-retry attempts (0-10)"
- addChangeListener {
-
- maxRetriesField.value = value
- }
- }
- val additionalInstructionsField = JTextArea().apply {
- rows = TEXT_AREA_ROWS
- columns = 60
- lineWrap = true
- wrapStyleWord = true
- border = BorderFactory.createLoweredBevelBorder()
- minimumSize = Dimension(400, 100)
-
- }
-
- val autoFixCheckBox = JCheckBox("Auto-apply fixes").apply {
- isSelected = false
- }
-
- fun saveCurrentConfig() {
- val configName = JOptionPane.showInputDialog(
- null, "Enter configuration name:", "Save Configuration", JOptionPane.PLAIN_MESSAGE
- )?.trim()
- if (configName.isNullOrBlank()) {
- JOptionPane.showMessageDialog(
- null, "Please enter a valid configuration name", "Invalid Name", JOptionPane.WARNING_MESSAGE
- )
- return
- }
- val config = CommandConfig(
- commands = commandsList.map { it.toCommandSettings() },
- exitCodeOption = when (exitCodeOption) {
- ExitCodeOption.ZERO -> "0"
- ExitCodeOption.ANY -> "any"
- ExitCodeOption.NONZERO -> "nonzero"
- },
- autoFix = autoFixCheckBox.isSelected,
- maxRetries = maxRetriesSlider.value,
- includeGitDiffs = includeGitDiffsCheckBox.isSelected,
- includeLineNumbers = includeLineNumbersCheckBox.isSelected,
- additionalInstructions = additionalInstructionsField.text,
- )
- AppSettingsState.instance.savedCommandConfigsJson?.set(configName, config.toJson())
- savedConfigsCombo.addItem(configName)
- savedConfigsCombo.selectedItem = configName
- }
-
- fun loadConfig(configName: String) {
- val config =
- AppSettingsState.instance.savedCommandConfigsJson?.get(configName)?.let { fromJson(it, CommandConfig::class.java) } ?: return
- commandsList.clear()
- commandsPanel.removeAll()
- config.commands.forEach {
- val panel = CommandPanel(workingDirectory, folders)
- panel.loadFromSettings(it)
-
- panel.workingDirectoryField.selectedItem = workingDirectory.absolutePath
- commandsList.add(panel)
- commandsPanel.add(panel)
- commandsPanel.add(Box.createVerticalStrut(5))
- }
- exitCodeOption = when (config.exitCodeOption) {
- "0" -> ExitCodeOption.ZERO
- "any" -> ExitCodeOption.ANY
- else -> ExitCodeOption.NONZERO
- }
- autoFixCheckBox.isSelected = config.autoFix
- maxRetriesSlider.value = config.maxRetries
- includeGitDiffsCheckBox.isSelected = config.includeGitDiffs
- includeLineNumbersCheckBox.isSelected = config.includeLineNumbers ?: true
- additionalInstructionsField.text = config.additionalInstructions
-
- commandsPanel.revalidate()
- commandsPanel.repaint()
- }
-
- class CommandPanel(workingDirectory: File, folders: List) : JPanel() {
- val workingDirectoryField = ComboBox().apply {
- isEditable = true
- val items = mutableListOf()
- AppSettingsState.instance.recentWorkingDirs?.forEach { addItem(it); items.add(it) }
- if (AppSettingsState.instance.recentWorkingDirs?.isEmpty() == true) {
- addItem(workingDirectory.absolutePath)
- }
- folders.forEach {
- val absolutePath = it.toFile().absolutePath
- if (!items.contains(absolutePath)) {
- addItem(absolutePath)
- items.add(absolutePath)
+ maxRetriesSlider.value = value as Int
+ }
}
- }
- selectedItem = workingDirectory.absolutePath
- preferredSize = Dimension(400, preferredSize.height)
- }
- val executables : List? = ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings()
- .tools.flatMap { it.absoluteExecutablePaths() }.distinct().sorted()
- val commandField = ComboBox(executables?.toTypedArray() ?: emptyArray()).apply {
- isEditable = true
- preferredSize = Dimension(400, preferredSize.height)
- }
- val workingDirectoryButton = JButton("...").apply {
- addActionListener {
- val fileChooser = JFileChooser().apply {
- fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
- isMultiSelectionEnabled = false
- this.selectedFile = File(workingDirectoryField.selectedItem?.toString() ?: workingDirectory.absolutePath)
+ val maxRetriesSlider = JSlider(JSlider.HORIZONTAL, 0, 10, 3).apply {
+ majorTickSpacing = 2
+ minorTickSpacing = 1
+ paintTicks = true
+ paintLabels = true
+ toolTipText = "Maximum number of auto-retry attempts (0-10)"
+ addChangeListener {
+
+ maxRetriesField.value = value
+ }
}
- if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
- workingDirectoryField.selectedItem = fileChooser.selectedFile.absolutePath
+ val additionalInstructionsField = JTextArea().apply {
+ rows = TEXT_AREA_ROWS
+ columns = 60
+ lineWrap = true
+ wrapStyleWord = true
+ border = BorderFactory.createLoweredBevelBorder()
+ minimumSize = Dimension(400, 100)
+
}
- }
- }
- val commandButton = JButton("...").apply {
- addActionListener {
- val fileChooser = JFileChooser().apply {
- fileSelectionMode = JFileChooser.FILES_ONLY
- isMultiSelectionEnabled = false
+ val autoFixCheckBox = JCheckBox("Auto-apply fixes").apply {
+ isSelected = false
}
- if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
- commandField.selectedItem = fileChooser.selectedFile.absolutePath
+
+ fun saveCurrentConfig() {
+ val configName = JOptionPane.showInputDialog(
+ null, "Enter configuration name:", "Save Configuration", JOptionPane.PLAIN_MESSAGE
+ )?.trim()
+ if (configName.isNullOrBlank()) {
+ JOptionPane.showMessageDialog(
+ null, "Please enter a valid configuration name", "Invalid Name", JOptionPane.WARNING_MESSAGE
+ )
+ return
+ }
+ val config = CommandConfig(
+ commands = commandsList.map { it.toCommandSettings() },
+ exitCodeOption = when (exitCodeOption) {
+ ExitCodeOption.ZERO -> "0"
+ ExitCodeOption.ANY -> "any"
+ ExitCodeOption.NONZERO -> "nonzero"
+ },
+ autoFix = autoFixCheckBox.isSelected,
+ maxRetries = maxRetriesSlider.value,
+ includeGitDiffs = includeGitDiffsCheckBox.isSelected,
+ includeLineNumbers = includeLineNumbersCheckBox.isSelected,
+ additionalInstructions = additionalInstructionsField.text,
+ )
+ AppSettingsState.instance.savedCommandConfigsJson?.set(configName, config.toJson())
+ savedConfigsCombo.addItem(configName)
+ savedConfigsCombo.selectedItem = configName
}
- }
- }
- val argumentsField = ComboBox().apply {
- isEditable = true
- AppSettingsState.instance.recentArguments?.forEach { addItem(it) }
- if (AppSettingsState.instance.recentArguments?.isEmpty() == true) {
- addItem("")
- }
- preferredSize = Dimension(450, preferredSize.height)
- }
- init {
- border = BorderFactory.createCompoundBorder(
- BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory.createEtchedBorder()
- )
- layout = BorderLayout()
-
- minimumSize = Dimension(650, 120)
-
- val fieldsPanel = JPanel().apply {
- layout = BoxLayout(this, BoxLayout.Y_AXIS)
- border = BorderFactory.createEmptyBorder(5, 5, 5, 5)
- }
-
- val headerPanel = JPanel(BorderLayout()).apply {
- border = BorderFactory.createEmptyBorder(0, 0, 5, 0)
- }
- val removeButton = JButton("Remove").apply {
- addActionListener {
- val parent = SwingUtilities.getAncestorOfClass(
- SettingsUI::class.java, this@CommandPanel
- ) as? SettingsUI
- parent?.removeCommandPanel(this@CommandPanel)
+ fun loadConfig(configName: String) {
+ val config =
+ AppSettingsState.instance.savedCommandConfigsJson?.get(configName)
+ ?.let { fromJson(it, CommandConfig::class.java) } ?: return
+ commandsList.clear()
+ commandsPanel.removeAll()
+ config.commands.forEach {
+ val panel = CommandPanel(workingDirectory, folders)
+ panel.loadFromSettings(it)
+
+ panel.workingDirectoryField.selectedItem = workingDirectory.absolutePath
+ commandsList.add(panel)
+ commandsPanel.add(panel)
+ commandsPanel.add(Box.createVerticalStrut(5))
+ }
+ exitCodeOption = when (config.exitCodeOption) {
+ "0" -> ExitCodeOption.ZERO
+ "any" -> ExitCodeOption.ANY
+ else -> ExitCodeOption.NONZERO
+ }
+ autoFixCheckBox.isSelected = config.autoFix
+ maxRetriesSlider.value = config.maxRetries
+ includeGitDiffsCheckBox.isSelected = config.includeGitDiffs
+ includeLineNumbersCheckBox.isSelected = config.includeLineNumbers ?: true
+ additionalInstructionsField.text = config.additionalInstructions
+
+ commandsPanel.revalidate()
+ commandsPanel.repaint()
}
- }
- headerPanel.add(removeButton, BorderLayout.EAST)
- add(headerPanel, BorderLayout.NORTH)
-
- fieldsPanel.add(JPanel(BorderLayout(5, 0)).apply {
- add(JLabel("Command:", SwingConstants.RIGHT).apply {
- preferredSize = Dimension(100, preferredSize.height)
- }, BorderLayout.WEST)
- add(commandField, BorderLayout.CENTER)
- add(commandButton, BorderLayout.EAST)
- maximumSize = Dimension(Int.MAX_VALUE, preferredSize.height)
- alignmentX = LEFT_ALIGNMENT
- })
- fieldsPanel.add(Box.createVerticalStrut(5))
-
- fieldsPanel.add(JPanel(BorderLayout(5, 0)).apply {
- add(JLabel("Arguments:", SwingConstants.RIGHT).apply {
- preferredSize = Dimension(100, preferredSize.height)
- }, BorderLayout.WEST)
- add(argumentsField, BorderLayout.CENTER)
- maximumSize = Dimension(Int.MAX_VALUE, preferredSize.height)
- alignmentX = LEFT_ALIGNMENT
- })
- fieldsPanel.add(Box.createVerticalStrut(5))
-
- fieldsPanel.add(JPanel(BorderLayout(5, 0)).apply {
- add(JLabel("Directory:", SwingConstants.RIGHT).apply {
- preferredSize = Dimension(100, preferredSize.height)
- }, BorderLayout.WEST)
- add(workingDirectoryField, BorderLayout.CENTER)
- add(workingDirectoryButton, BorderLayout.EAST)
- maximumSize = Dimension(Int.MAX_VALUE, preferredSize.height)
- alignmentX = LEFT_ALIGNMENT
- })
- add(fieldsPanel, BorderLayout.CENTER)
- }
- fun toCommandSettings(): PatchApp.CommandSettings {
- return PatchApp.CommandSettings(
- executable = File(commandField.selectedItem?.toString() ?: ""),
- arguments = argumentsField.selectedItem?.toString() ?: "",
- workingDirectory = File(workingDirectoryField.selectedItem?.toString() ?: ""),
- additionalInstructions = ""
- )
- }
+ class CommandPanel(workingDirectory: File, folders: List) : JPanel() {
+ val workingDirectoryField = ComboBox().apply {
+ isEditable = true
+ val items = mutableListOf()
+ AppSettingsState.instance.recentWorkingDirs?.forEach { addItem(it); items.add(it) }
+ if (AppSettingsState.instance.recentWorkingDirs?.isEmpty() == true) {
+ addItem(workingDirectory.absolutePath)
+ }
+ folders.forEach {
+ val absolutePath = it.toFile().absolutePath
+ if (!items.contains(absolutePath)) {
+ addItem(absolutePath)
+ items.add(absolutePath)
+ }
+ }
+ selectedItem = workingDirectory.absolutePath
+ preferredSize = Dimension(400, preferredSize.height)
+ }
+ val executables: List? =
+ ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings()
+ .tools.flatMap { it.absoluteExecutablePaths() }.distinct().sorted()
+ val commandField = ComboBox(executables?.toTypedArray() ?: emptyArray()).apply {
+ isEditable = true
+ preferredSize = Dimension(400, preferredSize.height)
+ }
+ val workingDirectoryButton = JButton("...").apply {
+ addActionListener {
+ val fileChooser = JFileChooser().apply {
+ fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
+ isMultiSelectionEnabled = false
+ this.selectedFile =
+ File(workingDirectoryField.selectedItem?.toString() ?: workingDirectory.absolutePath)
+ }
+ if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
+ workingDirectoryField.selectedItem = fileChooser.selectedFile.absolutePath
+ }
+ }
+ }
+
+ val commandButton = JButton("...").apply {
+ addActionListener {
+ val fileChooser = JFileChooser().apply {
+ fileSelectionMode = JFileChooser.FILES_ONLY
+ isMultiSelectionEnabled = false
+ }
+ if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
+ commandField.selectedItem = fileChooser.selectedFile.absolutePath
+ }
+ }
+ }
+ val argumentsField = ComboBox().apply {
+ isEditable = true
+ AppSettingsState.instance.recentArguments?.forEach { addItem(it) }
+ if (AppSettingsState.instance.recentArguments?.isEmpty() == true) {
+ addItem("")
+ }
+ preferredSize = Dimension(450, preferredSize.height)
+ }
+
+ init {
+ border = BorderFactory.createCompoundBorder(
+ BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory.createEtchedBorder()
+ )
+ layout = BorderLayout()
+
+ minimumSize = Dimension(650, 120)
+
+ val fieldsPanel = JPanel().apply {
+ layout = BoxLayout(this, BoxLayout.Y_AXIS)
+ border = BorderFactory.createEmptyBorder(5, 5, 5, 5)
+ }
+
+ val headerPanel = JPanel(BorderLayout()).apply {
+ border = BorderFactory.createEmptyBorder(0, 0, 5, 0)
+ }
+ val removeButton = JButton("Remove").apply {
+ addActionListener {
+ val parent = SwingUtilities.getAncestorOfClass(
+ SettingsUI::class.java, this@CommandPanel
+ ) as? SettingsUI
+ parent?.removeCommandPanel(this@CommandPanel)
+ }
+ }
+ headerPanel.add(removeButton, BorderLayout.EAST)
+ add(headerPanel, BorderLayout.NORTH)
+
+ fieldsPanel.add(JPanel(BorderLayout(5, 0)).apply {
+ add(JLabel("Command:", SwingConstants.RIGHT).apply {
+ preferredSize = Dimension(100, preferredSize.height)
+ }, BorderLayout.WEST)
+ add(commandField, BorderLayout.CENTER)
+ add(commandButton, BorderLayout.EAST)
+ maximumSize = Dimension(Int.MAX_VALUE, preferredSize.height)
+ alignmentX = LEFT_ALIGNMENT
+ })
+ fieldsPanel.add(Box.createVerticalStrut(5))
+
+ fieldsPanel.add(JPanel(BorderLayout(5, 0)).apply {
+ add(JLabel("Arguments:", SwingConstants.RIGHT).apply {
+ preferredSize = Dimension(100, preferredSize.height)
+ }, BorderLayout.WEST)
+ add(argumentsField, BorderLayout.CENTER)
+ maximumSize = Dimension(Int.MAX_VALUE, preferredSize.height)
+ alignmentX = LEFT_ALIGNMENT
+ })
+ fieldsPanel.add(Box.createVerticalStrut(5))
+
+ fieldsPanel.add(JPanel(BorderLayout(5, 0)).apply {
+ add(JLabel("Directory:", SwingConstants.RIGHT).apply {
+ preferredSize = Dimension(100, preferredSize.height)
+ }, BorderLayout.WEST)
+ add(workingDirectoryField, BorderLayout.CENTER)
+ add(workingDirectoryButton, BorderLayout.EAST)
+ maximumSize = Dimension(Int.MAX_VALUE, preferredSize.height)
+ alignmentX = LEFT_ALIGNMENT
+ })
+ add(fieldsPanel, BorderLayout.CENTER)
+ }
- fun loadFromSettings(settings: PatchApp.CommandSettings) {
- commandField.selectedItem = settings.executable.absolutePath
- argumentsField.selectedItem = settings.arguments
+ fun toCommandSettings(): PatchApp.CommandSettings {
+ return PatchApp.CommandSettings(
+ executable = File(commandField.selectedItem?.toString() ?: ""),
+ arguments = argumentsField.selectedItem?.toString() ?: "",
+ workingDirectory = File(workingDirectoryField.selectedItem?.toString() ?: ""),
+ additionalInstructions = ""
+ )
+ }
+
+ fun loadFromSettings(settings: PatchApp.CommandSettings) {
+ commandField.selectedItem = settings.executable.absolutePath
+ argumentsField.selectedItem = settings.arguments
+ }
+ }
}
- }
}
- }
-
- data class CommandConfig(
- val commands: List,
- val exitCodeOption: String,
- val autoFix: Boolean,
- val maxRetries: Int,
- val additionalInstructions: String,
- val includeGitDiffs: Boolean = false,
- val includeLineNumbers: Boolean = false,
- )
+
+ data class CommandConfig(
+ val commands: List,
+ val exitCodeOption: String,
+ val autoFix: Boolean,
+ val maxRetries: Int,
+ val additionalInstructions: String,
+ val includeGitDiffs: Boolean = false,
+ val includeLineNumbers: Boolean = false,
+ )
}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/agent/CustomFileSetPatchAction.kt b/intellij/src/main/kotlin/cognotik/actions/agent/CustomFileSetPatchAction.kt
index 3ffcc562e..b4eb71ba3 100644
--- a/intellij/src/main/kotlin/cognotik/actions/agent/CustomFileSetPatchAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/agent/CustomFileSetPatchAction.kt
@@ -572,13 +572,13 @@ class CustomFileSetPatchAction : BaseAction() {
treatDocumentsAsText.isSelected
)
}.filter { path ->
- val relativePath = patternRoot.relativize(path)
- // Skip current directory references
- if (relativePath.toString().isEmpty() || relativePath.toString() == ".") {
- false
- } else {
- matcher.matches(relativePath)
- }
+ val relativePath = patternRoot.relativize(path)
+ // Skip current directory references
+ if (relativePath.toString().isEmpty() || relativePath.toString() == ".") {
+ false
+ } else {
+ matcher.matches(relativePath)
+ }
}.toList()
}
} catch (e: Exception) {
@@ -735,9 +735,9 @@ class CustomFileSetPatchAction : BaseAction() {
return try {
val dirFiles = Files.walk(directory, MAX_WALK_DEPTH).use { stream ->
stream.filter {
- Files.isRegularFile(it) && isLLMTextFile(
- it.toFile(), treatDocumentsAsText.isSelected
- )
+ Files.isRegularFile(it) && isLLMTextFile(
+ it.toFile(), treatDocumentsAsText.isSelected
+ )
}.limit(MAX_FILES_PER_PATTERN).toList()
}
if (dirFiles.isNotEmpty()) {
diff --git a/intellij/src/main/kotlin/cognotik/actions/agent/CustomFileSetPatchServer.kt b/intellij/src/main/kotlin/cognotik/actions/agent/CustomFileSetPatchServer.kt
index 3788f94ce..a7accd253 100644
--- a/intellij/src/main/kotlin/cognotik/actions/agent/CustomFileSetPatchServer.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/agent/CustomFileSetPatchServer.kt
@@ -589,7 +589,8 @@ class CustomFileSetPatchServer(
else -> {
val newSession = task.newSession()
- status = task.add("""Processing ${fileSet.name}...
""")!!
+ status =
+ task.add("""Processing ${fileSet.name}...
""")!!
newSession.newTask()
}
}
@@ -728,7 +729,7 @@ class CustomFileSetPatchServer(
toInput: (String) -> List,
socketManager: SocketManager
) {
- val design = mainActor.answer(toInput(userMessage),).toContentList().firstOrNull()?.text ?: ""
+ val design = mainActor.answer(toInput(userMessage)).toContentList().firstOrNull()?.text ?: ""
if (design.isNotBlank()) {
task.add(
AddApplyFileDiffLinks.instrumentFileDiffs(
diff --git a/intellij/src/main/kotlin/cognotik/actions/agent/DocumentedMassPatchAction.kt b/intellij/src/main/kotlin/cognotik/actions/agent/DocumentedMassPatchAction.kt
index 03a64a218..d975d5b81 100644
--- a/intellij/src/main/kotlin/cognotik/actions/agent/DocumentedMassPatchAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/agent/DocumentedMassPatchAction.kt
@@ -21,13 +21,13 @@ import com.simiacryptus.cognotik.util.getSelectedFiles
import com.simiacryptus.cognotik.util.getSelectedFolder
import com.simiacryptus.cognotik.webui.application.AppInfoData
import com.simiacryptus.cognotik.webui.application.ApplicationServer
- import java.awt.BorderLayout
- import java.awt.Dimension
+import java.awt.BorderLayout
+import java.awt.Dimension
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
- import java.io.File
- import java.nio.file.Files
- import java.nio.file.Path
+import java.io.File
+import java.nio.file.Files
+import java.nio.file.Path
import java.text.SimpleDateFormat
import javax.swing.*
@@ -153,39 +153,39 @@ class DocumentedMassPatchAction : BaseAction() {
add(JLabel("Documentation Files"), BorderLayout.NORTH)
add(JBScrollPane(settingsUI.documentationFiles), BorderLayout.CENTER)
}
-
+
val codePanel = JPanel(BorderLayout()).apply {
add(JLabel("Code Files"), BorderLayout.NORTH)
add(JBScrollPane(settingsUI.codeFiles), BorderLayout.CENTER)
}
-
+
val buttonPanel = JPanel().apply {
layout = BoxLayout(this, BoxLayout.X_AXIS)
border = JBUI.Borders.empty(10)
-
+
add(Box.createHorizontalGlue())
-
+
val moveDownButton = JButton("↓").apply {
toolTipText = "Move selected documentation file to code files"
addActionListener {
- moveSelectedItems(settingsUI.documentationFiles, settingsUI.codeFiles)
+ moveSelectedItems(settingsUI.documentationFiles, settingsUI.codeFiles)
}
}
add(moveDownButton)
-
+
add(Box.createHorizontalStrut(10))
-
+
val moveUpButton = JButton("↑").apply {
toolTipText = "Move selected code file to documentation files"
addActionListener {
- moveSelectedItems(settingsUI.codeFiles, settingsUI.documentationFiles)
+ moveSelectedItems(settingsUI.codeFiles, settingsUI.documentationFiles)
}
}
add(moveUpButton)
-
+
add(Box.createHorizontalGlue())
}
-
+
val centerPanel = JPanel(GridBagLayout()).apply {
val c = GridBagConstraints()
c.gridx = 0
@@ -206,7 +206,7 @@ class DocumentedMassPatchAction : BaseAction() {
c.fill = GridBagConstraints.BOTH
add(codePanel, c)
}
-
+
add(centerPanel, BorderLayout.CENTER)
preferredSize = Dimension(500, 600)
}
@@ -221,22 +221,23 @@ class DocumentedMassPatchAction : BaseAction() {
}, BorderLayout.SOUTH)
}
}
- private fun moveSelectedItems(sourceList: CheckBoxList, targetList: CheckBoxList) {
- val selectedIndices = sourceList.selectedIndices
- if (selectedIndices.isEmpty()) return
-
- val selectedItems = selectedIndices.map { sourceList.items[it] }
-
+
+ private fun moveSelectedItems(sourceList: CheckBoxList, targetList: CheckBoxList) {
+ val selectedIndices = sourceList.selectedIndices
+ if (selectedIndices.isEmpty()) return
+
+ val selectedItems = selectedIndices.map { sourceList.items[it] }
+
// Remove from source list
val sourceItems = sourceList.items.toMutableList()
- selectedItems.forEach { sourceItems.remove(it) }
-
+ selectedItems.forEach { sourceItems.remove(it) }
+
// Add to target list
val targetItems = targetList.items.toMutableList()
- targetItems.addAll(selectedItems)
-
+ targetItems.addAll(selectedItems)
+
// Update both lists
- val root = userSettings.documentationFiles.firstOrNull()?.parent
+ val root = userSettings.documentationFiles.firstOrNull()?.parent
?: userSettings.codeFilePaths.firstOrNull()?.parent
sourceList.setItems(sourceItems) { path ->
root?.relativize(path)?.toString() ?: path.toString()
@@ -244,14 +245,14 @@ class DocumentedMassPatchAction : BaseAction() {
targetList.setItems(targetItems) { path ->
root?.relativize(path)?.toString() ?: path.toString()
}
-
- // Select the moved items in target list
- val newIndices = selectedItems.mapNotNull { item ->
- val index = targetItems.indexOf(item)
- if (index >= 0) index else null
- }.toIntArray()
- if (newIndices.isNotEmpty()) {
- targetList.selectedIndices = newIndices
+
+ // Select the moved items in target list
+ val newIndices = selectedItems.mapNotNull { item ->
+ val index = targetItems.indexOf(item)
+ if (index >= 0) index else null
+ }.toIntArray()
+ if (newIndices.isNotEmpty()) {
+ targetList.selectedIndices = newIndices
}
}
diff --git a/intellij/src/main/kotlin/cognotik/actions/agent/MultiStepPatchAction.kt b/intellij/src/main/kotlin/cognotik/actions/agent/MultiStepPatchAction.kt
index 113b3a09a..225f4b44c 100644
--- a/intellij/src/main/kotlin/cognotik/actions/agent/MultiStepPatchAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/agent/MultiStepPatchAction.kt
@@ -192,8 +192,8 @@ class MultiStepPatchAction : BaseAction() {
AgentPatterns.displayMapInTabs(
mapOf(
- "Text" to design.text.renderMarkdown,
- "JSON" to "```json\n${toJson(design.obj)/*.indent(" ")*/}\n```".renderMarkdown,
+ "Text" to design.text.renderMarkdown,
+ "JSON" to "```json\n${toJson(design.obj)/*.indent(" ")*/}\n```".renderMarkdown,
)
)
},
diff --git a/intellij/src/main/kotlin/cognotik/actions/agent/ValidateCodeAction.kt b/intellij/src/main/kotlin/cognotik/actions/agent/ValidateCodeAction.kt
deleted file mode 100644
index 09c04312b..000000000
--- a/intellij/src/main/kotlin/cognotik/actions/agent/ValidateCodeAction.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-package cognotik.actions.agent
-
-import cognotik.actions.BaseAction
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.simiacryptus.cognotik.CognotikAppServer
-import com.simiacryptus.cognotik.apps.general.PatchApp
-import com.simiacryptus.cognotik.apps.general.ValidationPatchApp
-import com.simiacryptus.cognotik.config.AppSettingsState
-import com.simiacryptus.cognotik.platform.Session
-import com.simiacryptus.cognotik.util.*
-import com.simiacryptus.cognotik.util.BrowseUtil.browse
-import com.simiacryptus.cognotik.webui.application.AppInfoData
-import com.simiacryptus.cognotik.webui.application.ApplicationServer
-import java.text.SimpleDateFormat
-
-/**
- * Action that provides code validation and syntax checking through AI assistance
- */
-class ValidateCodeAction : BaseAction() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
-
- override fun handle(event: AnActionEvent) {
- try {
- UITools.runAsync(event.project, "Initializing Code Validation", true) { progress ->
- progress.isIndeterminate = true
- progress.text = "Setting up validation..."
-
- val files = event.getSelectedFiles()
- val folders = event.getSelectedFolders()
- if (files.isEmpty() && folders.isEmpty()) {
- UITools.showErrorDialog("Please select files or folders to validate", "No Selection")
- return@runAsync
- }
-
- val root = folders.firstOrNull()?.toFile?.toPath()
- ?: files.firstOrNull()?.parent?.toFile?.toPath()
- ?: event.project?.basePath?.let { java.io.File(it).toPath() }
- ?: throw IllegalStateException("Could not determine project root")
-
- val settings = PatchApp.Settings(
- commands = listOf(),
- autoFix = true,
- maxRetries = 1,
- includeLineNumbers = false
- )
-
- val session = Session.newGlobalID()
- val patchApp = ValidationPatchApp(
- root = root.toFile(),
- settings = settings,
- files = files.map { it.toFile }.toTypedArray(),
- model = AppSettingsState.instance.smartChatClient,
- parsingModel = AppSettingsState.instance.fastChatClient,
- processor = AppSettingsState.instance.processor
- )
-
- SessionProxyServer.chats[session] = patchApp
- ApplicationServer.appInfoMap[session] = AppInfoData(
- applicationName = "Code Validator",
- inputCnt = 1,
- stickyInput = false,
- loadImages = false,
- showMenubar = false
- )
-
- val dateFormat = SimpleDateFormat("HH:mm:ss")
- val sessionName = "${javaClass.simpleName} @ ${dateFormat.format(System.currentTimeMillis())}"
- SessionProxyServer.metadataStorage.setSessionName(null, session, sessionName)
-
- Thread {
- Thread.sleep(500)
- try {
- val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
- log.info("Opening browser to $uri")
- browse(uri)
- } catch (e: Throwable) {
- log.warn("Error opening browser", e)
- UITools.showErrorDialog("Failed to open browser: ${e.message}", "Error")
- }
- }.start()
- }
- } catch (e: Throwable) {
- log.error("Failed to execute code validation", e)
- UITools.showErrorDialog("Failed to execute code validation: ${e.message}", "Error")
- }
- }
-
- override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
- if (event.project == null) return false
- val hasSelection = event.getSelectedFiles().isNotEmpty() ||
- event.getSelectedFolders().isNotEmpty()
- return hasSelection
- }
-
- companion object {
- private val log = LoggerFactory.getLogger(ValidateCodeAction::class.java)
- }
-}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/analysis/SymbolExtractionAction.kt b/intellij/src/main/kotlin/cognotik/actions/analysis/SymbolExtractionAction.kt
new file mode 100644
index 000000000..b328b4283
--- /dev/null
+++ b/intellij/src/main/kotlin/cognotik/actions/analysis/SymbolExtractionAction.kt
@@ -0,0 +1,192 @@
+package cognotik.actions.analysis
+
+import cognotik.actions.BaseAction
+import com.simiacryptus.cognotik.apps.SymbolGraphService
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.ReadAction
+import com.intellij.openapi.progress.ProgressIndicator
+import com.intellij.openapi.progress.ProgressManager
+import com.intellij.openapi.progress.Task
+import com.intellij.openapi.roots.ProjectRootManager
+import com.intellij.openapi.ui.Messages
+import com.intellij.openapi.vfs.VfsUtilCore
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.PsiDocumentManager
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiManager
+import com.intellij.psi.PsiNamedElement
+import com.intellij.psi.PsiRecursiveElementVisitor
+import com.simiacryptus.cognotik.util.LoggerFactory
+import org.jetbrains.kotlin.com.intellij.psi.PsiModifier
+import org.jetbrains.kotlin.com.intellij.psi.PsiModifierListOwner
+import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
+import org.jetbrains.kotlin.psi.KtModifierListOwner
+import java.io.File
+import java.util.*
+
+class SymbolExtractionAction : BaseAction() {
+
+ val verbose = false
+ override fun isEnabled(event: AnActionEvent): Boolean {
+ return true
+ }
+
+ override fun handle(e: AnActionEvent) {
+ val project = e.project
+ if (project == null) {
+ log.warn("Project is null")
+ return
+ }
+ if(verbose) log.info("Starting symbol extraction for project: ${project.name}")
+
+ ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Extracting Symbols", true) {
+ override fun run(indicator: ProgressIndicator) {
+ if(verbose) log.info("Background task started")
+ val service = SymbolGraphService()
+ val fileList = mutableListOf()
+
+ ReadAction.run {
+ if(verbose) log.info("Collecting source roots")
+ ProjectRootManager.getInstance(project).contentSourceRoots.forEach { root ->
+ if(verbose) log.info("Processing root: ${root.path}")
+ VfsUtilCore.iterateChildrenRecursively(root, null) { file ->
+ if (!file.isDirectory) {
+ fileList.add(file)
+ }
+ true
+ }
+ }
+ if(verbose) log.info("Collected ${fileList.size} files")
+ }
+
+ indicator.isIndeterminate = false
+ val totalFiles = fileList.size
+
+
+ fileList.forEachIndexed { index, virtualFile ->
+ if (indicator.isCanceled) {
+ if(verbose) log.warn("Task canceled")
+ return
+ }
+ indicator.fraction = index.toDouble() / totalFiles
+ indicator.text = "Processing ${virtualFile.name} ($index/$totalFiles)"
+
+ ReadAction.run {
+ if (virtualFile.isValid) {
+ val psiFile = PsiManager.getInstance(project).findFile(virtualFile)
+ if (psiFile != null) {
+ if(verbose) log.debug("Analyzing file: ${virtualFile.path}")
+
+ val fileId = virtualFile.path
+ service.addFile(fileId, virtualFile.name)
+ val scopeStack = Stack()
+
+ psiFile.accept(object : PsiRecursiveElementVisitor() {
+ override fun visitElement(element: PsiElement) {
+ var pushed = false
+ if (element is PsiNamedElement) {
+ element.name?.let { elementName ->
+ val nodeId = "$fileId::$elementName"
+ var startOffset: Int? = null
+ var endOffset: Int? = null
+ var line: Int? = null
+ val range = element.textRange
+ if (range != null) {
+ startOffset = range.startOffset
+ endOffset = range.endOffset
+ val document = PsiDocumentManager.getInstance(project).getDocument(psiFile)
+ if (document != null) {
+ line = document.getLineNumber(range.startOffset) + 1
+ }
+ }
+ var visibility: String? = null
+ var modifiersStr: String? = null
+ var annotationsStr: String? = null
+ if (element is KtModifierListOwner) {
+ element.modifierList?.let { modList ->
+ visibility = when {
+ modList.hasModifier(KtModifierKeywordToken.keywordModifier("public")) -> "public"
+ modList.hasModifier(KtModifierKeywordToken.keywordModifier("private")) -> "private"
+ modList.hasModifier(KtModifierKeywordToken.keywordModifier("internal")) -> "internal"
+ else -> "package"
+ }
+ val modifiers = listOf(PsiModifier.STATIC, PsiModifier.FINAL, PsiModifier.ABSTRACT, PsiModifier.SYNCHRONIZED)
+ .filter { m -> modList.hasModifier(KtModifierKeywordToken.keywordModifier(m.lowercase())) }
+ if (modifiers.isNotEmpty()) modifiersStr = modifiers.joinToString(",")
+ val annotations = modList.annotations.mapNotNull { a -> a.name }
+ if (annotations.isNotEmpty()) annotationsStr = annotations.joinToString(",")
+ }
+ }
+
+ service.addSymbol(nodeId, elementName, fileId, startOffset, endOffset, line, visibility, modifiersStr, annotationsStr)
+ scopeStack.push(nodeId)
+ pushed = true
+ if(verbose) log.trace("Found definition: $elementName")
+ }
+ }
+ try {
+ for (ref in element.references) {
+ try {
+ val resolved = ref.resolve()
+ if (resolved is PsiNamedElement) {
+ val resolvedFile = resolved.containingFile?.virtualFile?.path
+ val name = resolved.name
+ if (name != null && resolvedFile != null) {
+ val targetId = "$resolvedFile::$name"
+ val sourceId = if (scopeStack.isNotEmpty()) scopeStack.peek() else fileId
+ service.addReference(sourceId, targetId, name, resolvedFile)
+ }
+ }
+ } catch (e: Exception) {
+ if (verbose) log.warn(
+ "Error resolving reference in ${virtualFile.name}",
+ e
+ )
+ }
+ }
+ } catch (e: Exception) {
+ if (verbose) log.warn(
+ "Error processing element in ${virtualFile.name}",
+ e
+ )
+ }
+ super.visitElement(element)
+ if (pushed) {
+ scopeStack.pop()
+ }
+ }
+ })
+ } else {
+ if(verbose) log.warn("PsiFile not found for ${virtualFile.path}")
+ }
+ } else {
+ if(verbose) log.warn("VirtualFile is invalid: ${virtualFile.path}")
+ }
+ }
+ }
+
+ try {
+ if(verbose) log.info("Serializing result")
+
+ val jsonFile = File(project.basePath, "symbol_graph.json")
+ service.save(jsonFile.absolutePath)
+
+
+ ApplicationManager.getApplication().invokeLater {
+ Messages.showInfoMessage(project, "Symbol graph saved to ${jsonFile.absolutePath}", "Analysis Complete")
+ }
+ } catch (e: Exception) {
+ log.error("Error saving analysis", e)
+ ApplicationManager.getApplication().invokeLater {
+ Messages.showErrorDialog(project, "Error saving analysis: ${e.message}", "Error")
+ }
+ }
+ }
+ })
+ }
+
+ companion object {
+ val log = LoggerFactory.getLogger(SymbolExtractionAction::class.java)
+ }
+}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/chat/ImageChatAction.kt b/intellij/src/main/kotlin/cognotik/actions/chat/ImageChatAction.kt
index c69a4a7b3..399d5a4f7 100644
--- a/intellij/src/main/kotlin/cognotik/actions/chat/ImageChatAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/chat/ImageChatAction.kt
@@ -121,12 +121,13 @@ class ImageChatAction : BaseAction() {
""".trimIndent()
override val sysMessage: ChatMessage
- get() = ChatMessage(ModelSchema.Role.system, listOf(
+ get() = ChatMessage(
+ ModelSchema.Role.system, listOf(
ContentPart(text = super.systemPrompt)
) + codeFiles.filter { isImg(it.name) }.map { path ->
val bufferedImage = root.resolve(path.toFile()).readBufferedImage()
ContentPart(text = "${path}").apply { image = bufferedImage }
- } )
+ })
fun File.readBufferedImage(): BufferedImage? {
return try {
@@ -241,7 +242,7 @@ class ImageChatAction : BaseAction() {
fun readFileContent(file: File): String {
return try {
- if(isImg(file.name.lowercase())) {
+ if (isImg(file.name.lowercase())) {
return ""
}
file.getDocumentReader().use { reader ->
diff --git a/intellij/src/main/kotlin/cognotik/actions/chat/ModifyFilesAction.kt b/intellij/src/main/kotlin/cognotik/actions/chat/ModifyFilesAction.kt
index 7daf1ea17..217a5636a 100644
--- a/intellij/src/main/kotlin/cognotik/actions/chat/ModifyFilesAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/chat/ModifyFilesAction.kt
@@ -33,7 +33,7 @@ open class ModifyFilesAction(
override fun getActionUpdateThread() = ActionUpdateThread.BGT
override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
+ if (!super.isEnabled(event)) return false
try {
val virtualFiles = PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(event.dataContext)
val files = virtualFiles?.map { it.toFile }?.toTypedArray()
diff --git a/intellij/src/main/kotlin/cognotik/actions/chat/MultiCodeChatAction.kt b/intellij/src/main/kotlin/cognotik/actions/chat/MultiCodeChatAction.kt
index 5b911d613..094fef301 100644
--- a/intellij/src/main/kotlin/cognotik/actions/chat/MultiCodeChatAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/chat/MultiCodeChatAction.kt
@@ -205,6 +205,7 @@ class MultiCodeChatAction : BaseAction() {
name.endsWith(".rtf") ||
name.endsWith(".html") || name.endsWith(".htm") ||
name.endsWith(".eml") -> true
+
file.inputStream.use { it.isBinary } -> true
else -> false
}
diff --git a/intellij/src/main/kotlin/cognotik/actions/chat/SmartCodeChatAction.kt b/intellij/src/main/kotlin/cognotik/actions/chat/SmartCodeChatAction.kt
index 24cc92f3d..e77e2fda4 100644
--- a/intellij/src/main/kotlin/cognotik/actions/chat/SmartCodeChatAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/chat/SmartCodeChatAction.kt
@@ -5,12 +5,10 @@ import cognotik.actions.agent.toFile
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.PlatformDataKeys
-import com.intellij.openapi.vfs.VirtualFile
import com.simiacryptus.cognotik.CognotikAppServer
import com.simiacryptus.cognotik.apps.general.renderMarkdown
import com.simiacryptus.cognotik.chat.model.ChatInterface
import com.simiacryptus.cognotik.config.AppSettingsState
-import com.simiacryptus.cognotik.input.getDocumentReader
import com.simiacryptus.cognotik.models.ModelSchema
import com.simiacryptus.cognotik.platform.ApplicationServices
import com.simiacryptus.cognotik.platform.Session
@@ -37,7 +35,10 @@ class SmartCodeChatAction : BaseAction() {
override fun handle(event: AnActionEvent) {
val root = getRoot(event) ?: return
val codeFiles =
- MultiCodeChatAction.getFiles(PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(event.dataContext) ?: arrayOf(), root).toMutableSet()
+ MultiCodeChatAction.getFiles(
+ PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(event.dataContext) ?: arrayOf(),
+ root
+ ).toMutableSet()
try {
UITools.runAsync(event.project, "Initializing Smart Code Chat", true) { progress ->
diff --git a/intellij/src/main/kotlin/cognotik/actions/editor/PasteAction.kt b/intellij/src/main/kotlin/cognotik/actions/editor/PasteAction.kt
index 5f204bee5..3bddd4d73 100644
--- a/intellij/src/main/kotlin/cognotik/actions/editor/PasteAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/editor/PasteAction.kt
@@ -25,7 +25,8 @@ import java.awt.datatransfer.DataFlavor.*
* Base class for paste actions that convert clipboard content to appropriate code format
* Supports both text and HTML clipboard content with automatic language detection
*/
-abstract class PasteActionBase(private val model: (AppSettingsState) -> ChatInterface) : SelectionAction(false) {
+abstract class PasteActionBase(private val model: (AppSettingsState) -> ChatInterface) :
+ SelectionAction(false) {
override fun getActionUpdateThread() = ActionUpdateThread.BGT
/**
@@ -240,8 +241,6 @@ private fun String.makeAbsolute(): String {
}
}
-class SmartPasteAction : PasteActionBase({ it.smartChatClient })
-
/**
* Fast paste action using faster but simpler model
*/
diff --git a/intellij/src/main/kotlin/cognotik/actions/editor/RecentCodeEditsAction.kt b/intellij/src/main/kotlin/cognotik/actions/editor/RecentCodeEditsAction.kt
deleted file mode 100644
index 047b751cc..000000000
--- a/intellij/src/main/kotlin/cognotik/actions/editor/RecentCodeEditsAction.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package cognotik.actions.editor
-
-import com.intellij.openapi.actionSystem.ActionGroup
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.project.Project
-import com.simiacryptus.cognotik.config.AppSettingsState
-import com.simiacryptus.cognotik.util.ComputerLanguage
-import com.simiacryptus.cognotik.util.hasSelection
-
-class RecentCodeEditsAction : ActionGroup() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
-
- override fun update(e: AnActionEvent) {
- e.presentation.isEnabledAndVisible = isEnabled(e)
- super.update(e)
- }
-
- override fun getChildren(e: AnActionEvent?): Array {
- if (e == null) return emptyArray()
- val children = mutableListOf()
- for (instruction in AppSettingsState.instance.getRecentCommands("customEdits").getMostRecent(10)) {
- val id = children.size + 1
- val text = if (id < 10) "_$id: $instruction" else "$id: $instruction"
- val element = object : CustomEditAction() {
- override fun getConfig(project: Project?): String {
- return instruction
- }
- }
- element.templatePresentation.text = text
- element.templatePresentation.description = instruction
- element.templatePresentation.icon = null
- children.add(element)
- }
- return children.toTypedArray()
- }
-
- companion object {
- fun isEnabled(e: AnActionEvent): Boolean {
- if (!e.hasSelection()) return false
- val computerLanguage = ComputerLanguage.getComputerLanguage(e)
- return computerLanguage != ComputerLanguage.Text
- }
- }
-}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/editor/RedoLast.kt b/intellij/src/main/kotlin/cognotik/actions/editor/RedoLast.kt
deleted file mode 100644
index e7c97f5b4..000000000
--- a/intellij/src/main/kotlin/cognotik/actions/editor/RedoLast.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package cognotik.actions.editor
-
-import cognotik.actions.BaseAction
-import com.intellij.openapi.actionSystem.ActionUpdateThread
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.actionSystem.CommonDataKeys
-import com.simiacryptus.cognotik.util.UITools.retry
-
-/**
- * The RedoLast action is an IntelliJ action that allows users to redo the last AI Coder action they performed in the editor.
- * To use this action, open the editor and select the RedoLast action from the editor context menu.
- * This will redo the last action that was performed in the editor.
- */
-class RedoLast : BaseAction() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
-
- override fun handle(e: AnActionEvent) {
- val editor = e.getData(CommonDataKeys.EDITOR) ?: return
- retry[editor.document]?.run()
- }
-
- override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
- val editor = event.getData(CommonDataKeys.EDITOR) ?: return false
- return retry[editor.document] != null
- }
-
-}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/find/FindResultsChatAction.kt b/intellij/src/main/kotlin/cognotik/actions/find/FindResultsChatAction.kt
index d298d33d2..aba101a51 100644
--- a/intellij/src/main/kotlin/cognotik/actions/find/FindResultsChatAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/find/FindResultsChatAction.kt
@@ -101,7 +101,7 @@ class FindResultsChatAction(
}
override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
+ if (!super.isEnabled(event)) return false
val usageView = event.getData(UsageView.USAGE_VIEW_KEY)
return usageView != null && usageView.usages.isNotEmpty()
}
@@ -186,7 +186,7 @@ class FindResultsChatAction(
You will be answering questions about code with the following find results:
""".trimIndent() + getCodeContext(),
model = model
- ).answer(listOf(userMessage),)
+ ).answer(listOf(userMessage))
) + ""
)
task.placeholder
diff --git a/intellij/src/main/kotlin/cognotik/actions/find/FindResultsModificationAction.kt b/intellij/src/main/kotlin/cognotik/actions/find/FindResultsModificationAction.kt
index a80882fe6..6415e0e3c 100644
--- a/intellij/src/main/kotlin/cognotik/actions/find/FindResultsModificationAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/find/FindResultsModificationAction.kt
@@ -227,7 +227,7 @@ class FindResultsModificationAction(
}
override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
+ if (!super.isEnabled(event)) return false
val usageView = event.getData(UsageView.USAGE_VIEW_KEY)
return usageView != null && usageView.usages.isNotEmpty()
}
diff --git a/intellij/src/main/kotlin/cognotik/actions/generate/CreateFileFromDescriptionAction.kt b/intellij/src/main/kotlin/cognotik/actions/generate/CreateFileFromDescriptionAction.kt
index ceb8ae758..03f6e172f 100644
--- a/intellij/src/main/kotlin/cognotik/actions/generate/CreateFileFromDescriptionAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/generate/CreateFileFromDescriptionAction.kt
@@ -8,7 +8,8 @@ import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.project.Project
import com.simiacryptus.cognotik.chat.model.ChatInterface
import com.simiacryptus.cognotik.config.AppSettingsState
-import com.simiacryptus.cognotik.models.ModelSchema.*
+import com.simiacryptus.cognotik.models.ModelSchema.ChatMessage
+import com.simiacryptus.cognotik.models.ModelSchema.Role
import com.simiacryptus.cognotik.util.UITools
import com.simiacryptus.cognotik.util.toContentList
import java.io.File
diff --git a/intellij/src/main/kotlin/cognotik/actions/generate/CreateImageAction.kt b/intellij/src/main/kotlin/cognotik/actions/generate/CreateImageAction.kt
index caab22e68..d9f73689e 100644
--- a/intellij/src/main/kotlin/cognotik/actions/generate/CreateImageAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/generate/CreateImageAction.kt
@@ -10,8 +10,8 @@ import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.util.ui.JBUI
-import com.simiacryptus.cognotik.agents.ImageGenerationAgent
import com.simiacryptus.cognotik.agents.ImageAndText
+import com.simiacryptus.cognotik.agents.ImageGenerationAgent
import com.simiacryptus.cognotik.config.AppSettingsState
import com.simiacryptus.cognotik.util.*
import java.awt.GridBagConstraints
@@ -27,183 +27,183 @@ import javax.imageio.ImageIO
import javax.swing.*
class CreateImageAction : BaseAction() {
- inner class ImageGenerationDialog(project: Project) : DialogWrapper(project) {
- private val fileNameField = JTextField(generateDefaultFileName(), 20)
- private val instructionsArea = JTextArea(3, 20)
-
- init {
- log.debug("Initializing ImageGenerationDialog")
- title = "Generate Image"
- init()
- }
-
- private fun generateDefaultFileName(): String {
- val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"))
- return "generated_image_$timestamp.png"
- }
-
- override fun createCenterPanel(): JComponent {
- return JPanel(GridBagLayout()).apply {
- val c = GridBagConstraints()
- c.fill = GridBagConstraints.HORIZONTAL
- c.insets = JBUI.insets(5)
- c.gridx = 0; c.gridy = 0
- add(JLabel("Output filename:"), c)
- c.gridx = 1; c.gridy = 0
- add(fileNameField, c)
- c.gridx = 0; c.gridy = 1
- add(JLabel("Special instructions:"), c)
- c.gridx = 1; c.gridy = 1
- c.fill = GridBagConstraints.BOTH
- add(JScrollPane(instructionsArea), c)
- }
- }
+ inner class ImageGenerationDialog(project: Project) : DialogWrapper(project) {
+ private val fileNameField = JTextField(generateDefaultFileName(), 20)
+ private val instructionsArea = JTextArea(3, 20)
+
+ init {
+ log.debug("Initializing ImageGenerationDialog")
+ title = "Generate Image"
+ init()
+ }
- fun getFileName() = fileNameField.text
- fun getInstructions() = instructionsArea.text
- }
+ private fun generateDefaultFileName(): String {
+ val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"))
+ return "generated_image_$timestamp.png"
+ }
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
+ override fun createCenterPanel(): JComponent {
+ return JPanel(GridBagLayout()).apply {
+ val c = GridBagConstraints()
+ c.fill = GridBagConstraints.HORIZONTAL
+ c.insets = JBUI.insets(5)
+ c.gridx = 0; c.gridy = 0
+ add(JLabel("Output filename:"), c)
+ c.gridx = 1; c.gridy = 0
+ add(fileNameField, c)
+ c.gridx = 0; c.gridy = 1
+ add(JLabel("Special instructions:"), c)
+ c.gridx = 1; c.gridy = 1
+ c.fill = GridBagConstraints.BOTH
+ add(JScrollPane(instructionsArea), c)
+ }
+ }
- override fun handle(e: AnActionEvent) {
- log.info("Starting CreateImageAction handler")
- val rootRef = AtomicReference(null)
- val codeFiles: MutableSet = mutableSetOf()
- val dialog = ImageGenerationDialog(e.project!!)
- if (!dialog.showAndGet()) {
- log.debug("Dialog cancelled by user")
- return
+ fun getFileName() = fileNameField.text
+ fun getInstructions() = instructionsArea.text
}
- UITools.runAsync(e.project, "Creating Image", true) { progress ->
- try {
- progress.text = "Analyzing code files..."
- log.debug("Beginning code analysis")
- fun codeSummary() = codeFiles.filter {
- rootRef.get()?.resolve(it)?.toFile()?.exists() ?: false
- }.associateWith { rootRef.get()?.resolve(it)?.toFile()?.readText(Charsets.UTF_8) }.entries.joinToString(
- "\n\n"
- ) { (path, code) ->
- val extension =
- path.toString().split('.').lastOrNull()?.let { /*escapeHtml4*/(it)/*.indent(" ")*/ }
- "# $path\n```$extension\n${code}\n```"
- }
- val dataContext = e.dataContext
- val virtualFiles = PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext)
- log.debug("Found ${virtualFiles?.size ?: 0} virtual files")
- progress.text = "Determining root directory..."
- val folder = e.getSelectedFolder()
- rootRef.set(
- if (null != folder) {
- log.debug("Using selected folder as root: {}", folder.toFile)
- folder.toFile.toPath()
- } else if (1 == virtualFiles?.size) {
- log.debug("Using parent of single file as root")
- e.getSelectedFile()?.parent?.toNioPath()
- } else {
- log.debug("Using module root as root directory")
- getModuleRootForFile(
- e.getSelectedFile()?.parent?.toFile ?: throw RuntimeException("No file selected")
- ).toPath()
- }
- )
- progress.text = "Collecting files..."
-
- val root = rootRef.get() ?: throw RuntimeException("Root path not set")
- if (!Files.exists(root)) {
- throw IOException("Root directory does not exist: $root")
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+
+ override fun handle(e: AnActionEvent) {
+ log.info("Starting CreateImageAction handler")
+ val rootRef = AtomicReference(null)
+ val codeFiles: MutableSet = mutableSetOf()
+ val dialog = ImageGenerationDialog(e.project!!)
+ if (!dialog.showAndGet()) {
+ log.debug("Dialog cancelled by user")
+ return
}
- log.info("Using root directory: $root")
- val files = getFiles(virtualFiles, root)
- codeFiles.addAll(files)
- log.debug("Collected ${codeFiles.size} code files")
- progress.text = "Generating image..."
- log.info("Starting image generation with ${codeFiles.size} files")
- val imageActor = ImageGenerationAgent(
- prompt = """
+ UITools.runAsync(e.project, "Creating Image", true) { progress ->
+ try {
+ progress.text = "Analyzing code files..."
+ log.debug("Beginning code analysis")
+ fun codeSummary() = codeFiles.filter {
+ rootRef.get()?.resolve(it)?.toFile()?.exists() ?: false
+ }.associateWith { rootRef.get()?.resolve(it)?.toFile()?.readText(Charsets.UTF_8) }.entries.joinToString(
+ "\n\n"
+ ) { (path, code) ->
+ val extension =
+ path.toString().split('.').lastOrNull()?.let { /*escapeHtml4*/(it)/*.indent(" ")*/ }
+ "# $path\n```$extension\n${code}\n```"
+ }
+
+ val dataContext = e.dataContext
+ val virtualFiles = PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext)
+ log.debug("Found ${virtualFiles?.size ?: 0} virtual files")
+ progress.text = "Determining root directory..."
+ val folder = e.getSelectedFolder()
+ rootRef.set(
+ if (null != folder) {
+ log.debug("Using selected folder as root: {}", folder.toFile)
+ folder.toFile.toPath()
+ } else if (1 == virtualFiles?.size) {
+ log.debug("Using parent of single file as root")
+ e.getSelectedFile()?.parent?.toNioPath()
+ } else {
+ log.debug("Using module root as root directory")
+ getModuleRootForFile(
+ e.getSelectedFile()?.parent?.toFile ?: throw RuntimeException("No file selected")
+ ).toPath()
+ }
+ )
+ progress.text = "Collecting files..."
+
+ val root = rootRef.get() ?: throw RuntimeException("Root path not set")
+ if (!Files.exists(root)) {
+ throw IOException("Root directory does not exist: $root")
+ }
+ log.info("Using root directory: $root")
+ val files = getFiles(virtualFiles, root)
+ codeFiles.addAll(files)
+ log.debug("Collected ${codeFiles.size} code files")
+ progress.text = "Generating image..."
+ log.info("Starting image generation with ${codeFiles.size} files")
+ val imageActor = ImageGenerationAgent(
+ prompt = """
You are a technical drawing assistant.
You will be composing an image about the following code:
${codeSummary()}
Special instructions: ${dialog.getInstructions()}
""".trimIndent(),
- textModel = AppSettingsState.instance.smartChatClient,
- imageModel = AppSettingsState.instance.imageModel?.model,
- imageClient = AppSettingsState.instance.imageClient
- )
- log.debug("Sending request to image generation API")
- val response = imageActor.answer(listOf(codeSummary(), dialog.getInstructions()))
- log.debug("Image generation completed successfully")
- val imagePath = root.resolve(dialog.getFileName())
- write(response, imagePath)
- VirtualFileManager.getInstance().findFileByNioPath(imagePath)?.refresh(false, false)
- } catch (ex: Throwable) {
- when (ex) {
- is IOException -> log.error("IO error during image creation: ${ex.message}", ex)
- is SecurityException -> log.error("Security error during image creation: ${ex.message}", ex)
- is IllegalArgumentException -> log.error(
- "Invalid argument during image creation: ${ex.message}",
- ex
- )
-
- else -> log.error("Unexpected error during image creation", ex)
+ textModel = AppSettingsState.instance.smartChatClient,
+ imageModel = AppSettingsState.instance.imageModel?.model,
+ imageClient = AppSettingsState.instance.imageClient
+ )
+ log.debug("Sending request to image generation API")
+ val response = imageActor.answer(listOf(codeSummary(), dialog.getInstructions()))
+ log.debug("Image generation completed successfully")
+ val imagePath = root.resolve(dialog.getFileName())
+ write(response, imagePath)
+ VirtualFileManager.getInstance().findFileByNioPath(imagePath)?.refresh(false, false)
+ } catch (ex: Throwable) {
+ when (ex) {
+ is IOException -> log.error("IO error during image creation: ${ex.message}", ex)
+ is SecurityException -> log.error("Security error during image creation: ${ex.message}", ex)
+ is IllegalArgumentException -> log.error(
+ "Invalid argument during image creation: ${ex.message}",
+ ex
+ )
+
+ else -> log.error("Unexpected error during image creation", ex)
+ }
+ UITools.showErrorDialog("Failed to create image: ${ex.message}", "Error")
+ }
+ }
+ }
+
+ private fun write(
+ code: ImageAndText, path: Path
+ ) = try {
+ log.debug("Creating parent directories for: {}", path)
+ path.parent?.toFile()?.mkdirs()
+ val format = path.toString().split(".").last()
+ log.debug("Writing image in format: $format")
+
+ val bytes = ByteArrayOutputStream().use { outputStream ->
+ if (!ImageIO.write(
+ code.image, format, outputStream
+ )
+ ) {
+ throw IOException("Unsupported or invalid image format: $format")
+ }
+ outputStream.toByteArray()
+ }
+ path.toFile().writeBytes(bytes)
+ path
+ } catch (e: Exception) {
+ log.error("Failed to write image to $path", e)
+ when (e) {
+ is IOException -> throw IOException("Failed to write image: ${e.message}", e)
+ is SecurityException -> throw SecurityException("Security error writing image: ${e.message}", e)
+ else -> throw RuntimeException("Unexpected error writing image: ${e.message}", e)
}
- UITools.showErrorDialog("Failed to create image: ${ex.message}", "Error")
- }
}
- }
-
- private fun write(
- code: ImageAndText, path: Path
- ) = try {
- log.debug("Creating parent directories for: {}", path)
- path.parent?.toFile()?.mkdirs()
- val format = path.toString().split(".").last()
- log.debug("Writing image in format: $format")
-
- val bytes = ByteArrayOutputStream().use { outputStream ->
- if (!ImageIO.write(
- code.image, format, outputStream
- )
- ) {
- throw IOException("Unsupported or invalid image format: $format")
- }
- outputStream.toByteArray()
+
+ private fun getFiles(
+ virtualFiles: Array?, root: Path
+ ): MutableSet {
+ val codeFiles = mutableSetOf()
+ virtualFiles?.forEach { file ->
+ if (file.isDirectory) {
+ getFiles(file.children, root)
+ } else {
+ val relative = root.relativize(file.toNioPath())
+ codeFiles.add(relative)
+
+ }
+ }
+ return codeFiles
}
- path.toFile().writeBytes(bytes)
- path
- } catch (e: Exception) {
- log.error("Failed to write image to $path", e)
- when (e) {
- is IOException -> throw IOException("Failed to write image: ${e.message}", e)
- is SecurityException -> throw SecurityException("Security error writing image: ${e.message}", e)
- else -> throw RuntimeException("Unexpected error writing image: ${e.message}", e)
+
+ override fun isEnabled(event: AnActionEvent): Boolean {
+ if (!super.isEnabled(event)) return false
+ event.getSelectedFile() ?: return false
+ AppSettingsState.instance.imageModel ?: return false
+ return true
}
- }
-
- private fun getFiles(
- virtualFiles: Array?, root: Path
- ): MutableSet {
- val codeFiles = mutableSetOf()
- virtualFiles?.forEach { file ->
- if (file.isDirectory) {
- getFiles(file.children, root)
- } else {
- val relative = root.relativize(file.toNioPath())
- codeFiles.add(relative)
-
- }
+
+ companion object {
+ private val log = LoggerFactory.getLogger(CreateImageAction::class.java)
}
- return codeFiles
- }
-
- override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
- event.getSelectedFile() ?: return false
- AppSettingsState.instance.imageModel ?: return false
- return true
- }
-
- companion object {
- private val log = LoggerFactory.getLogger(CreateImageAction::class.java)
- }
}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/generate/GenerateDocumentationAction.kt b/intellij/src/main/kotlin/cognotik/actions/generate/GenerateDocumentationAction.kt
index 9e492d52f..acf05654c 100644
--- a/intellij/src/main/kotlin/cognotik/actions/generate/GenerateDocumentationAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/generate/GenerateDocumentationAction.kt
@@ -37,357 +37,376 @@ import java.util.concurrent.TimeoutException
import javax.swing.*
class GenerateDocumentationAction : FileContextAction() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
-
- class SettingsUI {
- @Name("Single Output File")
- val singleOutputFile = JCheckBox("Produce a single output file", true)
-
- @Name("Files to Process")
- val filesToProcess = CheckBoxList()
-
- @Name("AI Instruction")
- val transformationMessage = JBTextArea(4, 40)
-
- @Name("Recent Instructions")
- val recentInstructions = JComboBox()
-
- @Name("Output File")
- val outputFilename = JBTextField()
-
- @Name("Output Directory")
- val outputDirectory = JBTextField()
- }
-
- class UserSettings(
- var transformationMessage: String = "Create user documentation",
- var outputFilename: String = "compiled_documentation.md",
- var filesToProcess: List = listOf(),
- var singleOutputFile: Boolean = true,
- var outputDirectory: String = "docs/"
- )
-
- class Settings(
- val settings: UserSettings? = null,
- val project: Project? = null,
- val root: Path? = null
- )
-
- override fun getConfig(project: Project?, e: AnActionEvent): Settings? {
- var root = e.getSelectedFolder()?.toNioPath()
- val files = if (root == null) {
- e.getSelectedFiles().map { it.toNioPath() }.toTypedArray()
- } else {
- Files.walk(root).filter { Files.isRegularFile(it) && !Files.isDirectory(it) }.toList().filterNotNull().sortedBy { it.toString() }.toTypedArray()
- }
- val settingsUI = SettingsUI().apply {
- filesToProcess.setItems(files.toMutableList()) { path ->
- root?.relativize(path)?.toString() ?: path.toString()
- }
- files.forEach { path ->
- filesToProcess.setItemSelected(path, true)
- }
- outputDirectory.text = "docs/"
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+
+ class SettingsUI {
+ @Name("Single Output File")
+ val singleOutputFile = JCheckBox("Produce a single output file", true)
+
+ @Name("Files to Process")
+ val filesToProcess = CheckBoxList()
+
+ @Name("AI Instruction")
+ val transformationMessage = JBTextArea(4, 40)
+
+ @Name("Recent Instructions")
+ val recentInstructions = JComboBox()
+
+ @Name("Output File")
+ val outputFilename = JBTextField()
+
+ @Name("Output Directory")
+ val outputDirectory = JBTextField()
}
- val mruDocumentationInstructions = AppSettingsState.instance.getRecentCommands("DocumentationInstructions")
- settingsUI.recentInstructions.model = DefaultComboBoxModel(
- mruDocumentationInstructions.getMostRecent(10).map {
- "${it.split(" ").first()} ${it.split(" ").drop(1).joinToString(" ")}"
- }.toTypedArray()
+
+ class UserSettings(
+ var transformationMessage: String = "Create user documentation",
+ var outputFilename: String = "compiled_documentation.md",
+ var filesToProcess: List = listOf(),
+ var singleOutputFile: Boolean = true,
+ var outputDirectory: String = "docs/"
)
- settingsUI.recentInstructions.selectedIndex = -1
- settingsUI.recentInstructions.addActionListener { updateUIFromSelection(settingsUI) }
- val dialog = DocumentationCompilerDialog(project, settingsUI)
- dialog.show()
- val settings: UserSettings = dialog.userSettings
- settings.singleOutputFile = settingsUI.singleOutputFile.isSelected
- settings.outputDirectory = settingsUI.outputDirectory.text
- val result = dialog.isOK
- settings.filesToProcess = when {
- result -> files.filter { path -> settingsUI.filesToProcess.isItemSelected(path) }.sortedBy { it.toString() }.toList()
-
- else -> listOf()
- }
- if (settings.filesToProcess.isEmpty()) return null
- mruDocumentationInstructions.addInstructionToHistory("${settings.outputFilename} ${settings.transformationMessage}")
-
- return Settings(settings, project, root)
- }
-
- private fun updateUIFromSelection(settingsUI: SettingsUI) {
- val selected = settingsUI.recentInstructions.selectedItem as? String
- if (selected != null) {
- val parts = selected.split(" ", limit = 2)
- if (parts.size == 2) {
- settingsUI.outputFilename.text = parts[0]
- settingsUI.transformationMessage.text = parts[1]
- } else {
- settingsUI.transformationMessage.text = selected
- }
- } else {
- settingsUI.transformationMessage.text = ""
- }
- }
- override fun processSelection(state: SelectionState, config: Settings?, progress: ProgressIndicator): Array {
- progress.fraction = 0.0
- if (config?.settings == null) {
+ class Settings(
+ val settings: UserSettings? = null,
+ val project: Project? = null,
+ val root: Path? = null
+ )
- return emptyArray().also {
+ override fun getConfig(project: Project?, e: AnActionEvent): Settings? {
+ var root = e.getSelectedFolder()?.toNioPath()
+ val files = if (root == null) {
+ e.getSelectedFiles().map { it.toNioPath() }.toTypedArray()
+ } else {
+ Files.walk(root).filter { Files.isRegularFile(it) && !Files.isDirectory(it) }.toList().filterNotNull()
+ .sortedBy { it.toString() }.toTypedArray()
+ }
+ val settingsUI = SettingsUI().apply {
+ filesToProcess.setItems(files.toMutableList()) { path ->
+ root?.relativize(path)?.toString() ?: path.toString()
+ }
+ files.forEach { path ->
+ filesToProcess.setItemSelected(path, true)
+ }
+ outputDirectory.text = "docs/"
+ }
+ val mruDocumentationInstructions = AppSettingsState.instance.getRecentCommands("DocumentationInstructions")
+ settingsUI.recentInstructions.model = DefaultComboBoxModel(
+ mruDocumentationInstructions.getMostRecent(10).map {
+ "${it.split(" ").first()} ${it.split(" ").drop(1).joinToString(" ")}"
+ }.toTypedArray()
+ )
+ settingsUI.recentInstructions.selectedIndex = -1
+ settingsUI.recentInstructions.addActionListener { updateUIFromSelection(settingsUI) }
+ val dialog = DocumentationCompilerDialog(project, settingsUI)
+ dialog.show()
+ val settings: UserSettings = dialog.userSettings
+ settings.singleOutputFile = settingsUI.singleOutputFile.isSelected
+ settings.outputDirectory = settingsUI.outputDirectory.text
+ val result = dialog.isOK
+ settings.filesToProcess = when {
+ result -> files.filter { path -> settingsUI.filesToProcess.isItemSelected(path) }.sortedBy { it.toString() }
+ .toList()
+
+ else -> listOf()
+ }
+ if (settings.filesToProcess.isEmpty()) return null
+ mruDocumentationInstructions.addInstructionToHistory("${settings.outputFilename} ${settings.transformationMessage}")
- return@also
- }
+ return Settings(settings, project, root)
}
- progress.text = "Initializing documentation generation..."
-
- val projectRoot = state.projectRoot.toPath()
- val outputDirectory = config.settings.outputDirectory
- var outputPath = (config.root ?: state.projectRoot.toPath()).resolve(config.settings.outputFilename)
- val relativePath = (config.root ?: state.projectRoot.toPath())?.relativize(outputPath) ?: outputPath
- outputPath = state.projectRoot.toPath().resolve(outputDirectory).resolve(relativePath)
-
- if (outputPath.toFile().exists()) {
- val extension = outputPath.toString().split(".").last()
- val name = outputPath.toString().split(".").dropLast(1).joinToString(".")
- val fileIndex = (1..Int.MAX_VALUE).find {
- !projectRoot.resolve("$name.$it.$extension").toFile().exists()
- }
- outputPath = projectRoot.resolve("$name.$fileIndex.$extension") ?: outputPath
+
+ private fun updateUIFromSelection(settingsUI: SettingsUI) {
+ val selected = settingsUI.recentInstructions.selectedItem as? String
+ if (selected != null) {
+ val parts = selected.split(" ", limit = 2)
+ if (parts.size == 2) {
+ settingsUI.outputFilename.text = parts[0]
+ settingsUI.transformationMessage.text = parts[1]
+ } else {
+ settingsUI.transformationMessage.text = selected
+ }
+ } else {
+ settingsUI.transformationMessage.text = ""
+ }
}
- val executorService = Executors.newFixedThreadPool(4)
- val transformationMessage = config.settings.transformationMessage
- val markdownContent = TreeMap()
- try {
- val selectedPaths = config.settings.filesToProcess.sortedBy { it.toString() }
- val partitionedPaths = if(null != projectRoot) Files.walk(projectRoot).filter { Files.isRegularFile(it) && !Files.isDirectory(it) }.toList().sortedBy { it.toString() }
- .groupBy { selectedPaths.contains(it) } else selectedPaths.groupBy { true }
- val totalFiles = partitionedPaths[true]?.size ?: 0
- var processedFiles = 0
- val pathList = partitionedPaths[true]?.toList()?.filterNotNull()?.map> { path ->
- executorService.submit {
- var retries = 0
- val maxRetries = 3
- while (retries < maxRetries) {
- try {
- val fileContent = IOUtils.toString(FileInputStream(path.toFile()), "UTF-8") ?: return@submit null
- val transformContent = transformContent(
- path, fileContent, transformationMessage, AppSettingsState.instance.smartChatClient, projectRoot
- )
- processTransformedContent(
- path, transformContent, config, projectRoot, outputDirectory, outputPath, markdownContent
- )
- synchronized(progress) {
- processedFiles++
- progress.fraction = processedFiles.toDouble() / totalFiles
- progress.text = "Processing file ${processedFiles} of ${totalFiles}"
- }
- return@submit path
- } catch (e: Exception) {
- retries++
- if (retries >= maxRetries) {
- log.error("Failed to process file after $maxRetries attempts: $path", e)
- return@submit null
- }
- log.warn("Error processing file: $path. Retrying (attempt $retries)", e)
- Thread.sleep(1000L * retries)
+ override fun processSelection(state: SelectionState, config: Settings?, progress: ProgressIndicator): Array {
+ progress.fraction = 0.0
+ if (config?.settings == null) {
+
+ return emptyArray().also {
+
+ return@also
}
- }
- null
}
- }?.toTypedArray()?.mapNotNull { future ->
- try {
- future.get(2, TimeUnit.MINUTES)
-
- } catch (e: Exception) {
- when (e) {
- is TimeoutException -> log.error("File processing timed out", e)
- else -> log.error("Error processing file", e)
- }
- null
+ progress.text = "Initializing documentation generation..."
+
+ val projectRoot = state.projectRoot.toPath()
+ val outputDirectory = config.settings.outputDirectory
+ var outputPath = (config.root ?: state.projectRoot.toPath()).resolve(config.settings.outputFilename)
+ val relativePath = (config.root ?: state.projectRoot.toPath())?.relativize(outputPath) ?: outputPath
+ outputPath = state.projectRoot.toPath().resolve(outputDirectory).resolve(relativePath)
+
+ if (outputPath.toFile().exists()) {
+ val extension = outputPath.toString().split(".").last()
+ val name = outputPath.toString().split(".").dropLast(1).joinToString(".")
+ val fileIndex = (1..Int.MAX_VALUE).find {
+ !projectRoot.resolve("$name.$it.$extension").toFile().exists()
+ }
+ outputPath = projectRoot.resolve("$name.$fileIndex.$extension") ?: outputPath
}
- } ?: listOf()
- if (config.settings.singleOutputFile == true) {
- val sortedContent = markdownContent.entries.joinToString("\n\n") { (path, content) ->
- "# $path\n\n$content"
+
+ val executorService = Executors.newFixedThreadPool(4)
+ val transformationMessage = config.settings.transformationMessage
+ val markdownContent = TreeMap()
+ try {
+ val selectedPaths = config.settings.filesToProcess.sortedBy { it.toString() }
+ val partitionedPaths = if (null != projectRoot) Files.walk(projectRoot)
+ .filter { Files.isRegularFile(it) && !Files.isDirectory(it) }.toList().sortedBy { it.toString() }
+ .groupBy { selectedPaths.contains(it) } else selectedPaths.groupBy { true }
+ val totalFiles = partitionedPaths[true]?.size ?: 0
+ var processedFiles = 0
+ val pathList = partitionedPaths[true]?.toList()?.filterNotNull()?.map> { path ->
+ executorService.submit {
+ var retries = 0
+ val maxRetries = 3
+ while (retries < maxRetries) {
+ try {
+ val fileContent =
+ IOUtils.toString(FileInputStream(path.toFile()), "UTF-8") ?: return@submit null
+ val transformContent = transformContent(
+ path,
+ fileContent,
+ transformationMessage,
+ AppSettingsState.instance.smartChatClient,
+ projectRoot
+ )
+ processTransformedContent(
+ path,
+ transformContent,
+ config,
+ projectRoot,
+ outputDirectory,
+ outputPath,
+ markdownContent
+ )
+ synchronized(progress) {
+ processedFiles++
+ progress.fraction = processedFiles.toDouble() / totalFiles
+ progress.text = "Processing file ${processedFiles} of ${totalFiles}"
+ }
+ return@submit path
+ } catch (e: Exception) {
+ retries++
+ if (retries >= maxRetries) {
+ log.error("Failed to process file after $maxRetries attempts: $path", e)
+ return@submit null
+ }
+ log.warn("Error processing file: $path. Retrying (attempt $retries)", e)
+ Thread.sleep(1000L * retries)
+ }
+ }
+ null
+ }
+ }?.toTypedArray()?.mapNotNull { future ->
+ try {
+ future.get(2, TimeUnit.MINUTES)
+
+ } catch (e: Exception) {
+ when (e) {
+ is TimeoutException -> log.error("File processing timed out", e)
+ else -> log.error("Error processing file", e)
+ }
+ null
+ }
+ } ?: listOf()
+ if (config.settings.singleOutputFile == true) {
+ val sortedContent = markdownContent.entries.joinToString("\n\n") { (path, content) ->
+ "# $path\n\n$content"
+ }
+ outputPath.parent.toFile().mkdirs()
+ Files.write(outputPath, sortedContent.toByteArray())
+ open(config.project!!, outputPath)
+ return arrayOf(outputPath.toFile())
+ } else {
+ val outputDir = projectRoot.resolve(outputDirectory) ?: File(outputDirectory).toPath()
+ outputDir.toFile().mkdirs()
+ open(config.project!!, projectRoot.resolve(outputDirectory) ?: outputDir)
+ return pathList.map { it.toFile() }.toTypedArray()
+ }
+ } finally {
+ executorService.shutdown()
}
- outputPath.parent.toFile().mkdirs()
- Files.write(outputPath, sortedContent.toByteArray())
- open(config.project!!, outputPath)
- return arrayOf(outputPath.toFile())
- } else {
- val outputDir = projectRoot.resolve(outputDirectory) ?: File(outputDirectory).toPath()
- outputDir.toFile().mkdirs()
- open(config.project!!, projectRoot.resolve(outputDirectory) ?: outputDir)
- return pathList.map { it.toFile() }.toTypedArray()
- }
- } finally {
- executorService.shutdown()
}
- }
-
- private fun processTransformedContent(
- path: Path,
- transformContent: String,
- config: Settings?,
- projectRoot: Path,
- outputDirectory: String,
- outputPath: Path,
- markdownContent: TreeMap
- ) {
- if (config?.settings?.singleOutputFile == true) {
- markdownContent[projectRoot.relativize(path).toString()] = transformContent.replace("(?s)(?
+ ) {
+ if (config?.settings?.singleOutputFile == true) {
+ markdownContent[projectRoot.relativize(path).toString()] =
+ transformContent.replace("(?s)(? Unit
- function = {
- val file = outputPath.toFile()
- if (file.exists()) {
-
- ApplicationManager.getApplication().invokeLater {
- val ioFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file)
- if (false == (ioFile?.let { FileEditorManager.getInstance(project).isFileOpen(it) })) {
- val localFileSystem = LocalFileSystem.getInstance()
-
- val virtualFile = localFileSystem.refreshAndFindFileByIoFile(file)
- virtualFile?.let {
- FileEditorManager.getInstance(project).openFile(it, true)
- } ?: scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS)
- } else {
- scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS)
- }
- }
- } else {
- scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS)
- }
+ ),
+ ModelSchema.ChatMessage(
+ ModelSchema.Role.user,
+ "## Project:\n${getProjectStructure(projectRoot)}\n\n## $path:\n```\n$fileContent\n```\n\nInstructions: $transformationMessage".toContentList()
+ ),
+ )
+ ).choices.first().message?.content?.trim()
+ } ?: fileContent
+
+ companion object {
+ private val scheduledPool = Executors.newScheduledThreadPool(1)
}
- scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS)
- }
- inner class DocumentationCompilerDialog(project: Project?, private val settingsUI: SettingsUI) : DialogWrapper(project) {
- val userSettings = UserSettings()
-
- init {
- title = "Compile Documentation"
-
- settingsUI.transformationMessage.text = userSettings.transformationMessage
- settingsUI.outputFilename.text = userSettings.outputFilename
- settingsUI.outputDirectory.text = userSettings.outputDirectory
- settingsUI.singleOutputFile.isSelected = userSettings.singleOutputFile
- settingsUI.recentInstructions.addActionListener {
- val selected = settingsUI.recentInstructions.selectedItem as? String
- selected?.let {
- updateUIFromSelection(settingsUI)
+ fun open(project: Project, outputPath: Path) {
+ lateinit var function: () -> Unit
+ function = {
+ val file = outputPath.toFile()
+ if (file.exists()) {
+
+ ApplicationManager.getApplication().invokeLater {
+ val ioFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file)
+ if (false == (ioFile?.let { FileEditorManager.getInstance(project).isFileOpen(it) })) {
+ val localFileSystem = LocalFileSystem.getInstance()
+
+ val virtualFile = localFileSystem.refreshAndFindFileByIoFile(file)
+ virtualFile?.let {
+ FileEditorManager.getInstance(project).openFile(it, true)
+ } ?: scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS)
+ } else {
+ scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS)
+ }
+ }
+ } else {
+ scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS)
+ }
}
- }
- init()
+ scheduledPool.schedule(function, 100, TimeUnit.MILLISECONDS)
}
- override fun createCenterPanel(): JComponent {
- val panel = JPanel(BorderLayout()).apply {
- val filesScrollPane = JBScrollPane(settingsUI.filesToProcess).apply {
- preferredSize = Dimension(600, 400)
-
+ inner class DocumentationCompilerDialog(project: Project?, private val settingsUI: SettingsUI) :
+ DialogWrapper(project) {
+ val userSettings = UserSettings()
+
+ init {
+ title = "Compile Documentation"
+
+ settingsUI.transformationMessage.text = userSettings.transformationMessage
+ settingsUI.outputFilename.text = userSettings.outputFilename
+ settingsUI.outputDirectory.text = userSettings.outputDirectory
+ settingsUI.singleOutputFile.isSelected = userSettings.singleOutputFile
+ settingsUI.recentInstructions.addActionListener {
+ val selected = settingsUI.recentInstructions.selectedItem as? String
+ selected?.let {
+ updateUIFromSelection(settingsUI)
+ }
+ }
+ init()
}
- add(filesScrollPane, BorderLayout.CENTER)
-
-
- val optionsPanel = JPanel().apply {
- layout = BoxLayout(this, BoxLayout.Y_AXIS)
- border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
-
- add(JLabel("Recent Instructions"))
- add(settingsUI.recentInstructions)
- add(Box.createVerticalStrut(10))
- add(JLabel("AI Instruction"))
- add(settingsUI.transformationMessage)
- add(Box.createVerticalStrut(10))
- add(Box.createVerticalStrut(10))
-
- add(JLabel("Output File"))
- add(settingsUI.outputFilename)
- add(Box.createVerticalStrut(10))
- add(JLabel("Output Directory"))
- add(settingsUI.outputDirectory)
- add(Box.createVerticalStrut(10))
- add(settingsUI.singleOutputFile)
+
+ override fun createCenterPanel(): JComponent {
+ val panel = JPanel(BorderLayout()).apply {
+ val filesScrollPane = JBScrollPane(settingsUI.filesToProcess).apply {
+ preferredSize = Dimension(600, 400)
+
+ }
+ add(filesScrollPane, BorderLayout.CENTER)
+
+
+ val optionsPanel = JPanel().apply {
+ layout = BoxLayout(this, BoxLayout.Y_AXIS)
+ border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
+
+ add(JLabel("Recent Instructions"))
+ add(settingsUI.recentInstructions)
+ add(Box.createVerticalStrut(10))
+ add(JLabel("AI Instruction"))
+ add(settingsUI.transformationMessage)
+ add(Box.createVerticalStrut(10))
+ add(Box.createVerticalStrut(10))
+
+ add(JLabel("Output File"))
+ add(settingsUI.outputFilename)
+ add(Box.createVerticalStrut(10))
+ add(JLabel("Output Directory"))
+ add(settingsUI.outputDirectory)
+ add(Box.createVerticalStrut(10))
+ add(settingsUI.singleOutputFile)
+ }
+ add(optionsPanel, BorderLayout.SOUTH)
+ }
+ return panel
}
- add(optionsPanel, BorderLayout.SOUTH)
- }
- return panel
- }
- override fun doOKAction() {
- if (!validateInput()) {
- return
- }
- super.doOKAction()
- userSettings.transformationMessage = settingsUI.transformationMessage.text
- userSettings.outputFilename = settingsUI.outputFilename.text
- userSettings.outputDirectory = settingsUI.outputDirectory.text
+ override fun doOKAction() {
+ if (!validateInput()) {
+ return
+ }
+ super.doOKAction()
+ userSettings.transformationMessage = settingsUI.transformationMessage.text
+ userSettings.outputFilename = settingsUI.outputFilename.text
+ userSettings.outputDirectory = settingsUI.outputDirectory.text
- userSettings.filesToProcess = settingsUI.filesToProcess.items.filter { path -> settingsUI.filesToProcess.isItemSelected(path) }
- userSettings.singleOutputFile = settingsUI.singleOutputFile.isSelected
- }
+ userSettings.filesToProcess =
+ settingsUI.filesToProcess.items.filter { path -> settingsUI.filesToProcess.isItemSelected(path) }
+ userSettings.singleOutputFile = settingsUI.singleOutputFile.isSelected
+ }
- private fun validateInput(): Boolean {
- if (settingsUI.transformationMessage.text.isBlank()) {
- Messages.showErrorDialog("AI Instruction cannot be empty", "Input Error")
- return false
- }
- if (settingsUI.outputFilename.text.isBlank()) {
- Messages.showErrorDialog("Output File cannot be empty", "Input Error")
- return false
- }
- if (settingsUI.outputDirectory.text.isBlank()) {
- Messages.showErrorDialog("Output Directory cannot be empty", "Input Error")
- return false
- }
- return true
+ private fun validateInput(): Boolean {
+ if (settingsUI.transformationMessage.text.isBlank()) {
+ Messages.showErrorDialog("AI Instruction cannot be empty", "Input Error")
+ return false
+ }
+ if (settingsUI.outputFilename.text.isBlank()) {
+ Messages.showErrorDialog("Output File cannot be empty", "Input Error")
+ return false
+ }
+ if (settingsUI.outputDirectory.text.isBlank()) {
+ Messages.showErrorDialog("Output Directory cannot be empty", "Input Error")
+ return false
+ }
+ return true
+ }
}
- }
}
val CheckBoxList.items: List
- get() {
- val items = mutableListOf()
- for (i in 0 until model.size) {
- items.add(getItemAt(i)!!)
- }
- return items
- }
\ No newline at end of file
+ get() {
+ val items = mutableListOf()
+ for (i in 0 until model.size) {
+ items.add(getItemAt(i)!!)
+ }
+ return items
+ }
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/generate/OCRAction.kt b/intellij/src/main/kotlin/cognotik/actions/generate/OCRAction.kt
index 21880d6bd..e16a8776d 100644
--- a/intellij/src/main/kotlin/cognotik/actions/generate/OCRAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/generate/OCRAction.kt
@@ -13,7 +13,6 @@ import com.simiacryptus.cognotik.input.getDocumentReader
import com.simiacryptus.cognotik.models.ModelSchema
import com.simiacryptus.cognotik.util.UITools
import java.io.File
-import javax.imageio.ImageIO
class OCRAction : BaseAction() {
override fun getActionUpdateThread() = ActionUpdateThread.BGT
@@ -48,7 +47,9 @@ class OCRAction : BaseAction() {
),
ModelSchema.ChatMessage(
ModelSchema.Role.user,
- listOf(ModelSchema.ContentPart("Convert this page").apply { this.image = image })
+ listOf(
+ ModelSchema.ContentPart("Convert this page")
+ .apply { this.image = image })
)
)
).choices.first().message?.content ?: ""
diff --git a/intellij/src/main/kotlin/cognotik/actions/plan/CognitiveConfigDialog.kt b/intellij/src/main/kotlin/cognotik/actions/plan/CognitiveConfigDialog.kt
index 64de1fd1f..12f3a8c4a 100644
--- a/intellij/src/main/kotlin/cognotik/actions/plan/CognitiveConfigDialog.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/plan/CognitiveConfigDialog.kt
@@ -150,13 +150,17 @@ class CognitiveConfigDialog(
else -> text.ifEmpty { null }
}
}
+
is JBTextArea -> component.text.trim()
is ComboBox<*> -> {
val selected = component.selectedItem as? String
val paramClass = prop.returnType.classifier as? KClass<*>
if (selected != null && paramClass?.java?.isEnum == true) {
paramClass.java.enumConstants.find { it.toString() == selected }
- } else if (selected != null && paramClass != null && DynamicEnum::class.java.isAssignableFrom(paramClass.java)) {
+ } else if (selected != null && paramClass != null && DynamicEnum::class.java.isAssignableFrom(
+ paramClass.java
+ )
+ ) {
val companion = paramClass.java.getDeclaredField("Companion").get(null)
val valueOfMethod = companion.javaClass.getMethod("valueOf", String::class.java)
try {
@@ -168,9 +172,10 @@ class CognitiveConfigDialog(
null
}
}
+
else -> null
}
-
+
if (prop is KMutableProperty<*>) {
try {
prop.setter.call(config, value)
diff --git a/intellij/src/main/kotlin/cognotik/actions/plan/PlanConfigDialog.kt b/intellij/src/main/kotlin/cognotik/actions/plan/PlanConfigDialog.kt
index 6edbc31b2..9592ac086 100644
--- a/intellij/src/main/kotlin/cognotik/actions/plan/PlanConfigDialog.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/plan/PlanConfigDialog.kt
@@ -14,7 +14,6 @@ import com.simiacryptus.cognotik.models.AIModel
import com.simiacryptus.cognotik.plan.OrchestrationConfig
import com.simiacryptus.cognotik.plan.TaskType
import com.simiacryptus.cognotik.plan.TaskTypeConfig
-import com.simiacryptus.cognotik.plan.cognitive.CognitiveMode
import com.simiacryptus.cognotik.plan.cognitive.CognitiveModeConfig
import com.simiacryptus.cognotik.plan.cognitive.CognitiveModeType
import com.simiacryptus.cognotik.plan.newSettings
@@ -109,7 +108,8 @@ class PlanConfigDialog(
ComboBox(visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()).apply {
maximumSize = Dimension(CONFIG_COMBO_WIDTH, CONFIG_COMBO_HEIGHT)
selectedItem =
- settings.defaultImageModel?.model?.modelName ?: AppSettingsState.instance.imageChatModel?.model?.modelName
+ settings.defaultImageModel?.model?.modelName
+ ?: AppSettingsState.instance.imageChatModel?.model?.modelName
toolTipText = "Multimodal AI model for image-related tasks"
}
diff --git a/intellij/src/main/kotlin/cognotik/actions/plan/TaskConfigDialog.kt b/intellij/src/main/kotlin/cognotik/actions/plan/TaskConfigDialog.kt
index 805312d29..5921308fe 100644
--- a/intellij/src/main/kotlin/cognotik/actions/plan/TaskConfigDialog.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/plan/TaskConfigDialog.kt
@@ -66,6 +66,7 @@ class TaskConfigDialog(
title = "Edit ${taskType.name} Configuration"
isResizable = true
}
+
override fun getDimensionServiceKey(): String = "TaskConfigEditDialog"
@@ -365,7 +366,6 @@ class TaskConfigDialog(
}
-
override fun doOKAction() {
val name = configNameField.text.trim()
@@ -692,7 +692,6 @@ class TaskConfigDialog(
val selectedModel = availableModels.find { it.modelName == selectedModelName }
val subPlanConfig = config as SubPlanTask.SubPlanTaskTypeConfig
return SubPlanTask.SubPlanTaskTypeConfig(
- task_type = config.task_type!!,
name = configNameField.text.trim(),
model = selectedModel?.toApiChatModel(),
purpose = (configFields["purpose"] as? JBTextArea)?.text?.trim() ?: "",
diff --git a/intellij/src/main/kotlin/cognotik/actions/plan/TaskTypeSelectionDialog.kt b/intellij/src/main/kotlin/cognotik/actions/plan/TaskTypeSelectionDialog.kt
index 5ae96cfce..8cb262a84 100644
--- a/intellij/src/main/kotlin/cognotik/actions/plan/TaskTypeSelectionDialog.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/plan/TaskTypeSelectionDialog.kt
@@ -2,6 +2,8 @@ package cognotik.actions.plan
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
+import com.intellij.ui.DocumentAdapter
+import com.intellij.ui.SearchTextField
import com.intellij.ui.components.JBScrollPane
import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.panel
@@ -10,6 +12,7 @@ import java.awt.Dimension
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.*
+import javax.swing.event.DocumentEvent
import javax.swing.event.TreeSelectionEvent
import javax.swing.event.TreeSelectionListener
import javax.swing.tree.*
@@ -20,6 +23,7 @@ class TaskTypeSelectionDialog(
) : DialogWrapper(project) {
private val selectedTaskTypes = mutableSetOf>()
+ private val searchField = SearchTextField(false)
private val descriptionPane = JEditorPane().apply {
contentType = "text/html"
isEditable = false
@@ -39,20 +43,8 @@ class TaskTypeSelectionDialog(
val root = DefaultMutableTreeNode("Task Types")
val treeModel = DefaultTreeModel(root)
- // Group task types by package
- val tasksByPackage = TaskType.values()
- .groupBy { it.category }
- .toSortedMap()
- tasksByPackage.forEach { (packageName, tasks) ->
- val packageNode = DefaultMutableTreeNode(packageName)
- root.add(packageNode)
- tasks.sortedBy { it.name }.forEach { taskType ->
- val taskNode = DefaultMutableTreeNode(TaskTypeNode(taskType))
- packageNode.add(taskNode)
- }
- }
taskTree = JTree(treeModel).apply {
selectionModel.selectionMode = if (allowMultipleSelection) {
@@ -147,15 +139,46 @@ class TaskTypeSelectionDialog(
})
}
- // Expand all package nodes by default
- for (i in 0 until root.childCount) {
- taskTree.expandPath(TreePath(arrayOf(root, root.getChildAt(i))))
- }
+ searchField.addDocumentListener(object : DocumentAdapter() {
+ override fun textChanged(e: DocumentEvent) {
+ updateTreeModel(searchField.text)
+ }
+ })
+
+ updateTreeModel("")
init()
title = if (allowMultipleSelection) "Select Task Types" else "Select Task Type"
}
+
override fun getDimensionServiceKey(): String = "TaskTypeSelectionDialog"
+ private fun updateTreeModel(filter: String) {
+ val root = DefaultMutableTreeNode("Task Types")
+ val filterText = filter.trim().lowercase()
+ val tasksByPackage = TaskType.values()
+ .filter {
+ if (filterText.isEmpty()) true
+ else it.name.lowercase().contains(filterText) ||
+ (it.description?.lowercase()?.contains(filterText) == true) ||
+ it.category.lowercase().contains(filterText)
+ }
+ .groupBy { it.category }
+ .toSortedMap()
+ tasksByPackage.forEach { (packageName, tasks) ->
+ val packageNode = DefaultMutableTreeNode(packageName)
+ root.add(packageNode)
+ tasks.sortedBy { it.name }.forEach { taskType ->
+ val taskNode = DefaultMutableTreeNode(TaskTypeNode(taskType))
+ packageNode.add(taskNode)
+ }
+ }
+ val model = DefaultTreeModel(root)
+ taskTree.model = model
+ // Expand all package nodes
+ for (i in 0 until root.childCount) {
+ taskTree.expandPath(TreePath(arrayOf(root, root.getChildAt(i))))
+ }
+ }
private fun updateDescription(taskTypes: List>) {
@@ -220,6 +243,9 @@ class TaskTypeSelectionDialog(
}
override fun createCenterPanel(): JComponent = panel {
+ row {
+ cell(searchField).align(Align.FILL)
+ }
row {
cell(
JSplitPane(
diff --git a/intellij/src/main/kotlin/cognotik/actions/plan/UnifiedPlanAction.kt b/intellij/src/main/kotlin/cognotik/actions/plan/UnifiedPlanAction.kt
index 959e48d1b..a89427cc8 100644
--- a/intellij/src/main/kotlin/cognotik/actions/plan/UnifiedPlanAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/plan/UnifiedPlanAction.kt
@@ -1,28 +1,28 @@
package cognotik.actions.plan
- import cognotik.actions.BaseAction
- import cognotik.actions.agent.toFile
- import com.intellij.openapi.actionSystem.ActionUpdateThread
- import com.intellij.openapi.actionSystem.AnActionEvent
- import com.intellij.openapi.progress.ProgressIndicator
- import com.intellij.openapi.project.Project
- import com.simiacryptus.cognotik.CognotikAppServer
- import com.simiacryptus.cognotik.apps.general.UnifiedPlanApp
- import com.simiacryptus.cognotik.config.AppSettingsState
- import com.simiacryptus.cognotik.config.instance
- import com.simiacryptus.cognotik.plan.OrchestrationConfig
- import com.simiacryptus.cognotik.plan.cognitive.CognitiveModeType
- import com.simiacryptus.cognotik.platform.Session
- import com.simiacryptus.cognotik.platform.file.DataStorage
- import com.simiacryptus.cognotik.platform.file.UserSettingsManager
- import com.simiacryptus.cognotik.platform.model.ApiChatModel
- import com.simiacryptus.cognotik.util.*
- import com.simiacryptus.cognotik.util.BrowseUtil.browse
- import com.simiacryptus.cognotik.webui.application.AppInfoData
- import com.simiacryptus.cognotik.webui.application.ApplicationServer
- import java.io.File
- import java.text.SimpleDateFormat
- import java.util.*
+import cognotik.actions.BaseAction
+import cognotik.actions.agent.toFile
+import com.intellij.openapi.actionSystem.ActionUpdateThread
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.openapi.progress.ProgressIndicator
+import com.intellij.openapi.project.Project
+import com.simiacryptus.cognotik.CognotikAppServer
+import com.simiacryptus.cognotik.apps.general.UnifiedPlanApp
+import com.simiacryptus.cognotik.config.AppSettingsState
+import com.simiacryptus.cognotik.config.instance
+import com.simiacryptus.cognotik.plan.OrchestrationConfig
+import com.simiacryptus.cognotik.plan.cognitive.CognitiveModeType
+import com.simiacryptus.cognotik.platform.Session
+import com.simiacryptus.cognotik.platform.file.DataStorage
+import com.simiacryptus.cognotik.platform.file.UserSettingsManager
+import com.simiacryptus.cognotik.platform.model.ApiChatModel
+import com.simiacryptus.cognotik.util.*
+import com.simiacryptus.cognotik.util.BrowseUtil.browse
+import com.simiacryptus.cognotik.webui.application.AppInfoData
+import com.simiacryptus.cognotik.webui.application.ApplicationServer
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.*
open class UnifiedPlanAction(
private val useProjectRoot: Boolean = true
@@ -31,12 +31,13 @@ open class UnifiedPlanAction(
override fun getActionUpdateThread() = ActionUpdateThread.BGT
override fun handle(e: AnActionEvent) {
- val root: File = if (useProjectRoot) {
- getProjectRoot(e) ?: createTemporaryDirectory(e.project)
- } else {
- createTemporaryDirectory(e.project)
- }
- OrchestrationConfig.instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
+ val root: File = if (useProjectRoot) {
+ getProjectRoot(e) ?: createTemporaryDirectory(e.project)
+ } else {
+ createTemporaryDirectory(e.project)
+ }
+ OrchestrationConfig.instanceFn =
+ { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
val dialog = PlanConfigDialog(
e.project,
OrchestrationConfig(
@@ -50,7 +51,7 @@ open class UnifiedPlanAction(
),
temperature = AppSettingsState.instance.temperature.coerceIn(0.0, 1.0),
env = mapOf(),
- workingDir = root.absolutePath,
+ workingDir = root.absolutePath,
),
)
@@ -74,7 +75,7 @@ open class UnifiedPlanAction(
) {
progress.text = "Setting up session..."
val session = Session.newGlobalID()
- val root = File(orchestrationConfig.workingDir)
+ val root = File(orchestrationConfig.workingDir)
progress.text = "Processing files..."
setupChatSession(
session,
@@ -102,28 +103,30 @@ open class UnifiedPlanAction(
getModuleRootForFile(file)
}
}
- private fun createTemporaryDirectory(project: Project?): File {
- val timestamp = SimpleDateFormat("yyyyMMddHHmmss").format(Date())
- val scratchesDir = getScratchesDirectory()
- val tempDir = File(scratchesDir, "cognotik/$timestamp")
- tempDir.mkdirs()
- log.info("Created temporary directory: ${tempDir.absolutePath}")
- return tempDir
- }
- private fun getScratchesDirectory(): File {
- val useSystemPath = AppSettingsState.instance.useScratchesSystemPath
- val basePath = if (useSystemPath) {
- System.getProperty("idea.system.path")
- } else {
- System.getProperty("idea.config.path")
+
+ private fun createTemporaryDirectory(project: Project?): File {
+ val timestamp = SimpleDateFormat("yyyyMMddHHmmss").format(Date())
+ val scratchesDir = getScratchesDirectory()
+ val tempDir = File(scratchesDir, "cognotik/$timestamp")
+ tempDir.mkdirs()
+ log.info("Created temporary directory: ${tempDir.absolutePath}")
+ return tempDir
}
- return if (basePath != null) {
- File(basePath, "scratches")
- } else {
- // Fallback to user home if properties are not set
- File(System.getProperty("user.home"), ".cognotik/scratches")
+
+ private fun getScratchesDirectory(): File {
+ val useSystemPath = AppSettingsState.instance.useScratchesSystemPath
+ val basePath = if (useSystemPath) {
+ System.getProperty("idea.system.path")
+ } else {
+ System.getProperty("idea.config.path")
+ }
+ return if (basePath != null) {
+ File(basePath, "scratches")
+ } else {
+ // Fallback to user home if properties are not set
+ File(System.getProperty("user.home"), ".cognotik/scratches")
+ }
}
- }
private fun setupChatSession(
@@ -144,9 +147,9 @@ open class UnifiedPlanAction(
SessionProxyServer.chats[session] = app
ApplicationServer.appInfoMap[session] = AppInfoData(
applicationName = "Cognotik",
- inputCnt = when(orchestrationConfig.cognitiveMode) {
+ inputCnt = when (orchestrationConfig.cognitiveMode) {
CognitiveModeType.Chat -> 0
- else -> 4
+ else -> 4
},
stickyInput = app.stickyInput,
showMenubar = app.showMenubar
diff --git a/intellij/src/main/kotlin/cognotik/actions/problems/AnalyzeProblemAction.kt b/intellij/src/main/kotlin/cognotik/actions/problems/AnalyzeProblemAction.kt
index 4b8a21145..06111c518 100644
--- a/intellij/src/main/kotlin/cognotik/actions/problems/AnalyzeProblemAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/problems/AnalyzeProblemAction.kt
@@ -168,7 +168,7 @@ class AnalyzeProblemAction : AnAction() {
""".trimIndent(),
model = AppSettingsState.instance.smartChatClient,
parsingChatter = AppSettingsState.instance.fastChatClient,
- ).answer(listOf(problemInfo),)
+ ).answer(listOf(problemInfo))
task.add(
AgentPatterns.displayMapInTabs(
@@ -228,7 +228,7 @@ class AnalyzeProblemAction : AnAction() {
The diff should include 2 lines of context before and after every change.
""".trimIndent(),
model = AppSettingsState.instance.smartChatClient
- ).answer(listOf(error.message ?: ""),)
+ ).answer(listOf(error.message ?: ""))
return "${
renderMarkdown(
diff --git a/intellij/src/main/kotlin/cognotik/actions/task/BusinessProposalAction.kt b/intellij/src/main/kotlin/cognotik/actions/task/BusinessProposalAction.kt
index e483198aa..c123806bd 100644
--- a/intellij/src/main/kotlin/cognotik/actions/task/BusinessProposalAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/task/BusinessProposalAction.kt
@@ -38,444 +38,445 @@ import javax.swing.*
class BusinessProposalAction : BaseAction() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
- override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
- if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
- return true
- }
-
- override fun handle(e: AnActionEvent) {
- val root = getProjectRoot(e) ?: return
- val relatedFiles = getFiles(e)
- val dialog = BusinessProposalDialog(
- e.project, root, relatedFiles
- )
-
- if (dialog.showAndGet()) {
- try {
- val taskConfig = dialog.getTaskConfig()
- val orchestrationConfig = dialog.getOrchestrationConfig()
-
- UITools.runAsync(e.project, "Initializing Business Proposal Task", true) { progress ->
- initializeTask(e, progress, orchestrationConfig, taskConfig, root)
- }
- } catch (ex: Exception) {
- log.error("Failed to initialize business proposal task", ex)
- UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
- }
- }
- }
-
- private fun initializeTask(
- e: AnActionEvent,
- progress: ProgressIndicator,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: BusinessProposalTask.BusinessProposalTaskExecutionConfigData,
- root: File
- ) {
- progress.text = "Setting up session..."
- val session = Session.newGlobalID()
-
- DataStorage.sessionPaths[session] = root
-
- progress.text = "Starting server..."
- setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
-
- Thread {
- Thread.sleep(500)
- try {
- val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
- log.info("Opening browser to $uri")
- browse(uri)
- } catch (e: Throwable) {
- log.warn("Error opening browser", e)
- }
- }.start()
- }
-
- private fun setupTaskSession(
- session: Session,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: BusinessProposalTask.BusinessProposalTaskExecutionConfigData,
- root: File
- ) {
- val app = object : SingleTaskApp(
- applicationName = "Business Proposal Generation",
- path = "/businessProposal",
- showMenubar = false,
- taskType = BusinessProposalTask.BusinessProposal,
- taskConfig = taskConfig,
- instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
- ) {
- override fun instance(model: ApiChatModel) = model.instance() ?: throw IllegalStateException("Model or Provider not set")
- }
-
- app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
- SessionProxyServer.chats[session] = app
- ApplicationServer.appInfoMap[session] = AppInfoData(
- applicationName = "Business Proposal Generation", inputCnt = 0, stickyInput = false, showMenubar = false
- )
- SessionProxyServer.metadataStorage.setSessionName(
- null, session, "Business Proposal @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
- )
- }
-
- private fun getProjectRoot(e: AnActionEvent): File? {
- val folder = e.getSelectedFolder()
- return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
- getModuleRootForFile(file)
- }
- }
-
- class BusinessProposalDialog(
- project: Project?,
- private val root: File,
- val relatedFiles: List
- ) : DialogWrapper(project) {
-
- private val proposalTitleField = JBTextField().apply {
- toolTipText = "The title or name of the proposal"
- text = "Project Proposal"
- }
-
- private val proposalTypeCombo = ComboBox(
- arrayOf("project", "investment", "grant", "partnership", "rfp_response")
- ).apply {
- toolTipText = "The type of proposal"
- selectedItem = "project"
- }
-
- private val objectiveArea = JBTextArea(4, 40).apply {
- lineWrap = true
- wrapStyleWord = true
- toolTipText = "The primary objective or goal of the proposal"
- }
-
- private val proposingOrgField = JBTextField().apply {
- toolTipText = "The organization or individual submitting the proposal"
- }
-
- private val decisionMakersField = JBTextField().apply {
- toolTipText = "Comma-separated list of decision-makers (e.g., 'CEO, CFO, Board of Directors')"
- }
-
- private val budgetRangeField = JBTextField().apply {
- toolTipText = "Budget range or financial scope (e.g., '$50,000-$100,000', 'under $1M')"
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+ override fun isEnabled(event: AnActionEvent): Boolean {
+ if (!super.isEnabled(event)) return false
+ if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
+ return true
}
- private val timelineField = JBTextField().apply {
- toolTipText = "Project timeline or duration (e.g., '6 months', '2024-2025', 'Q1-Q3')"
- }
-
- private val stakeholdersArea = JBTextArea(3, 40).apply {
- lineWrap = true
- wrapStyleWord = true
- toolTipText = "Key stakeholders and their interests (format: 'Name: Interest' per line)"
- }
-
- private val includeROICheckbox = JBCheckBox("Include ROI Analysis", true).apply {
- toolTipText = "Include detailed ROI calculations and financial projections"
- }
-
- private val includeRiskCheckbox = JBCheckBox("Include Risk Assessment", true).apply {
- toolTipText = "Include risk assessment and mitigation strategies"
- }
-
- private val includeCompetitiveCheckbox = JBCheckBox("Include Competitive Analysis", true).apply {
- toolTipText = "Include competitive analysis or alternatives comparison"
- }
-
- private val includeTimelineCheckbox = JBCheckBox("Include Timeline & Milestones", true).apply {
- toolTipText = "Include detailed timeline with milestones"
- }
-
- private val includeResourcesCheckbox = JBCheckBox("Include Resource Requirements", true).apply {
- toolTipText = "Include team/resource requirements"
- }
-
- private val includeAppendicesCheckbox = JBCheckBox("Include Appendices", true).apply {
- toolTipText = "Include appendices and supporting documents"
- }
-
- private val urgencyCombo = ComboBox(
- arrayOf("critical", "high", "moderate", "low")
- ).apply {
- toolTipText = "Urgency level of the opportunity"
- selectedItem = "moderate"
- }
-
- private val toneCombo = ComboBox(
- arrayOf("formal", "professional", "persuasive", "collaborative")
- ).apply {
- toolTipText = "Tone of the proposal"
- selectedItem = "professional"
- }
-
- private val targetWordCountSpinner = JSpinner(SpinnerNumberModel(3000, 1000, 10000, 500)).apply {
- toolTipText = "Target word count for the complete proposal"
- }
-
- private val revisionPassesSpinner = JSpinner(SpinnerNumberModel(1, 0, 5, 1)).apply {
- toolTipText = "Number of revision passes for quality improvement (0-5)"
- }
+ override fun handle(e: AnActionEvent) {
+ val root = getProjectRoot(e) ?: return
+ val relatedFiles = getFiles(e)
+ val dialog = BusinessProposalDialog(
+ e.project, root, relatedFiles
+ )
- private val relatedFilesField = JBTextField().apply {
- toolTipText = "Comma-separated list of related files to incorporate"
- text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
+ if (dialog.showAndGet()) {
+ try {
+ val taskConfig = dialog.getTaskConfig()
+ val orchestrationConfig = dialog.getOrchestrationConfig()
+
+ UITools.runAsync(e.project, "Initializing Business Proposal Task", true) { progress ->
+ initializeTask(e, progress, orchestrationConfig, taskConfig, root)
+ }
+ } catch (ex: Exception) {
+ log.error("Failed to initialize business proposal task", ex)
+ UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
+ }
+ }
}
- private val inputFilesField = JBTextField().apply {
- toolTipText = "Comma-separated list of input files or patterns (e.g., **/*.kt)"
- text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
+ private fun initializeTask(
+ e: AnActionEvent,
+ progress: ProgressIndicator,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: BusinessProposalTask.BusinessProposalTaskExecutionConfigData,
+ root: File
+ ) {
+ progress.text = "Setting up session..."
+ val session = Session.newGlobalID()
+
+ DataStorage.sessionPaths[session] = root
+
+ progress.text = "Starting server..."
+ setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
+
+ Thread {
+ Thread.sleep(500)
+ try {
+ val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
+ log.info("Opening browser to $uri")
+ browse(uri)
+ } catch (e: Throwable) {
+ log.warn("Error opening browser", e)
+ }
+ }.start()
}
- private val visibleModelsCache by lazy { getVisibleModels() }
+ private fun setupTaskSession(
+ session: Session,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: BusinessProposalTask.BusinessProposalTaskExecutionConfigData,
+ root: File
+ ) {
+ val app = object : SingleTaskApp(
+ applicationName = "Business Proposal Generation",
+ path = "/businessProposal",
+ showMenubar = false,
+ taskType = BusinessProposalTask.BusinessProposal,
+ taskConfig = taskConfig,
+ instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
+ ) {
+ override fun instance(model: ApiChatModel) =
+ model.instance() ?: throw IllegalStateException("Model or Provider not set")
+ }
- private val modelCombo = ComboBox(
- visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
- ).apply {
- maximumSize = Dimension(200, 30)
- selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
- toolTipText = "AI model to use for generating the proposal"
+ app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
+ SessionProxyServer.chats[session] = app
+ ApplicationServer.appInfoMap[session] = AppInfoData(
+ applicationName = "Business Proposal Generation", inputCnt = 0, stickyInput = false, showMenubar = false
+ )
+ SessionProxyServer.metadataStorage.setSessionName(
+ null, session, "Business Proposal @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
+ )
}
- private val temperatureSlider = JSlider(0, 100, 70).apply {
- addChangeListener {
- temperatureLabel.text = "%.2f".format(value / 100.0)
- }
+ private fun getProjectRoot(e: AnActionEvent): File? {
+ val folder = e.getSelectedFolder()
+ return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
+ getModuleRootForFile(file)
+ }
}
- private val temperatureLabel = JLabel("0.70")
+ class BusinessProposalDialog(
+ project: Project?,
+ private val root: File,
+ val relatedFiles: List
+ ) : DialogWrapper(project) {
- private val autoFixCheckbox = JBCheckBox("Auto-apply generated proposal", false).apply {
- toolTipText = "Automatically save the generated proposal without manual confirmation"
- }
+ private val proposalTitleField = JBTextField().apply {
+ toolTipText = "The title or name of the proposal"
+ text = "Project Proposal"
+ }
- init {
- init()
- title = "Configure Business Proposal Generation"
- }
+ private val proposalTypeCombo = ComboBox(
+ arrayOf("project", "investment", "grant", "partnership", "rfp_response")
+ ).apply {
+ toolTipText = "The type of proposal"
+ selectedItem = "project"
+ }
- override fun createCenterPanel(): JComponent = panel {
- group("Proposal Information") {
- row("Proposal Title:") {
- cell(proposalTitleField)
- .align(Align.FILL)
- .comment("The title or name of the proposal")
+ private val objectiveArea = JBTextArea(4, 40).apply {
+ lineWrap = true
+ wrapStyleWord = true
+ toolTipText = "The primary objective or goal of the proposal"
}
- row("Proposal Type:") {
- cell(proposalTypeCombo)
- .align(Align.FILL)
- .comment("Type: project, investment, grant, partnership, or RFP response")
+ private val proposingOrgField = JBTextField().apply {
+ toolTipText = "The organization or individual submitting the proposal"
}
- row("Objective:") {
- scrollCell(objectiveArea)
- .align(Align.FILL)
- .comment("The primary objective or goal of the proposal")
- .resizableColumn()
- }.resizableRow()
-
- row("Proposing Organization:") {
- cell(proposingOrgField)
- .align(Align.FILL)
- .comment("Organization or individual submitting the proposal")
+ private val decisionMakersField = JBTextField().apply {
+ toolTipText = "Comma-separated list of decision-makers (e.g., 'CEO, CFO, Board of Directors')"
}
- }
- group("Stakeholders & Audience") {
- row("Decision Makers:") {
- cell(decisionMakersField)
- .align(Align.FILL)
- .comment("Comma-separated list (e.g., 'CEO, CFO, Board of Directors')")
+ private val budgetRangeField = JBTextField().apply {
+ toolTipText = "Budget range or financial scope (e.g., '$50,000-$100,000', 'under $1M')"
}
- row("Stakeholders:") {
- scrollCell(stakeholdersArea)
- .align(Align.FILL)
- .comment("Key stakeholders and interests (format: 'Name: Interest' per line)")
- .resizableColumn()
- }.resizableRow()
- }
-
- group("Budget & Timeline") {
- row("Budget Range:") {
- cell(budgetRangeField)
- .align(Align.FILL)
- .comment("e.g., '$50,000-$100,000', 'under $1M'")
+ private val timelineField = JBTextField().apply {
+ toolTipText = "Project timeline or duration (e.g., '6 months', '2024-2025', 'Q1-Q3')"
}
- row("Timeline:") {
- cell(timelineField)
- .align(Align.FILL)
- .comment("e.g., '6 months', '2024-2025', 'Q1-Q3'")
+ private val stakeholdersArea = JBTextArea(3, 40).apply {
+ lineWrap = true
+ wrapStyleWord = true
+ toolTipText = "Key stakeholders and their interests (format: 'Name: Interest' per line)"
}
- }
- group("Analysis Components") {
- row {
- cell(includeROICheckbox)
+ private val includeROICheckbox = JBCheckBox("Include ROI Analysis", true).apply {
+ toolTipText = "Include detailed ROI calculations and financial projections"
}
- row {
- cell(includeRiskCheckbox)
+
+ private val includeRiskCheckbox = JBCheckBox("Include Risk Assessment", true).apply {
+ toolTipText = "Include risk assessment and mitigation strategies"
}
- row {
- cell(includeCompetitiveCheckbox)
+
+ private val includeCompetitiveCheckbox = JBCheckBox("Include Competitive Analysis", true).apply {
+ toolTipText = "Include competitive analysis or alternatives comparison"
}
- row {
- cell(includeTimelineCheckbox)
+
+ private val includeTimelineCheckbox = JBCheckBox("Include Timeline & Milestones", true).apply {
+ toolTipText = "Include detailed timeline with milestones"
}
- row {
- cell(includeResourcesCheckbox)
+
+ private val includeResourcesCheckbox = JBCheckBox("Include Resource Requirements", true).apply {
+ toolTipText = "Include team/resource requirements"
}
- row {
- cell(includeAppendicesCheckbox)
+
+ private val includeAppendicesCheckbox = JBCheckBox("Include Appendices", true).apply {
+ toolTipText = "Include appendices and supporting documents"
}
- }
- group("Proposal Settings") {
- row("Urgency Level:") {
- cell(urgencyCombo)
- .align(Align.FILL)
- .comment("Urgency: critical, high, moderate, or low")
+ private val urgencyCombo = ComboBox(
+ arrayOf("critical", "high", "moderate", "low")
+ ).apply {
+ toolTipText = "Urgency level of the opportunity"
+ selectedItem = "moderate"
}
- row("Tone:") {
- cell(toneCombo)
- .align(Align.FILL)
- .comment("Tone: formal, professional, persuasive, or collaborative")
+ private val toneCombo = ComboBox(
+ arrayOf("formal", "professional", "persuasive", "collaborative")
+ ).apply {
+ toolTipText = "Tone of the proposal"
+ selectedItem = "professional"
}
- row("Target Word Count:") {
- cell(targetWordCountSpinner)
- .comment("Target word count for the complete proposal (1000-10000)")
+ private val targetWordCountSpinner = JSpinner(SpinnerNumberModel(3000, 1000, 10000, 500)).apply {
+ toolTipText = "Target word count for the complete proposal"
}
- row("Revision Passes:") {
- cell(revisionPassesSpinner)
- .comment("Number of revision passes for quality improvement (0-5)")
+ private val revisionPassesSpinner = JSpinner(SpinnerNumberModel(1, 0, 5, 1)).apply {
+ toolTipText = "Number of revision passes for quality improvement (0-5)"
}
- }
- group("Context Files") {
- row("Related Files:") {
- cell(relatedFilesField)
- .align(Align.FILL)
- .comment("Comma-separated list of related files to incorporate")
+ private val relatedFilesField = JBTextField().apply {
+ toolTipText = "Comma-separated list of related files to incorporate"
+ text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
}
- row("Input Files:") {
- cell(inputFilesField)
- .align(Align.FILL)
- .comment("Comma-separated list of input files or patterns (e.g., **/*.kt)")
+ private val inputFilesField = JBTextField().apply {
+ toolTipText = "Comma-separated list of input files or patterns (e.g., **/*.kt)"
+ text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
}
- }
- group("Model Settings") {
- row("Model:") {
- cell(modelCombo)
- .align(Align.FILL)
- .comment("AI model for generating the proposal")
+ private val visibleModelsCache by lazy { getVisibleModels() }
+
+ private val modelCombo = ComboBox(
+ visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
+ ).apply {
+ maximumSize = Dimension(200, 30)
+ selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
+ toolTipText = "AI model to use for generating the proposal"
}
- row("Temperature:") {
- cell(temperatureSlider)
- .align(Align.FILL)
- .comment("Higher values = more creative, lower = more focused")
- cell(temperatureLabel)
+ private val temperatureSlider = JSlider(0, 100, 70).apply {
+ addChangeListener {
+ temperatureLabel.text = "%.2f".format(value / 100.0)
+ }
}
- row {
- cell(autoFixCheckbox)
+ private val temperatureLabel = JLabel("0.70")
+
+ private val autoFixCheckbox = JBCheckBox("Auto-apply generated proposal", false).apply {
+ toolTipText = "Automatically save the generated proposal without manual confirmation"
}
- }
- }
- override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
- if (proposalTitleField.text.isBlank()) {
- return com.intellij.openapi.ui.ValidationInfo("Proposal title is required", proposalTitleField)
- }
+ init {
+ init()
+ title = "Configure Business Proposal Generation"
+ }
- if (objectiveArea.text.isBlank()) {
- return com.intellij.openapi.ui.ValidationInfo("Objective is required", objectiveArea)
- }
+ override fun createCenterPanel(): JComponent = panel {
+ group("Proposal Information") {
+ row("Proposal Title:") {
+ cell(proposalTitleField)
+ .align(Align.FILL)
+ .comment("The title or name of the proposal")
+ }
+
+ row("Proposal Type:") {
+ cell(proposalTypeCombo)
+ .align(Align.FILL)
+ .comment("Type: project, investment, grant, partnership, or RFP response")
+ }
+
+ row("Objective:") {
+ scrollCell(objectiveArea)
+ .align(Align.FILL)
+ .comment("The primary objective or goal of the proposal")
+ .resizableColumn()
+ }.resizableRow()
+
+ row("Proposing Organization:") {
+ cell(proposingOrgField)
+ .align(Align.FILL)
+ .comment("Organization or individual submitting the proposal")
+ }
+ }
+
+ group("Stakeholders & Audience") {
+ row("Decision Makers:") {
+ cell(decisionMakersField)
+ .align(Align.FILL)
+ .comment("Comma-separated list (e.g., 'CEO, CFO, Board of Directors')")
+ }
+
+ row("Stakeholders:") {
+ scrollCell(stakeholdersArea)
+ .align(Align.FILL)
+ .comment("Key stakeholders and interests (format: 'Name: Interest' per line)")
+ .resizableColumn()
+ }.resizableRow()
+ }
+
+ group("Budget & Timeline") {
+ row("Budget Range:") {
+ cell(budgetRangeField)
+ .align(Align.FILL)
+ .comment("e.g., '$50,000-$100,000', 'under $1M'")
+ }
+
+ row("Timeline:") {
+ cell(timelineField)
+ .align(Align.FILL)
+ .comment("e.g., '6 months', '2024-2025', 'Q1-Q3'")
+ }
+ }
+
+ group("Analysis Components") {
+ row {
+ cell(includeROICheckbox)
+ }
+ row {
+ cell(includeRiskCheckbox)
+ }
+ row {
+ cell(includeCompetitiveCheckbox)
+ }
+ row {
+ cell(includeTimelineCheckbox)
+ }
+ row {
+ cell(includeResourcesCheckbox)
+ }
+ row {
+ cell(includeAppendicesCheckbox)
+ }
+ }
+
+ group("Proposal Settings") {
+ row("Urgency Level:") {
+ cell(urgencyCombo)
+ .align(Align.FILL)
+ .comment("Urgency: critical, high, moderate, or low")
+ }
+
+ row("Tone:") {
+ cell(toneCombo)
+ .align(Align.FILL)
+ .comment("Tone: formal, professional, persuasive, or collaborative")
+ }
+
+ row("Target Word Count:") {
+ cell(targetWordCountSpinner)
+ .comment("Target word count for the complete proposal (1000-10000)")
+ }
+
+ row("Revision Passes:") {
+ cell(revisionPassesSpinner)
+ .comment("Number of revision passes for quality improvement (0-5)")
+ }
+ }
+
+ group("Context Files") {
+ row("Related Files:") {
+ cell(relatedFilesField)
+ .align(Align.FILL)
+ .comment("Comma-separated list of related files to incorporate")
+ }
+
+ row("Input Files:") {
+ cell(inputFilesField)
+ .align(Align.FILL)
+ .comment("Comma-separated list of input files or patterns (e.g., **/*.kt)")
+ }
+ }
+
+ group("Model Settings") {
+ row("Model:") {
+ cell(modelCombo)
+ .align(Align.FILL)
+ .comment("AI model for generating the proposal")
+ }
+
+ row("Temperature:") {
+ cell(temperatureSlider)
+ .align(Align.FILL)
+ .comment("Higher values = more creative, lower = more focused")
+ cell(temperatureLabel)
+ }
+
+ row {
+ cell(autoFixCheckbox)
+ }
+ }
+ }
- return null
- }
+ override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
+ if (proposalTitleField.text.isBlank()) {
+ return com.intellij.openapi.ui.ValidationInfo("Proposal title is required", proposalTitleField)
+ }
- fun getTaskConfig(): BusinessProposalTask.BusinessProposalTaskExecutionConfigData {
- val relatedFiles = relatedFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
- .takeIf { it.isNotEmpty() }
+ if (objectiveArea.text.isBlank()) {
+ return com.intellij.openapi.ui.ValidationInfo("Objective is required", objectiveArea)
+ }
- val inputFiles = inputFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
- .takeIf { it.isNotEmpty() }
+ return null
+ }
- val decisionMakers = decisionMakersField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
- .takeIf { it.isNotEmpty() }
+ fun getTaskConfig(): BusinessProposalTask.BusinessProposalTaskExecutionConfigData {
+ val relatedFiles = relatedFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+ .takeIf { it.isNotEmpty() }
+
+ val inputFiles = inputFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+ .takeIf { it.isNotEmpty() }
+
+ val decisionMakers = decisionMakersField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+ .takeIf { it.isNotEmpty() }
+
+ val stakeholders = stakeholdersArea.text.lines()
+ .filter { it.contains(":") }
+ .associate {
+ val parts = it.split(":", limit = 2)
+ parts[0].trim() to parts[1].trim()
+ }
+ .takeIf { it.isNotEmpty() }
+
+ return BusinessProposalTask.BusinessProposalTaskExecutionConfigData(
+ proposal_title = proposalTitleField.text,
+ proposal_type = proposalTypeCombo.selectedItem as String,
+ objective = objectiveArea.text,
+ proposing_organization = proposingOrgField.text.takeIf { it.isNotBlank() },
+ decision_makers = decisionMakers,
+ budget_range = budgetRangeField.text.takeIf { it.isNotBlank() },
+ timeline = timelineField.text.takeIf { it.isNotBlank() },
+ stakeholders = stakeholders,
+ include_roi_analysis = includeROICheckbox.isSelected,
+ include_risk_assessment = includeRiskCheckbox.isSelected,
+ include_competitive_analysis = includeCompetitiveCheckbox.isSelected,
+ include_timeline_milestones = includeTimelineCheckbox.isSelected,
+ include_resource_requirements = includeResourcesCheckbox.isSelected,
+ include_appendices = includeAppendicesCheckbox.isSelected,
+ urgency_level = urgencyCombo.selectedItem as String,
+ tone = toneCombo.selectedItem as String,
+ target_word_count = targetWordCountSpinner.value as Int,
+ revision_passes = revisionPassesSpinner.value as Int,
+ related_files = relatedFiles,
+ input_files = inputFiles,
+ state = TaskState.Pending
+ )
+ }
- val stakeholders = stakeholdersArea.text.lines()
- .filter { it.contains(":") }
- .associate {
- val parts = it.split(":", limit = 2)
- parts[0].trim() to parts[1].trim()
+ fun getOrchestrationConfig(): OrchestrationConfig {
+ val selectedModel = modelCombo.selectedItem as? String
+ val model = selectedModel?.let { modelName ->
+ visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
+ }
+
+ return OrchestrationConfig(
+ "Config",
+ defaultSmartModel = model ?: AppSettingsState.instance.smartModel
+ ?: throw IllegalStateException("No model configured"),
+ defaultFastModel = AppSettingsState.instance.fastModel
+ ?: throw IllegalStateException("Fast model not configured"),
+ temperature = temperatureSlider.value / 100.0,
+ autoFix = autoFixCheckbox.isSelected,
+ workingDir = root.absolutePath,
+ shellCmd = listOf(
+ if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
+ )
+ )
}
- .takeIf { it.isNotEmpty() }
-
- return BusinessProposalTask.BusinessProposalTaskExecutionConfigData(
- proposal_title = proposalTitleField.text,
- proposal_type = proposalTypeCombo.selectedItem as String,
- objective = objectiveArea.text,
- proposing_organization = proposingOrgField.text.takeIf { it.isNotBlank() },
- decision_makers = decisionMakers,
- budget_range = budgetRangeField.text.takeIf { it.isNotBlank() },
- timeline = timelineField.text.takeIf { it.isNotBlank() },
- stakeholders = stakeholders,
- include_roi_analysis = includeROICheckbox.isSelected,
- include_risk_assessment = includeRiskCheckbox.isSelected,
- include_competitive_analysis = includeCompetitiveCheckbox.isSelected,
- include_timeline_milestones = includeTimelineCheckbox.isSelected,
- include_resource_requirements = includeResourcesCheckbox.isSelected,
- include_appendices = includeAppendicesCheckbox.isSelected,
- urgency_level = urgencyCombo.selectedItem as String,
- tone = toneCombo.selectedItem as String,
- target_word_count = targetWordCountSpinner.value as Int,
- revision_passes = revisionPassesSpinner.value as Int,
- related_files = relatedFiles,
- input_files = inputFiles,
- state = TaskState.Pending
- )
- }
- fun getOrchestrationConfig(): OrchestrationConfig {
- val selectedModel = modelCombo.selectedItem as? String
- val model = selectedModel?.let { modelName ->
- visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
- }
-
- return OrchestrationConfig(
- "Config",
- defaultSmartModel = model ?: AppSettingsState.instance.smartModel
- ?: throw IllegalStateException("No model configured"),
- defaultFastModel = AppSettingsState.instance.fastModel
- ?: throw IllegalStateException("Fast model not configured"),
- temperature = temperatureSlider.value / 100.0,
- autoFix = autoFixCheckbox.isSelected,
- workingDir = root.absolutePath,
- shellCmd = listOf(
- if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
- )
- )
+ private fun getVisibleModels() =
+ ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
+ model.provider == apiData.provider &&
+ model.modelName?.isNotBlank() == true &&
+ PlanConfigDialog.isVisible(model)
+ } ?: listOf()
+ }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
}
-
- private fun getVisibleModels() =
- ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
- model.provider == apiData.provider &&
- model.modelName?.isNotBlank() == true &&
- PlanConfigDialog.isVisible(model)
- } ?: listOf()
- }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
- }
}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/task/DataIngestAction.kt b/intellij/src/main/kotlin/cognotik/actions/task/DataIngestAction.kt
index 09f342c92..87d3fc77a 100644
--- a/intellij/src/main/kotlin/cognotik/actions/task/DataIngestAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/task/DataIngestAction.kt
@@ -40,245 +40,249 @@ import javax.swing.SpinnerNumberModel
class DataIngestAction : BaseAction() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
- override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
- if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
- return true
- }
-
- override fun handle(e: AnActionEvent) {
- val root = getProjectRoot(e) ?: return
- val files = getFiles(e)
- val dialog = DataIngestTaskDialog(e.project, root, files)
-
- if (dialog.showAndGet()) {
- try {
- val taskConfig = dialog.getTaskConfig()
- val orchestrationConfig = dialog.getOrchestrationConfig()
-
- UITools.runAsync(e.project, "Initializing Data Ingestion Task", true) { progress ->
- initializeTask(e, progress, orchestrationConfig, taskConfig, root)
- }
- } catch (ex: Exception) {
- log.error("Failed to initialize Data Ingestion task", ex)
- UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
- }
- }
- }
-
- private fun initializeTask(
- event: AnActionEvent,
- progress: ProgressIndicator,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: DataIngestTask.DataIngestTaskExecutionConfigData,
- root: File
- ) {
- progress.text = "Setting up session..."
- val session = Session.newGlobalID()
-
- DataStorage.sessionPaths[session] = root
-
- progress.text = "Starting server..."
- setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
-
- Thread {
- Thread.sleep(500)
- try {
- val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
- log.info("Opening browser to $uri")
- browse(uri)
- } catch (e: Throwable) {
- log.warn("Error opening browser", e)
- }
- }.start()
- }
-
- private fun setupTaskSession(
- session: Session, orchestrationConfig: OrchestrationConfig, taskConfig: DataIngestTask.DataIngestTaskExecutionConfigData, root: File
- ) {
- val app = object : SingleTaskApp(
- applicationName = "Data Ingestion Task",
- path = "/dataIngestTask",
- showMenubar = false,
- taskType = DataIngestTask.DataIngest,
- taskConfig = taskConfig,
- instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
- ) {
- override fun instance(model: ApiChatModel) = model.instance() ?: throw IllegalStateException("Model or Provider not set")
- }
-
- app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
- SessionProxyServer.chats[session] = app
- ApplicationServer.appInfoMap[session] = AppInfoData(
- applicationName = "Data Ingestion Task", inputCnt = 0, stickyInput = false, showMenubar = false
- )
- SessionProxyServer.metadataStorage.setSessionName(
- null, session, "Data Ingestion @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
- )
- }
-
- private fun getProjectRoot(e: AnActionEvent): File? {
- val folder = e.getSelectedFolder()
- return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
- getModuleRootForFile(file)
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+ override fun isEnabled(event: AnActionEvent): Boolean {
+ if (!super.isEnabled(event)) return false
+ if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
+ return true
}
- }
-
- class DataIngestTaskDialog(
- project: Project?,
- private val root: File,
- files: List
- ) : DialogWrapper(project) {
- private val inputFilesField = JBTextField().apply {
- toolTipText = "Glob patterns for input files (e.g. **/*.log)"
- text = if (files.isNotEmpty()) files.joinToString(", ") { it.relativeTo(root).path } else "**/*.log"
+ override fun handle(e: AnActionEvent) {
+ val root = getProjectRoot(e) ?: return
+ val files = getFiles(e)
+ val dialog = DataIngestTaskDialog(e.project, root, files)
+
+ if (dialog.showAndGet()) {
+ try {
+ val taskConfig = dialog.getTaskConfig()
+ val orchestrationConfig = dialog.getOrchestrationConfig()
+
+ UITools.runAsync(e.project, "Initializing Data Ingestion Task", true) { progress ->
+ initializeTask(e, progress, orchestrationConfig, taskConfig, root)
+ }
+ } catch (ex: Exception) {
+ log.error("Failed to initialize Data Ingestion task", ex)
+ UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
+ }
+ }
}
- private val sampleSizeSpinner = JSpinner(SpinnerNumberModel(1000, 100, 10000, 100)).apply {
- toolTipText = "Number of lines to sample for pattern discovery"
+ private fun initializeTask(
+ event: AnActionEvent,
+ progress: ProgressIndicator,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: DataIngestTask.DataIngestTaskExecutionConfigData,
+ root: File
+ ) {
+ progress.text = "Setting up session..."
+ val session = Session.newGlobalID()
+
+ DataStorage.sessionPaths[session] = root
+
+ progress.text = "Starting server..."
+ setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
+
+ Thread {
+ Thread.sleep(500)
+ try {
+ val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
+ log.info("Opening browser to $uri")
+ browse(uri)
+ } catch (e: Throwable) {
+ log.warn("Error opening browser", e)
+ }
+ }.start()
}
- private val maxIterationsSpinner = JSpinner(SpinnerNumberModel(10, 1, 50, 1)).apply {
- toolTipText = "Maximum number of discovery iterations"
- }
+ private fun setupTaskSession(
+ session: Session,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: DataIngestTask.DataIngestTaskExecutionConfigData,
+ root: File
+ ) {
+ val app = object : SingleTaskApp(
+ applicationName = "Data Ingestion Task",
+ path = "/dataIngestTask",
+ showMenubar = false,
+ taskType = DataIngestTask.DataIngest,
+ taskConfig = taskConfig,
+ instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
+ ) {
+ override fun instance(model: ApiChatModel) =
+ model.instance() ?: throw IllegalStateException("Model or Provider not set")
+ }
- private val coverageThresholdSlider = JSlider(0, 100, 95).apply {
- addChangeListener {
- coverageLabel.text = "${value}%"
- }
+ app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
+ SessionProxyServer.chats[session] = app
+ ApplicationServer.appInfoMap[session] = AppInfoData(
+ applicationName = "Data Ingestion Task", inputCnt = 0, stickyInput = false, showMenubar = false
+ )
+ SessionProxyServer.metadataStorage.setSessionName(
+ null, session, "Data Ingestion @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
+ )
}
- private val coverageLabel = javax.swing.JLabel("95%")
- private val taskDescriptionArea = JBTextArea(4, 40).apply {
- lineWrap = true
- wrapStyleWord = true
- toolTipText = "Optional description of the data or specific parsing requirements"
+ private fun getProjectRoot(e: AnActionEvent): File? {
+ val folder = e.getSelectedFolder()
+ return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
+ getModuleRootForFile(file)
+ }
}
- private val visibleModelsCache by lazy { getVisibleModels() }
+ class DataIngestTaskDialog(
+ project: Project?,
+ private val root: File,
+ files: List
+ ) : DialogWrapper(project) {
- private val modelCombo = ComboBox(
- visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
- ).apply {
- maximumSize = Dimension(200, 30)
- selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
- toolTipText = "AI model to use for pattern discovery"
- }
+ private val inputFilesField = JBTextField().apply {
+ toolTipText = "Glob patterns for input files (e.g. **/*.log)"
+ text = if (files.isNotEmpty()) files.joinToString(", ") { it.relativeTo(root).path } else "**/*.log"
+ }
- private val temperatureSlider = JSlider(0, 100, 10).apply {
- addChangeListener {
- temperatureLabel.text = "%.2f".format(value / 100.0)
- }
- }
+ private val sampleSizeSpinner = JSpinner(SpinnerNumberModel(1000, 100, 10000, 100)).apply {
+ toolTipText = "Number of lines to sample for pattern discovery"
+ }
- private val temperatureLabel = javax.swing.JLabel("0.10")
+ private val maxIterationsSpinner = JSpinner(SpinnerNumberModel(10, 1, 50, 1)).apply {
+ toolTipText = "Maximum number of discovery iterations"
+ }
- init {
- init()
- title = "Configure Data Ingestion Task"
- }
+ private val coverageThresholdSlider = JSlider(0, 100, 95).apply {
+ addChangeListener {
+ coverageLabel.text = "${value}%"
+ }
+ }
+ private val coverageLabel = javax.swing.JLabel("95%")
- override fun createCenterPanel(): JComponent = panel {
- group("Data Configuration") {
- row("Input Files:") {
- cell(inputFilesField)
- .align(Align.FILL)
- .comment("Glob patterns (e.g. **/*.log, logs/app-*.txt)")
+ private val taskDescriptionArea = JBTextArea(4, 40).apply {
+ lineWrap = true
+ wrapStyleWord = true
+ toolTipText = "Optional description of the data or specific parsing requirements"
}
- row("Sample Size:") {
- cell(sampleSizeSpinner)
- .comment("Lines to sample for pattern discovery")
+ private val visibleModelsCache by lazy { getVisibleModels() }
+
+ private val modelCombo = ComboBox(
+ visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
+ ).apply {
+ maximumSize = Dimension(200, 30)
+ selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
+ toolTipText = "AI model to use for pattern discovery"
}
- row("Max Iterations:") {
- cell(maxIterationsSpinner)
- .comment("Max discovery loops")
+ private val temperatureSlider = JSlider(0, 100, 10).apply {
+ addChangeListener {
+ temperatureLabel.text = "%.2f".format(value / 100.0)
+ }
}
- row("Coverage Threshold:") {
- cell(coverageThresholdSlider)
- .align(Align.FILL)
- .comment("Stop when this % of sample is covered")
- cell(coverageLabel)
+ private val temperatureLabel = javax.swing.JLabel("0.10")
+
+ init {
+ init()
+ title = "Configure Data Ingestion Task"
}
- row("Description:") {
- scrollCell(taskDescriptionArea)
- .align(Align.FILL)
- .comment("Optional context about the data format")
- .resizableColumn()
- }.resizableRow()
- }
-
- group("Model Settings") {
- row("Model:") {
- cell(modelCombo)
- .align(Align.FILL)
- .comment("AI model for regex generation")
+ override fun createCenterPanel(): JComponent = panel {
+ group("Data Configuration") {
+ row("Input Files:") {
+ cell(inputFilesField)
+ .align(Align.FILL)
+ .comment("Glob patterns (e.g. **/*.log, logs/app-*.txt)")
+ }
+
+ row("Sample Size:") {
+ cell(sampleSizeSpinner)
+ .comment("Lines to sample for pattern discovery")
+ }
+
+ row("Max Iterations:") {
+ cell(maxIterationsSpinner)
+ .comment("Max discovery loops")
+ }
+
+ row("Coverage Threshold:") {
+ cell(coverageThresholdSlider)
+ .align(Align.FILL)
+ .comment("Stop when this % of sample is covered")
+ cell(coverageLabel)
+ }
+
+ row("Description:") {
+ scrollCell(taskDescriptionArea)
+ .align(Align.FILL)
+ .comment("Optional context about the data format")
+ .resizableColumn()
+ }.resizableRow()
+ }
+
+ group("Model Settings") {
+ row("Model:") {
+ cell(modelCombo)
+ .align(Align.FILL)
+ .comment("AI model for regex generation")
+ }
+
+ row("Temperature:") {
+ cell(temperatureSlider)
+ .align(Align.FILL)
+ .comment("Lower values recommended for precise regex generation")
+ cell(temperatureLabel)
+ }
+ }
}
- row("Temperature:") {
- cell(temperatureSlider)
- .align(Align.FILL)
- .comment("Lower values recommended for precise regex generation")
- cell(temperatureLabel)
+ override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
+ if (inputFilesField.text.isBlank()) {
+ return com.intellij.openapi.ui.ValidationInfo("Input files pattern is required", inputFilesField)
+ }
+ return null
}
- }
- }
- override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
- if (inputFilesField.text.isBlank()) {
- return com.intellij.openapi.ui.ValidationInfo("Input files pattern is required", inputFilesField)
- }
- return null
- }
+ fun getTaskConfig(): DataIngestTask.DataIngestTaskExecutionConfigData {
+ val patterns = inputFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+
+ return DataIngestTask.DataIngestTaskExecutionConfigData(
+ input_files = patterns,
+ sample_size = sampleSizeSpinner.value as Int,
+ max_iterations = maxIterationsSpinner.value as Int,
+ coverage_threshold = coverageThresholdSlider.value / 100.0,
+ task_description = taskDescriptionArea.text.takeIf { it.isNotBlank() },
+ state = TaskState.Pending
+ )
+ }
- fun getTaskConfig(): DataIngestTask.DataIngestTaskExecutionConfigData {
- val patterns = inputFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
-
- return DataIngestTask.DataIngestTaskExecutionConfigData(
- input_files = patterns,
- sample_size = sampleSizeSpinner.value as Int,
- max_iterations = maxIterationsSpinner.value as Int,
- coverage_threshold = coverageThresholdSlider.value / 100.0,
- task_description = taskDescriptionArea.text.takeIf { it.isNotBlank() },
- state = TaskState.Pending
- )
- }
+ fun getOrchestrationConfig(): OrchestrationConfig {
+ val selectedModel = modelCombo.selectedItem as? String
+ val model = selectedModel?.let { modelName ->
+ visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
+ }
+
+ return OrchestrationConfig(
+ "Config",
+ defaultSmartModel = model ?: AppSettingsState.instance.smartModel
+ ?: throw IllegalStateException("No model configured"),
+ defaultFastModel = AppSettingsState.instance.fastModel
+ ?: throw IllegalStateException("Fast model not configured"),
+ defaultImageModel = AppSettingsState.instance.imageChatModel ?: AppSettingsState.instance.smartModel
+ ?: throw IllegalStateException("No image model configured"),
+ temperature = temperatureSlider.value / 100.0,
+ autoFix = false,
+ workingDir = root.absolutePath,
+ shellCmd = listOf(
+ if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
+ )
+ )
+ }
- fun getOrchestrationConfig(): OrchestrationConfig {
- val selectedModel = modelCombo.selectedItem as? String
- val model = selectedModel?.let { modelName ->
- visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
- }
-
- return OrchestrationConfig(
- "Config",
- defaultSmartModel = model ?: AppSettingsState.instance.smartModel
- ?: throw IllegalStateException("No model configured"),
- defaultFastModel = AppSettingsState.instance.fastModel
- ?: throw IllegalStateException("Fast model not configured"),
- defaultImageModel = AppSettingsState.instance.imageChatModel ?: AppSettingsState.instance.smartModel
- ?: throw IllegalStateException("No image model configured"),
- temperature = temperatureSlider.value / 100.0,
- autoFix = false,
- workingDir = root.absolutePath,
- shellCmd = listOf(
- if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
- )
- )
+ private fun getVisibleModels() =
+ ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
+ model.provider == apiData.provider &&
+ model.modelName?.isNotBlank() == true &&
+ PlanConfigDialog.isVisible(model)
+ } ?: listOf()
+ }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
}
-
- private fun getVisibleModels() =
- ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
- model.provider == apiData.provider &&
- model.modelName?.isNotBlank() == true &&
- PlanConfigDialog.isVisible(model)
- } ?: listOf()
- }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
- }
}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/task/FileModificationTaskAction.kt b/intellij/src/main/kotlin/cognotik/actions/task/FileModificationTaskAction.kt
index 127c99a3b..fb8943aa0 100644
--- a/intellij/src/main/kotlin/cognotik/actions/task/FileModificationTaskAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/task/FileModificationTaskAction.kt
@@ -41,266 +41,275 @@ import javax.swing.JSlider
class FileModificationTaskAction : BaseAction() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
- override fun handle(e: AnActionEvent) {
- val root = getProjectRoot(e) ?: return
- val files = getFiles(e)
+ override fun handle(e: AnActionEvent) {
+ val root = getProjectRoot(e) ?: return
+ val files = getFiles(e)
- val dialog = FileModificationTaskDialog(
- e.project,
- root,
- files
- )
-
- if (dialog.showAndGet()) {
- try {
- val taskConfig = dialog.getTaskConfig()
- val orchestrationConfig = dialog.getOrchestrationConfig()
+ val dialog = FileModificationTaskDialog(
+ e.project,
+ root,
+ files
+ )
- UITools.runAsync(e.project, "Initializing File Modification Task", true) { progress ->
- initializeTask(e, progress, orchestrationConfig, taskConfig, root)
+ if (dialog.showAndGet()) {
+ try {
+ val taskConfig = dialog.getTaskConfig()
+ val orchestrationConfig = dialog.getOrchestrationConfig()
+
+ UITools.runAsync(e.project, "Initializing File Modification Task", true) { progress ->
+ initializeTask(e, progress, orchestrationConfig, taskConfig, root)
+ }
+ } catch (ex: Exception) {
+ log.error("Failed to initialize file modification task", ex)
+ UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
+ }
}
- } catch (ex: Exception) {
- log.error("Failed to initialize file modification task", ex)
- UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
- }
- }
- }
-
- private fun initializeTask(
- e: AnActionEvent,
- progress: ProgressIndicator,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: FileModificationTask.FileModificationTaskExecutionConfigData,
- root: File
- ) {
- progress.text = "Setting up session..."
- val session = Session.newGlobalID()
-
- DataStorage.sessionPaths[session] = root
-
- progress.text = "Starting server..."
- setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
-
- Thread {
- Thread.sleep(500)
- try {
- val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
- log.info("Opening browser to $uri")
- browse(uri)
- } catch (e: Throwable) {
- log.warn("Error opening browser", e)
- }
- }.start()
- }
-
- private fun setupTaskSession(
- session: Session,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: FileModificationTask.FileModificationTaskExecutionConfigData,
- root: File
- ) {
- val app = object : SingleTaskApp(
- applicationName = "File Modification Task",
- path = "/fileModificationTask",
- showMenubar = false,
- taskType = FileModification,
- taskConfig = taskConfig,
- instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
- ) {
- override fun instance(model: ApiChatModel) = model.instance()
- ?: throw IllegalStateException("Model or Provider not set")
- }
-
- app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
- SessionProxyServer.chats[session] = app
- ApplicationServer.appInfoMap[session] = AppInfoData(
- applicationName = "File Modification Task",
- inputCnt = 0,
- stickyInput = false,
- showMenubar = false
- )
- SessionProxyServer.metadataStorage.setSessionName(
- null,
- session,
- "File Modification @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
- )
- }
-
- private fun getProjectRoot(e: AnActionEvent): File? {
- val folder = e.getSelectedFolder()
- return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
- getModuleRootForFile(file)
}
- }
-
- class FileModificationTaskDialog(
- project: Project?, private val root: File, val files: List
- ) : DialogWrapper(project) {
- private val taskDescriptionArea = JBTextArea(5, 40).apply {
- lineWrap = true
- wrapStyleWord = true
- toolTipText = "Describe what modifications should be made to the files"
- }
-
- private val filesField = JBTextField().apply {
- toolTipText = "Comma-separated list of file paths (relative to project root) to modify or create"
- text = files.joinToString(", ") { it.relativeTo(root).path }
+ private fun initializeTask(
+ e: AnActionEvent,
+ progress: ProgressIndicator,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: FileModificationTask.FileModificationTaskExecutionConfigData,
+ root: File
+ ) {
+ progress.text = "Setting up session..."
+ val session = Session.newGlobalID()
+
+ DataStorage.sessionPaths[session] = root
+
+ progress.text = "Starting server..."
+ setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
+
+ Thread {
+ Thread.sleep(500)
+ try {
+ val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
+ log.info("Opening browser to $uri")
+ browse(uri)
+ } catch (e: Throwable) {
+ log.warn("Error opening browser", e)
+ }
+ }.start()
}
- private val relatedFilesField = JBTextField().apply {
- toolTipText = "Comma-separated list of related files to consider for context"
- }
+ private fun setupTaskSession(
+ session: Session,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: FileModificationTask.FileModificationTaskExecutionConfigData,
+ root: File
+ ) {
+ val app = object : SingleTaskApp(
+ applicationName = "File Modification Task",
+ path = "/fileModificationTask",
+ showMenubar = false,
+ taskType = FileModification,
+ taskConfig = taskConfig,
+ instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
+ ) {
+ override fun instance(model: ApiChatModel) = model.instance()
+ ?: throw IllegalStateException("Model or Provider not set")
+ }
- private val extractContentCheckbox = JBCheckBox("Extract content from non-text files", false).apply {
- toolTipText = "Extract text content from PDF, HTML, and other document formats"
+ app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
+ SessionProxyServer.chats[session] = app
+ ApplicationServer.appInfoMap[session] = AppInfoData(
+ applicationName = "File Modification Task",
+ inputCnt = 0,
+ stickyInput = false,
+ showMenubar = false
+ )
+ SessionProxyServer.metadataStorage.setSessionName(
+ null,
+ session,
+ "File Modification @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
+ )
}
- private val includeGitDiffCheckbox = JBCheckBox("Include git diff with HEAD", false).apply {
- toolTipText = "Include git diff information to show recent changes"
+ private fun getProjectRoot(e: AnActionEvent): File? {
+ val folder = e.getSelectedFolder()
+ return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
+ getModuleRootForFile(file)
+ }
}
- private val visibleModelsCache by lazy { getVisibleModels() }
+ class FileModificationTaskDialog(
+ project: Project?, private val root: File, val files: List
+ ) : DialogWrapper(project) {
- private val modelCombo = ComboBox(
- visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
- ).apply {
- maximumSize = Dimension(200, 30)
- selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
- toolTipText = "AI model to use for this task"
- }
+ private val taskDescriptionArea = JBTextArea(5, 40).apply {
+ lineWrap = true
+ wrapStyleWord = true
+ toolTipText = "Describe what modifications should be made to the files"
+ }
- private val temperatureSlider = JSlider(0, 100, 70).apply {
- addChangeListener {
- temperatureLabel.text = "%.2f".format(value / 100.0)
- }
- }
+ private val filesField = JBTextField().apply {
+ toolTipText = "Comma-separated list of file paths (relative to project root) to modify or create"
+ text = files.joinToString(", ") { it.relativeTo(root).path }
+ }
- private val temperatureLabel = javax.swing.JLabel("0.70")
+ private val relatedFilesField = JBTextField().apply {
+ toolTipText = "Comma-separated list of related files to consider for context"
+ }
- private val autoFixCheckbox = JBCheckBox("Auto-apply fixes", false).apply {
- toolTipText = "Automatically apply suggested changes without manual confirmation"
- }
+ private val extractContentCheckbox = JBCheckBox("Extract content from non-text files", false).apply {
+ toolTipText = "Extract text content from PDF, HTML, and other document formats"
+ }
- init {
- init()
- title = "Configure File Modification Task"
- }
+ private val includeGitDiffCheckbox = JBCheckBox("Include git diff with HEAD", false).apply {
+ toolTipText = "Include git diff information to show recent changes"
+ }
- override fun createCenterPanel(): JComponent = panel {
- group("Task Configuration") {
- row("Task Description:") {
- scrollCell(taskDescriptionArea).align(Align.FILL).comment("Describe the modifications to be made").resizableColumn()
- }.resizableRow()
+ private val visibleModelsCache by lazy { getVisibleModels() }
- row("Files to Modify:") {
- cell(filesField).align(Align.FILL).comment("Comma-separated file paths (e.g., src/main.kt, src/utils.kt)")
+ private val modelCombo = ComboBox(
+ visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
+ ).apply {
+ maximumSize = Dimension(200, 30)
+ selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
+ toolTipText = "AI model to use for this task"
}
- row("Related Files:") {
- cell(relatedFilesField).align(Align.FILL).comment("Additional files for context (optional)")
+ private val temperatureSlider = JSlider(0, 100, 70).apply {
+ addChangeListener {
+ temperatureLabel.text = "%.2f".format(value / 100.0)
+ }
}
- row {
- cell(extractContentCheckbox)
- }
+ private val temperatureLabel = javax.swing.JLabel("0.70")
- row {
- cell(includeGitDiffCheckbox)
+ private val autoFixCheckbox = JBCheckBox("Auto-apply fixes", false).apply {
+ toolTipText = "Automatically apply suggested changes without manual confirmation"
}
- }
- group("Model Settings") {
- row("Model:") {
- cell(modelCombo).align(Align.FILL).comment("AI model to use for generating modifications")
+ init {
+ init()
+ title = "Configure File Modification Task"
}
- row("Temperature:") {
- cell(temperatureSlider).align(Align.FILL).comment("Higher values = more creative, lower = more focused")
- cell(temperatureLabel)
+ override fun createCenterPanel(): JComponent = panel {
+ group("Task Configuration") {
+ row("Task Description:") {
+ scrollCell(taskDescriptionArea).align(Align.FILL).comment("Describe the modifications to be made")
+ .resizableColumn()
+ }.resizableRow()
+
+ row("Files to Modify:") {
+ cell(filesField).align(Align.FILL)
+ .comment("Comma-separated file paths (e.g., src/main.kt, src/utils.kt)")
+ }
+
+ row("Related Files:") {
+ cell(relatedFilesField).align(Align.FILL).comment("Additional files for context (optional)")
+ }
+
+ row {
+ cell(extractContentCheckbox)
+ }
+
+ row {
+ cell(includeGitDiffCheckbox)
+ }
+ }
+
+ group("Model Settings") {
+ row("Model:") {
+ cell(modelCombo).align(Align.FILL).comment("AI model to use for generating modifications")
+ }
+
+ row("Temperature:") {
+ cell(temperatureSlider).align(Align.FILL)
+ .comment("Higher values = more creative, lower = more focused")
+ cell(temperatureLabel)
+ }
+
+ row {
+ cell(autoFixCheckbox)
+ }
+ }
}
- row {
- cell(autoFixCheckbox)
- }
- }
- }
+ override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
+ if (taskDescriptionArea.text.isBlank()) {
+ return com.intellij.openapi.ui.ValidationInfo("Task description is required", taskDescriptionArea)
+ }
- override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
- if (taskDescriptionArea.text.isBlank()) {
- return com.intellij.openapi.ui.ValidationInfo("Task description is required", taskDescriptionArea)
- }
+ if (filesField.text.isBlank()) {
+ return com.intellij.openapi.ui.ValidationInfo("At least one file must be specified", filesField)
+ }
- if (filesField.text.isBlank()) {
- return com.intellij.openapi.ui.ValidationInfo("At least one file must be specified", filesField)
- }
+ return null
+ }
- return null
- }
+ fun getTaskConfig(): FileModificationTask.FileModificationTaskExecutionConfigData {
+ val files = filesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+ val relatedFiles = relatedFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+ .takeIf { it.isNotEmpty() }
+
+ return FileModificationTask.FileModificationTaskExecutionConfigData(
+ task_description = taskDescriptionArea.text,
+ files = files,
+ related_files = relatedFiles,
+ extractContent = extractContentCheckbox.isSelected,
+ includeGitDiff = includeGitDiffCheckbox.isSelected,
+ state = TaskState.Pending
+ )
+ }
- fun getTaskConfig(): FileModificationTask.FileModificationTaskExecutionConfigData {
- val files = filesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
- val relatedFiles = relatedFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }.takeIf { it.isNotEmpty() }
-
- return FileModificationTask.FileModificationTaskExecutionConfigData(
- task_description = taskDescriptionArea.text,
- files = files,
- related_files = relatedFiles,
- extractContent = extractContentCheckbox.isSelected,
- includeGitDiff = includeGitDiffCheckbox.isSelected,
- state = TaskState.Pending
- )
- }
+ fun getOrchestrationConfig(): OrchestrationConfig {
+ val selectedModel = modelCombo.selectedItem as? String
+ val model = selectedModel?.let { modelName ->
+ visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
+ }
+
+ return OrchestrationConfig(
+ "Config",
+ defaultSmartModel = model ?: AppSettingsState.instance.smartModel
+ ?: throw IllegalStateException("No model configured"),
+ defaultFastModel = AppSettingsState.instance.fastModel
+ ?: throw IllegalStateException("Fast model not configured"),
+ temperature = temperatureSlider.value / 100.0,
+ autoFix = autoFixCheckbox.isSelected,
+ workingDir = root.absolutePath,
+ shellCmd = listOf(
+ if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
+ ),
+ taskSettings = mutableMapOf(
+ FileModificationTask.FileModification.name to TaskTypeConfig(task_type = FileModificationTask.FileModification.name)
+ )
+ )
+ }
- fun getOrchestrationConfig(): OrchestrationConfig {
- val selectedModel = modelCombo.selectedItem as? String
- val model = selectedModel?.let { modelName ->
- visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
- }
-
- return OrchestrationConfig(
- "Config",
- defaultSmartModel = model ?: AppSettingsState.instance.smartModel ?: throw IllegalStateException("No model configured"),
- defaultFastModel = AppSettingsState.instance.fastModel ?: throw IllegalStateException("Fast model not configured"),
- temperature = temperatureSlider.value / 100.0,
- autoFix = autoFixCheckbox.isSelected,
- workingDir = root.absolutePath,
- shellCmd = listOf(
- if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
- ),
- taskSettings = mutableMapOf(
- FileModificationTask.FileModification.name to TaskTypeConfig(task_type = FileModificationTask.FileModification.name)
- )
- )
+ private fun getVisibleModels() =
+ ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
+ model.provider == apiData.provider && model.modelName?.isNotBlank() == true && PlanConfigDialog.isVisible(
+ model
+ )
+ } ?: listOf()
+ }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
}
- private fun getVisibleModels() = ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
- model.provider == apiData.provider && model.modelName?.isNotBlank() == true && PlanConfigDialog.isVisible(model)
- } ?: listOf()
- }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
- }
-
}
fun getFiles(e: AnActionEvent): List {
- val selectedFiles = e.getSelectedFiles()
- val relatedFiles = if (selectedFiles.isEmpty()) {
- e.getSelectedFolder()?.toFile?.absoluteFile?.let {
- FileSelectionUtils.filteredWalk(it) { file ->
- when {
- FileSelectionUtils.isLLMIgnored(file.toPath()) -> false
- it.isDirectory -> true
- else -> false
- }
- }
- } ?: emptyList()
- } else {
- selectedFiles.map { it.toFile }
- }
- return relatedFiles
+ val selectedFiles = e.getSelectedFiles()
+ val relatedFiles = if (selectedFiles.isEmpty()) {
+ e.getSelectedFolder()?.toFile?.absoluteFile?.let {
+ FileSelectionUtils.filteredWalk(it) { file ->
+ when {
+ FileSelectionUtils.isLLMIgnored(file.toPath()) -> false
+ it.isDirectory -> true
+ else -> false
+ }
+ }
+ } ?: emptyList()
+ } else {
+ selectedFiles.map { it.toFile }
+ }
+ return relatedFiles
}
diff --git a/intellij/src/main/kotlin/cognotik/actions/task/GeneratePresentationAction.kt b/intellij/src/main/kotlin/cognotik/actions/task/GeneratePresentationAction.kt
index c113e6b83..15dea7647 100644
--- a/intellij/src/main/kotlin/cognotik/actions/task/GeneratePresentationAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/task/GeneratePresentationAction.kt
@@ -41,292 +41,298 @@ import javax.swing.SpinnerNumberModel
class GeneratePresentationAction : BaseAction() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
- override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
- if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
- return true
- }
-
- override fun handle(e: AnActionEvent) {
- val root = getProjectRoot(e) ?: return
- val relatedFiles = getFiles(e)
- val dialog = GeneratePresentationTaskDialog(
- e.project, root, relatedFiles
- )
-
- if (dialog.showAndGet()) {
- try {
- val taskConfig = dialog.getTaskConfig()
- val orchestrationConfig = dialog.getOrchestrationConfig()
-
- UITools.runAsync(e.project, "Initializing Presentation Generation Task", true) { progress ->
- initializeTask(e, progress, orchestrationConfig, taskConfig, root)
- }
- } catch (ex: Exception) {
- log.error("Failed to initialize presentation generation task", ex)
- UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
- }
- }
- }
-
- private fun initializeTask(
- e: AnActionEvent,
- progress: ProgressIndicator,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: GeneratePresentationTask.GeneratePresentationTaskExecutionConfigData,
- root: File
- ) {
- progress.text = "Setting up session..."
- val session = Session.newGlobalID()
-
- DataStorage.sessionPaths[session] = root
-
- progress.text = "Starting server..."
- setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
-
- Thread {
- Thread.sleep(500)
- try {
- val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
- log.info("Opening browser to $uri")
- browse(uri)
- } catch (e: Throwable) {
- log.warn("Error opening browser", e)
- }
- }.start()
- }
-
- private fun setupTaskSession(
- session: Session, orchestrationConfig: OrchestrationConfig, taskConfig: GeneratePresentationTask.GeneratePresentationTaskExecutionConfigData, root: File
- ) {
- val app = object : SingleTaskApp(
- applicationName = "Presentation Generation Task",
- path = "/generatePresentationTask",
- showMenubar = false,
- taskType = GeneratePresentationTask.GeneratePresentation,
- taskConfig = taskConfig,
- instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
- ) {
- override fun instance(model: ApiChatModel) = model.instance() ?: throw IllegalStateException("Model or Provider not set")
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+ override fun isEnabled(event: AnActionEvent): Boolean {
+ if (!super.isEnabled(event)) return false
+ if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
+ return true
}
- app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
- SessionProxyServer.chats[session] = app
- ApplicationServer.appInfoMap[session] = AppInfoData(
- applicationName = "Presentation Generation Task", inputCnt = 0, stickyInput = false, showMenubar = false
- )
- SessionProxyServer.metadataStorage.setSessionName(
- null, session, "Presentation Generation @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
- )
- }
-
- private fun getProjectRoot(e: AnActionEvent): File? {
- val folder = e.getSelectedFolder()
- return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
- getModuleRootForFile(file)
- }
- }
-
- class GeneratePresentationTaskDialog(
- project: Project?,
- private val root: File,
- val relatedFiles: List
- ) : DialogWrapper(project) {
-
- private val taskDescriptionArea = JBTextArea(8, 40).apply {
- lineWrap = true
- wrapStyleWord = true
- toolTipText = "Describe the presentation including topic, key points, target audience, and desired style"
- }
+ override fun handle(e: AnActionEvent) {
+ val root = getProjectRoot(e) ?: return
+ val relatedFiles = getFiles(e)
+ val dialog = GeneratePresentationTaskDialog(
+ e.project, root, relatedFiles
+ )
- private val htmlFileField = JBTextField().apply {
- toolTipText = "Path for the HTML presentation file to create (must end with .html)"
- text = "${relatedFiles.firstOrNull()?.nameWithoutExtension?.let { "${it}_presentation" } ?: "presentation"}.html"
+ if (dialog.showAndGet()) {
+ try {
+ val taskConfig = dialog.getTaskConfig()
+ val orchestrationConfig = dialog.getOrchestrationConfig()
+
+ UITools.runAsync(e.project, "Initializing Presentation Generation Task", true) { progress ->
+ initializeTask(e, progress, orchestrationConfig, taskConfig, root)
+ }
+ } catch (ex: Exception) {
+ log.error("Failed to initialize presentation generation task", ex)
+ UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
+ }
+ }
}
- private val relatedFilesField = JBTextField().apply {
- toolTipText = "Comma-separated list of related files to consider for context (e.g., reference materials)"
- text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
+ private fun initializeTask(
+ e: AnActionEvent,
+ progress: ProgressIndicator,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: GeneratePresentationTask.GeneratePresentationTaskExecutionConfigData,
+ root: File
+ ) {
+ progress.text = "Setting up session..."
+ val session = Session.newGlobalID()
+
+ DataStorage.sessionPaths[session] = root
+
+ progress.text = "Starting server..."
+ setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
+
+ Thread {
+ Thread.sleep(500)
+ try {
+ val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
+ log.info("Opening browser to $uri")
+ browse(uri)
+ } catch (e: Throwable) {
+ log.warn("Error opening browser", e)
+ }
+ }.start()
}
- private val generateImagesCheckbox = JBCheckBox("Generate images for key slides", false).apply {
- toolTipText = "Use AI to generate images for important slides in the presentation"
- addActionListener {
- imageCountSpinner.isEnabled = isSelected
- imageModelCombo.isEnabled = isSelected
- }
- }
+ private fun setupTaskSession(
+ session: Session,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: GeneratePresentationTask.GeneratePresentationTaskExecutionConfigData,
+ root: File
+ ) {
+ val app = object : SingleTaskApp(
+ applicationName = "Presentation Generation Task",
+ path = "/generatePresentationTask",
+ showMenubar = false,
+ taskType = GeneratePresentationTask.GeneratePresentation,
+ taskConfig = taskConfig,
+ instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
+ ) {
+ override fun instance(model: ApiChatModel) =
+ model.instance() ?: throw IllegalStateException("Model or Provider not set")
+ }
- private val imageCountSpinner = JSpinner(SpinnerNumberModel(5, 1, 10, 1)).apply {
- toolTipText = "Maximum number of images to generate (1-10)"
- isEnabled = false
+ app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
+ SessionProxyServer.chats[session] = app
+ ApplicationServer.appInfoMap[session] = AppInfoData(
+ applicationName = "Presentation Generation Task", inputCnt = 0, stickyInput = false, showMenubar = false
+ )
+ SessionProxyServer.metadataStorage.setSessionName(
+ null,
+ session,
+ "Presentation Generation @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
+ )
}
- private val visibleModelsCache by lazy { getVisibleModels() }
-
- private val modelCombo = ComboBox(
- visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
- ).apply {
- maximumSize = Dimension(200, 30)
- selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
- toolTipText = "AI model to use for generating presentation content"
+ private fun getProjectRoot(e: AnActionEvent): File? {
+ val folder = e.getSelectedFolder()
+ return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
+ getModuleRootForFile(file)
+ }
}
- private val imageModelCombo = ComboBox(
- visibleModelsCache
- .distinctBy { it.modelName }
- .map { it.modelName }
- .toTypedArray()
- ).apply {
- maximumSize = Dimension(200, 30)
- selectedItem = AppSettingsState.instance.imageChatModel?.model?.modelName
- toolTipText = "AI model to use for generating images"
- isEnabled = false
- }
+ class GeneratePresentationTaskDialog(
+ project: Project?,
+ private val root: File,
+ val relatedFiles: List
+ ) : DialogWrapper(project) {
- private val temperatureSlider = JSlider(0, 100, 70).apply {
- addChangeListener {
- temperatureLabel.text = "%.2f".format(value / 100.0)
- }
- }
-
- private val temperatureLabel = javax.swing.JLabel("0.70")
+ private val taskDescriptionArea = JBTextArea(8, 40).apply {
+ lineWrap = true
+ wrapStyleWord = true
+ toolTipText = "Describe the presentation including topic, key points, target audience, and desired style"
+ }
- private val autoFixCheckbox = JBCheckBox("Auto-apply generated presentation", false).apply {
- toolTipText = "Automatically write the generated presentation files without manual confirmation"
- }
+ private val htmlFileField = JBTextField().apply {
+ toolTipText = "Path for the HTML presentation file to create (must end with .html)"
+ text =
+ "${relatedFiles.firstOrNull()?.nameWithoutExtension?.let { "${it}_presentation" } ?: "presentation"}.html"
+ }
- init {
- init()
- title = "Configure Presentation Generation Task"
- }
+ private val relatedFilesField = JBTextField().apply {
+ toolTipText = "Comma-separated list of related files to consider for context (e.g., reference materials)"
+ text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
+ }
- override fun createCenterPanel(): JComponent = panel {
- group("Presentation Configuration") {
- row("HTML File:") {
- cell(htmlFileField)
- .align(Align.FILL)
- .comment("Output path for the presentation file (e.g., presentation.html, slides/demo.html)")
+ private val generateImagesCheckbox = JBCheckBox("Generate images for key slides", false).apply {
+ toolTipText = "Use AI to generate images for important slides in the presentation"
+ addActionListener {
+ imageCountSpinner.isEnabled = isSelected
+ imageModelCombo.isEnabled = isSelected
+ }
}
- row("Presentation Description:") {
- scrollCell(taskDescriptionArea)
- .align(Align.FILL)
- .comment("Describe the presentation topic, key points, target audience, number of slides, and style preferences")
- .resizableColumn()
- }.resizableRow()
-
- row("Related Files:") {
- cell(relatedFilesField)
- .align(Align.FILL)
- .comment("Additional files for context (optional)")
+ private val imageCountSpinner = JSpinner(SpinnerNumberModel(5, 1, 10, 1)).apply {
+ toolTipText = "Maximum number of images to generate (1-10)"
+ isEnabled = false
}
- }
- group("Image Generation") {
- row {
- cell(generateImagesCheckbox)
+ private val visibleModelsCache by lazy { getVisibleModels() }
+
+ private val modelCombo = ComboBox(
+ visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
+ ).apply {
+ maximumSize = Dimension(200, 30)
+ selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
+ toolTipText = "AI model to use for generating presentation content"
}
- row("Maximum Images:") {
- cell(imageCountSpinner)
- .comment("Maximum number of images to generate for key slides (1-10)")
+ private val imageModelCombo = ComboBox(
+ visibleModelsCache
+ .distinctBy { it.modelName }
+ .map { it.modelName }
+ .toTypedArray()
+ ).apply {
+ maximumSize = Dimension(200, 30)
+ selectedItem = AppSettingsState.instance.imageChatModel?.model?.modelName
+ toolTipText = "AI model to use for generating images"
+ isEnabled = false
}
- row("Image Model:") {
- cell(imageModelCombo)
- .align(Align.FILL)
- .comment("AI model for image generation")
+ private val temperatureSlider = JSlider(0, 100, 70).apply {
+ addChangeListener {
+ temperatureLabel.text = "%.2f".format(value / 100.0)
+ }
}
- }
- group("Model Settings") {
- row("Text Model:") {
- cell(modelCombo)
- .align(Align.FILL)
- .comment("AI model for generating presentation content")
+ private val temperatureLabel = javax.swing.JLabel("0.70")
+
+ private val autoFixCheckbox = JBCheckBox("Auto-apply generated presentation", false).apply {
+ toolTipText = "Automatically write the generated presentation files without manual confirmation"
}
- row("Temperature:") {
- cell(temperatureSlider)
- .align(Align.FILL)
- .comment("Higher values = more creative, lower = more focused")
- cell(temperatureLabel)
+ init {
+ init()
+ title = "Configure Presentation Generation Task"
}
- row {
- cell(autoFixCheckbox)
+ override fun createCenterPanel(): JComponent = panel {
+ group("Presentation Configuration") {
+ row("HTML File:") {
+ cell(htmlFileField)
+ .align(Align.FILL)
+ .comment("Output path for the presentation file (e.g., presentation.html, slides/demo.html)")
+ }
+
+ row("Presentation Description:") {
+ scrollCell(taskDescriptionArea)
+ .align(Align.FILL)
+ .comment("Describe the presentation topic, key points, target audience, number of slides, and style preferences")
+ .resizableColumn()
+ }.resizableRow()
+
+ row("Related Files:") {
+ cell(relatedFilesField)
+ .align(Align.FILL)
+ .comment("Additional files for context (optional)")
+ }
+ }
+
+ group("Image Generation") {
+ row {
+ cell(generateImagesCheckbox)
+ }
+
+ row("Maximum Images:") {
+ cell(imageCountSpinner)
+ .comment("Maximum number of images to generate for key slides (1-10)")
+ }
+
+ row("Image Model:") {
+ cell(imageModelCombo)
+ .align(Align.FILL)
+ .comment("AI model for image generation")
+ }
+ }
+
+ group("Model Settings") {
+ row("Text Model:") {
+ cell(modelCombo)
+ .align(Align.FILL)
+ .comment("AI model for generating presentation content")
+ }
+
+ row("Temperature:") {
+ cell(temperatureSlider)
+ .align(Align.FILL)
+ .comment("Higher values = more creative, lower = more focused")
+ cell(temperatureLabel)
+ }
+
+ row {
+ cell(autoFixCheckbox)
+ }
+ }
}
- }
- }
- override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
- if (htmlFileField.text.isBlank()) {
- return com.intellij.openapi.ui.ValidationInfo("HTML file path is required", htmlFileField)
- }
+ override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
+ if (htmlFileField.text.isBlank()) {
+ return com.intellij.openapi.ui.ValidationInfo("HTML file path is required", htmlFileField)
+ }
+
+ if (!htmlFileField.text.endsWith(".html", ignoreCase = true)) {
+ return com.intellij.openapi.ui.ValidationInfo("File must have .html extension", htmlFileField)
+ } else {
+ if (htmlFileField.text.let { root.resolve(it) }.exists()) {
+ return com.intellij.openapi.ui.ValidationInfo("HTML file path must not exist", htmlFileField)
+ }
+ }
- if (!htmlFileField.text.endsWith(".html", ignoreCase = true)) {
- return com.intellij.openapi.ui.ValidationInfo("File must have .html extension", htmlFileField)
- } else {
- if (htmlFileField.text.let { root.resolve(it) }.exists()) {
- return com.intellij.openapi.ui.ValidationInfo("HTML file path must not exist", htmlFileField)
+ return null
}
- }
- return null
- }
+ fun getTaskConfig(): GeneratePresentationTask.GeneratePresentationTaskExecutionConfigData {
+ val relatedFiles = relatedFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+ .takeIf { it.isNotEmpty() }
+
+ return GeneratePresentationTask.GeneratePresentationTaskExecutionConfigData(
+ files = listOf(htmlFileField.text),
+ related_files = relatedFiles,
+ task_description = taskDescriptionArea.text,
+ generate_images = generateImagesCheckbox.isSelected,
+ max_images = imageCountSpinner.value as Int,
+ state = TaskState.Pending
+ )
+ }
- fun getTaskConfig(): GeneratePresentationTask.GeneratePresentationTaskExecutionConfigData {
- val relatedFiles = relatedFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
- .takeIf { it.isNotEmpty() }
-
- return GeneratePresentationTask.GeneratePresentationTaskExecutionConfigData(
- files = listOf(htmlFileField.text),
- related_files = relatedFiles,
- task_description = taskDescriptionArea.text,
- generate_images = generateImagesCheckbox.isSelected,
- image_model = imageModelCombo.selectedItem as? String ?: "DallE3",
- max_images = imageCountSpinner.value as Int,
- state = TaskState.Pending
- )
- }
+ fun getOrchestrationConfig(): OrchestrationConfig {
+ val selectedModel = modelCombo.selectedItem as? String
+ val model = selectedModel?.let { modelName ->
+ visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
+ }
+
+ val selectedImageModel = imageModelCombo.selectedItem as? String
+ val imageModel = selectedImageModel?.let { modelName ->
+ visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
+ }
+
+ return OrchestrationConfig(
+ "Config",
+ defaultSmartModel = model ?: AppSettingsState.instance.smartModel
+ ?: throw IllegalStateException("No model configured"),
+ defaultFastModel = AppSettingsState.instance.fastModel
+ ?: throw IllegalStateException("Fast model not configured"),
+ defaultImageModel = imageModel ?: AppSettingsState.instance.imageChatModel
+ ?: throw IllegalStateException("No image model configured"),
+ temperature = temperatureSlider.value / 100.0,
+ autoFix = autoFixCheckbox.isSelected,
+ workingDir = root.absolutePath,
+ shellCmd = listOf(
+ if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
+ )
+ )
+ }
- fun getOrchestrationConfig(): OrchestrationConfig {
- val selectedModel = modelCombo.selectedItem as? String
- val model = selectedModel?.let { modelName ->
- visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
- }
-
- val selectedImageModel = imageModelCombo.selectedItem as? String
- val imageModel = selectedImageModel?.let { modelName ->
- visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
- }
-
- return OrchestrationConfig(
- "Config",
- defaultSmartModel = model ?: AppSettingsState.instance.smartModel
- ?: throw IllegalStateException("No model configured"),
- defaultFastModel = AppSettingsState.instance.fastModel
- ?: throw IllegalStateException("Fast model not configured"),
- defaultImageModel = imageModel ?: AppSettingsState.instance.imageChatModel
- ?: throw IllegalStateException("No image model configured"),
- temperature = temperatureSlider.value / 100.0,
- autoFix = autoFixCheckbox.isSelected,
- workingDir = root.absolutePath,
- shellCmd = listOf(
- if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
- )
- )
+ private fun getVisibleModels() =
+ ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
+ model.provider == apiData.provider &&
+ model.modelName?.isNotBlank() == true &&
+ PlanConfigDialog.isVisible(model)
+ } ?: listOf()
+ }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
}
-
- private fun getVisibleModels() =
- ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
- model.provider == apiData.provider &&
- model.modelName?.isNotBlank() == true &&
- PlanConfigDialog.isVisible(model)
- } ?: listOf()
- }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
- }
}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/task/IllustrateDocumentAction.kt b/intellij/src/main/kotlin/cognotik/actions/task/IllustrateDocumentAction.kt
index 75aaec4c5..716d68d4b 100644
--- a/intellij/src/main/kotlin/cognotik/actions/task/IllustrateDocumentAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/task/IllustrateDocumentAction.kt
@@ -40,264 +40,265 @@ import javax.swing.SpinnerNumberModel
class IllustrateDocumentAction : BaseAction() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
-
- override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
- val selectedFile = event.getSelectedFile()
- if (selectedFile == null) return false
- val fileName = selectedFile.name.lowercase()
- return fileName.endsWith(".md") || fileName.endsWith(".html")
- }
-
-override fun handle(e: AnActionEvent) {
- val root = getProjectRoot(e) ?: return
- val selectedFile = e.getSelectedFile() ?: return
-
- val dialog = IllustrateDocumentTaskDialog(
- e.project, root, selectedFile.toFile
- )
-
- if (dialog.showAndGet()) {
- try {
- val taskConfig = dialog.getTaskConfig()
- val orchestrationConfig = dialog.getOrchestrationConfig()
-
- val session = Session.newGlobalID()
- DataStorage.sessionPaths[session] = root
-
- UITools.runAsync(e.project, "Initializing Document Illustration Task", true) { progress ->
- initializeTask(progress, orchestrationConfig, taskConfig, session)
- }
- } catch (ex: Exception) {
- log.error("Failed to initialize document illustration task", ex)
- UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
- }
- }
- }
-
- private fun initializeTask(
- progress: ProgressIndicator,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: IllustrateDocumentTask.IllustrateDocumentTaskExecutionConfigData,
- session: Session
- ) {
-
-
- progress.text = "Starting server..."
- setupTaskSession(session, orchestrationConfig, taskConfig)
-
- progress.text = "Opening browser..."
- try {
- Thread.sleep(500)
- val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
- log.info("Opening browser to $uri")
- browse(uri)
- } catch (e: Throwable) {
- log.warn("Error opening browser", e)
- }
- }
-
- private fun setupTaskSession(
- session: Session,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: IllustrateDocumentTask.IllustrateDocumentTaskExecutionConfigData
- ) {
- val app = object : SingleTaskApp(
- applicationName = "Document Illustration Task",
- path = "/illustrateDocumentTask",
- showMenubar = false,
- taskType = IllustrateDocumentTask.IllustrateDocument,
- taskConfig = taskConfig,
- instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
- ) {
- override fun instance(model: ApiChatModel) = model.instance() ?: throw IllegalStateException("Model or Provider not set")
- }
-
- app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
- SessionProxyServer.chats[session] = app
- ApplicationServer.appInfoMap[session] = AppInfoData(
- applicationName = "Document Illustration Task",
- inputCnt = 0,
- stickyInput = false,
- showMenubar = false
- )
- SessionProxyServer.metadataStorage.setSessionName(
- null, session, "Document Illustration @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
- )
- }
-
- private fun getProjectRoot(e: AnActionEvent): File? {
- val folder = e.getSelectedFolder()
- return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
- getModuleRootForFile(file)
- }
- }
-
- class IllustrateDocumentTaskDialog(
- project: Project?,
- private val root: File,
- private val documentFile: File
- ) : DialogWrapper(project) {
-
- private val documentPathField = JBTextField().apply {
- text = documentFile.relativeTo(root).path
- isEditable = false
- toolTipText = "Document to illustrate (Markdown or HTML)"
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+
+ override fun isEnabled(event: AnActionEvent): Boolean {
+ if (!super.isEnabled(event)) return false
+ val selectedFile = event.getSelectedFile()
+ if (selectedFile == null) return false
+ val fileName = selectedFile.name.lowercase()
+ return fileName.endsWith(".md") || fileName.endsWith(".html")
}
- private val maxImagesSpinner = JSpinner(SpinnerNumberModel(5, 1, 20, 1)).apply {
- toolTipText = "Maximum number of images to generate (1-20)"
- }
+ override fun handle(e: AnActionEvent) {
+ val root = getProjectRoot(e) ?: return
+ val selectedFile = e.getSelectedFile() ?: return
- private val imageFormatCombo = ComboBox(arrayOf("png", "jpg", "jpeg")).apply {
- selectedItem = "png"
- toolTipText = "Image format for generated files"
- }
+ val dialog = IllustrateDocumentTaskDialog(
+ e.project, root, selectedFile.toFile
+ )
- private val autoInsertCheckbox = JBCheckBox("Automatically insert image references", true).apply {
- toolTipText = "Insert image references into the document at appropriate locations"
- }
- private val imageInstructionsField = com.intellij.ui.components.JBTextArea().apply {
- text = ""
- rows = 3
- lineWrap = true
- wrapStyleWord = true
- toolTipText = "Additional instructions for image generation (e.g., 'Use a minimalist style', 'Include company branding colors')"
- }
- private val composerDirectiveField = com.intellij.ui.components.JBTextArea().apply {
- text = ""
- rows = 3
- lineWrap = true
- wrapStyleWord = true
- toolTipText = "Directives for the image composer (e.g., 'Generate a background wallpaper', 'Create hero images', 'Focus on technical diagrams')"
- }
- private val integratorDirectiveField = com.intellij.ui.components.JBTextArea().apply {
- text = ""
- rows = 3
- lineWrap = true
- wrapStyleWord = true
- toolTipText = "Directives for the image integrator (e.g., 'Insert as page background', 'Place images in sidebars', 'Create image galleries')"
+ if (dialog.showAndGet()) {
+ try {
+ val taskConfig = dialog.getTaskConfig()
+ val orchestrationConfig = dialog.getOrchestrationConfig()
+
+ val session = Session.newGlobalID()
+ DataStorage.sessionPaths[session] = root
+
+ UITools.runAsync(e.project, "Initializing Document Illustration Task", true) { progress ->
+ initializeTask(progress, orchestrationConfig, taskConfig, session)
+ }
+ } catch (ex: Exception) {
+ log.error("Failed to initialize document illustration task", ex)
+ UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
+ }
+ }
}
-
- private val taskDescriptionField = com.intellij.ui.components.JBTextArea().apply {
- text = "Illustrate document: ${documentFile.name}"
- rows = 3
- lineWrap = true
- wrapStyleWord = true
- toolTipText = "Description of the illustration task"
+ private fun initializeTask(
+ progress: ProgressIndicator,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: IllustrateDocumentTask.IllustrateDocumentTaskExecutionConfigData,
+ session: Session
+ ) {
+ progress.text = "Starting server..."
+ setupTaskSession(session, orchestrationConfig, taskConfig)
+ try {
+ Thread.sleep(500)
+ progress.text = "Opening browser..."
+ val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
+ log.info("Opening browser to $uri")
+ browse(uri)
+ } catch (e: Throwable) {
+ log.warn("Error opening browser", e)
+ }
}
+ private fun setupTaskSession(
+ session: Session,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: IllustrateDocumentTask.IllustrateDocumentTaskExecutionConfigData
+ ) {
+ val app = object : SingleTaskApp(
+ applicationName = "Document Illustration Task",
+ path = "/illustrateDocumentTask",
+ showMenubar = false,
+ taskType = IllustrateDocumentTask.IllustrateDocument,
+ taskConfig = taskConfig,
+ instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
+ ) {
+ override fun instance(model: ApiChatModel) =
+ model.instance() ?: throw IllegalStateException("Model or Provider not set")
+ }
- private val visibleModelsCache by lazy { getVisibleModels() }
-
- private val textModelCombo = ComboBox(
- visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
- ).apply {
- maximumSize = Dimension(200, 30)
- selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
- toolTipText = "AI model for analyzing document and generating image prompts"
- }
-
- private val imageModelCombo = ComboBox(
- visibleModelsCache
- .distinctBy { it.modelName }
- .map { it.modelName }
- .toTypedArray()
- ).apply {
- maximumSize = Dimension(200, 30)
- selectedItem = AppSettingsState.instance.imageChatModel?.model?.modelName
- toolTipText = "AI model for generating images"
+ app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
+ SessionProxyServer.chats[session] = app
+ ApplicationServer.appInfoMap[session] = AppInfoData(
+ applicationName = "Document Illustration Task",
+ inputCnt = 0,
+ stickyInput = false,
+ showMenubar = false
+ )
+ SessionProxyServer.metadataStorage.setSessionName(
+ null, session, "Document Illustration @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
+ )
}
- private val temperatureSlider = JSlider(0, 100, 50).apply {
- addChangeListener {
- temperatureLabel.text = "%.2f".format(value / 100.0)
- }
+ private fun getProjectRoot(e: AnActionEvent): File? {
+ val folder = e.getSelectedFolder()
+ return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
+ getModuleRootForFile(file)
+ }
}
- private val temperatureLabel = javax.swing.JLabel("0.50")
+ class IllustrateDocumentTaskDialog(
+ project: Project?,
+ private val root: File,
+ private val documentFile: File
+ ) : DialogWrapper(project) {
- init {
- init()
- title = "Configure Document Illustration Task"
- }
+ private val documentPathField = JBTextField().apply {
+ text = documentFile.relativeTo(root).path
+ isEditable = false
+ toolTipText = "Document to illustrate (Markdown or HTML)"
+ }
- override fun createCenterPanel(): JComponent = panel {
- group("Document Configuration") {
- row("Document File:") {
- cell(documentPathField)
- .align(Align.FILL)
- .comment("The Markdown or HTML document to illustrate")
+ private val maxImagesSpinner = JSpinner(SpinnerNumberModel(5, 1, 20, 1)).apply {
+ toolTipText = "Maximum number of images to generate (1-20)"
}
- row("Max Images:") {
- cell(maxImagesSpinner)
- .comment("Maximum number of images to generate (1-20)")
+ private val imageFormatCombo = ComboBox(arrayOf("png", "jpg", "jpeg")).apply {
+ selectedItem = "png"
+ toolTipText = "Image format for generated files"
}
- row("Image Format:") {
- cell(imageFormatCombo)
- .comment("File format for generated images")
+ private val autoInsertCheckbox = JBCheckBox("Automatically insert image references", true).apply {
+ toolTipText = "Insert image references into the document at appropriate locations"
}
- }
-
- row {
- cell(autoInsertCheckbox)
- .comment("Automatically insert image references at appropriate locations in the document")
- }
-
- row("Image Instructions:") {
- scrollCell(imageInstructionsField)
- .align(Align.FILL)
- .comment("Additional instructions to append to all image generation prompts (optional)")
- }
- group("Advanced Directives") {
- row("Composer Directive:") {
- scrollCell(composerDirectiveField)
- .align(Align.FILL)
- .comment("Instructions for how to compose and generate images (e.g., 'Generate a background wallpaper for the page', 'Create hero images for each section')")
+ private val imageInstructionsField = com.intellij.ui.components.JBTextArea().apply {
+ text = ""
+ rows = 3
+ lineWrap = true
+ wrapStyleWord = true
+ toolTipText =
+ "Additional instructions for image generation (e.g., 'Use a minimalist style', 'Include company branding colors')"
}
- row("Integrator Directive:") {
- scrollCell(integratorDirectiveField)
- .align(Align.FILL)
- .comment("Instructions for how to integrate images into the document (e.g., 'Insert as CSS background', 'Create floating sidebars', 'Build image galleries')")
+ private val composerDirectiveField = com.intellij.ui.components.JBTextArea().apply {
+ text = ""
+ rows = 3
+ lineWrap = true
+ wrapStyleWord = true
+ toolTipText =
+ "Directives for the image composer (e.g., 'Generate a background wallpaper', 'Create hero images', 'Focus on technical diagrams')"
+ }
+ private val integratorDirectiveField = com.intellij.ui.components.JBTextArea().apply {
+ text = ""
+ rows = 3
+ lineWrap = true
+ wrapStyleWord = true
+ toolTipText =
+ "Directives for the image integrator (e.g., 'Insert as page background', 'Place images in sidebars', 'Create image galleries')"
}
- }
- // row("Task Description:") {
- // scrollCell(taskDescriptionField)
- // .align(Align.FILL)
-// .comment("Describe what you want to achieve with this illustration task")
-// }
+ private val taskDescriptionField = com.intellij.ui.components.JBTextArea().apply {
+ text = "Illustrate document: ${documentFile.name}"
+ rows = 3
+ lineWrap = true
+ wrapStyleWord = true
+ toolTipText = "Description of the illustration task"
+ }
+
+
+ private val visibleModelsCache by lazy { getVisibleModels() }
- group("Model Settings") {
- row("Text Model:") {
- cell(textModelCombo)
- .align(Align.FILL)
- .comment("AI model for document analysis and image prompt generation")
+ private val textModelCombo = ComboBox(
+ visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
+ ).apply {
+ maximumSize = Dimension(200, 30)
+ selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
+ toolTipText = "AI model for analyzing document and generating image prompts"
}
- row("Image Model:") {
- cell(imageModelCombo)
- .align(Align.FILL)
- .comment("AI model for generating images")
+ private val imageModelCombo = ComboBox(
+ visibleModelsCache
+ .distinctBy { it.modelName }
+ .map { it.modelName }
+ .toTypedArray()
+ ).apply {
+ maximumSize = Dimension(200, 30)
+ selectedItem = AppSettingsState.instance.imageChatModel?.model?.modelName
+ toolTipText = "AI model for generating images"
}
- row("Temperature:") {
- cell(temperatureSlider)
- .align(Align.FILL)
- .comment("Higher values = more creative, lower = more focused")
- cell(temperatureLabel)
+ private val temperatureSlider = JSlider(0, 100, 50).apply {
+ addChangeListener {
+ temperatureLabel.text = "%.2f".format(value / 100.0)
+ }
+ }
+
+ private val temperatureLabel = javax.swing.JLabel("0.50")
+
+ init {
+ init()
+ title = "Configure Document Illustration Task"
}
- }
- group("About") {
- row {
- text(
- """
+ override fun createCenterPanel(): JComponent = panel {
+ group("Document Configuration") {
+ row("Document File:") {
+ cell(documentPathField)
+ .align(Align.FILL)
+ .comment("The Markdown or HTML document to illustrate")
+ }
+
+ row("Max Images:") {
+ cell(maxImagesSpinner)
+ .comment("Maximum number of images to generate (1-20)")
+ }
+
+ row("Image Format:") {
+ cell(imageFormatCombo)
+ .comment("File format for generated images")
+ }
+ }
+
+ row {
+ cell(autoInsertCheckbox)
+ .comment("Automatically insert image references at appropriate locations in the document")
+ }
+
+ row("Image Instructions:") {
+ scrollCell(imageInstructionsField)
+ .align(Align.FILL)
+ .comment("Additional instructions to append to all image generation prompts (optional)")
+ }
+ group("Advanced Directives") {
+ row("Composer Directive:") {
+ scrollCell(composerDirectiveField)
+ .align(Align.FILL)
+ .comment("Instructions for how to compose and generate images (e.g., 'Generate a background wallpaper for the page', 'Create hero images for each section')")
+ }
+ row("Integrator Directive:") {
+ scrollCell(integratorDirectiveField)
+ .align(Align.FILL)
+ .comment("Instructions for how to integrate images into the document (e.g., 'Insert as CSS background', 'Create floating sidebars', 'Build image galleries')")
+ }
+ }
+
+
+ // row("Task Description:") {
+ // scrollCell(taskDescriptionField)
+ // .align(Align.FILL)
+// .comment("Describe what you want to achieve with this illustration task")
+// }
+
+ group("Model Settings") {
+ row("Text Model:") {
+ cell(textModelCombo)
+ .align(Align.FILL)
+ .comment("AI model for document analysis and image prompt generation")
+ }
+
+ row("Image Model:") {
+ cell(imageModelCombo)
+ .align(Align.FILL)
+ .comment("AI model for generating images")
+ }
+
+ row("Temperature:") {
+ cell(temperatureSlider)
+ .align(Align.FILL)
+ .comment("Higher values = more creative, lower = more focused")
+ cell(temperatureLabel)
+ }
+ }
+
+ group("About") {
+ row {
+ text(
+ """
This task will:
- Analyze your document to identify sections that would benefit from images
@@ -306,71 +307,73 @@ override fun handle(e: AnActionEvent) {
- Optionally insert image references at appropriate locations
""".trimIndent()
- )
+ )
+ }
+ }
}
- }
- }
- override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
- if (!documentFile.exists()) {
- return com.intellij.openapi.ui.ValidationInfo("Document file does not exist", documentPathField)
- }
+ override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
+ if (!documentFile.exists()) {
+ return com.intellij.openapi.ui.ValidationInfo("Document file does not exist", documentPathField)
+ }
- val fileName = documentFile.name.lowercase()
- if (!fileName.endsWith(".md") && !fileName.endsWith(".html")) {
- return com.intellij.openapi.ui.ValidationInfo("Document must be .md or .html file", documentPathField)
- }
+ val fileName = documentFile.name.lowercase()
+ if (!fileName.endsWith(".md") && !fileName.endsWith(".html")) {
+ return com.intellij.openapi.ui.ValidationInfo("Document must be .md or .html file", documentPathField)
+ }
- return null
- }
+ return null
+ }
- fun getTaskConfig(): IllustrateDocumentTask.IllustrateDocumentTaskExecutionConfigData {
- return IllustrateDocumentTask.IllustrateDocumentTaskExecutionConfigData(
- files = listOf(documentFile.relativeTo(root).path),
- maxImages = maxImagesSpinner.value as Int,
- imageFormat = imageFormatCombo.selectedItem as String,
- autoInsert = autoInsertCheckbox.isSelected,
- imageInstructions = imageInstructionsField.text.takeIf { it.isNotBlank() },
- composerDirective = composerDirectiveField.text.takeIf { it.isNotBlank() },
- integratorDirective = integratorDirectiveField.text.takeIf { it.isNotBlank() },
- task_description = taskDescriptionField.text,
- state = TaskState.Pending
- )
- }
+ fun getTaskConfig(): IllustrateDocumentTask.IllustrateDocumentTaskExecutionConfigData {
+ return IllustrateDocumentTask.IllustrateDocumentTaskExecutionConfigData(
+ files = listOf(documentFile.relativeTo(root).path),
+ maxImages = maxImagesSpinner.value as Int,
+ imageFormat = imageFormatCombo.selectedItem as String,
+ autoInsert = autoInsertCheckbox.isSelected,
+ imageInstructions = imageInstructionsField.text.takeIf { it.isNotBlank() },
+ composerDirective = composerDirectiveField.text.takeIf { it.isNotBlank() },
+ integratorDirective = integratorDirectiveField.text.takeIf { it.isNotBlank() },
+ task_description = taskDescriptionField.text,
+ state = TaskState.Pending
+ )
+ }
- fun getOrchestrationConfig(): OrchestrationConfig {
- val selectedTextModel = textModelCombo.selectedItem as? String
- val textModel = selectedTextModel?.let { modelName ->
- visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
- }
-
- val selectedImageModel = imageModelCombo.selectedItem as? String
- val imageModel = selectedImageModel?.let { modelName ->
- visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
- }
-
- return OrchestrationConfig(
- "Config",
- defaultSmartModel = textModel ?: AppSettingsState.instance.smartModel
- ?: throw IllegalStateException("No model configured"),
- defaultFastModel = AppSettingsState.instance.fastModel
- ?: throw IllegalStateException("Fast model not configured"),
- defaultImageModel = imageModel ?: AppSettingsState.instance.imageChatModel
- ?: throw IllegalStateException("No image model configured"),
- temperature = temperatureSlider.value / 100.0,
- autoFix = true,
- workingDir = root.absolutePath,
- shellCmd = listOf(
- if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
- )
- )
- }
+ fun getOrchestrationConfig(): OrchestrationConfig {
+ val selectedTextModel = textModelCombo.selectedItem as? String
+ val textModel = selectedTextModel?.let { modelName ->
+ visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
+ }
+
+ val selectedImageModel = imageModelCombo.selectedItem as? String
+ val imageModel = selectedImageModel?.let { modelName ->
+ visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
+ }
+
+ return OrchestrationConfig(
+ "Config",
+ defaultSmartModel = textModel ?: AppSettingsState.instance.smartModel
+ ?: throw IllegalStateException("No model configured"),
+ defaultFastModel = AppSettingsState.instance.fastModel
+ ?: throw IllegalStateException("Fast model not configured"),
+ defaultImageModel = imageModel ?: AppSettingsState.instance.imageChatModel
+ ?: throw IllegalStateException("No image model configured"),
+ temperature = temperatureSlider.value / 100.0,
+ autoFix = true,
+ workingDir = root.absolutePath,
+ shellCmd = listOf(
+ if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
+ )
+ )
+ }
- private fun getVisibleModels() =
- fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
- model.provider == apiData.provider && model.modelName.isNotBlank() && PlanConfigDialog.isVisible(model)
- } ?: listOf()
- }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
- }
+ private fun getVisibleModels() =
+ fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
+ model.provider == apiData.provider && model.modelName.isNotBlank() && PlanConfigDialog.isVisible(
+ model
+ )
+ } ?: listOf()
+ }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
+ }
}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/task/NarrativeGenerationAction.kt b/intellij/src/main/kotlin/cognotik/actions/task/NarrativeGenerationAction.kt
index 506d238d5..c1d56d60f 100644
--- a/intellij/src/main/kotlin/cognotik/actions/task/NarrativeGenerationAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/task/NarrativeGenerationAction.kt
@@ -41,394 +41,396 @@ import javax.swing.SpinnerNumberModel
class NarrativeGenerationAction : BaseAction() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
- override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
- if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
- return true
- }
-
- override fun handle(e: AnActionEvent) {
- val root = getProjectRoot(e) ?: return
- val relatedFiles = getFiles(e)
- val dialog = NarrativeGenerationDialog(
- e.project, root, relatedFiles
- )
-
- if (dialog.showAndGet()) {
- try {
- val taskConfig = dialog.getTaskConfig()
- val orchestrationConfig = dialog.getOrchestrationConfig()
-
- UITools.runAsync(e.project, "Initializing Narrative Generation Task", true) { progress ->
- initializeTask(e, progress, orchestrationConfig, taskConfig, root)
- }
- } catch (ex: Exception) {
- log.error("Failed to initialize narrative generation task", ex)
- UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
- }
- }
- }
-
- private fun initializeTask(
- e: AnActionEvent,
- progress: ProgressIndicator,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: NarrativeGenerationTask.NarrativeGenerationTaskExecutionConfigData,
- root: File
- ) {
- progress.text = "Setting up session..."
- val session = Session.newGlobalID()
-
- DataStorage.sessionPaths[session] = root
-
- progress.text = "Starting server..."
- setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
-
- Thread {
- Thread.sleep(500)
- try {
- val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
- log.info("Opening browser to $uri")
- browse(uri)
- } catch (e: Throwable) {
- log.warn("Error opening browser", e)
- }
- }.start()
- }
-
- private fun setupTaskSession(
- session: Session,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: NarrativeGenerationTask.NarrativeGenerationTaskExecutionConfigData,
- root: File
- ) {
- val app = object : SingleTaskApp(
- applicationName = "Narrative Generation Task",
- path = "/narrativeGenerationTask",
- showMenubar = false,
- taskType = NarrativeGenerationTask.NarrativeGeneration,
- taskConfig = taskConfig,
- instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
- ) {
- override fun instance(model: ApiChatModel) = model.instance() ?: throw IllegalStateException("Model or Provider not set")
- }
-
- app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
- SessionProxyServer.chats[session] = app
- ApplicationServer.appInfoMap[session] = AppInfoData(
- applicationName = "Narrative Generation Task", inputCnt = 0, stickyInput = false, showMenubar = false
- )
- SessionProxyServer.metadataStorage.setSessionName(
- null, session, "Narrative Generation @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
- )
- }
-
- private fun getProjectRoot(e: AnActionEvent): File? {
- val folder = e.getSelectedFolder()
- return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
- getModuleRootForFile(file)
- }
- }
-
- class NarrativeGenerationDialog(
- project: Project?,
- private val root: File,
- val relatedFiles: List
- ) : DialogWrapper(project) {
-
- private val subjectField = JBTextField().apply {
- toolTipText = "The subject or scenario to develop into a full narrative"
- text = "A compelling story"
- }
-
- private val inputFilesField = JBTextField().apply {
- toolTipText = "Comma-separated file patterns for context (e.g., **/*.kt, docs/*.md)"
- text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
- }
-
- private val targetWordCountSpinner = JSpinner(SpinnerNumberModel(5000, 500, 50000, 500)).apply {
- toolTipText = "Target word count for the complete narrative"
- }
-
- private val numberOfActsSpinner = JSpinner(SpinnerNumberModel(3, 1, 10, 1)).apply {
- toolTipText = "Number of acts in the narrative structure (typically 3 or 5)"
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+ override fun isEnabled(event: AnActionEvent): Boolean {
+ if (!super.isEnabled(event)) return false
+ if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
+ return true
}
- private val scenesPerActSpinner = JSpinner(SpinnerNumberModel(3, 1, 10, 1)).apply {
- toolTipText = "Average number of scenes per act"
- }
-
- private val writingStyleCombo = ComboBox(
- arrayOf("literary", "thriller", "technical", "conversational", "academic", "journalistic")
- ).apply {
- selectedItem = "literary"
- toolTipText = "Writing style for the narrative"
- }
-
- private val pointOfViewCombo = ComboBox(
- arrayOf("first person", "third person limited", "third person omniscient", "second person")
- ).apply {
- selectedItem = "third person limited"
- toolTipText = "Point of view for the narrative"
- }
-
- private val toneCombo = ComboBox(
- arrayOf("dramatic", "humorous", "suspenseful", "reflective", "inspirational", "dark")
- ).apply {
- selectedItem = "dramatic"
- toolTipText = "Overall tone of the narrative"
- }
-
- private val detailedDescriptionsCheckbox = JBCheckBox("Include detailed scene descriptions", true).apply {
- toolTipText = "Whether to include vivid, sensory descriptions"
- }
-
- private val includeDialogueCheckbox = JBCheckBox("Include character dialogue", true).apply {
- toolTipText = "Whether to include natural dialogue between characters"
- }
-
- private val showInternalThoughtsCheckbox = JBCheckBox("Show internal character thoughts", true).apply {
- toolTipText = "Whether to reveal character internal thoughts and feelings"
- }
+ override fun handle(e: AnActionEvent) {
+ val root = getProjectRoot(e) ?: return
+ val relatedFiles = getFiles(e)
+ val dialog = NarrativeGenerationDialog(
+ e.project, root, relatedFiles
+ )
- private val revisionPassesSpinner = JSpinner(SpinnerNumberModel(1, 0, 5, 1)).apply {
- toolTipText = "Number of revision passes for each scene (0 = no revisions)"
+ if (dialog.showAndGet()) {
+ try {
+ val taskConfig = dialog.getTaskConfig()
+ val orchestrationConfig = dialog.getOrchestrationConfig()
+
+ UITools.runAsync(e.project, "Initializing Narrative Generation Task", true) { progress ->
+ initializeTask(e, progress, orchestrationConfig, taskConfig, root)
+ }
+ } catch (ex: Exception) {
+ log.error("Failed to initialize narrative generation task", ex)
+ UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
+ }
+ }
}
- private val generateSceneImagesCheckbox = JBCheckBox("Generate images for each scene", false).apply {
- toolTipText = "Use AI to generate visualization images for each scene"
+ private fun initializeTask(
+ e: AnActionEvent,
+ progress: ProgressIndicator,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: NarrativeGenerationTask.NarrativeGenerationTaskExecutionConfigData,
+ root: File
+ ) {
+ progress.text = "Setting up session..."
+ val session = Session.newGlobalID()
+
+ DataStorage.sessionPaths[session] = root
+
+ progress.text = "Starting server..."
+ setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
+
+ Thread {
+ Thread.sleep(500)
+ try {
+ val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
+ log.info("Opening browser to $uri")
+ browse(uri)
+ } catch (e: Throwable) {
+ log.warn("Error opening browser", e)
+ }
+ }.start()
}
- private val generateCoverImageCheckbox = JBCheckBox("Generate cover image", false).apply {
- toolTipText = "Use AI to generate a cover image for the narrative"
- }
+ private fun setupTaskSession(
+ session: Session,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: NarrativeGenerationTask.NarrativeGenerationTaskExecutionConfigData,
+ root: File
+ ) {
+ val app = object : SingleTaskApp(
+ applicationName = "Narrative Generation Task",
+ path = "/narrativeGenerationTask",
+ showMenubar = false,
+ taskType = NarrativeGenerationTask.NarrativeGeneration,
+ taskConfig = taskConfig,
+ instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
+ ) {
+ override fun instance(model: ApiChatModel) =
+ model.instance() ?: throw IllegalStateException("Model or Provider not set")
+ }
- private val narrativeElementsArea = JBTextArea(4, 40).apply {
- lineWrap = true
- wrapStyleWord = true
- toolTipText = "Optional: Define narrative elements as key:value pairs (one per line)\nExample:\nprotagonist: John Smith\nsetting: Victorian London\nconflict: Man vs Society"
+ app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
+ SessionProxyServer.chats[session] = app
+ ApplicationServer.appInfoMap[session] = AppInfoData(
+ applicationName = "Narrative Generation Task", inputCnt = 0, stickyInput = false, showMenubar = false
+ )
+ SessionProxyServer.metadataStorage.setSessionName(
+ null, session, "Narrative Generation @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
+ )
}
- private val visibleModelsCache by lazy { getVisibleModels() }
-
- private val modelCombo = ComboBox(
- visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
- ).apply {
- maximumSize = Dimension(200, 30)
- selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
- toolTipText = "AI model to use for narrative generation"
+ private fun getProjectRoot(e: AnActionEvent): File? {
+ val folder = e.getSelectedFolder()
+ return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
+ getModuleRootForFile(file)
+ }
}
- private val imageModelCombo = ComboBox(
- visibleModelsCache
- .distinctBy { it.modelName }
- .map { it.modelName }
- .toTypedArray()
- ).apply {
- maximumSize = Dimension(200, 30)
- selectedItem = AppSettingsState.instance.imageChatModel?.model?.modelName
- toolTipText = "AI model to use for generating images"
- }
+ class NarrativeGenerationDialog(
+ project: Project?,
+ private val root: File,
+ val relatedFiles: List
+ ) : DialogWrapper(project) {
- private val temperatureSlider = JSlider(0, 100, 80).apply {
- addChangeListener {
- temperatureLabel.text = "%.2f".format(value / 100.0)
- }
- }
+ private val subjectField = JBTextField().apply {
+ toolTipText = "The subject or scenario to develop into a full narrative"
+ text = "A compelling story"
+ }
- private val temperatureLabel = javax.swing.JLabel("0.80")
+ private val inputFilesField = JBTextField().apply {
+ toolTipText = "Comma-separated file patterns for context (e.g., **/*.kt, docs/*.md)"
+ text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
+ }
- init {
- init()
- title = "Configure Narrative Generation Task"
- }
+ private val targetWordCountSpinner = JSpinner(SpinnerNumberModel(5000, 500, 50000, 500)).apply {
+ toolTipText = "Target word count for the complete narrative"
+ }
- override fun createCenterPanel(): JComponent = panel {
- group("Narrative Configuration") {
- row("Subject:") {
- cell(subjectField)
- .align(Align.FILL)
- .comment("The subject or scenario to develop into a full narrative")
+ private val numberOfActsSpinner = JSpinner(SpinnerNumberModel(3, 1, 10, 1)).apply {
+ toolTipText = "Number of acts in the narrative structure (typically 3 or 5)"
}
- row("Input Files:") {
- cell(inputFilesField)
- .align(Align.FILL)
- .comment("File patterns for context (optional, e.g., **/*.kt, docs/*.md)")
+ private val scenesPerActSpinner = JSpinner(SpinnerNumberModel(3, 1, 10, 1)).apply {
+ toolTipText = "Average number of scenes per act"
}
- row("Narrative Elements:") {
- scrollCell(narrativeElementsArea)
- .align(Align.FILL)
- .comment("Optional: Define characters, setting, conflict, etc. (key:value pairs, one per line)")
- .resizableColumn()
- }.resizableRow()
- }
-
- group("Structure") {
- row("Target Word Count:") {
- cell(targetWordCountSpinner)
- .comment("Total words for the complete narrative")
+ private val writingStyleCombo = ComboBox(
+ arrayOf("literary", "thriller", "technical", "conversational", "academic", "journalistic")
+ ).apply {
+ selectedItem = "literary"
+ toolTipText = "Writing style for the narrative"
}
- row("Number of Acts:") {
- cell(numberOfActsSpinner)
- .comment("Narrative structure (typically 3 or 5 acts)")
+ private val pointOfViewCombo = ComboBox(
+ arrayOf("first person", "third person limited", "third person omniscient", "second person")
+ ).apply {
+ selectedItem = "third person limited"
+ toolTipText = "Point of view for the narrative"
}
- row("Scenes per Act:") {
- cell(scenesPerActSpinner)
- .comment("Average scenes in each act")
+ private val toneCombo = ComboBox(
+ arrayOf("dramatic", "humorous", "suspenseful", "reflective", "inspirational", "dark")
+ ).apply {
+ selectedItem = "dramatic"
+ toolTipText = "Overall tone of the narrative"
}
- }
- group("Writing Style") {
- row("Style:") {
- cell(writingStyleCombo)
- .align(Align.FILL)
- .comment("Overall writing style")
+ private val detailedDescriptionsCheckbox = JBCheckBox("Include detailed scene descriptions", true).apply {
+ toolTipText = "Whether to include vivid, sensory descriptions"
}
- row("Point of View:") {
- cell(pointOfViewCombo)
- .align(Align.FILL)
- .comment("Narrative perspective")
+ private val includeDialogueCheckbox = JBCheckBox("Include character dialogue", true).apply {
+ toolTipText = "Whether to include natural dialogue between characters"
}
- row("Tone:") {
- cell(toneCombo)
- .align(Align.FILL)
- .comment("Emotional tone of the narrative")
+ private val showInternalThoughtsCheckbox = JBCheckBox("Show internal character thoughts", true).apply {
+ toolTipText = "Whether to reveal character internal thoughts and feelings"
}
- row {
- cell(detailedDescriptionsCheckbox)
+ private val revisionPassesSpinner = JSpinner(SpinnerNumberModel(1, 0, 5, 1)).apply {
+ toolTipText = "Number of revision passes for each scene (0 = no revisions)"
}
- row {
- cell(includeDialogueCheckbox)
+ private val generateSceneImagesCheckbox = JBCheckBox("Generate images for each scene", false).apply {
+ toolTipText = "Use AI to generate visualization images for each scene"
}
- row {
- cell(showInternalThoughtsCheckbox)
+ private val generateCoverImageCheckbox = JBCheckBox("Generate cover image", false).apply {
+ toolTipText = "Use AI to generate a cover image for the narrative"
}
- }
- group("Quality & Images") {
- row("Revision Passes:") {
- cell(revisionPassesSpinner)
- .comment("Number of editing passes per scene (0 = no revisions)")
+ private val narrativeElementsArea = JBTextArea(4, 40).apply {
+ lineWrap = true
+ wrapStyleWord = true
+ toolTipText =
+ "Optional: Define narrative elements as key:value pairs (one per line)\nExample:\nprotagonist: John Smith\nsetting: Victorian London\nconflict: Man vs Society"
}
- row {
- cell(generateSceneImagesCheckbox)
+ private val visibleModelsCache by lazy { getVisibleModels() }
+
+ private val modelCombo = ComboBox(
+ visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
+ ).apply {
+ maximumSize = Dimension(200, 30)
+ selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
+ toolTipText = "AI model to use for narrative generation"
}
- row {
- cell(generateCoverImageCheckbox)
+ private val imageModelCombo = ComboBox(
+ visibleModelsCache
+ .distinctBy { it.modelName }
+ .map { it.modelName }
+ .toTypedArray()
+ ).apply {
+ maximumSize = Dimension(200, 30)
+ selectedItem = AppSettingsState.instance.imageChatModel?.model?.modelName
+ toolTipText = "AI model to use for generating images"
}
- row("Image Model:") {
- cell(imageModelCombo)
- .align(Align.FILL)
- .comment("AI model for image generation")
+ private val temperatureSlider = JSlider(0, 100, 80).apply {
+ addChangeListener {
+ temperatureLabel.text = "%.2f".format(value / 100.0)
+ }
}
- }
- group("Model Settings") {
- row("Text Model:") {
- cell(modelCombo)
- .align(Align.FILL)
- .comment("AI model for narrative generation")
+ private val temperatureLabel = javax.swing.JLabel("0.80")
+
+ init {
+ init()
+ title = "Configure Narrative Generation Task"
}
- row("Temperature:") {
- cell(temperatureSlider)
- .align(Align.FILL)
- .comment("Higher values = more creative, lower = more focused")
- cell(temperatureLabel)
+ override fun createCenterPanel(): JComponent = panel {
+ group("Narrative Configuration") {
+ row("Subject:") {
+ cell(subjectField)
+ .align(Align.FILL)
+ .comment("The subject or scenario to develop into a full narrative")
+ }
+
+ row("Input Files:") {
+ cell(inputFilesField)
+ .align(Align.FILL)
+ .comment("File patterns for context (optional, e.g., **/*.kt, docs/*.md)")
+ }
+
+ row("Narrative Elements:") {
+ scrollCell(narrativeElementsArea)
+ .align(Align.FILL)
+ .comment("Optional: Define characters, setting, conflict, etc. (key:value pairs, one per line)")
+ .resizableColumn()
+ }.resizableRow()
+ }
+
+ group("Structure") {
+ row("Target Word Count:") {
+ cell(targetWordCountSpinner)
+ .comment("Total words for the complete narrative")
+ }
+
+ row("Number of Acts:") {
+ cell(numberOfActsSpinner)
+ .comment("Narrative structure (typically 3 or 5 acts)")
+ }
+
+ row("Scenes per Act:") {
+ cell(scenesPerActSpinner)
+ .comment("Average scenes in each act")
+ }
+ }
+
+ group("Writing Style") {
+ row("Style:") {
+ cell(writingStyleCombo)
+ .align(Align.FILL)
+ .comment("Overall writing style")
+ }
+
+ row("Point of View:") {
+ cell(pointOfViewCombo)
+ .align(Align.FILL)
+ .comment("Narrative perspective")
+ }
+
+ row("Tone:") {
+ cell(toneCombo)
+ .align(Align.FILL)
+ .comment("Emotional tone of the narrative")
+ }
+
+ row {
+ cell(detailedDescriptionsCheckbox)
+ }
+
+ row {
+ cell(includeDialogueCheckbox)
+ }
+
+ row {
+ cell(showInternalThoughtsCheckbox)
+ }
+ }
+
+ group("Quality & Images") {
+ row("Revision Passes:") {
+ cell(revisionPassesSpinner)
+ .comment("Number of editing passes per scene (0 = no revisions)")
+ }
+
+ row {
+ cell(generateSceneImagesCheckbox)
+ }
+
+ row {
+ cell(generateCoverImageCheckbox)
+ }
+
+ row("Image Model:") {
+ cell(imageModelCombo)
+ .align(Align.FILL)
+ .comment("AI model for image generation")
+ }
+ }
+
+ group("Model Settings") {
+ row("Text Model:") {
+ cell(modelCombo)
+ .align(Align.FILL)
+ .comment("AI model for narrative generation")
+ }
+
+ row("Temperature:") {
+ cell(temperatureSlider)
+ .align(Align.FILL)
+ .comment("Higher values = more creative, lower = more focused")
+ cell(temperatureLabel)
+ }
+ }
}
- }
- }
- override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
- if (subjectField.text.isBlank()) {
- return com.intellij.openapi.ui.ValidationInfo("Subject is required", subjectField)
- }
+ override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
+ if (subjectField.text.isBlank()) {
+ return com.intellij.openapi.ui.ValidationInfo("Subject is required", subjectField)
+ }
- return null
- }
+ return null
+ }
- fun getTaskConfig(): NarrativeGenerationTask.NarrativeGenerationTaskExecutionConfigData {
- val inputFiles = inputFilesField.text.split(",")
- .map { it.trim() }
- .filter { it.isNotEmpty() }
- .takeIf { it.isNotEmpty() }
-
- val narrativeElements = narrativeElementsArea.text.lines()
- .filter { it.contains(":") }
- .associate {
- val (key, value) = it.split(":", limit = 2)
- key.trim() to value.trim()
+ fun getTaskConfig(): NarrativeGenerationTask.NarrativeGenerationTaskExecutionConfigData {
+ val inputFiles = inputFilesField.text.split(",")
+ .map { it.trim() }
+ .filter { it.isNotEmpty() }
+ .takeIf { it.isNotEmpty() }
+
+ val narrativeElements = narrativeElementsArea.text.lines()
+ .filter { it.contains(":") }
+ .associate {
+ val (key, value) = it.split(":", limit = 2)
+ key.trim() to value.trim()
+ }
+ .takeIf { it.isNotEmpty() }
+
+ return NarrativeGenerationTask.NarrativeGenerationTaskExecutionConfigData(
+ subject = subjectField.text,
+ input_files = inputFiles,
+ narrative_elements = narrativeElements,
+ target_word_count = targetWordCountSpinner.value as Int,
+ number_of_acts = numberOfActsSpinner.value as Int,
+ scenes_per_act = scenesPerActSpinner.value as Int,
+ writing_style = writingStyleCombo.selectedItem as String,
+ point_of_view = pointOfViewCombo.selectedItem as String,
+ tone = toneCombo.selectedItem as String,
+ detailed_descriptions = detailedDescriptionsCheckbox.isSelected,
+ include_dialogue = includeDialogueCheckbox.isSelected,
+ show_internal_thoughts = showInternalThoughtsCheckbox.isSelected,
+ revision_passes = revisionPassesSpinner.value as Int,
+ generate_scene_images = generateSceneImagesCheckbox.isSelected,
+ generate_cover_image = generateCoverImageCheckbox.isSelected,
+ state = TaskState.Pending
+ )
}
- .takeIf { it.isNotEmpty() }
-
- return NarrativeGenerationTask.NarrativeGenerationTaskExecutionConfigData(
- subject = subjectField.text,
- input_files = inputFiles,
- narrative_elements = narrativeElements,
- target_word_count = targetWordCountSpinner.value as Int,
- number_of_acts = numberOfActsSpinner.value as Int,
- scenes_per_act = scenesPerActSpinner.value as Int,
- writing_style = writingStyleCombo.selectedItem as String,
- point_of_view = pointOfViewCombo.selectedItem as String,
- tone = toneCombo.selectedItem as String,
- detailed_descriptions = detailedDescriptionsCheckbox.isSelected,
- include_dialogue = includeDialogueCheckbox.isSelected,
- show_internal_thoughts = showInternalThoughtsCheckbox.isSelected,
- revision_passes = revisionPassesSpinner.value as Int,
- generate_scene_images = generateSceneImagesCheckbox.isSelected,
- generate_cover_image = generateCoverImageCheckbox.isSelected,
- state = TaskState.Pending
- )
- }
- fun getOrchestrationConfig(): OrchestrationConfig {
- val selectedModel = modelCombo.selectedItem as? String
- val model = selectedModel?.let { modelName ->
- visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
- }
-
- val selectedImageModel = imageModelCombo.selectedItem as? String
- val imageModel = selectedImageModel?.let { modelName ->
- visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
- }
-
- return OrchestrationConfig(
- "Config",
- defaultSmartModel = model ?: AppSettingsState.instance.smartModel
- ?: throw IllegalStateException("No model configured"),
- defaultFastModel = AppSettingsState.instance.fastModel
- ?: throw IllegalStateException("Fast model not configured"),
- defaultImageModel = imageModel ?: AppSettingsState.instance.imageChatModel
- ?: throw IllegalStateException("No image model configured"),
- temperature = temperatureSlider.value / 100.0,
- autoFix = false,
- workingDir = root.absolutePath,
- shellCmd = listOf(
- if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
- )
- )
- }
+ fun getOrchestrationConfig(): OrchestrationConfig {
+ val selectedModel = modelCombo.selectedItem as? String
+ val model = selectedModel?.let { modelName ->
+ visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
+ }
+
+ val selectedImageModel = imageModelCombo.selectedItem as? String
+ val imageModel = selectedImageModel?.let { modelName ->
+ visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
+ }
+
+ return OrchestrationConfig(
+ "Config",
+ defaultSmartModel = model ?: AppSettingsState.instance.smartModel
+ ?: throw IllegalStateException("No model configured"),
+ defaultFastModel = AppSettingsState.instance.fastModel
+ ?: throw IllegalStateException("Fast model not configured"),
+ defaultImageModel = imageModel ?: AppSettingsState.instance.imageChatModel
+ ?: throw IllegalStateException("No image model configured"),
+ temperature = temperatureSlider.value / 100.0,
+ autoFix = false,
+ workingDir = root.absolutePath,
+ shellCmd = listOf(
+ if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
+ )
+ )
+ }
- private fun getVisibleModels() =
- ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
- model.provider == apiData.provider &&
- model.modelName?.isNotBlank() == true &&
- PlanConfigDialog.isVisible(model)
- } ?: listOf()
- }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
- }
+ private fun getVisibleModels() =
+ ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
+ model.provider == apiData.provider &&
+ model.modelName?.isNotBlank() == true &&
+ PlanConfigDialog.isVisible(model)
+ } ?: listOf()
+ }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
+ }
}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/task/PersuasiveEssayAction.kt b/intellij/src/main/kotlin/cognotik/actions/task/PersuasiveEssayAction.kt
index 01a851110..025d03a85 100644
--- a/intellij/src/main/kotlin/cognotik/actions/task/PersuasiveEssayAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/task/PersuasiveEssayAction.kt
@@ -41,354 +41,359 @@ import javax.swing.SpinnerNumberModel
class PersuasiveEssayAction : BaseAction() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
- override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
- if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
- return true
- }
-
- override fun handle(e: AnActionEvent) {
- val root = getProjectRoot(e) ?: return
- val relatedFiles = getFiles(e)
- val dialog = PersuasiveEssayTaskDialog(
- e.project, root, relatedFiles
- )
-
- if (dialog.showAndGet()) {
- try {
- val taskConfig = dialog.getTaskConfig()
- val orchestrationConfig = dialog.getOrchestrationConfig()
-
- UITools.runAsync(e.project, "Initializing Persuasive Essay Task", true) { progress ->
- initializeTask(e, progress, orchestrationConfig, taskConfig, root)
- }
- } catch (ex: Exception) {
- log.error("Failed to initialize persuasive essay task", ex)
- UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
- }
- }
- }
-
- private fun initializeTask(
- e: AnActionEvent,
- progress: ProgressIndicator,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: PersuasiveEssayTask.PersuasiveEssayTaskExecutionConfigData,
- root: File
- ) {
- progress.text = "Setting up session..."
- val session = Session.newGlobalID()
-
- DataStorage.sessionPaths[session] = root
-
- progress.text = "Starting server..."
- setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
-
- Thread {
- Thread.sleep(500)
- try {
- val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
- log.info("Opening browser to $uri")
- browse(uri)
- } catch (e: Throwable) {
- log.warn("Error opening browser", e)
- }
- }.start()
- }
-
- private fun setupTaskSession(
- session: Session, orchestrationConfig: OrchestrationConfig, taskConfig: PersuasiveEssayTask.PersuasiveEssayTaskExecutionConfigData, root: File
- ) {
- val app = object : SingleTaskApp(
- applicationName = "Persuasive Essay Task",
- path = "/persuasiveEssayTask",
- showMenubar = false,
- taskType = PersuasiveEssayTask.PersuasiveEssay,
- taskConfig = taskConfig,
- instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
- ) {
- override fun instance(model: ApiChatModel) = model.instance() ?: throw IllegalStateException("Model or Provider not set")
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+ override fun isEnabled(event: AnActionEvent): Boolean {
+ if (!super.isEnabled(event)) return false
+ if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
+ return true
}
- app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
- SessionProxyServer.chats[session] = app
- ApplicationServer.appInfoMap[session] = AppInfoData(
- applicationName = "Persuasive Essay Task", inputCnt = 0, stickyInput = false, showMenubar = false
- )
- SessionProxyServer.metadataStorage.setSessionName(
- null, session, "Persuasive Essay @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
- )
- }
-
- private fun getProjectRoot(e: AnActionEvent): File? {
- val folder = e.getSelectedFolder()
- return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
- getModuleRootForFile(file)
- }
- }
-
- class PersuasiveEssayTaskDialog(
- project: Project?,
- private val root: File,
- val relatedFiles: List
- ) : DialogWrapper(project) {
-
- private val thesisArea = JBTextArea(4, 40).apply {
- lineWrap = true
- wrapStyleWord = true
- toolTipText = "Enter the thesis statement or position you want to argue for"
- }
-
- private val targetAudienceField = JBTextField().apply {
- toolTipText = "Target audience (e.g., 'general public', 'academics', 'policymakers', 'business leaders')"
- text = "general public"
- }
-
- private val toneField = JBTextField().apply {
- toolTipText = "Tone of the essay (e.g., 'formal', 'conversational', 'passionate', 'analytical')"
- text = "formal"
- }
-
- private val wordCountSpinner = JSpinner(SpinnerNumberModel(1500, 500, 5000, 100)).apply {
- toolTipText = "Target word count for the complete essay (500-5000)"
- }
-
- private val numArgumentsSpinner = JSpinner(SpinnerNumberModel(3, 1, 10, 1)).apply {
- toolTipText = "Number of main arguments to develop (1-10)"
- }
-
- private val includeCounterargumentsCheckbox = JBCheckBox("Include counterarguments and rebuttals", true).apply {
- toolTipText = "Address opposing viewpoints and provide rebuttals"
- }
-
- private val useRhetoricalDevicesCheckbox = JBCheckBox("Use rhetorical devices (ethos, pathos, logos)", true).apply {
- toolTipText = "Employ classical rhetorical techniques for persuasive impact"
- }
-
- private val includeEvidenceCheckbox = JBCheckBox("Include statistical evidence and citations", true).apply {
- toolTipText = "Use data, statistics, and expert testimony"
- }
-
- private val useAnalogiesCheckbox = JBCheckBox("Use analogies and examples", true).apply {
- toolTipText = "Include concrete examples and analogies for clarity"
- }
-
- private val callToActionCombo = ComboBox(arrayOf("strong", "moderate", "reflective", "none")).apply {
- selectedItem = "strong"
- toolTipText = "Type of call to action in the conclusion"
- }
-
- private val revisionPassesSpinner = JSpinner(SpinnerNumberModel(1, 0, 5, 1)).apply {
- toolTipText = "Number of revision passes for quality improvement (0-5)"
- }
+ override fun handle(e: AnActionEvent) {
+ val root = getProjectRoot(e) ?: return
+ val relatedFiles = getFiles(e)
+ val dialog = PersuasiveEssayTaskDialog(
+ e.project, root, relatedFiles
+ )
- private val inputFilesField = JBTextField().apply {
- toolTipText = "Comma-separated list of input files or patterns (e.g., research/*.md, **/*.txt)"
- text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
+ if (dialog.showAndGet()) {
+ try {
+ val taskConfig = dialog.getTaskConfig()
+ val orchestrationConfig = dialog.getOrchestrationConfig()
+
+ UITools.runAsync(e.project, "Initializing Persuasive Essay Task", true) { progress ->
+ initializeTask(e, progress, orchestrationConfig, taskConfig, root)
+ }
+ } catch (ex: Exception) {
+ log.error("Failed to initialize persuasive essay task", ex)
+ UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
+ }
+ }
}
- private val relatedFilesField = JBTextField().apply {
- toolTipText = "Additional related files for context (optional)"
- text = ""
+ private fun initializeTask(
+ e: AnActionEvent,
+ progress: ProgressIndicator,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: PersuasiveEssayTask.PersuasiveEssayTaskExecutionConfigData,
+ root: File
+ ) {
+ progress.text = "Setting up session..."
+ val session = Session.newGlobalID()
+
+ DataStorage.sessionPaths[session] = root
+
+ progress.text = "Starting server..."
+ setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
+
+ Thread {
+ Thread.sleep(500)
+ try {
+ val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
+ log.info("Opening browser to $uri")
+ browse(uri)
+ } catch (e: Throwable) {
+ log.warn("Error opening browser", e)
+ }
+ }.start()
}
- private val visibleModelsCache by lazy { getVisibleModels() }
+ private fun setupTaskSession(
+ session: Session,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: PersuasiveEssayTask.PersuasiveEssayTaskExecutionConfigData,
+ root: File
+ ) {
+ val app = object : SingleTaskApp(
+ applicationName = "Persuasive Essay Task",
+ path = "/persuasiveEssayTask",
+ showMenubar = false,
+ taskType = PersuasiveEssayTask.PersuasiveEssay,
+ taskConfig = taskConfig,
+ instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
+ ) {
+ override fun instance(model: ApiChatModel) =
+ model.instance() ?: throw IllegalStateException("Model or Provider not set")
+ }
- private val modelCombo = ComboBox(
- visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
- ).apply {
- maximumSize = Dimension(200, 30)
- selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
- toolTipText = "AI model to use for generating the essay"
+ app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
+ SessionProxyServer.chats[session] = app
+ ApplicationServer.appInfoMap[session] = AppInfoData(
+ applicationName = "Persuasive Essay Task", inputCnt = 0, stickyInput = false, showMenubar = false
+ )
+ SessionProxyServer.metadataStorage.setSessionName(
+ null, session, "Persuasive Essay @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
+ )
}
- private val temperatureSlider = JSlider(0, 100, 70).apply {
- addChangeListener {
- temperatureLabel.text = "%.2f".format(value / 100.0)
- }
+ private fun getProjectRoot(e: AnActionEvent): File? {
+ val folder = e.getSelectedFolder()
+ return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
+ getModuleRootForFile(file)
+ }
}
- private val temperatureLabel = javax.swing.JLabel("0.70")
+ class PersuasiveEssayTaskDialog(
+ project: Project?,
+ private val root: File,
+ val relatedFiles: List
+ ) : DialogWrapper(project) {
- private val autoFixCheckbox = JBCheckBox("Auto-apply generated essay", false).apply {
- toolTipText = "Automatically save the generated essay without manual confirmation"
- }
+ private val thesisArea = JBTextArea(4, 40).apply {
+ lineWrap = true
+ wrapStyleWord = true
+ toolTipText = "Enter the thesis statement or position you want to argue for"
+ }
- init {
- init()
- title = "Configure Persuasive Essay Task"
- }
+ private val targetAudienceField = JBTextField().apply {
+ toolTipText = "Target audience (e.g., 'general public', 'academics', 'policymakers', 'business leaders')"
+ text = "general public"
+ }
- override fun createCenterPanel(): JComponent = panel {
- group("Essay Configuration") {
- row("Thesis Statement:") {
- scrollCell(thesisArea)
- .align(Align.FILL)
- .comment("The main position or argument you want to defend")
- .resizableColumn()
- }.resizableRow()
-
- row("Target Audience:") {
- cell(targetAudienceField)
- .align(Align.FILL)
- .comment("Who you're writing for (affects tone and approach)")
+ private val toneField = JBTextField().apply {
+ toolTipText = "Tone of the essay (e.g., 'formal', 'conversational', 'passionate', 'analytical')"
+ text = "formal"
}
- row("Tone:") {
- cell(toneField)
- .align(Align.FILL)
- .comment("Overall tone of the essay")
+ private val wordCountSpinner = JSpinner(SpinnerNumberModel(1500, 500, 5000, 100)).apply {
+ toolTipText = "Target word count for the complete essay (500-5000)"
}
- row("Target Word Count:") {
- cell(wordCountSpinner)
- .comment("Approximate length of the complete essay")
+ private val numArgumentsSpinner = JSpinner(SpinnerNumberModel(3, 1, 10, 1)).apply {
+ toolTipText = "Number of main arguments to develop (1-10)"
}
- row("Number of Arguments:") {
- cell(numArgumentsSpinner)
- .comment("How many main arguments to develop")
+ private val includeCounterargumentsCheckbox = JBCheckBox("Include counterarguments and rebuttals", true).apply {
+ toolTipText = "Address opposing viewpoints and provide rebuttals"
}
- }
- group("Persuasive Techniques") {
- row {
- cell(includeCounterargumentsCheckbox)
+ private val useRhetoricalDevicesCheckbox =
+ JBCheckBox("Use rhetorical devices (ethos, pathos, logos)", true).apply {
+ toolTipText = "Employ classical rhetorical techniques for persuasive impact"
+ }
+
+ private val includeEvidenceCheckbox = JBCheckBox("Include statistical evidence and citations", true).apply {
+ toolTipText = "Use data, statistics, and expert testimony"
}
- row {
- cell(useRhetoricalDevicesCheckbox)
+ private val useAnalogiesCheckbox = JBCheckBox("Use analogies and examples", true).apply {
+ toolTipText = "Include concrete examples and analogies for clarity"
}
- row {
- cell(includeEvidenceCheckbox)
+ private val callToActionCombo = ComboBox(arrayOf("strong", "moderate", "reflective", "none")).apply {
+ selectedItem = "strong"
+ toolTipText = "Type of call to action in the conclusion"
}
- row {
- cell(useAnalogiesCheckbox)
+ private val revisionPassesSpinner = JSpinner(SpinnerNumberModel(1, 0, 5, 1)).apply {
+ toolTipText = "Number of revision passes for quality improvement (0-5)"
}
- row("Call to Action:") {
- cell(callToActionCombo)
- .comment("Type of conclusion and call to action")
+ private val inputFilesField = JBTextField().apply {
+ toolTipText = "Comma-separated list of input files or patterns (e.g., research/*.md, **/*.txt)"
+ text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
}
- }
- group("Input Files") {
- row("Input Files:") {
- cell(inputFilesField)
- .align(Align.FILL)
- .comment("Research files to incorporate (supports glob patterns)")
+ private val relatedFilesField = JBTextField().apply {
+ toolTipText = "Additional related files for context (optional)"
+ text = ""
}
- row("Related Files:") {
- cell(relatedFilesField)
- .align(Align.FILL)
- .comment("Additional context files (optional)")
+ private val visibleModelsCache by lazy { getVisibleModels() }
+
+ private val modelCombo = ComboBox(
+ visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
+ ).apply {
+ maximumSize = Dimension(200, 30)
+ selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
+ toolTipText = "AI model to use for generating the essay"
}
- }
- group("Model Settings") {
- row("Model:") {
- cell(modelCombo)
- .align(Align.FILL)
- .comment("AI model for generating the essay")
+ private val temperatureSlider = JSlider(0, 100, 70).apply {
+ addChangeListener {
+ temperatureLabel.text = "%.2f".format(value / 100.0)
+ }
}
- row("Temperature:") {
- cell(temperatureSlider)
- .align(Align.FILL)
- .comment("Higher values = more creative, lower = more focused")
- cell(temperatureLabel)
+ private val temperatureLabel = javax.swing.JLabel("0.70")
+
+ private val autoFixCheckbox = JBCheckBox("Auto-apply generated essay", false).apply {
+ toolTipText = "Automatically save the generated essay without manual confirmation"
}
- row("Revision Passes:") {
- cell(revisionPassesSpinner)
- .comment("Number of quality improvement passes")
+ init {
+ init()
+ title = "Configure Persuasive Essay Task"
}
- row {
- cell(autoFixCheckbox)
+ override fun createCenterPanel(): JComponent = panel {
+ group("Essay Configuration") {
+ row("Thesis Statement:") {
+ scrollCell(thesisArea)
+ .align(Align.FILL)
+ .comment("The main position or argument you want to defend")
+ .resizableColumn()
+ }.resizableRow()
+
+ row("Target Audience:") {
+ cell(targetAudienceField)
+ .align(Align.FILL)
+ .comment("Who you're writing for (affects tone and approach)")
+ }
+
+ row("Tone:") {
+ cell(toneField)
+ .align(Align.FILL)
+ .comment("Overall tone of the essay")
+ }
+
+ row("Target Word Count:") {
+ cell(wordCountSpinner)
+ .comment("Approximate length of the complete essay")
+ }
+
+ row("Number of Arguments:") {
+ cell(numArgumentsSpinner)
+ .comment("How many main arguments to develop")
+ }
+ }
+
+ group("Persuasive Techniques") {
+ row {
+ cell(includeCounterargumentsCheckbox)
+ }
+
+ row {
+ cell(useRhetoricalDevicesCheckbox)
+ }
+
+ row {
+ cell(includeEvidenceCheckbox)
+ }
+
+ row {
+ cell(useAnalogiesCheckbox)
+ }
+
+ row("Call to Action:") {
+ cell(callToActionCombo)
+ .comment("Type of conclusion and call to action")
+ }
+ }
+
+ group("Input Files") {
+ row("Input Files:") {
+ cell(inputFilesField)
+ .align(Align.FILL)
+ .comment("Research files to incorporate (supports glob patterns)")
+ }
+
+ row("Related Files:") {
+ cell(relatedFilesField)
+ .align(Align.FILL)
+ .comment("Additional context files (optional)")
+ }
+ }
+
+ group("Model Settings") {
+ row("Model:") {
+ cell(modelCombo)
+ .align(Align.FILL)
+ .comment("AI model for generating the essay")
+ }
+
+ row("Temperature:") {
+ cell(temperatureSlider)
+ .align(Align.FILL)
+ .comment("Higher values = more creative, lower = more focused")
+ cell(temperatureLabel)
+ }
+
+ row("Revision Passes:") {
+ cell(revisionPassesSpinner)
+ .comment("Number of quality improvement passes")
+ }
+
+ row {
+ cell(autoFixCheckbox)
+ }
+ }
}
- }
- }
- override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
- if (thesisArea.text.isBlank()) {
- return com.intellij.openapi.ui.ValidationInfo("Thesis statement is required", thesisArea)
- }
+ override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
+ if (thesisArea.text.isBlank()) {
+ return com.intellij.openapi.ui.ValidationInfo("Thesis statement is required", thesisArea)
+ }
- if (targetAudienceField.text.isBlank()) {
- return com.intellij.openapi.ui.ValidationInfo("Target audience is required", targetAudienceField)
- }
+ if (targetAudienceField.text.isBlank()) {
+ return com.intellij.openapi.ui.ValidationInfo("Target audience is required", targetAudienceField)
+ }
- if (toneField.text.isBlank()) {
- return com.intellij.openapi.ui.ValidationInfo("Tone is required", toneField)
- }
+ if (toneField.text.isBlank()) {
+ return com.intellij.openapi.ui.ValidationInfo("Tone is required", toneField)
+ }
- val validCallToActions = setOf("strong", "moderate", "reflective", "none")
- if (callToActionCombo.selectedItem.toString().lowercase() !in validCallToActions) {
- return com.intellij.openapi.ui.ValidationInfo("Invalid call to action type", callToActionCombo)
- }
+ val validCallToActions = setOf("strong", "moderate", "reflective", "none")
+ if (callToActionCombo.selectedItem.toString().lowercase() !in validCallToActions) {
+ return com.intellij.openapi.ui.ValidationInfo("Invalid call to action type", callToActionCombo)
+ }
- return null
- }
+ return null
+ }
- fun getTaskConfig(): PersuasiveEssayTask.PersuasiveEssayTaskExecutionConfigData {
- val inputFiles = inputFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
- .takeIf { it.isNotEmpty() }
-
- val relatedFiles = relatedFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
- .takeIf { it.isNotEmpty() }
-
- return PersuasiveEssayTask.PersuasiveEssayTaskExecutionConfigData(
- thesis = thesisArea.text,
- target_audience = targetAudienceField.text,
- tone = toneField.text,
- target_word_count = wordCountSpinner.value as Int,
- num_arguments = numArgumentsSpinner.value as Int,
- include_counterarguments = includeCounterargumentsCheckbox.isSelected,
- use_rhetorical_devices = useRhetoricalDevicesCheckbox.isSelected,
- include_evidence = includeEvidenceCheckbox.isSelected,
- use_analogies = useAnalogiesCheckbox.isSelected,
- call_to_action = callToActionCombo.selectedItem.toString(),
- revision_passes = revisionPassesSpinner.value as Int,
- input_files = inputFiles,
- related_files = relatedFiles,
- state = TaskState.Pending
- )
- }
+ fun getTaskConfig(): PersuasiveEssayTask.PersuasiveEssayTaskExecutionConfigData {
+ val inputFiles = inputFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+ .takeIf { it.isNotEmpty() }
+
+ val relatedFiles = relatedFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+ .takeIf { it.isNotEmpty() }
+
+ return PersuasiveEssayTask.PersuasiveEssayTaskExecutionConfigData(
+ thesis = thesisArea.text,
+ target_audience = targetAudienceField.text,
+ tone = toneField.text,
+ target_word_count = wordCountSpinner.value as Int,
+ num_arguments = numArgumentsSpinner.value as Int,
+ include_counterarguments = includeCounterargumentsCheckbox.isSelected,
+ use_rhetorical_devices = useRhetoricalDevicesCheckbox.isSelected,
+ include_evidence = includeEvidenceCheckbox.isSelected,
+ use_analogies = useAnalogiesCheckbox.isSelected,
+ call_to_action = callToActionCombo.selectedItem.toString(),
+ revision_passes = revisionPassesSpinner.value as Int,
+ input_files = inputFiles,
+ related_files = relatedFiles,
+ state = TaskState.Pending
+ )
+ }
- fun getOrchestrationConfig(): OrchestrationConfig {
- val selectedModel = modelCombo.selectedItem as? String
- val model = selectedModel?.let { modelName ->
- visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
- }
-
- return OrchestrationConfig(
- "Config",
- defaultSmartModel = model ?: AppSettingsState.instance.smartModel
- ?: throw IllegalStateException("No model configured"),
- defaultFastModel = AppSettingsState.instance.fastModel
- ?: throw IllegalStateException("Fast model not configured"),
- temperature = temperatureSlider.value / 100.0,
- autoFix = autoFixCheckbox.isSelected,
- workingDir = root.absolutePath,
- shellCmd = listOf(
- if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
- )
- )
- }
+ fun getOrchestrationConfig(): OrchestrationConfig {
+ val selectedModel = modelCombo.selectedItem as? String
+ val model = selectedModel?.let { modelName ->
+ visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
+ }
+
+ return OrchestrationConfig(
+ "Config",
+ defaultSmartModel = model ?: AppSettingsState.instance.smartModel
+ ?: throw IllegalStateException("No model configured"),
+ defaultFastModel = AppSettingsState.instance.fastModel
+ ?: throw IllegalStateException("Fast model not configured"),
+ temperature = temperatureSlider.value / 100.0,
+ autoFix = autoFixCheckbox.isSelected,
+ workingDir = root.absolutePath,
+ shellCmd = listOf(
+ if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
+ )
+ )
+ }
- private fun getVisibleModels() =
- ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
- model.provider == apiData.provider &&
- model.modelName?.isNotBlank() == true &&
- PlanConfigDialog.isVisible(model)
- } ?: listOf()
- }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
- }
+ private fun getVisibleModels() =
+ ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
+ model.provider == apiData.provider &&
+ model.modelName?.isNotBlank() == true &&
+ PlanConfigDialog.isVisible(model)
+ } ?: listOf()
+ }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
+ }
}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/task/ResearchPaperAction.kt b/intellij/src/main/kotlin/cognotik/actions/task/ResearchPaperAction.kt
index 3fc16ac8e..a5d8e5020 100644
--- a/intellij/src/main/kotlin/cognotik/actions/task/ResearchPaperAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/task/ResearchPaperAction.kt
@@ -41,375 +41,376 @@ import javax.swing.SpinnerNumberModel
class ResearchPaperAction : BaseAction() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
- override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
- if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
- return true
- }
-
- override fun handle(e: AnActionEvent) {
- val root = getProjectRoot(e) ?: return
- val relatedFiles = getFiles(e)
- val dialog = ResearchPaperTaskDialog(
- e.project, root, relatedFiles
- )
-
- if (dialog.showAndGet()) {
- try {
- val taskConfig = dialog.getTaskConfig()
- val orchestrationConfig = dialog.getOrchestrationConfig()
-
- UITools.runAsync(e.project, "Initializing Research Paper Generation Task", true) { progress ->
- initializeTask(e, progress, orchestrationConfig, taskConfig, root)
- }
- } catch (ex: Exception) {
- log.error("Failed to initialize research paper generation task", ex)
- UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
- }
- }
- }
-
- private fun initializeTask(
- e: AnActionEvent,
- progress: ProgressIndicator,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: ResearchPaperGenerationTask.ResearchPaperGenerationTaskExecutionConfigData,
- root: File
- ) {
- progress.text = "Setting up session..."
- val session = Session.newGlobalID()
-
- DataStorage.sessionPaths[session] = root
-
- progress.text = "Starting server..."
- setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
-
- Thread {
- Thread.sleep(500)
- try {
- val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
- log.info("Opening browser to $uri")
- browse(uri)
- } catch (e: Throwable) {
- log.warn("Error opening browser", e)
- }
- }.start()
- }
-
- private fun setupTaskSession(
- session: Session,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: ResearchPaperGenerationTask.ResearchPaperGenerationTaskExecutionConfigData,
- root: File
- ) {
- val app = object : SingleTaskApp(
- applicationName = "Research Paper Generation Task",
- path = "/researchPaperTask",
- showMenubar = false,
- taskType = ResearchPaperGenerationTask.ResearchPaperGeneration,
- taskConfig = taskConfig,
- instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
- ) {
- override fun instance(model: ApiChatModel) = model.instance() ?: throw IllegalStateException("Model or Provider not set")
- }
-
- app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
- SessionProxyServer.chats[session] = app
- ApplicationServer.appInfoMap[session] = AppInfoData(
- applicationName = "Research Paper Generation Task", inputCnt = 0, stickyInput = false, showMenubar = false
- )
- SessionProxyServer.metadataStorage.setSessionName(
- null, session, "Research Paper @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
- )
- }
-
- private fun getProjectRoot(e: AnActionEvent): File? {
- val folder = e.getSelectedFolder()
- return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
- getModuleRootForFile(file)
- }
- }
-
- class ResearchPaperTaskDialog(
- project: Project?,
- private val root: File,
- val relatedFiles: List
- ) : DialogWrapper(project) {
-
- private val researchTopicArea = JBTextArea(4, 40).apply {
- lineWrap = true
- wrapStyleWord = true
- toolTipText = "Describe the main research question or topic for the paper"
- }
-
- private val paperTypeCombo = ComboBox(
- arrayOf("empirical", "theoretical", "review", "meta-analysis", "systematic-review")
- ).apply {
- selectedItem = "empirical"
- toolTipText = "Type of research paper to generate"
- }
-
- private val academicLevelCombo = ComboBox(
- arrayOf("undergraduate", "masters", "phd", "postdoc")
- ).apply {
- selectedItem = "masters"
- toolTipText = "Academic level for the paper's complexity and rigor"
- }
-
- private val citationStyleCombo = ComboBox(
- arrayOf("apa", "mla", "chicago", "ieee")
- ).apply {
- selectedItem = "apa"
- toolTipText = "Citation style to use throughout the paper"
- }
-
- private val targetWordCountSpinner = JSpinner(SpinnerNumberModel(8000, 1000, 50000, 1000)).apply {
- toolTipText = "Target word count for the complete paper (1000-50000)"
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+ override fun isEnabled(event: AnActionEvent): Boolean {
+ if (!super.isEnabled(event)) return false
+ if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
+ return true
}
- private val numberOfSectionsSpinner = JSpinner(SpinnerNumberModel(6, 3, 15, 1)).apply {
- toolTipText = "Number of main sections (3-15)"
- }
-
- private val revisionPassesSpinner = JSpinner(SpinnerNumberModel(1, 0, 5, 1)).apply {
- toolTipText = "Number of revision passes for quality improvement (0-5)"
- }
-
- private val includeLiteratureReviewCheckbox = JBCheckBox("Include Literature Review", true).apply {
- toolTipText = "Include a comprehensive literature review section"
- }
-
- private val includeMethodologyCheckbox = JBCheckBox("Include Methodology", true).apply {
- toolTipText = "Include a methodology section describing research methods"
- }
-
- private val includeStatisticalAnalysisCheckbox = JBCheckBox("Include Statistical Analysis", true).apply {
- toolTipText = "Include descriptions of statistical analysis methods"
- }
-
- private val includePeerReviewCheckbox = JBCheckBox("Include Peer Review Simulation", true).apply {
- toolTipText = "Simulate peer review to identify weaknesses and improvements"
- }
+ override fun handle(e: AnActionEvent) {
+ val root = getProjectRoot(e) ?: return
+ val relatedFiles = getFiles(e)
+ val dialog = ResearchPaperTaskDialog(
+ e.project, root, relatedFiles
+ )
- private val inputFilesField = JBTextField().apply {
- toolTipText = "Comma-separated list of input files or patterns (e.g., **/*.kt, docs/*.md)"
- text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
+ if (dialog.showAndGet()) {
+ try {
+ val taskConfig = dialog.getTaskConfig()
+ val orchestrationConfig = dialog.getOrchestrationConfig()
+
+ UITools.runAsync(e.project, "Initializing Research Paper Generation Task", true) { progress ->
+ initializeTask(e, progress, orchestrationConfig, taskConfig, root)
+ }
+ } catch (ex: Exception) {
+ log.error("Failed to initialize research paper generation task", ex)
+ UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
+ }
+ }
}
- private val researchFilesField = JBTextField().apply {
- toolTipText = "Comma-separated list of research source files to incorporate"
- text = ""
+ private fun initializeTask(
+ e: AnActionEvent,
+ progress: ProgressIndicator,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: ResearchPaperGenerationTask.ResearchPaperGenerationTaskExecutionConfigData,
+ root: File
+ ) {
+ progress.text = "Setting up session..."
+ val session = Session.newGlobalID()
+
+ DataStorage.sessionPaths[session] = root
+
+ progress.text = "Starting server..."
+ setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
+
+ Thread {
+ Thread.sleep(500)
+ try {
+ val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
+ log.info("Opening browser to $uri")
+ browse(uri)
+ } catch (e: Throwable) {
+ log.warn("Error opening browser", e)
+ }
+ }.start()
}
- private val visibleModelsCache by lazy { getVisibleModels() }
-
- private val modelCombo = ComboBox(
- visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
- ).apply {
- maximumSize = Dimension(200, 30)
- selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
- toolTipText = "AI model to use for generating the research paper"
- }
+ private fun setupTaskSession(
+ session: Session,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: ResearchPaperGenerationTask.ResearchPaperGenerationTaskExecutionConfigData,
+ root: File
+ ) {
+ val app = object : SingleTaskApp(
+ applicationName = "Research Paper Generation Task",
+ path = "/researchPaperTask",
+ showMenubar = false,
+ taskType = ResearchPaperGenerationTask.ResearchPaperGeneration,
+ taskConfig = taskConfig,
+ instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
+ ) {
+ override fun instance(model: ApiChatModel) =
+ model.instance() ?: throw IllegalStateException("Model or Provider not set")
+ }
- private val temperatureSlider = JSlider(0, 100, 70).apply {
- addChangeListener {
- temperatureLabel.text = "%.2f".format(value / 100.0)
- }
+ app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
+ SessionProxyServer.chats[session] = app
+ ApplicationServer.appInfoMap[session] = AppInfoData(
+ applicationName = "Research Paper Generation Task", inputCnt = 0, stickyInput = false, showMenubar = false
+ )
+ SessionProxyServer.metadataStorage.setSessionName(
+ null, session, "Research Paper @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
+ )
}
- private val temperatureLabel = javax.swing.JLabel("0.70")
-
- private val autoFixCheckbox = JBCheckBox("Auto-apply generated content", false).apply {
- toolTipText = "Automatically apply generated content without manual confirmation"
+ private fun getProjectRoot(e: AnActionEvent): File? {
+ val folder = e.getSelectedFolder()
+ return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
+ getModuleRootForFile(file)
+ }
}
- init {
- init()
- title = "Configure Research Paper Generation Task"
- }
+ class ResearchPaperTaskDialog(
+ project: Project?,
+ private val root: File,
+ val relatedFiles: List
+ ) : DialogWrapper(project) {
- override fun createCenterPanel(): JComponent = panel {
- group("Research Configuration") {
- row("Research Topic:") {
- scrollCell(researchTopicArea)
- .align(Align.FILL)
- .comment("Describe the main research question or topic")
- .resizableColumn()
- }.resizableRow()
-
- row("Paper Type:") {
- cell(paperTypeCombo)
- .align(Align.FILL)
- .comment("Type of research paper (empirical, theoretical, review, etc.)")
+ private val researchTopicArea = JBTextArea(4, 40).apply {
+ lineWrap = true
+ wrapStyleWord = true
+ toolTipText = "Describe the main research question or topic for the paper"
}
- row("Academic Level:") {
- cell(academicLevelCombo)
- .align(Align.FILL)
- .comment("Academic level for complexity and rigor")
+ private val paperTypeCombo = ComboBox(
+ arrayOf("empirical", "theoretical", "review", "meta-analysis", "systematic-review")
+ ).apply {
+ selectedItem = "empirical"
+ toolTipText = "Type of research paper to generate"
}
- row("Citation Style:") {
- cell(citationStyleCombo)
- .align(Align.FILL)
- .comment("Citation format (APA, MLA, Chicago, IEEE)")
+ private val academicLevelCombo = ComboBox(
+ arrayOf("undergraduate", "masters", "phd", "postdoc")
+ ).apply {
+ selectedItem = "masters"
+ toolTipText = "Academic level for the paper's complexity and rigor"
}
- row("Target Word Count:") {
- cell(targetWordCountSpinner)
- .comment("Target word count for the complete paper")
+ private val citationStyleCombo = ComboBox(
+ arrayOf("apa", "mla", "chicago", "ieee")
+ ).apply {
+ selectedItem = "apa"
+ toolTipText = "Citation style to use throughout the paper"
}
- row("Number of Sections:") {
- cell(numberOfSectionsSpinner)
- .comment("Number of main sections (excluding abstract/conclusion)")
+ private val targetWordCountSpinner = JSpinner(SpinnerNumberModel(8000, 1000, 50000, 1000)).apply {
+ toolTipText = "Target word count for the complete paper (1000-50000)"
}
- row("Revision Passes:") {
- cell(revisionPassesSpinner)
- .comment("Number of revision passes for quality improvement")
+ private val numberOfSectionsSpinner = JSpinner(SpinnerNumberModel(6, 3, 15, 1)).apply {
+ toolTipText = "Number of main sections (3-15)"
}
- }
- group("Paper Features") {
- row {
- cell(includeLiteratureReviewCheckbox)
+ private val revisionPassesSpinner = JSpinner(SpinnerNumberModel(1, 0, 5, 1)).apply {
+ toolTipText = "Number of revision passes for quality improvement (0-5)"
}
- row {
- cell(includeMethodologyCheckbox)
+ private val includeLiteratureReviewCheckbox = JBCheckBox("Include Literature Review", true).apply {
+ toolTipText = "Include a comprehensive literature review section"
}
- row {
- cell(includeStatisticalAnalysisCheckbox)
+ private val includeMethodologyCheckbox = JBCheckBox("Include Methodology", true).apply {
+ toolTipText = "Include a methodology section describing research methods"
}
- row {
- cell(includePeerReviewCheckbox)
+ private val includeStatisticalAnalysisCheckbox = JBCheckBox("Include Statistical Analysis", true).apply {
+ toolTipText = "Include descriptions of statistical analysis methods"
}
- }
- group("Source Files") {
- row("Input Files:") {
- cell(inputFilesField)
- .align(Align.FILL)
- .comment("Files or patterns to use as input (e.g., **/*.kt, docs/*.md)")
+ private val includePeerReviewCheckbox = JBCheckBox("Include Peer Review Simulation", true).apply {
+ toolTipText = "Simulate peer review to identify weaknesses and improvements"
}
- row("Research Files:") {
- cell(researchFilesField)
- .align(Align.FILL)
- .comment("Research source files to incorporate (optional)")
+ private val inputFilesField = JBTextField().apply {
+ toolTipText = "Comma-separated list of input files or patterns (e.g., **/*.kt, docs/*.md)"
+ text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
}
- }
- group("Model Settings") {
- row("Model:") {
- cell(modelCombo)
- .align(Align.FILL)
- .comment("AI model for generating the research paper")
+ private val researchFilesField = JBTextField().apply {
+ toolTipText = "Comma-separated list of research source files to incorporate"
+ text = ""
}
- row("Temperature:") {
- cell(temperatureSlider)
- .align(Align.FILL)
- .comment("Higher values = more creative, lower = more focused")
- cell(temperatureLabel)
+ private val visibleModelsCache by lazy { getVisibleModels() }
+
+ private val modelCombo = ComboBox(
+ visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
+ ).apply {
+ maximumSize = Dimension(200, 30)
+ selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
+ toolTipText = "AI model to use for generating the research paper"
}
- row {
- cell(autoFixCheckbox)
+ private val temperatureSlider = JSlider(0, 100, 70).apply {
+ addChangeListener {
+ temperatureLabel.text = "%.2f".format(value / 100.0)
+ }
}
- }
- }
- override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
- if (researchTopicArea.text.isBlank()) {
- return com.intellij.openapi.ui.ValidationInfo("Research topic is required", researchTopicArea)
- }
+ private val temperatureLabel = javax.swing.JLabel("0.70")
- val targetWordCount = targetWordCountSpinner.value as Int
- if (targetWordCount < 1000 || targetWordCount > 50000) {
- return com.intellij.openapi.ui.ValidationInfo(
- "Target word count must be between 1000 and 50000",
- targetWordCountSpinner
- )
- }
+ private val autoFixCheckbox = JBCheckBox("Auto-apply generated content", false).apply {
+ toolTipText = "Automatically apply generated content without manual confirmation"
+ }
- val numberOfSections = numberOfSectionsSpinner.value as Int
- if (numberOfSections < 3 || numberOfSections > 15) {
- return com.intellij.openapi.ui.ValidationInfo(
- "Number of sections must be between 3 and 15",
- numberOfSectionsSpinner
- )
- }
+ init {
+ init()
+ title = "Configure Research Paper Generation Task"
+ }
- val revisionPasses = revisionPassesSpinner.value as Int
- if (revisionPasses < 0 || revisionPasses > 5) {
- return com.intellij.openapi.ui.ValidationInfo(
- "Revision passes must be between 0 and 5",
- revisionPassesSpinner
- )
- }
+ override fun createCenterPanel(): JComponent = panel {
+ group("Research Configuration") {
+ row("Research Topic:") {
+ scrollCell(researchTopicArea)
+ .align(Align.FILL)
+ .comment("Describe the main research question or topic")
+ .resizableColumn()
+ }.resizableRow()
+
+ row("Paper Type:") {
+ cell(paperTypeCombo)
+ .align(Align.FILL)
+ .comment("Type of research paper (empirical, theoretical, review, etc.)")
+ }
+
+ row("Academic Level:") {
+ cell(academicLevelCombo)
+ .align(Align.FILL)
+ .comment("Academic level for complexity and rigor")
+ }
+
+ row("Citation Style:") {
+ cell(citationStyleCombo)
+ .align(Align.FILL)
+ .comment("Citation format (APA, MLA, Chicago, IEEE)")
+ }
+
+ row("Target Word Count:") {
+ cell(targetWordCountSpinner)
+ .comment("Target word count for the complete paper")
+ }
+
+ row("Number of Sections:") {
+ cell(numberOfSectionsSpinner)
+ .comment("Number of main sections (excluding abstract/conclusion)")
+ }
+
+ row("Revision Passes:") {
+ cell(revisionPassesSpinner)
+ .comment("Number of revision passes for quality improvement")
+ }
+ }
+
+ group("Paper Features") {
+ row {
+ cell(includeLiteratureReviewCheckbox)
+ }
+
+ row {
+ cell(includeMethodologyCheckbox)
+ }
+
+ row {
+ cell(includeStatisticalAnalysisCheckbox)
+ }
+
+ row {
+ cell(includePeerReviewCheckbox)
+ }
+ }
+
+ group("Source Files") {
+ row("Input Files:") {
+ cell(inputFilesField)
+ .align(Align.FILL)
+ .comment("Files or patterns to use as input (e.g., **/*.kt, docs/*.md)")
+ }
+
+ row("Research Files:") {
+ cell(researchFilesField)
+ .align(Align.FILL)
+ .comment("Research source files to incorporate (optional)")
+ }
+ }
+
+ group("Model Settings") {
+ row("Model:") {
+ cell(modelCombo)
+ .align(Align.FILL)
+ .comment("AI model for generating the research paper")
+ }
+
+ row("Temperature:") {
+ cell(temperatureSlider)
+ .align(Align.FILL)
+ .comment("Higher values = more creative, lower = more focused")
+ cell(temperatureLabel)
+ }
+
+ row {
+ cell(autoFixCheckbox)
+ }
+ }
+ }
- return null
- }
+ override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
+ if (researchTopicArea.text.isBlank()) {
+ return com.intellij.openapi.ui.ValidationInfo("Research topic is required", researchTopicArea)
+ }
+
+ val targetWordCount = targetWordCountSpinner.value as Int
+ if (targetWordCount < 1000 || targetWordCount > 50000) {
+ return com.intellij.openapi.ui.ValidationInfo(
+ "Target word count must be between 1000 and 50000",
+ targetWordCountSpinner
+ )
+ }
+
+ val numberOfSections = numberOfSectionsSpinner.value as Int
+ if (numberOfSections < 3 || numberOfSections > 15) {
+ return com.intellij.openapi.ui.ValidationInfo(
+ "Number of sections must be between 3 and 15",
+ numberOfSectionsSpinner
+ )
+ }
+
+ val revisionPasses = revisionPassesSpinner.value as Int
+ if (revisionPasses < 0 || revisionPasses > 5) {
+ return com.intellij.openapi.ui.ValidationInfo(
+ "Revision passes must be between 0 and 5",
+ revisionPassesSpinner
+ )
+ }
+
+ return null
+ }
- fun getTaskConfig(): ResearchPaperGenerationTask.ResearchPaperGenerationTaskExecutionConfigData {
- val inputFiles = inputFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
- .takeIf { it.isNotEmpty() }
-
- val researchFiles = researchFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
- .takeIf { it.isNotEmpty() }
-
- return ResearchPaperGenerationTask.ResearchPaperGenerationTaskExecutionConfigData(
- research_topic = researchTopicArea.text,
- paper_type = paperTypeCombo.selectedItem as String,
- academic_level = academicLevelCombo.selectedItem as String,
- target_word_count = targetWordCountSpinner.value as Int,
- citation_style = citationStyleCombo.selectedItem as String,
- include_literature_review = includeLiteratureReviewCheckbox.isSelected,
- include_methodology = includeMethodologyCheckbox.isSelected,
- include_statistical_analysis = includeStatisticalAnalysisCheckbox.isSelected,
- include_peer_review = includePeerReviewCheckbox.isSelected,
- number_of_sections = numberOfSectionsSpinner.value as Int,
- revision_passes = revisionPassesSpinner.value as Int,
- research_files = researchFiles,
- input_files = inputFiles,
- state = TaskState.Pending
- )
- }
+ fun getTaskConfig(): ResearchPaperGenerationTask.ResearchPaperGenerationTaskExecutionConfigData {
+ val inputFiles = inputFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+ .takeIf { it.isNotEmpty() }
+
+ val researchFiles = researchFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+ .takeIf { it.isNotEmpty() }
+
+ return ResearchPaperGenerationTask.ResearchPaperGenerationTaskExecutionConfigData(
+ research_topic = researchTopicArea.text,
+ paper_type = paperTypeCombo.selectedItem as String,
+ academic_level = academicLevelCombo.selectedItem as String,
+ target_word_count = targetWordCountSpinner.value as Int,
+ citation_style = citationStyleCombo.selectedItem as String,
+ include_literature_review = includeLiteratureReviewCheckbox.isSelected,
+ include_methodology = includeMethodologyCheckbox.isSelected,
+ include_statistical_analysis = includeStatisticalAnalysisCheckbox.isSelected,
+ include_peer_review = includePeerReviewCheckbox.isSelected,
+ number_of_sections = numberOfSectionsSpinner.value as Int,
+ revision_passes = revisionPassesSpinner.value as Int,
+ research_files = researchFiles,
+ input_files = inputFiles,
+ state = TaskState.Pending
+ )
+ }
- fun getOrchestrationConfig(): OrchestrationConfig {
- val selectedModel = modelCombo.selectedItem as? String
- val model = selectedModel?.let { modelName ->
- visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
- }
-
- return OrchestrationConfig(
- "Config",
- defaultSmartModel = model ?: AppSettingsState.instance.smartModel
- ?: throw IllegalStateException("No model configured"),
- defaultFastModel = AppSettingsState.instance.fastModel
- ?: throw IllegalStateException("Fast model not configured"),
- temperature = temperatureSlider.value / 100.0,
- autoFix = autoFixCheckbox.isSelected,
- workingDir = root.absolutePath,
- shellCmd = listOf(
- if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
- )
- )
- }
+ fun getOrchestrationConfig(): OrchestrationConfig {
+ val selectedModel = modelCombo.selectedItem as? String
+ val model = selectedModel?.let { modelName ->
+ visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
+ }
+
+ return OrchestrationConfig(
+ "Config",
+ defaultSmartModel = model ?: AppSettingsState.instance.smartModel
+ ?: throw IllegalStateException("No model configured"),
+ defaultFastModel = AppSettingsState.instance.fastModel
+ ?: throw IllegalStateException("Fast model not configured"),
+ temperature = temperatureSlider.value / 100.0,
+ autoFix = autoFixCheckbox.isSelected,
+ workingDir = root.absolutePath,
+ shellCmd = listOf(
+ if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
+ )
+ )
+ }
- private fun getVisibleModels() =
- ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
- model.provider == apiData.provider &&
- model.modelName?.isNotBlank() == true &&
- PlanConfigDialog.isVisible(model)
- } ?: listOf()
- }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
- }
+ private fun getVisibleModels() =
+ ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
+ model.provider == apiData.provider &&
+ model.modelName?.isNotBlank() == true &&
+ PlanConfigDialog.isVisible(model)
+ } ?: listOf()
+ }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
+ }
}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/cognotik/actions/task/WriteHtmlAction.kt b/intellij/src/main/kotlin/cognotik/actions/task/WriteHtmlAction.kt
index b6bcfeae8..484c350d6 100644
--- a/intellij/src/main/kotlin/cognotik/actions/task/WriteHtmlAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/task/WriteHtmlAction.kt
@@ -41,294 +41,298 @@ import javax.swing.SpinnerNumberModel
class WriteHtmlAction : BaseAction() {
- override fun getActionUpdateThread() = ActionUpdateThread.BGT
- override fun isEnabled(event: AnActionEvent): Boolean {
- if (!super.isEnabled(event)) return false
- if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
- return true
- }
-
- override fun handle(e: AnActionEvent) {
- val root = getProjectRoot(e) ?: return
- val relatedFiles = getFiles(e)
- val dialog = WriteHtmlTaskDialog(
- e.project, root, relatedFiles
- )
-
- if (dialog.showAndGet()) {
- try {
- val taskConfig = dialog.getTaskConfig()
- val orchestrationConfig = dialog.getOrchestrationConfig()
-
- UITools.runAsync(e.project, "Initializing HTML Generation Task", true) { progress ->
- initializeTask(e, progress, orchestrationConfig, taskConfig, root)
- }
- } catch (ex: Exception) {
- log.error("Failed to initialize HTML generation task", ex)
- UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
- }
- }
- }
-
- private fun initializeTask(
- e: AnActionEvent,
- progress: ProgressIndicator,
- orchestrationConfig: OrchestrationConfig,
- taskConfig: WriteHtmlTask.WriteHtmlTaskExecutionConfigData,
- root: File
- ) {
- progress.text = "Setting up session..."
- val session = Session.newGlobalID()
-
- DataStorage.sessionPaths[session] = root
-
- progress.text = "Starting server..."
- setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
-
- Thread {
- Thread.sleep(500)
- try {
- val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
- log.info("Opening browser to $uri")
- browse(uri)
- } catch (e: Throwable) {
- log.warn("Error opening browser", e)
- }
- }.start()
- }
-
- private fun setupTaskSession(
- session: Session, orchestrationConfig: OrchestrationConfig, taskConfig: WriteHtmlTask.WriteHtmlTaskExecutionConfigData, root: File
- ) {
- val app = object : SingleTaskApp(
- applicationName = "HTML Generation Task",
- path = "/writeHtmlTask",
- showMenubar = false,
- taskType = WriteHtmlTask.WriteHtml,
- taskConfig = taskConfig,
- instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
- ) {
- override fun instance(model: ApiChatModel) = model.instance() ?: throw IllegalStateException("Model or Provider not set")
+ override fun getActionUpdateThread() = ActionUpdateThread.BGT
+ override fun isEnabled(event: AnActionEvent): Boolean {
+ if (!super.isEnabled(event)) return false
+ if (event.getSelectedFiles().isEmpty() && event.getSelectedFolder() == null) return false
+ return true
}
- app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
- SessionProxyServer.chats[session] = app
- ApplicationServer.appInfoMap[session] = AppInfoData(
- applicationName = "HTML Generation Task", inputCnt = 0, stickyInput = false, showMenubar = false
- )
- SessionProxyServer.metadataStorage.setSessionName(
- null, session, "HTML Generation @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
- )
- }
-
- private fun getProjectRoot(e: AnActionEvent): File? {
- val folder = e.getSelectedFolder()
- return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
- getModuleRootForFile(file)
- }
- }
-
- class WriteHtmlTaskDialog(
- project: Project?,
- private val root: File,
- val relatedFiles: List
- ) : DialogWrapper(project) {
-
- private val taskDescriptionArea = JBTextArea(8, 40).apply {
- lineWrap = true
- wrapStyleWord = true
- toolTipText = "Describe the HTML page to create, including layout, styling, and functionality requirements"
- }
-
- private val htmlFileField = JBTextField().apply {
- toolTipText = "Path for the HTML file to create (must end with .html)"
- text = "${relatedFiles.firstOrNull()?.nameWithoutExtension ?: "index"}.html"
- }
-
- private val relatedFilesField = JBTextField().apply {
- toolTipText = "Comma-separated list of related files to consider for context (e.g., existing templates)"
- text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
- }
+ override fun handle(e: AnActionEvent) {
+ val root = getProjectRoot(e) ?: return
+ val relatedFiles = getFiles(e)
+ val dialog = WriteHtmlTaskDialog(
+ e.project, root, relatedFiles
+ )
- private val generateImagesCheckbox = JBCheckBox("Generate images for the page", false).apply {
- toolTipText = "Use AI to generate images for the HTML page"
- addActionListener {
- imageCountSpinner.isEnabled = isSelected
- }
+ if (dialog.showAndGet()) {
+ try {
+ val taskConfig = dialog.getTaskConfig()
+ val orchestrationConfig = dialog.getOrchestrationConfig()
+
+ UITools.runAsync(e.project, "Initializing HTML Generation Task", true) { progress ->
+ initializeTask(e, progress, orchestrationConfig, taskConfig, root)
+ }
+ } catch (ex: Exception) {
+ log.error("Failed to initialize HTML generation task", ex)
+ UITools.showError(e.project, "Failed to initialize task: ${ex.message}")
+ }
+ }
}
- private val imageCountSpinner = JSpinner(SpinnerNumberModel(3, 0, 10, 1)).apply {
- toolTipText = "Number of images to generate (0-10)"
- isEnabled = false
+ private fun initializeTask(
+ e: AnActionEvent,
+ progress: ProgressIndicator,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: WriteHtmlTask.WriteHtmlTaskExecutionConfigData,
+ root: File
+ ) {
+ progress.text = "Setting up session..."
+ val session = Session.newGlobalID()
+
+ DataStorage.sessionPaths[session] = root
+
+ progress.text = "Starting server..."
+ setupTaskSession(session, orchestrationConfig.copy(sessionId = session.sessionId), taskConfig, root)
+
+ Thread {
+ Thread.sleep(500)
+ try {
+ val uri = CognotikAppServer.getServer().server.uri.resolve("/#$session")
+ log.info("Opening browser to $uri")
+ browse(uri)
+ } catch (e: Throwable) {
+ log.warn("Error opening browser", e)
+ }
+ }.start()
}
- private val visibleModelsCache by lazy { getVisibleModels() }
+ private fun setupTaskSession(
+ session: Session,
+ orchestrationConfig: OrchestrationConfig,
+ taskConfig: WriteHtmlTask.WriteHtmlTaskExecutionConfigData,
+ root: File
+ ) {
+ val app = object : SingleTaskApp(
+ applicationName = "HTML Generation Task",
+ path = "/writeHtmlTask",
+ showMenubar = false,
+ taskType = WriteHtmlTask.WriteHtml,
+ taskConfig = taskConfig,
+ instanceFn = { model -> model.instance() ?: throw IllegalStateException("Model or Provider not set") }
+ ) {
+ override fun instance(model: ApiChatModel) =
+ model.instance() ?: throw IllegalStateException("Model or Provider not set")
+ }
- private val modelCombo = ComboBox(
- visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
- ).apply {
- maximumSize = Dimension(200, 30)
- selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
- toolTipText = "AI model to use for generating HTML, CSS, and JavaScript"
+ app.getSettingsFile(session, UserSettingsManager.defaultUser).writeText(orchestrationConfig.toJson())
+ SessionProxyServer.chats[session] = app
+ ApplicationServer.appInfoMap[session] = AppInfoData(
+ applicationName = "HTML Generation Task", inputCnt = 0, stickyInput = false, showMenubar = false
+ )
+ SessionProxyServer.metadataStorage.setSessionName(
+ null, session, "HTML Generation @ ${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}"
+ )
}
- private val imageModelCombo = ComboBox(
- visibleModelsCache
- .distinctBy { it.modelName }
- .map { it.modelName }
- .toTypedArray()
- ).apply {
- maximumSize = Dimension(200, 30)
- selectedItem = AppSettingsState.instance.imageChatModel?.model?.modelName
- toolTipText = "AI model to use for generating images"
+ private fun getProjectRoot(e: AnActionEvent): File? {
+ val folder = e.getSelectedFolder()
+ return folder?.toFile ?: e.getSelectedFile()?.parent?.toFile?.let { file ->
+ getModuleRootForFile(file)
+ }
}
- private val temperatureSlider = JSlider(0, 100, 70).apply {
- addChangeListener {
- temperatureLabel.text = "%.2f".format(value / 100.0)
- }
- }
+ class WriteHtmlTaskDialog(
+ project: Project?,
+ private val root: File,
+ val relatedFiles: List
+ ) : DialogWrapper(project) {
- private val temperatureLabel = javax.swing.JLabel("0.70")
+ private val taskDescriptionArea = JBTextArea(8, 40).apply {
+ lineWrap = true
+ wrapStyleWord = true
+ toolTipText = "Describe the HTML page to create, including layout, styling, and functionality requirements"
+ }
- private val autoFixCheckbox = JBCheckBox("Auto-apply generated HTML", false).apply {
- toolTipText = "Automatically write the generated HTML file without manual confirmation"
- }
+ private val htmlFileField = JBTextField().apply {
+ toolTipText = "Path for the HTML file to create (must end with .html)"
+ text = "${relatedFiles.firstOrNull()?.nameWithoutExtension ?: "index"}.html"
+ }
- init {
- init()
- title = "Configure HTML Generation Task"
- }
+ private val relatedFilesField = JBTextField().apply {
+ toolTipText = "Comma-separated list of related files to consider for context (e.g., existing templates)"
+ text = relatedFiles.joinToString(", ") { it.relativeTo(root).path }
+ }
- override fun createCenterPanel(): JComponent = panel {
- group("HTML Configuration") {
- row("HTML File:") {
- cell(htmlFileField)
- .align(Align.FILL)
- .comment("Output path for the HTML file (e.g., index.html, pages/about.html)")
+ private val generateImagesCheckbox = JBCheckBox("Generate images for the page", false).apply {
+ toolTipText = "Use AI to generate images for the HTML page"
+ addActionListener {
+ imageCountSpinner.isEnabled = isSelected
+ }
}
- row("Page Description:") {
- scrollCell(taskDescriptionArea)
- .align(Align.FILL)
- .comment("Describe the page layout, styling, functionality, and any specific requirements")
- .resizableColumn()
- }.resizableRow()
-
- row("Related Files:") {
- cell(relatedFilesField)
- .align(Align.FILL)
- .comment("Additional files for context (optional)")
+ private val imageCountSpinner = JSpinner(SpinnerNumberModel(3, 0, 10, 1)).apply {
+ toolTipText = "Number of images to generate (0-10)"
+ isEnabled = false
}
- }
- group("Image Generation") {
- row {
- cell(generateImagesCheckbox)
+ private val visibleModelsCache by lazy { getVisibleModels() }
+
+ private val modelCombo = ComboBox(
+ visibleModelsCache.distinctBy { it.modelName }.map { it.modelName }.toTypedArray()
+ ).apply {
+ maximumSize = Dimension(200, 30)
+ selectedItem = AppSettingsState.instance.smartModel?.model?.modelName
+ toolTipText = "AI model to use for generating HTML, CSS, and JavaScript"
}
- row("Number of Images:") {
- cell(imageCountSpinner)
- .comment("How many images to generate (0-10)")
+ private val imageModelCombo = ComboBox(
+ visibleModelsCache
+ .distinctBy { it.modelName }
+ .map { it.modelName }
+ .toTypedArray()
+ ).apply {
+ maximumSize = Dimension(200, 30)
+ selectedItem = AppSettingsState.instance.imageChatModel?.model?.modelName
+ toolTipText = "AI model to use for generating images"
}
- row("Image Model:") {
- cell(imageModelCombo)
- .align(Align.FILL)
- .comment("AI model for image generation")
+ private val temperatureSlider = JSlider(0, 100, 70).apply {
+ addChangeListener {
+ temperatureLabel.text = "%.2f".format(value / 100.0)
+ }
}
- }
- group("Model Settings") {
- row("Text Model:") {
- cell(modelCombo)
- .align(Align.FILL)
- .comment("AI model for generating HTML, CSS, and JavaScript")
+ private val temperatureLabel = javax.swing.JLabel("0.70")
+
+ private val autoFixCheckbox = JBCheckBox("Auto-apply generated HTML", false).apply {
+ toolTipText = "Automatically write the generated HTML file without manual confirmation"
}
- row("Temperature:") {
- cell(temperatureSlider)
- .align(Align.FILL)
- .comment("Higher values = more creative, lower = more focused")
- cell(temperatureLabel)
+ init {
+ init()
+ title = "Configure HTML Generation Task"
}
- row {
- cell(autoFixCheckbox)
+ override fun createCenterPanel(): JComponent = panel {
+ group("HTML Configuration") {
+ row("HTML File:") {
+ cell(htmlFileField)
+ .align(Align.FILL)
+ .comment("Output path for the HTML file (e.g., index.html, pages/about.html)")
+ }
+
+ row("Page Description:") {
+ scrollCell(taskDescriptionArea)
+ .align(Align.FILL)
+ .comment("Describe the page layout, styling, functionality, and any specific requirements")
+ .resizableColumn()
+ }.resizableRow()
+
+ row("Related Files:") {
+ cell(relatedFilesField)
+ .align(Align.FILL)
+ .comment("Additional files for context (optional)")
+ }
+ }
+
+ group("Image Generation") {
+ row {
+ cell(generateImagesCheckbox)
+ }
+
+ row("Number of Images:") {
+ cell(imageCountSpinner)
+ .comment("How many images to generate (0-10)")
+ }
+
+ row("Image Model:") {
+ cell(imageModelCombo)
+ .align(Align.FILL)
+ .comment("AI model for image generation")
+ }
+ }
+
+ group("Model Settings") {
+ row("Text Model:") {
+ cell(modelCombo)
+ .align(Align.FILL)
+ .comment("AI model for generating HTML, CSS, and JavaScript")
+ }
+
+ row("Temperature:") {
+ cell(temperatureSlider)
+ .align(Align.FILL)
+ .comment("Higher values = more creative, lower = more focused")
+ cell(temperatureLabel)
+ }
+
+ row {
+ cell(autoFixCheckbox)
+ }
+ }
}
- }
- }
- override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
+ override fun doValidate(): com.intellij.openapi.ui.ValidationInfo? {
// if (taskDescriptionArea.text.isBlank()) {
// return com.intellij.openapi.ui.ValidationInfo("Page description is required", taskDescriptionArea)
// }
- if (htmlFileField.text.isBlank()) {
- return com.intellij.openapi.ui.ValidationInfo("HTML file path is required", htmlFileField)
- } else {
- if (htmlFileField.text.let { root.resolve(it) }.exists()) {
- return com.intellij.openapi.ui.ValidationInfo("HTML file path must not exist", htmlFileField)
- }
- }
+ if (htmlFileField.text.isBlank()) {
+ return com.intellij.openapi.ui.ValidationInfo("HTML file path is required", htmlFileField)
+ } else {
+ if (htmlFileField.text.let { root.resolve(it) }.exists()) {
+ return com.intellij.openapi.ui.ValidationInfo("HTML file path must not exist", htmlFileField)
+ }
+ }
- if (!htmlFileField.text.endsWith(".html", ignoreCase = true)) {
- return com.intellij.openapi.ui.ValidationInfo("File must have .html extension", htmlFileField)
- }
+ if (!htmlFileField.text.endsWith(".html", ignoreCase = true)) {
+ return com.intellij.openapi.ui.ValidationInfo("File must have .html extension", htmlFileField)
+ }
- return null
- }
+ return null
+ }
- fun getTaskConfig(): WriteHtmlTask.WriteHtmlTaskExecutionConfigData {
- val relatedFiles = relatedFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
- .takeIf { it.isNotEmpty() }
-
- return WriteHtmlTask.WriteHtmlTaskExecutionConfigData(
- files = listOf(htmlFileField.text),
- related_files = relatedFiles,
- task_description = taskDescriptionArea.text,
- generate_images = generateImagesCheckbox.isSelected,
- image_count = imageCountSpinner.value as Int,
- state = TaskState.Pending
- )
- }
+ fun getTaskConfig(): WriteHtmlTask.WriteHtmlTaskExecutionConfigData {
+ val relatedFiles = relatedFilesField.text.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+ .takeIf { it.isNotEmpty() }
+
+ return WriteHtmlTask.WriteHtmlTaskExecutionConfigData(
+ files = listOf(htmlFileField.text),
+ related_files = relatedFiles,
+ task_description = taskDescriptionArea.text,
+ generate_images = generateImagesCheckbox.isSelected,
+ image_count = imageCountSpinner.value as Int,
+ state = TaskState.Pending
+ )
+ }
- fun getOrchestrationConfig(): OrchestrationConfig {
- val selectedModel = modelCombo.selectedItem as? String
- val model = selectedModel?.let { modelName ->
- visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
- }
-
- val selectedImageModel = imageModelCombo.selectedItem as? String
- val imageModel = selectedImageModel?.let { modelName ->
- visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
- }
-
- return OrchestrationConfig(
- "Config",
- defaultSmartModel = model ?: AppSettingsState.instance.smartModel
- ?: throw IllegalStateException("No model configured"),
- defaultFastModel = AppSettingsState.instance.fastModel
- ?: throw IllegalStateException("Fast model not configured"),
- defaultImageModel = imageModel ?: AppSettingsState.instance.smartModel
- ?: throw IllegalStateException("No image model configured"),
- temperature = temperatureSlider.value / 100.0,
- autoFix = autoFixCheckbox.isSelected,
- workingDir = root.absolutePath,
- shellCmd = listOf(
- if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
- )
- )
- }
+ fun getOrchestrationConfig(): OrchestrationConfig {
+ val selectedModel = modelCombo.selectedItem as? String
+ val model = selectedModel?.let { modelName ->
+ visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
+ }
+
+ val selectedImageModel = imageModelCombo.selectedItem as? String
+ val imageModel = selectedImageModel?.let { modelName ->
+ visibleModelsCache.find { it.modelName == modelName }?.toApiChatModel()
+ }
+
+ return OrchestrationConfig(
+ "Config",
+ defaultSmartModel = model ?: AppSettingsState.instance.smartModel
+ ?: throw IllegalStateException("No model configured"),
+ defaultFastModel = AppSettingsState.instance.fastModel
+ ?: throw IllegalStateException("Fast model not configured"),
+ defaultImageModel = imageModel ?: AppSettingsState.instance.smartModel
+ ?: throw IllegalStateException("No image model configured"),
+ temperature = temperatureSlider.value / 100.0,
+ autoFix = autoFixCheckbox.isSelected,
+ workingDir = root.absolutePath,
+ shellCmd = listOf(
+ if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
+ )
+ )
+ }
- private fun getVisibleModels() =
- ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
- model.provider == apiData.provider &&
- model.modelName?.isNotBlank() == true &&
- PlanConfigDialog.isVisible(model)
- } ?: listOf()
- }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
- }
+ private fun getVisibleModels() =
+ ApplicationServices.fileApplicationServices().userSettingsManager.getUserSettings().apis.flatMap { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.filter { model ->
+ model.provider == apiData.provider &&
+ model.modelName?.isNotBlank() == true &&
+ PlanConfigDialog.isVisible(model)
+ } ?: listOf()
+ }.distinctBy { it.modelName }.sortedBy { "${it.provider?.name} - ${it.modelName}" }
+ }
}
diff --git a/intellij/src/main/kotlin/cognotik/actions/test/TestResultAutofixAction.kt b/intellij/src/main/kotlin/cognotik/actions/test/TestResultAutofixAction.kt
index 2a8b0e565..d2aa117d8 100644
--- a/intellij/src/main/kotlin/cognotik/actions/test/TestResultAutofixAction.kt
+++ b/intellij/src/main/kotlin/cognotik/actions/test/TestResultAutofixAction.kt
@@ -67,17 +67,6 @@ class TestResultAutofixAction : BaseAction() {
return str
}
- fun findGitRoot(path: Path?): Path? {
- var current: Path? = path
- while (current != null) {
- if (current.resolve(".git").toFile().exists()) {
- return current
- }
- current = current.parent
- }
- return null
- }
-
fun findGitRoot(virtualFile: VirtualFile?): VirtualFile? {
var current: VirtualFile? = virtualFile
while (current != null) {
@@ -109,7 +98,7 @@ class TestResultAutofixAction : BaseAction() {
}
override fun isEnabled(e: AnActionEvent): Boolean {
- if (!super.isEnabled(e)) return false
+ if (!super.isEnabled(e)) return false
val testProxy = e.getData(AbstractTestProxy.DATA_KEY)
return testProxy != null
}
@@ -206,7 +195,7 @@ class TestResultAutofixAction : BaseAction() {
""".trimIndent(),
model = AppSettingsState.instance.smartChatClient,
parsingChatter = AppSettingsState.instance.fastChatClient,
- ).answer(listOf(testInfo), )
+ ).answer(listOf(testInfo))
if (plan.obj.errors.isNullOrEmpty()) {
task.add("No errors identified in test result")
return@Retryable task.placeholder
@@ -215,8 +204,8 @@ class TestResultAutofixAction : BaseAction() {
task.add(
AgentPatterns.displayMapInTabs(
mapOf(
- "Text" to plan.text.renderMarkdown,
- "JSON" to "${tripleTilde}json\n${JsonUtil.toJson(plan.obj)}\n$tripleTilde".renderMarkdown,
+ "Text" to plan.text.renderMarkdown,
+ "JSON" to "${tripleTilde}json\n${JsonUtil.toJson(plan.obj)}\n$tripleTilde".renderMarkdown,
)
)
)
@@ -276,7 +265,7 @@ $projectStructure
The diff should include 2 lines of context before and after every change.
""".trimIndent(),
model = AppSettingsState.instance.smartChatClient
- ).answer(listOf(error.message ?: ""), )
+ ).answer(listOf(error.message ?: ""))
task.add("Processing suggested fixes...")
val markdown = AddApplyFileDiffLinks.instrumentFileDiffs(
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/CognotikAppServer.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/CognotikAppServer.kt
index 6dfa25097..ba0a5c995 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/CognotikAppServer.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/CognotikAppServer.kt
@@ -1,122 +1,66 @@
package com.simiacryptus.cognotik
-import com.intellij.openapi.diagnostic.Logger
import com.simiacryptus.cognotik.config.AppSettingsState
import com.simiacryptus.cognotik.util.SessionProxyServer
- import com.simiacryptus.cognotik.webui.chat.ChatServer
- import com.simiacryptus.cognotik.webui.servlet.CorsFilter
- import jakarta.servlet.DispatcherType
- import org.eclipse.jetty.server.Server
- import org.eclipse.jetty.server.handler.ContextHandlerCollection
- import org.eclipse.jetty.servlet.FilterHolder
- import org.eclipse.jetty.webapp.WebAppContext
- import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer
- import java.net.InetSocketAddress
- import java.util.*
+import com.simiacryptus.cognotik.webui.application.CognotikAppServer
+import com.simiacryptus.cognotik.webui.chat.ChatServer
+import com.simiacryptus.cognotik.webui.servlet.CorsFilter
+import jakarta.servlet.DispatcherType
+import org.eclipse.jetty.server.Server
+import org.eclipse.jetty.server.handler.ContextHandlerCollection
+import org.eclipse.jetty.servlet.FilterHolder
+import org.eclipse.jetty.webapp.WebAppContext
+import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer
+import org.slf4j.LoggerFactory
+import java.net.InetSocketAddress
+import java.util.EnumSet
+import kotlin.apply
+import kotlin.collections.map
+import kotlin.collections.toMutableList
+import kotlin.collections.toTypedArray
+import kotlin.compareTo
+import kotlin.jvm.java
+import kotlin.text.isBlank
-class CognotikAppServer(
- private val localName: String,
- private val port: Int
-) {
- val server by lazy {
- try {
- log.info("Initializing server on $localName:$port")
- val server = Server(InetSocketAddress(localName, port))
- server.handler = ContextHandlerCollection().apply {
- this.handlers = arrayOf(
- newWebAppContext(SessionProxyServer(), "/")
- ).map {
- try {
- it.addFilter(FilterHolder(CorsFilter()), "/*", EnumSet.of(DispatcherType.REQUEST))
- log.debug("Added CORS filter to context: ${it.contextPath}")
- it
- } catch (e: Exception) {
- log.error("Failed to add CORS filter to context", e)
- throw e
- }
- }.toMutableList().toTypedArray()
- }
- server
- } catch (e: Exception) {
- log.error("Failed to initialize server on $localName:$port", e)
- throw e
- }
- }
+object CognotikAppServer {
+ private val log = LoggerFactory.getLogger(CognotikAppServer::class.java)
- private fun newWebAppContext(server: ChatServer, path: String): WebAppContext {
- return try {
- log.debug("Creating new WebAppContext for path: $path")
- val context = WebAppContext()
- JettyWebSocketServletContainerInitializer.configure(context, null)
- context.baseResource = server.baseResource
- context.classLoader = CognotikAppServer::class.java.classLoader
- context.contextPath = path
- context.welcomeFiles = arrayOf("index.html")
- server.configure(context)
- log.info("Successfully created WebAppContext for path: $path")
- context
- } catch (e: Exception) {
- log.error("Failed to create WebAppContext for path: $path", e)
- throw e
- }
+ @Transient
+ private var server: CognotikAppServer? = null
+
+ fun isRunning(): Boolean {
+ val running = server?.server?.isRunning ?: false
+ log.debug("Server running status: $running")
+ return running
}
- fun start() {
+ fun getServer(): com.simiacryptus.cognotik.webui.application.CognotikAppServer {
try {
- log.info("Starting CognotikAppServer on $localName:$port")
- server.start()
- if (server.isStarted) {
- log.info("CognotikAppServer successfully started on $localName:$port")
- } else {
- log.warn("Server start() completed but server is not in started state")
- }
- } catch (e: Exception) {
- log.error("Failed to start CognotikAppServer", e)
- throw e
- }
- }
+ if (null == server || !server!!.server.isRunning) {
+ val endpoint = AppSettingsState.instance.listeningEndpoint
+ val port = AppSettingsState.instance.listeningPort
- companion object {
- private val log = Logger.getInstance(CognotikAppServer::class.java)
-
- @Transient
- private var server: CognotikAppServer? = null
-
- fun isRunning(): Boolean {
- val running = server?.server?.isRunning ?: false
- log.debug("Server running status: $running")
- return running
- }
+ if (endpoint.isBlank()) {
+ log.error("Listening endpoint is blank")
+ throw IllegalStateException("Listening endpoint cannot be blank")
+ }
- fun getServer(): CognotikAppServer {
- try {
- if (null == server || !server!!.server.isRunning) {
- val endpoint = AppSettingsState.instance.listeningEndpoint
- val port = AppSettingsState.instance.listeningPort
-
- if (endpoint.isBlank()) {
- log.error("Listening endpoint is blank")
- throw IllegalStateException("Listening endpoint cannot be blank")
- }
-
- if (port <= 0 || port > 65535) {
- log.error("Invalid port number: $port")
- throw IllegalArgumentException("Port must be between 1 and 65535, got: $port")
- }
-
- log.info("Creating new CognotikAppServer instance for endpoint: $endpoint:$port")
- server = CognotikAppServer(endpoint, port)
- server!!.start()
- } else {
- log.debug("Returning existing running server instance")
+ if (port <= 0 || port > 65535) {
+ log.error("Invalid port number: $port")
+ throw IllegalArgumentException("Port must be between 1 and 65535, got: $port")
}
- return server!!
- } catch (e: Exception) {
- log.error("Failed to get or create server instance", e)
- throw e
+
+ log.info("Creating new CognotikAppServer instance for endpoint: $endpoint:$port")
+ server = CognotikAppServer(endpoint, port)
+ server!!.start()
+ } else {
+ log.debug("Returning existing running server instance")
}
+ return server!!
+ } catch (e: Exception) {
+ log.error("Failed to get or create server instance", e)
+ throw e
}
-
}
-}
\ No newline at end of file
+}
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/PluginStartupActivity.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/PluginStartupActivity.kt
index b16d2e459..0558098fe 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/PluginStartupActivity.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/PluginStartupActivity.kt
@@ -11,6 +11,7 @@ import com.simiacryptus.cognotik.config.StaticAppSettingsConfigurable
import com.simiacryptus.cognotik.diff.SimpleDiffApplier
import com.simiacryptus.cognotik.models.ToolProvider
import com.simiacryptus.cognotik.plan.TaskType
+import com.simiacryptus.cognotik.plan.cognitive.CognitiveModeType
import com.simiacryptus.cognotik.platform.ApplicationServices
import com.simiacryptus.cognotik.platform.AwsPlatform
import com.simiacryptus.cognotik.platform.file.UserSettingsManager.Companion.defaultUser
@@ -31,6 +32,7 @@ class PluginStartupActivity : ProjectActivity {
override suspend fun execute(project: Project) {
require(TaskType.values().isNotEmpty())
require(ToolProvider.values().isNotEmpty())
+ require(CognitiveModeType.values().isNotEmpty())
log.info("Starting Cognotik plugin initialization for project: ${project.name}")
setLogInfo("org.apache.hc.client5.http")
setLogInfo("org.eclipse.jetty")
@@ -137,6 +139,7 @@ class PluginStartupActivity : ProjectActivity {
log.debug("AWS bucket not configured")
null
}
+
else -> AwsPlatform(
bucket = awsBucket!!,
region = Region.of(awsRegion!!),
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/SettingsWidgetFactory.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/SettingsWidgetFactory.kt
index 0f309cd33..b374a5b8f 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/SettingsWidgetFactory.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/SettingsWidgetFactory.kt
@@ -22,6 +22,7 @@ import com.simiacryptus.cognotik.platform.model.ApiChatModel
import com.simiacryptus.cognotik.platform.model.UserSettings
import com.simiacryptus.cognotik.util.BrowseUtil
import com.simiacryptus.cognotik.util.SessionProxyServer
+import com.simiacryptus.cognotik.CognotikAppServer
import icons.MyIcons
import java.awt.*
import java.awt.datatransfer.StringSelection
@@ -66,6 +67,7 @@ class SettingsWidgetFactory : StatusBarWidgetFactory {
}
return imageChatModelTree!!
}
+
private fun getPatchProcessorList(): JBList {
if (patchProcessorList == null) {
val listModel = DefaultListModel()
@@ -79,7 +81,8 @@ class SettingsWidgetFactory : StatusBarWidgetFactory {
isSelected: Boolean,
cellHasFocus: Boolean
): Component {
- val component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus)
+ val component =
+ super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus)
if (value is PatchProcessors) {
text = value.label
}
@@ -103,7 +106,7 @@ class SettingsWidgetFactory : StatusBarWidgetFactory {
private fun recreateModelTrees() {
smartModelTree = null
- patchProcessorList?.setSelectedValue(AppSettingsState.instance.processor, true)
+ patchProcessorList?.setSelectedValue(AppSettingsState.instance.processor, true)
fastModelTree = null
imageChatModelTree = null
}
@@ -336,7 +339,7 @@ class SettingsWidgetFactory : StatusBarWidgetFactory {
val threadFactory = ApplicationServices.threadPoolManager.getPool(value).threadFactory
val activeThreads = threadFactory.threads.filter {
- when(it.state) {
+ when (it.state) {
Thread.State.RUNNABLE -> true
Thread.State.BLOCKED, Thread.State.WAITING, Thread.State.TIMED_WAITING -> true
else -> false
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/AppSettingsComponent.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/AppSettingsComponent.kt
index 6feb9321d..1b56b82fe 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/AppSettingsComponent.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/AppSettingsComponent.kt
@@ -13,849 +13,851 @@ import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.ui.SimpleListCellRenderer
import com.intellij.ui.components.JBCheckBox
-import com.intellij.ui.components.JBList
import com.intellij.ui.components.JBTextField
import com.intellij.ui.table.JBTable
import com.simiacryptus.cognotik.diff.PatchProcessors
import com.simiacryptus.cognotik.embedding.EmbeddingModel
import com.simiacryptus.cognotik.image.ImageModel
import com.simiacryptus.cognotik.models.APIProvider
-import com.simiacryptus.cognotik.models.ToolData
import com.simiacryptus.cognotik.models.ToolProvider
import com.simiacryptus.cognotik.platform.ApplicationServices.fileApplicationServices
import com.simiacryptus.cognotik.util.LoggerFactory
import java.awt.*
import java.awt.event.ActionEvent
import javax.swing.*
-import javax.swing.event.ListSelectionEvent
-import javax.swing.event.ListSelectionListener
import javax.swing.table.DefaultTableCellRenderer
import javax.swing.table.DefaultTableModel
class AppSettingsComponent : Disposable {
- @Name("Enable Diff Logging")
- val diffLoggingEnabled = JBCheckBox()
-
- @Name("AWS Profile")
- val awsProfile = JBTextField().apply {
- toolTipText = "AWS Profile"
- columns = 30
- }
-
- @Name("AWS Region")
- val awsRegion = JBTextField().apply {
- toolTipText = "AWS Region"
- columns = 30
- }
-
- @Name("AWS Bucket")
- val awsBucket = JBTextField().apply {
- toolTipText = "AWS Bucket"
- columns = 30
- }
-
- @Suppress("unused")
- @Name("Store Metadata")
- val storeMetadata = JTextArea().apply {
- lineWrap = true
- wrapStyleWord = true
- }
-
- @Name("Listening Port")
- val listeningPort = JBTextField()
-
- @Name("Listening Endpoint")
- val listeningEndpoint = JBTextField()
-
- @Name("Suppress Errors")
- val suppressErrors = JBCheckBox()
-
- @Name("Use Scratches System Path")
- val useScratchesSystemPath = JBCheckBox()
-
- @Name("Model")
- val smartModel = ComboBox()
-
-@Name("Model")
- val fastModel = ComboBox()
- @Name("Model")
- val imageChatModel = ComboBox()
-
-
- @Name("Main Image Model")
- val mainImageModel = ComboBox()
-
- @Name("Embedding Model")
- val embeddingModel = ComboBox()
-
- @Name("Patch Processor")
- val patchProcessor = ComboBox()
-
-
- @Suppress("unused")
- @Name("Enable API Log")
- val apiLog = JBCheckBox()
-
- @Suppress("unused")
- val openApiLog = JButton(object : AbstractAction("Open API Log") {
- override fun actionPerformed(e: ActionEvent) {
- AppSettingsState.auxiliaryLog?.let {
- if (it.exists()) {
- val project = ApplicationManager.getApplication().runReadAction {
- ProjectManager.getInstance().openProjects.firstOrNull()
- }
- ApplicationManager.getApplication().invokeLater {
- val virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(it)
- val openFileDescriptor = OpenFileDescriptor(project, virtualFile!!, virtualFile.length.toInt())
- FileEditorManager.getInstance(project!!)
- .openTextEditor(openFileDescriptor, true)?.document?.setReadOnly(
- true
- )
- }
- }
- }
+ @Name("Enable Diff Logging")
+ val diffLoggingEnabled = JBCheckBox()
+
+ @Name("AWS Profile")
+ val awsProfile = JBTextField().apply {
+ toolTipText = "AWS Profile"
+ columns = 30
}
- })
-
- @Name("Developer Tools")
- val devActions = JBCheckBox()
-
- @Suppress("unused")
- @Name("Edit API Requests")
- val editRequests = JBCheckBox()
-
- @Name("Disable Auto-Open URLs")
- val disableAutoOpenUrls = JBCheckBox()
-
- @Name("Shell Command")
- val shellCommand = JBTextField()
-
- @Name("Show Welcome Screen")
- val showWelcomeScreen = JBCheckBox()
-
- @Name("Temperature")
- val temperature = JBTextField()
-
- @Name("APIs")
- val apis = JBTable(DefaultTableModel(arrayOf("Provider", "Name", "Key", "Base URL"), 0)).apply {
- columnModel.getColumn(0).preferredWidth = 100
- columnModel.getColumn(1).preferredWidth = 150
- columnModel.getColumn(2).preferredWidth = 200
- columnModel.getColumn(3).preferredWidth = 200
- val keyColumnIndex = 2
- columnModel.getColumn(keyColumnIndex).cellRenderer = object : DefaultTableCellRenderer() {
- override fun setValue(value: Any?) {
- text =
- if (value is String && value.isNotEmpty()) value.map { '*' }.joinToString("") else value?.toString()
- ?: ""
- }
+
+ @Name("AWS Region")
+ val awsRegion = JBTextField().apply {
+ toolTipText = "AWS Region"
+ columns = 30
}
- }
-
- @Name("API Management")
- val apiManagementPanel = JPanel(BorderLayout()).apply {
- val scrollPane = JScrollPane(apis)
- scrollPane.preferredSize = Dimension(600, 300)
- add(scrollPane, BorderLayout.CENTER)
-
- val buttonPanel = JPanel(FlowLayout(FlowLayout.LEFT))
- val addButton = JButton("Add API")
- val removeButton = JButton("Remove")
- val editButton = JButton("Edit")
-
- removeButton.isEnabled = false
- editButton.isEnabled = false
-
- addButton.addActionListener {
- val model = apis.model as DefaultTableModel
-
- // Create add dialog with all fields
- val dialog = JDialog(null as Frame?, "Add API Configuration", true)
- dialog.layout = GridBagLayout()
- val gbc = GridBagConstraints()
-
- gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST
- dialog.add(JLabel("Provider Type:"), gbc)
- gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
- val providerCombo = ComboBox(APIProvider.values().map { it.name }.toTypedArray())
- dialog.add(providerCombo, gbc)
-
- gbc.gridx = 0; gbc.gridy = 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
- dialog.add(JLabel("Name:"), gbc)
- gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
- val nameField = JBTextField(30)
- dialog.add(nameField, gbc)
-
- gbc.gridx = 0; gbc.gridy = 2; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
- dialog.add(JLabel("API Key:"), gbc)
- gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
- val keyField = JBTextField(30)
- dialog.add(keyField, gbc)
-
- gbc.gridx = 0; gbc.gridy = 3; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
- dialog.add(JLabel("Base URL:"), gbc)
- gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
- val urlField = JBTextField(30)
- dialog.add(urlField, gbc)
-
- // Auto-populate name and base URL when provider changes
- providerCombo.addActionListener {
- val selectedProvider = APIProvider.valueOf(providerCombo.selectedItem as String)
- urlField.text = selectedProvider.base
- nameField.text = selectedProvider.name
- }
-
- // Initialize with first provider's defaults
- val initialProvider = APIProvider.values().first()
- nameField.text = initialProvider.name
- urlField.text = initialProvider.base
-
- gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 2; gbc.fill = GridBagConstraints.NONE
- val buttonPanel = JPanel(FlowLayout())
- val okButton = JButton("OK")
- val cancelButton = JButton("Cancel")
-
- okButton.addActionListener {
- val provider = providerCombo.selectedItem as? String
- val name = nameField.text
-
- if (provider.isNullOrBlank()) {
- log.warn("Provider type is required")
- JOptionPane.showMessageDialog(
- dialog, "Provider type is required", "Validation Error", JOptionPane.WARNING_MESSAGE
- )
- return@addActionListener
- }
- if (name.isBlank()) {
- log.warn("API name is required")
- JOptionPane.showMessageDialog(
- dialog, "API name is required", "Validation Error", JOptionPane.WARNING_MESSAGE
- )
- return@addActionListener
- }
- model.addRow(
- arrayOf(
- providerCombo.selectedItem, nameField.text, keyField.text, urlField.text
- )
- )
- dialog.dispose()
- }
- cancelButton.addActionListener { dialog.dispose() }
-
- buttonPanel.add(okButton)
- buttonPanel.add(cancelButton)
- dialog.add(buttonPanel, gbc)
-
- dialog.pack()
- dialog.setLocationRelativeTo(this)
- dialog.isVisible = true
+ @Name("AWS Bucket")
+ val awsBucket = JBTextField().apply {
+ toolTipText = "AWS Bucket"
+ columns = 30
}
+ @Suppress("unused")
+ @Name("Store Metadata")
+ val storeMetadata = JTextArea().apply {
+ lineWrap = true
+ wrapStyleWord = true
+ }
+
+ @Name("Listening Port")
+ val listeningPort = JBTextField()
+
+ @Name("Listening Endpoint")
+ val listeningEndpoint = JBTextField()
+
+ @Name("Suppress Errors")
+ val suppressErrors = JBCheckBox()
+
+ @Name("Use Scratches System Path")
+ val useScratchesSystemPath = JBCheckBox()
+
+ @Name("Model")
+ val smartModel = ComboBox()
+
+ @Name("Model")
+ val fastModel = ComboBox()
+
+ @Name("Model")
+ val imageChatModel = ComboBox()
+
+
+ @Name("Main Image Model")
+ val mainImageModel = ComboBox()
+
+ @Name("Embedding Model")
+ val embeddingModel = ComboBox()
+
+ @Name("Patch Processor")
+ val patchProcessor = ComboBox()
+
+
+ @Suppress("unused")
+ @Name("Enable API Log")
+ val apiLog = JBCheckBox()
- removeButton.addActionListener {
- try {
- val selectedRows = apis.selectedRows
- if (selectedRows.isEmpty()) {
- log.warn("No API configurations selected for removal")
- return@addActionListener
+ @Suppress("unused")
+ val openApiLog = JButton(object : AbstractAction("Open API Log") {
+ override fun actionPerformed(e: ActionEvent) {
+ AppSettingsState.auxiliaryLog?.let {
+ if (it.exists()) {
+ val project = ApplicationManager.getApplication().runReadAction {
+ ProjectManager.getInstance().openProjects.firstOrNull()
+ }
+ ApplicationManager.getApplication().invokeLater {
+ val virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(it)
+ val openFileDescriptor = OpenFileDescriptor(project, virtualFile!!, virtualFile.length.toInt())
+ FileEditorManager.getInstance(project!!)
+ .openTextEditor(openFileDescriptor, true)?.document?.setReadOnly(
+ true
+ )
+ }
+ }
+ }
}
- val model = apis.model as DefaultTableModel
- for (i in selectedRows.reversed()) {
- val provider = model.getValueAt(i, 0) as? String
- val name = model.getValueAt(i, 1) as? String
- model.removeRow(i)
- log.debug("Successfully removed API configuration: $provider - $name")
+ })
+
+ @Name("Developer Tools")
+ val devActions = JBCheckBox()
+
+ @Suppress("unused")
+ @Name("Edit API Requests")
+ val editRequests = JBCheckBox()
+
+ @Name("Disable Auto-Open URLs")
+ val disableAutoOpenUrls = JBCheckBox()
+
+ @Name("Shell Command")
+ val shellCommand = JBTextField()
+
+ @Name("Show Welcome Screen")
+ val showWelcomeScreen = JBCheckBox()
+
+ @Name("Temperature")
+ val temperature = JBTextField()
+
+ @Name("APIs")
+ val apis = JBTable(DefaultTableModel(arrayOf("Provider", "Name", "Key", "Base URL"), 0)).apply {
+ columnModel.getColumn(0).preferredWidth = 100
+ columnModel.getColumn(1).preferredWidth = 150
+ columnModel.getColumn(2).preferredWidth = 200
+ columnModel.getColumn(3).preferredWidth = 200
+ val keyColumnIndex = 2
+ columnModel.getColumn(keyColumnIndex).cellRenderer = object : DefaultTableCellRenderer() {
+ override fun setValue(value: Any?) {
+ text =
+ if (value is String && value.isNotEmpty()) value.map { '*' }.joinToString("") else value?.toString()
+ ?: ""
+ }
}
- } catch (e: Exception) {
- log.error("Unexpected error removing API configuration: ${e.message}", e)
- JOptionPane.showMessageDialog(
- this, "Failed to remove API configuration: ${e.message}", "Error", JOptionPane.ERROR_MESSAGE
- )
- }
}
- editButton.addActionListener {
- val selectedRow = apis.selectedRow
- if (selectedRow != -1) {
- val model = apis.model as DefaultTableModel
- val currentProvider = model.getValueAt(selectedRow, 0) as String
- val currentName = model.getValueAt(selectedRow, 1) as String
- val currentKey = model.getValueAt(selectedRow, 2) as String
- val currentUrl = model.getValueAt(selectedRow, 3) as String
-
- // Create edit dialog
- val dialog = JDialog(null as Frame?, "Edit API Configuration", true)
- dialog.layout = GridBagLayout()
- val gbc = GridBagConstraints()
-
- gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST
- dialog.add(JLabel("Provider Type:"), gbc)
- gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
- val providerCombo = ComboBox(APIProvider.values().map { it.name }.toTypedArray())
- providerCombo.selectedItem = currentProvider
- dialog.add(providerCombo, gbc)
-
- gbc.gridx = 0; gbc.gridy = 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
- dialog.add(JLabel("Name:"), gbc)
- gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
- val nameField = JBTextField(currentName, 30)
- dialog.add(nameField, gbc)
- gbc.gridx = 0; gbc.gridy = 2; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
- dialog.add(JLabel("API Key:"), gbc)
- gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
- val keyField = JBTextField(currentKey, 30)
- dialog.add(keyField, gbc)
-
- gbc.gridx = 0; gbc.gridy = 3; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
- dialog.add(JLabel("Base URL:"), gbc)
- gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
- val urlField = JBTextField(currentUrl, 30)
- dialog.add(urlField, gbc)
- // Auto-populate base URL when provider changes
- providerCombo.addActionListener {
- val selectedProvider = APIProvider.valueOf(providerCombo.selectedItem as String)
- if (urlField.text == currentUrl || urlField.text.isBlank()) {
- urlField.text = selectedProvider.base
- }
+ @Name("API Management")
+ val apiManagementPanel = JPanel(BorderLayout()).apply {
+ val scrollPane = JScrollPane(apis)
+ scrollPane.preferredSize = Dimension(600, 300)
+ add(scrollPane, BorderLayout.CENTER)
+
+ val buttonPanel = JPanel(FlowLayout(FlowLayout.LEFT))
+ val addButton = JButton("Add API")
+ val removeButton = JButton("Remove")
+ val editButton = JButton("Edit")
+
+ removeButton.isEnabled = false
+ editButton.isEnabled = false
+
+ addButton.addActionListener {
+ val model = apis.model as DefaultTableModel
+
+ // Create add dialog with all fields
+ val dialog = JDialog(null as Frame?, "Add API Configuration", true)
+ dialog.layout = GridBagLayout()
+ val gbc = GridBagConstraints()
+
+ gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST
+ dialog.add(JLabel("Provider Type:"), gbc)
+ gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
+ val providerCombo = ComboBox(APIProvider.values().map { it.name }.toTypedArray())
+ dialog.add(providerCombo, gbc)
+
+ gbc.gridx = 0; gbc.gridy = 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
+ dialog.add(JLabel("Name:"), gbc)
+ gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
+ val nameField = JBTextField(30)
+ dialog.add(nameField, gbc)
+
+ gbc.gridx = 0; gbc.gridy = 2; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
+ dialog.add(JLabel("API Key:"), gbc)
+ gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
+ val keyField = JBTextField(30)
+ dialog.add(keyField, gbc)
+
+ gbc.gridx = 0; gbc.gridy = 3; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
+ dialog.add(JLabel("Base URL:"), gbc)
+ gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
+ val urlField = JBTextField(30)
+ dialog.add(urlField, gbc)
+
+ // Auto-populate name and base URL when provider changes
+ providerCombo.addActionListener {
+ val selectedProvider = APIProvider.valueOf(providerCombo.selectedItem as String)
+ urlField.text = selectedProvider.base
+ nameField.text = selectedProvider.name
+ }
+
+ // Initialize with first provider's defaults
+ val initialProvider = APIProvider.values().first()
+ nameField.text = initialProvider.name
+ urlField.text = initialProvider.base
+
+ gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 2; gbc.fill = GridBagConstraints.NONE
+ val buttonPanel = JPanel(FlowLayout())
+ val okButton = JButton("OK")
+ val cancelButton = JButton("Cancel")
+
+ okButton.addActionListener {
+ val provider = providerCombo.selectedItem as? String
+ val name = nameField.text
+
+ if (provider.isNullOrBlank()) {
+ log.warn("Provider type is required")
+ JOptionPane.showMessageDialog(
+ dialog, "Provider type is required", "Validation Error", JOptionPane.WARNING_MESSAGE
+ )
+ return@addActionListener
+ }
+ if (name.isBlank()) {
+ log.warn("API name is required")
+ JOptionPane.showMessageDialog(
+ dialog, "API name is required", "Validation Error", JOptionPane.WARNING_MESSAGE
+ )
+ return@addActionListener
+ }
+
+ model.addRow(
+ arrayOf(
+ providerCombo.selectedItem, nameField.text, keyField.text, urlField.text
+ )
+ )
+ dialog.dispose()
+ }
+ cancelButton.addActionListener { dialog.dispose() }
+
+ buttonPanel.add(okButton)
+ buttonPanel.add(cancelButton)
+ dialog.add(buttonPanel, gbc)
+
+ dialog.pack()
+ dialog.setLocationRelativeTo(this)
+ dialog.isVisible = true
}
- gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 2; gbc.fill = GridBagConstraints.NONE
- val buttonPanel = JPanel(FlowLayout())
- val okButton = JButton("OK")
- val cancelButton = JButton("Cancel")
- okButton.addActionListener {
- val provider = providerCombo.selectedItem as? String
- val name = nameField.text
- val key = keyField.text
- val url = urlField.text
+ removeButton.addActionListener {
+ try {
+ val selectedRows = apis.selectedRows
+ if (selectedRows.isEmpty()) {
+ log.warn("No API configurations selected for removal")
+ return@addActionListener
+ }
+ val model = apis.model as DefaultTableModel
+ for (i in selectedRows.reversed()) {
+ val provider = model.getValueAt(i, 0) as? String
+ val name = model.getValueAt(i, 1) as? String
+ model.removeRow(i)
+ log.debug("Successfully removed API configuration: $provider - $name")
+ }
+ } catch (e: Exception) {
+ log.error("Unexpected error removing API configuration: ${e.message}", e)
+ JOptionPane.showMessageDialog(
+ this, "Failed to remove API configuration: ${e.message}", "Error", JOptionPane.ERROR_MESSAGE
+ )
+ }
+ }
- if (provider.isNullOrBlank()) {
- log.warn("Provider type is required for editing")
- JOptionPane.showMessageDialog(
- dialog, "Provider type is required", "Validation Error", JOptionPane.WARNING_MESSAGE
- )
- return@addActionListener
- }
- if (name.isBlank()) {
- log.warn("API name is required for editing")
- JOptionPane.showMessageDialog(
- dialog, "API name is required", "Validation Error", JOptionPane.WARNING_MESSAGE
- )
- return@addActionListener
- }
-
- model.setValueAt(provider, selectedRow, 0)
- model.setValueAt(name, selectedRow, 1)
- model.setValueAt(key, selectedRow, 2)
- model.setValueAt(url, selectedRow, 3)
- log.debug("Updated API configuration: $provider - $name")
- dialog.dispose()
+ editButton.addActionListener {
+ val selectedRow = apis.selectedRow
+ if (selectedRow != -1) {
+ val model = apis.model as DefaultTableModel
+ val currentProvider = model.getValueAt(selectedRow, 0) as String
+ val currentName = model.getValueAt(selectedRow, 1) as String
+ val currentKey = model.getValueAt(selectedRow, 2) as String
+ val currentUrl = model.getValueAt(selectedRow, 3) as String
+
+ // Create edit dialog
+ val dialog = JDialog(null as Frame?, "Edit API Configuration", true)
+ dialog.layout = GridBagLayout()
+ val gbc = GridBagConstraints()
+
+ gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST
+ dialog.add(JLabel("Provider Type:"), gbc)
+ gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
+ val providerCombo = ComboBox(APIProvider.values().map { it.name }.toTypedArray())
+ providerCombo.selectedItem = currentProvider
+ dialog.add(providerCombo, gbc)
+
+ gbc.gridx = 0; gbc.gridy = 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
+ dialog.add(JLabel("Name:"), gbc)
+ gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
+ val nameField = JBTextField(currentName, 30)
+ dialog.add(nameField, gbc)
+ gbc.gridx = 0; gbc.gridy = 2; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
+ dialog.add(JLabel("API Key:"), gbc)
+ gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
+ val keyField = JBTextField(currentKey, 30)
+ dialog.add(keyField, gbc)
+
+ gbc.gridx = 0; gbc.gridy = 3; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
+ dialog.add(JLabel("Base URL:"), gbc)
+ gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
+ val urlField = JBTextField(currentUrl, 30)
+ dialog.add(urlField, gbc)
+ // Auto-populate base URL when provider changes
+ providerCombo.addActionListener {
+ val selectedProvider = APIProvider.valueOf(providerCombo.selectedItem as String)
+ if (urlField.text == currentUrl || urlField.text.isBlank()) {
+ urlField.text = selectedProvider.base
+ }
+ }
+
+ gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 2; gbc.fill = GridBagConstraints.NONE
+ val buttonPanel = JPanel(FlowLayout())
+ val okButton = JButton("OK")
+ val cancelButton = JButton("Cancel")
+
+ okButton.addActionListener {
+ val provider = providerCombo.selectedItem as? String
+ val name = nameField.text
+ val key = keyField.text
+ val url = urlField.text
+
+ if (provider.isNullOrBlank()) {
+ log.warn("Provider type is required for editing")
+ JOptionPane.showMessageDialog(
+ dialog, "Provider type is required", "Validation Error", JOptionPane.WARNING_MESSAGE
+ )
+ return@addActionListener
+ }
+ if (name.isBlank()) {
+ log.warn("API name is required for editing")
+ JOptionPane.showMessageDialog(
+ dialog, "API name is required", "Validation Error", JOptionPane.WARNING_MESSAGE
+ )
+ return@addActionListener
+ }
+
+ model.setValueAt(provider, selectedRow, 0)
+ model.setValueAt(name, selectedRow, 1)
+ model.setValueAt(key, selectedRow, 2)
+ model.setValueAt(url, selectedRow, 3)
+ log.debug("Updated API configuration: $provider - $name")
+ dialog.dispose()
+ }
+ cancelButton.addActionListener { dialog.dispose() }
+
+ buttonPanel.add(okButton)
+ buttonPanel.add(cancelButton)
+ dialog.add(buttonPanel, gbc)
+
+ dialog.pack()
+ dialog.setLocationRelativeTo(this)
+ dialog.isVisible = true
+ }
}
- cancelButton.addActionListener { dialog.dispose() }
- buttonPanel.add(okButton)
- buttonPanel.add(cancelButton)
- dialog.add(buttonPanel, gbc)
+ apis.selectionModel.addListSelectionListener {
+ val hasSelection = apis.selectedRow != -1
+ removeButton.isEnabled = hasSelection
+ editButton.isEnabled = hasSelection
+ }
- dialog.pack()
- dialog.setLocationRelativeTo(this)
- dialog.isVisible = true
- }
+ buttonPanel.add(addButton)
+ buttonPanel.add(removeButton)
+ buttonPanel.add(editButton)
+ add(buttonPanel, BorderLayout.SOUTH)
}
- apis.selectionModel.addListSelectionListener {
- val hasSelection = apis.selectedRow != -1
- removeButton.isEnabled = hasSelection
- editButton.isEnabled = hasSelection
+ @Name("Tools")
+ val tools = JBTable(DefaultTableModel(arrayOf("Tool", "Path"), 0)).apply {
+ columnModel.getColumn(0).preferredWidth = 100
+ columnModel.getColumn(1).preferredWidth = 400
}
- buttonPanel.add(addButton)
- buttonPanel.add(removeButton)
- buttonPanel.add(editButton)
- add(buttonPanel, BorderLayout.SOUTH)
- }
- @Name("Tools")
- val tools = JBTable(DefaultTableModel(arrayOf("Tool", "Path"), 0)).apply {
- columnModel.getColumn(0).preferredWidth = 100
- columnModel.getColumn(1).preferredWidth = 400
- }
- @Name("Tool Management")
- val toolManagementPanel = JPanel(BorderLayout()).apply {
- val scrollPane = JScrollPane(tools)
- scrollPane.preferredSize = Dimension(600, 300)
- add(scrollPane, BorderLayout.CENTER)
- val buttonPanel = JPanel(FlowLayout(FlowLayout.LEFT))
- val addButton = JButton("Add Tool")
- val removeButton = JButton("Remove")
- val editButton = JButton("Edit")
- val autoDetectButton = JButton("Auto-Detect")
- removeButton.isEnabled = false
- editButton.isEnabled = false
- addButton.addActionListener {
- val model = tools.model as DefaultTableModel
- val dialog = JDialog(null as Frame?, "Add Tool Configuration", true)
- dialog.layout = GridBagLayout()
- val gbc = GridBagConstraints()
- gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST
- dialog.add(JLabel("Tool Type:"), gbc)
- gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
- val providerCombo = ComboBox(ToolProvider.values().map { it.name }.toTypedArray())
- dialog.add(providerCombo, gbc)
- gbc.gridx = 0; gbc.gridy = 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
- dialog.add(JLabel("Path:"), gbc)
- gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
- val pathField = JBTextField(30)
- dialog.add(pathField, gbc)
- val browseButton = JButton("Browse")
- browseButton.addActionListener {
- val descriptor = FileChooserDescriptorFactory.createSingleFileDescriptor()
- FileChooser.chooseFile(descriptor, null, null) { file ->
- pathField.text = file.path
+ @Name("Tool Management")
+ val toolManagementPanel = JPanel(BorderLayout()).apply {
+ val scrollPane = JScrollPane(tools)
+ scrollPane.preferredSize = Dimension(600, 300)
+ add(scrollPane, BorderLayout.CENTER)
+ val buttonPanel = JPanel(FlowLayout(FlowLayout.LEFT))
+ val addButton = JButton("Add Tool")
+ val removeButton = JButton("Remove")
+ val editButton = JButton("Edit")
+ val autoDetectButton = JButton("Auto-Detect")
+ removeButton.isEnabled = false
+ editButton.isEnabled = false
+ addButton.addActionListener {
+ val model = tools.model as DefaultTableModel
+ val dialog = JDialog(null as Frame?, "Add Tool Configuration", true)
+ dialog.layout = GridBagLayout()
+ val gbc = GridBagConstraints()
+ gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST
+ dialog.add(JLabel("Tool Type:"), gbc)
+ gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
+ val providerCombo = ComboBox(ToolProvider.values().map { it.name }.toTypedArray())
+ dialog.add(providerCombo, gbc)
+ gbc.gridx = 0; gbc.gridy = 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
+ dialog.add(JLabel("Path:"), gbc)
+ gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
+ val pathField = JBTextField(30)
+ dialog.add(pathField, gbc)
+ val browseButton = JButton("Browse")
+ browseButton.addActionListener {
+ val descriptor = FileChooserDescriptorFactory.createSingleFileDescriptor()
+ FileChooser.chooseFile(descriptor, null, null) { file ->
+ pathField.text = file.path
+ }
+ }
+ gbc.gridx = 2; gbc.weightx = 0.0
+ dialog.add(browseButton, gbc)
+ gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 3; gbc.fill = GridBagConstraints.NONE
+ val buttonPanel = JPanel(FlowLayout())
+ val okButton = JButton("OK")
+ val cancelButton = JButton("Cancel")
+ okButton.addActionListener {
+ val provider = providerCombo.selectedItem as? String
+ val path = pathField.text
+ if (!provider.isNullOrBlank() && path.isNotBlank()) {
+ model.addRow(arrayOf(provider, path))
+ dialog.dispose()
+ }
+ }
+ cancelButton.addActionListener { dialog.dispose() }
+ buttonPanel.add(okButton)
+ buttonPanel.add(cancelButton)
+ dialog.add(buttonPanel, gbc)
+ dialog.pack()
+ dialog.setLocationRelativeTo(this)
+ dialog.isVisible = true
}
- }
- gbc.gridx = 2; gbc.weightx = 0.0
- dialog.add(browseButton, gbc)
- gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 3; gbc.fill = GridBagConstraints.NONE
- val buttonPanel = JPanel(FlowLayout())
- val okButton = JButton("OK")
- val cancelButton = JButton("Cancel")
- okButton.addActionListener {
- val provider = providerCombo.selectedItem as? String
- val path = pathField.text
- if (!provider.isNullOrBlank() && path.isNotBlank()) {
- model.addRow(arrayOf(provider, path))
- dialog.dispose()
+ removeButton.addActionListener {
+ val selectedRows = tools.selectedRows
+ if (selectedRows.isNotEmpty()) {
+ val model = tools.model as DefaultTableModel
+ for (i in selectedRows.reversed()) {
+ model.removeRow(i)
+ }
+ }
}
- }
- cancelButton.addActionListener { dialog.dispose() }
- buttonPanel.add(okButton)
- buttonPanel.add(cancelButton)
- dialog.add(buttonPanel, gbc)
- dialog.pack()
- dialog.setLocationRelativeTo(this)
- dialog.isVisible = true
- }
- removeButton.addActionListener {
- val selectedRows = tools.selectedRows
- if (selectedRows.isNotEmpty()) {
- val model = tools.model as DefaultTableModel
- for (i in selectedRows.reversed()) {
- model.removeRow(i)
+ editButton.addActionListener {
+ val selectedRow = tools.selectedRow
+ if (selectedRow != -1) {
+ val model = tools.model as DefaultTableModel
+ val currentProvider = model.getValueAt(selectedRow, 0) as String
+ val currentPath = model.getValueAt(selectedRow, 1) as String
+ val dialog = JDialog(null as Frame?, "Edit Tool Configuration", true)
+ dialog.layout = GridBagLayout()
+ val gbc = GridBagConstraints()
+ gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST
+ dialog.add(JLabel("Tool Type:"), gbc)
+ gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
+ val providerCombo = ComboBox(ToolProvider.values().map { it.name }.toTypedArray())
+ providerCombo.selectedItem = currentProvider
+ dialog.add(providerCombo, gbc)
+ gbc.gridx = 0; gbc.gridy = 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
+ dialog.add(JLabel("Path:"), gbc)
+ gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
+ val pathField = JBTextField(currentPath, 30)
+ dialog.add(pathField, gbc)
+ val browseButton = JButton("Browse")
+ browseButton.addActionListener {
+ val descriptor = FileChooserDescriptorFactory.createSingleFileDescriptor()
+ FileChooser.chooseFile(descriptor, null, null) { file ->
+ pathField.text = file.path
+ }
+ }
+ gbc.gridx = 2; gbc.weightx = 0.0
+ dialog.add(browseButton, gbc)
+ gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 3; gbc.fill = GridBagConstraints.NONE
+ val buttonPanel = JPanel(FlowLayout())
+ val okButton = JButton("OK")
+ val cancelButton = JButton("Cancel")
+ okButton.addActionListener {
+ val provider = providerCombo.selectedItem as? String
+ val path = pathField.text
+ if (!provider.isNullOrBlank() && path.isNotBlank()) {
+ model.setValueAt(provider, selectedRow, 0)
+ model.setValueAt(path, selectedRow, 1)
+ dialog.dispose()
+ }
+ }
+ cancelButton.addActionListener { dialog.dispose() }
+ buttonPanel.add(okButton)
+ buttonPanel.add(cancelButton)
+ dialog.add(buttonPanel, gbc)
+ dialog.pack()
+ dialog.setLocationRelativeTo(this)
+ dialog.isVisible = true
+ }
}
- }
- }
- editButton.addActionListener {
- val selectedRow = tools.selectedRow
- if (selectedRow != -1) {
- val model = tools.model as DefaultTableModel
- val currentProvider = model.getValueAt(selectedRow, 0) as String
- val currentPath = model.getValueAt(selectedRow, 1) as String
- val dialog = JDialog(null as Frame?, "Edit Tool Configuration", true)
- dialog.layout = GridBagLayout()
- val gbc = GridBagConstraints()
- gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST
- dialog.add(JLabel("Tool Type:"), gbc)
- gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
- val providerCombo = ComboBox(ToolProvider.values().map { it.name }.toTypedArray())
- providerCombo.selectedItem = currentProvider
- dialog.add(providerCombo, gbc)
- gbc.gridx = 0; gbc.gridy = 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0
- dialog.add(JLabel("Path:"), gbc)
- gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0
- val pathField = JBTextField(currentPath, 30)
- dialog.add(pathField, gbc)
- val browseButton = JButton("Browse")
- browseButton.addActionListener {
- val descriptor = FileChooserDescriptorFactory.createSingleFileDescriptor()
- FileChooser.chooseFile(descriptor, null, null) { file ->
- pathField.text = file.path
- }
+ autoDetectButton.addActionListener {
+ val model = tools.model as DefaultTableModel
+ val detected = ToolProvider.discoverAllToolsFromPath()
+ var addedCount = 0
+ detected.forEach { tool ->
+ var exists = false
+ for (i in 0 until model.rowCount) {
+ if (model.getValueAt(i, 0) == tool.provider?.name && model.getValueAt(i, 1) == tool.path) {
+ exists = true
+ break
+ }
+ }
+ if (!exists) {
+ model.addRow(arrayOf(tool.provider?.name, tool.path))
+ addedCount++
+ }
+ }
+ JOptionPane.showMessageDialog(this, "Detected and added $addedCount tools.")
}
- gbc.gridx = 2; gbc.weightx = 0.0
- dialog.add(browseButton, gbc)
- gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 3; gbc.fill = GridBagConstraints.NONE
- val buttonPanel = JPanel(FlowLayout())
- val okButton = JButton("OK")
- val cancelButton = JButton("Cancel")
- okButton.addActionListener {
- val provider = providerCombo.selectedItem as? String
- val path = pathField.text
- if (!provider.isNullOrBlank() && path.isNotBlank()) {
- model.setValueAt(provider, selectedRow, 0)
- model.setValueAt(path, selectedRow, 1)
- dialog.dispose()
- }
+ tools.selectionModel.addListSelectionListener {
+ val hasSelection = tools.selectedRow != -1
+ removeButton.isEnabled = hasSelection
+ editButton.isEnabled = hasSelection
}
- cancelButton.addActionListener { dialog.dispose() }
- buttonPanel.add(okButton)
- buttonPanel.add(cancelButton)
- dialog.add(buttonPanel, gbc)
- dialog.pack()
- dialog.setLocationRelativeTo(this)
- dialog.isVisible = true
- }
+ buttonPanel.add(addButton)
+ buttonPanel.add(removeButton)
+ buttonPanel.add(editButton)
+ buttonPanel.add(autoDetectButton)
+ add(buttonPanel, BorderLayout.SOUTH)
}
- autoDetectButton.addActionListener {
- val model = tools.model as DefaultTableModel
- val detected = ToolProvider.discoverAllToolsFromPath()
- var addedCount = 0
- detected.forEach { tool ->
- var exists = false
- for (i in 0 until model.rowCount) {
- if (model.getValueAt(i, 0) == tool.provider?.name && model.getValueAt(i, 1) == tool.path) {
- exists = true
- break
- }
+
+
+ @Name("Editor Actions")
+ var usage = UsageTable(fileApplicationServices(AppSettingsState.Companion.pluginHome).usageManager)
+
+ init {
+ log.debug("Initializing AppSettingsComponent")
+ try {
+ diffLoggingEnabled.isSelected = AppSettingsState.instance.diffLoggingEnabled
+ awsProfile.text = AppSettingsState.instance.awsProfile ?: ""
+ awsRegion.text = AppSettingsState.instance.awsRegion ?: ""
+ awsBucket.text = AppSettingsState.instance.awsBucket ?: ""
+ disableAutoOpenUrls.isSelected = AppSettingsState.instance.disableAutoOpenUrls
+ } catch (e: Exception) {
+ log.error("Error initializing basic settings: ${e.message}", e)
}
- if (!exists) {
- model.addRow(arrayOf(tool.provider?.name, tool.path))
- addedCount++
+ try {
+ // Populate API table first
+ populateApiTable()
+ populateToolsTable()
+ } catch (e: Exception) {
+ log.error("Error populating API table: ${e.message}", e)
}
- }
- JOptionPane.showMessageDialog(this, "Detected and added $addedCount tools.")
- }
- tools.selectionModel.addListSelectionListener {
- val hasSelection = tools.selectedRow != -1
- removeButton.isEnabled = hasSelection
- editButton.isEnabled = hasSelection
- }
- buttonPanel.add(addButton)
- buttonPanel.add(removeButton)
- buttonPanel.add(editButton)
- buttonPanel.add(autoDetectButton)
- add(buttonPanel, BorderLayout.SOUTH)
- }
-
-
- @Name("Editor Actions")
- var usage = UsageTable(fileApplicationServices(AppSettingsState.Companion.pluginHome).usageManager)
-
- init {
- log.debug("Initializing AppSettingsComponent")
- try {
- diffLoggingEnabled.isSelected = AppSettingsState.instance.diffLoggingEnabled
- awsProfile.text = AppSettingsState.instance.awsProfile ?: ""
- awsRegion.text = AppSettingsState.instance.awsRegion ?: ""
- awsBucket.text = AppSettingsState.instance.awsBucket ?: ""
- disableAutoOpenUrls.isSelected = AppSettingsState.instance.disableAutoOpenUrls
- } catch (e: Exception) {
- log.error("Error initializing basic settings: ${e.message}", e)
- }
- try {
- // Populate API table first
- populateApiTable()
- populateToolsTable()
- } catch (e: Exception) {
- log.error("Error populating API table: ${e.message}", e)
- }
- val apis =
- fileApplicationServices(AppSettingsState.Companion.pluginHome).userSettingsManager.getUserSettings().apis
- try {
-
- // Get all available models from APIs with valid keys
- val availableChatModels = try {
- apis.filter { api ->
- api.key != null
- }.flatMap { api ->
- try {
- api.provider?.getChatModels(api.key!!, api.baseUrl)?.filter { model ->
- isVisible(model)
- }?.map { it.name to it } ?: emptyList()
- } catch (e: Exception) {
- log.warn("Failed to get chat models for provider ${api.provider?.name}: ${e.message}")
- emptyList()
- }
- }.toMap().toSortedMap(compareBy { it })
- } catch (e: Exception) {
- log.error("Failed to load available models: ${e.message}", e)
- emptyMap()
- }
- availableChatModels.forEach {
- this.smartModel.addItem(it.value.modelName)
- this.fastModel.addItem(it.value.modelName)
- this.imageChatModel.addItem(it.value.modelName)
- }
- } catch (e: Exception) {
- log.error("Error loading models: ${e.message}", e)
- }
- try {
- val availableImageModels = try {
- apis.filter { api ->
- api.key != null
- }.flatMap { api ->
- try {
- val imageModels: List? =
- api.provider?.getImageModels(api.key!!, api.baseUrl)
- imageModels?.filter { model ->
- isVisible(model)
- }?.map { it.modelName to it } ?: emptyList()
- } catch (e: Exception) {
- log.warn("Failed to get chat models for provider ${api.provider?.name}: ${e.message}")
- emptyList()
- }
- }.toMap().toSortedMap(compareBy { it })
- } catch (e: Exception) {
- log.error("Failed to load available models: ${e.message}", e)
- emptyMap()
- }
- availableImageModels.forEach {
- this.mainImageModel.addItem(it.value.modelName)
- }
- } catch (e: Exception) {
- log.error("Error loading models: ${e.message}", e)
- }
- try {
- val availableEmbeddingModels = try {
- apis.filter { api ->
- api.key != null
- }.flatMap { api ->
- try {
- val embeddingModels: List? = api.provider?.getEmbeddingModels(api.key!!, api.baseUrl)
- embeddingModels?.filter { model ->
- isVisible(model)
- }?.map { it.modelName to it } ?: emptyList()
- } catch (e: Exception) {
- log.warn("Failed to get chat models for provider ${api.provider?.name}: ${e.message}")
- emptyList()
- }
- }.toMap().toSortedMap(compareBy { it })
- } catch (e: Exception) {
- log.error("Failed to load available models: ${e.message}", e)
- emptyMap()
- }
- availableEmbeddingModels.forEach {
- this.embeddingModel.addItem(it.value.modelName)
- }
- } catch (e: Exception) {
- log.error("Error loading models: ${e.message}", e)
- }
- try {
- PatchProcessors.values().forEach {
- this.patchProcessor.addItem(it.name)
- }
- } catch (e: Exception) {
- log.error("Error loading image and embedding models: ${e.message}", e)
- }
+ val apis =
+ fileApplicationServices(AppSettingsState.Companion.pluginHome).userSettingsManager.getUserSettings().apis
+ try {
+ // Get all available models from APIs with valid keys
+ val availableChatModels = try {
+ apis.filter { api ->
+ api.key != null
+ }.flatMap { api ->
+ try {
+ api.provider?.getChatModels(api.key!!, api.baseUrl)?.filter { model ->
+ isVisible(model)
+ }?.map { it.name to it } ?: emptyList()
+ } catch (e: Exception) {
+ log.warn("Failed to get chat models for provider ${api.provider?.name}: ${e.message}")
+ emptyList()
+ }
+ }.toMap().toSortedMap(compareBy { it })
+ } catch (e: Exception) {
+ log.error("Failed to load available models: ${e.message}", e)
+ emptyMap()
+ }
+ availableChatModels.forEach {
+ this.smartModel.addItem(it.value.modelName)
+ this.fastModel.addItem(it.value.modelName)
+ this.imageChatModel.addItem(it.value.modelName)
+ }
+ } catch (e: Exception) {
+ log.error("Error loading models: ${e.message}", e)
+ }
+ try {
+ val availableImageModels = try {
+ apis.filter { api ->
+ api.key != null
+ }.flatMap { api ->
+ try {
+ val imageModels: List? =
+ api.provider?.getImageModels(api.key!!, api.baseUrl)
+ imageModels?.filter { model ->
+ isVisible(model)
+ }?.map { it.modelName to it } ?: emptyList()
+ } catch (e: Exception) {
+ log.warn("Failed to get chat models for provider ${api.provider?.name}: ${e.message}")
+ emptyList()
+ }
+ }.toMap().toSortedMap(compareBy { it })
+ } catch (e: Exception) {
+ log.error("Failed to load available models: ${e.message}", e)
+ emptyMap()
+ }
+ availableImageModels.forEach {
+ this.mainImageModel.addItem(it.value.modelName)
+ }
+ } catch (e: Exception) {
+ log.error("Error loading models: ${e.message}", e)
+ }
+ try {
+ val availableEmbeddingModels = try {
+ apis.filter { api ->
+ api.key != null
+ }.flatMap { api ->
+ try {
+ val embeddingModels: List? =
+ api.provider?.getEmbeddingModels(api.key!!, api.baseUrl)
+ embeddingModels?.filter { model ->
+ isVisible(model)
+ }?.map { it.modelName to it } ?: emptyList()
+ } catch (e: Exception) {
+ log.warn("Failed to get chat models for provider ${api.provider?.name}: ${e.message}")
+ emptyList()
+ }
+ }.toMap().toSortedMap(compareBy { it })
+ } catch (e: Exception) {
+ log.error("Failed to load available models: ${e.message}", e)
+ emptyMap()
+ }
+ availableEmbeddingModels.forEach {
+ this.embeddingModel.addItem(it.value.modelName)
+ }
+ } catch (e: Exception) {
+ log.error("Error loading models: ${e.message}", e)
+ }
+ try {
+ PatchProcessors.values().forEach {
+ this.patchProcessor.addItem(it.name)
+ }
+ } catch (e: Exception) {
+ log.error("Error loading image and embedding models: ${e.message}", e)
+ }
- val smartModelItems = (0 until smartModel.itemCount).map { smartModel.getItemAt(it) }.filter { modelItem ->
- val chatModel = apis.filter { it.key != null }.firstNotNullOfOrNull { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.find { it.modelName == modelItem }
- }
- if (chatModel == null) {
- false
- } else {
- val visible = isVisible(chatModel)
- visible
- }
- }.filterNotNull().sortedBy { modelItem ->
- val model =
- apis.filter { it.key != null }
- .find { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
- ?.any { it.modelName == modelItem } == true
- }
- ?.let { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
- ?.find { it.modelName == modelItem }
- }!!
- "${model.provider?.name} - ${model.modelName}"
- }.toList()
-val fastModelItems = (0 until fastModel.itemCount).map { fastModel.getItemAt(it) }.filter { modelItem ->
- val chatModel = apis.filter { it.key != null }.firstNotNullOfOrNull { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.find { it.modelName == modelItem }
- }
- if (chatModel == null) {
- false
- } else {
- val visible = isVisible(chatModel)
- visible
- }
- }.filterNotNull().sortedBy { modelItem ->
- val model =
- //ChatModel.values().entries.find { it.value.modelName == modelItem }?.value ?: return@sortedBy ""
- apis.filter { it.key != null }
- .find { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
- ?.any { it.modelName == modelItem } == true
- }
- ?.let { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
- ?.find { it.modelName == modelItem }
- }
- "${model?.provider?.name} - ${model?.modelName}"
- }.toList()
- val imageChatModelItems = (0 until imageChatModel.itemCount).map { imageChatModel.getItemAt(it) }.filter { modelItem ->
- val chatModel = apis.filter { it.key != null }.firstNotNullOfOrNull { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.find { it.modelName == modelItem }
- }
- if (chatModel == null) {
- false
- } else {
- val visible = isVisible(chatModel)
- visible
- }
- }.filterNotNull().sortedBy { modelItem ->
- val model =
- apis.filter { it.key != null }
- .find { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
- ?.any { it.modelName == modelItem } == true
- }
- ?.let { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
- ?.find { it.modelName == modelItem }
- }
- "${model?.provider?.name} - ${model?.modelName}"
- }.toList()
- smartModel.removeAllItems()
- fastModel.removeAllItems()
- imageChatModel.removeAllItems()
- smartModelItems.forEach { smartModel.addItem(it) }
- fastModelItems.forEach { fastModel.addItem(it) }
- imageChatModelItems.forEach { imageChatModel.addItem(it) }
- this.smartModel.isEditable = true
- this.fastModel.isEditable = true
- this.imageChatModel.isEditable = true
- this.smartModel.renderer = getModelRenderer()
- this.fastModel.renderer = getModelRenderer()
- this.imageChatModel.renderer = getModelRenderer()
- this.mainImageModel.isEditable = true
- this.mainImageModel.renderer = getImageModelRenderer()
- this.embeddingModel.isEditable = true
- this.embeddingModel.renderer = getEmbeddingModelRenderer()
- this.patchProcessor.isEditable = false
- this.patchProcessor.renderer = getPatchProcessorRenderer()
- // Set current selections
- AppSettingsState.instance.smartModel?.model?.let { model ->
- this.smartModel.selectedItem = model.modelName
- }
-AppSettingsState.instance.fastModel?.model?.let { model ->
- this.fastModel.selectedItem = model.modelName
- }
- AppSettingsState.instance.imageChatModel?.model?.let { model ->
- this.imageChatModel.selectedItem = model.modelName
- }
- AppSettingsState.instance.embeddingModel?.let { model ->
- this.embeddingModel.selectedItem = model
+
+ val smartModelItems = (0 until smartModel.itemCount).map { smartModel.getItemAt(it) }.filter { modelItem ->
+ val chatModel = apis.filter { it.key != null }.firstNotNullOfOrNull { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.find { it.modelName == modelItem }
+ }
+ if (chatModel == null) {
+ false
+ } else {
+ val visible = isVisible(chatModel)
+ visible
+ }
+ }.filterNotNull().sortedBy { modelItem ->
+ val model =
+ apis.filter { it.key != null }
+ .find { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
+ ?.any { it.modelName == modelItem } == true
+ }
+ ?.let { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
+ ?.find { it.modelName == modelItem }
+ }!!
+ "${model.provider?.name} - ${model.modelName}"
+ }.toList()
+ val fastModelItems = (0 until fastModel.itemCount).map { fastModel.getItemAt(it) }.filter { modelItem ->
+ val chatModel = apis.filter { it.key != null }.firstNotNullOfOrNull { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.find { it.modelName == modelItem }
+ }
+ if (chatModel == null) {
+ false
+ } else {
+ val visible = isVisible(chatModel)
+ visible
+ }
+ }.filterNotNull().sortedBy { modelItem ->
+ val model =
+ //ChatModel.values().entries.find { it.value.modelName == modelItem }?.value ?: return@sortedBy ""
+ apis.filter { it.key != null }
+ .find { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
+ ?.any { it.modelName == modelItem } == true
+ }
+ ?.let { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
+ ?.find { it.modelName == modelItem }
+ }
+ "${model?.provider?.name} - ${model?.modelName}"
+ }.toList()
+ val imageChatModelItems =
+ (0 until imageChatModel.itemCount).map { imageChatModel.getItemAt(it) }.filter { modelItem ->
+ val chatModel = apis.filter { it.key != null }.firstNotNullOfOrNull { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.find { it.modelName == modelItem }
+ }
+ if (chatModel == null) {
+ false
+ } else {
+ val visible = isVisible(chatModel)
+ visible
+ }
+ }.filterNotNull().sortedBy { modelItem ->
+ val model =
+ apis.filter { it.key != null }
+ .find { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
+ ?.any { it.modelName == modelItem } == true
+ }
+ ?.let { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
+ ?.find { it.modelName == modelItem }
+ }
+ "${model?.provider?.name} - ${model?.modelName}"
+ }.toList()
+ smartModel.removeAllItems()
+ fastModel.removeAllItems()
+ imageChatModel.removeAllItems()
+ smartModelItems.forEach { smartModel.addItem(it) }
+ fastModelItems.forEach { fastModel.addItem(it) }
+ imageChatModelItems.forEach { imageChatModel.addItem(it) }
+ this.smartModel.isEditable = true
+ this.fastModel.isEditable = true
+ this.imageChatModel.isEditable = true
+ this.smartModel.renderer = getModelRenderer()
+ this.fastModel.renderer = getModelRenderer()
+ this.imageChatModel.renderer = getModelRenderer()
+ this.mainImageModel.isEditable = true
+ this.mainImageModel.renderer = getImageModelRenderer()
+ this.embeddingModel.isEditable = true
+ this.embeddingModel.renderer = getEmbeddingModelRenderer()
+ this.patchProcessor.isEditable = false
+ this.patchProcessor.renderer = getPatchProcessorRenderer()
+ // Set current selections
+ AppSettingsState.instance.smartModel?.model?.let { model ->
+ this.smartModel.selectedItem = model.modelName
+ }
+ AppSettingsState.instance.fastModel?.model?.let { model ->
+ this.fastModel.selectedItem = model.modelName
+ }
+ AppSettingsState.instance.imageChatModel?.model?.let { model ->
+ this.imageChatModel.selectedItem = model.modelName
+ }
+ AppSettingsState.instance.embeddingModel?.let { model ->
+ this.embeddingModel.selectedItem = model
+ }
+ AppSettingsState.instance.processor.let { processor ->
+ this.patchProcessor.selectedItem = processor.label
+ }
+ log.debug("AppSettingsComponent initialization completed")
}
- AppSettingsState.instance.processor.let { processor ->
- this.patchProcessor.selectedItem = processor.label
+
+ override fun dispose() {
+ log.debug("Disposing AppSettingsComponent")
}
- log.debug("AppSettingsComponent initialization completed")
- }
-
- override fun dispose() {
- log.debug("Disposing AppSettingsComponent")
- }
-
- private fun populateApiTable() {
- try {
- log.debug("Populating API table")
- val model = apis.model as DefaultTableModel
- model.rowCount = 0
- val userSettings = fileApplicationServices(
- AppSettingsState.Companion.pluginHome
- ).userSettingsManager.getUserSettings()
- userSettings.apis.forEach { api ->
- val providerName = api.provider?.name ?: ""
- val name = api.name ?: api.provider?.name ?: ""
- val key = api.key
- val url = api.baseUrl
- model.addRow(arrayOf(providerName, name, key, url))
- }
- log.debug("Successfully populated API table with ${userSettings.apis.size} entries")
- } catch (e: Exception) {
- log.error("Failed to populate API table: ${e.message}", e)
- JOptionPane.showMessageDialog(
- null, "Failed to load API configurations: ${e.message}", "Error", JOptionPane.ERROR_MESSAGE
- )
+
+ private fun populateApiTable() {
+ try {
+ log.debug("Populating API table")
+ val model = apis.model as DefaultTableModel
+ model.rowCount = 0
+ val userSettings = fileApplicationServices(
+ AppSettingsState.Companion.pluginHome
+ ).userSettingsManager.getUserSettings()
+ userSettings.apis.forEach { api ->
+ val providerName = api.provider?.name ?: ""
+ val name = api.name ?: api.provider?.name ?: ""
+ val key = api.key
+ val url = api.baseUrl
+ model.addRow(arrayOf(providerName, name, key, url))
+ }
+ log.debug("Successfully populated API table with ${userSettings.apis.size} entries")
+ } catch (e: Exception) {
+ log.error("Failed to populate API table: ${e.message}", e)
+ JOptionPane.showMessageDialog(
+ null, "Failed to load API configurations: ${e.message}", "Error", JOptionPane.ERROR_MESSAGE
+ )
+ }
}
- }
- private fun populateToolsTable() {
- try {
- log.debug("Populating Tools table")
- val model = tools.model as DefaultTableModel
- model.rowCount = 0
- val userSettings = fileApplicationServices(
- AppSettingsState.Companion.pluginHome
- ).userSettingsManager.getUserSettings()
- userSettings.tools.forEach { tool ->
- val providerName = tool.provider?.name ?: ""
- val path = tool.path ?: ""
- model.addRow(arrayOf(providerName, path))
- }
- log.debug("Successfully populated Tools table with ${userSettings.tools.size} entries")
- } catch (e: Exception) {
- log.error("Failed to populate Tools table: ${e.message}", e)
+
+ private fun populateToolsTable() {
+ try {
+ log.debug("Populating Tools table")
+ val model = tools.model as DefaultTableModel
+ model.rowCount = 0
+ val userSettings = fileApplicationServices(
+ AppSettingsState.Companion.pluginHome
+ ).userSettingsManager.getUserSettings()
+ userSettings.tools.forEach { tool ->
+ val providerName = tool.provider?.name ?: ""
+ val path = tool.path ?: ""
+ model.addRow(arrayOf(providerName, path))
+ }
+ log.debug("Successfully populated Tools table with ${userSettings.tools.size} entries")
+ } catch (e: Exception) {
+ log.error("Failed to populate Tools table: ${e.message}", e)
+ }
}
- }
-
-
- private fun getModelRenderer(): ListCellRenderer = object : SimpleListCellRenderer() {
- override fun customize(
- list: JList, value: String?, index: Int, selected: Boolean, hasFocus: Boolean
- ) {
- text = value
- if (value != null) {
- val fileApplicationServices = fileApplicationServices(AppSettingsState.Companion.pluginHome)
- val userSettings = fileApplicationServices.userSettingsManager.getUserSettings()
- val model = userSettings.apis
- .filter { it.key != null }
- .find { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
- ?.any { it.modelName == value } == true
- }
- ?.let { apiData ->
- apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.find { it.modelName == value }
- }
- text = "${model?.provider?.name} - $value"
- }
+
+
+ private fun getModelRenderer(): ListCellRenderer = object : SimpleListCellRenderer() {
+ override fun customize(
+ list: JList, value: String?, index: Int, selected: Boolean, hasFocus: Boolean
+ ) {
+ text = value
+ if (value != null) {
+ val fileApplicationServices = fileApplicationServices(AppSettingsState.Companion.pluginHome)
+ val userSettings = fileApplicationServices.userSettingsManager.getUserSettings()
+ val model = userSettings.apis
+ .filter { it.key != null }
+ .find { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)
+ ?.any { it.modelName == value } == true
+ }
+ ?.let { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl)?.find { it.modelName == value }
+ }
+ text = "${model?.provider?.name} - $value"
+ }
+ }
}
- }
- private fun getImageModelRenderer(): ListCellRenderer = object : SimpleListCellRenderer() {
- override fun customize(
- list: JList, value: String?, index: Int, selected: Boolean, hasFocus: Boolean
- ) {
- text = value
+ private fun getImageModelRenderer(): ListCellRenderer = object : SimpleListCellRenderer() {
+ override fun customize(
+ list: JList, value: String?, index: Int, selected: Boolean, hasFocus: Boolean
+ ) {
+ text = value
+ }
}
- }
-
- private fun getEmbeddingModelRenderer(): ListCellRenderer = object : SimpleListCellRenderer() {
- override fun customize(
- list: JList, value: String?, index: Int, selected: Boolean, hasFocus: Boolean
- ) {
- if (value != null) {
- val model = EmbeddingModel.values()[value]
- text = "${model?.provider?.name} - $value"
- } else {
- text = "None"
- }
+
+ private fun getEmbeddingModelRenderer(): ListCellRenderer = object : SimpleListCellRenderer() {
+ override fun customize(
+ list: JList, value: String?, index: Int, selected: Boolean, hasFocus: Boolean
+ ) {
+ if (value != null) {
+ val model = EmbeddingModel.values()[value]
+ text = "${model?.provider?.name} - $value"
+ } else {
+ text = "None"
+ }
+ }
}
- }
- private fun getPatchProcessorRenderer(): ListCellRenderer = object : SimpleListCellRenderer() {
- override fun customize(
- list: JList, value: String?, index: Int, selected: Boolean, hasFocus: Boolean
- ) {
- if (value != null) {
- try {
- val processor = PatchProcessors.valueOf(value)
- text = processor.label
- } catch (e: IllegalArgumentException) {
- text = value
+ private fun getPatchProcessorRenderer(): ListCellRenderer = object : SimpleListCellRenderer() {
+ override fun customize(
+ list: JList, value: String?, index: Int, selected: Boolean, hasFocus: Boolean
+ ) {
+ if (value != null) {
+ try {
+ val processor = PatchProcessors.valueOf(value)
+ text = processor.label
+ } catch (e: IllegalArgumentException) {
+ text = value
+ }
+ } else {
+ text = "Fuzzy Mode (Balanced)"
+ }
}
- } else {
- text = "Fuzzy Mode (Balanced)"
- }
}
- }
- companion object {
- private val log = LoggerFactory.getLogger(AppSettingsComponent::class.java)
- }
+ companion object {
+ private val log = LoggerFactory.getLogger(AppSettingsComponent::class.java)
+ }
}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/AppSettingsState.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/AppSettingsState.kt
index 0f1757e32..d9eb53df8 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/AppSettingsState.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/AppSettingsState.kt
@@ -37,320 +37,321 @@ import kotlin.random.Random
@State(name = "com.simiacryptus.cognotik.config.AppSettingsState", storages = [Storage("SdkSettingsPlugin.xml")])
data class AppSettingsState(
- /* Audio Settings */
- var selectedMicLine: String? = null,
- var talkTime: Double = 1.0,
- var memorySeconds: Double = 10.0,
- var lookbackSeconds: Double = 5.0,
- var minRMS: Double = 0.5,
- var minIEC61672: Double = 0.5,
- var minSpectralEntropy: Double = 0.5,
- var minimumTalkSeconds: Double = 1.0,
- var rmsLevel: Int = 0,
- var iec61672Level: Int = 0,
- var spectralEntropyLevel: Int = 0,
- var sampleRate: Int = 44100,
- var sampleSize: Int = 16,
- var channels: Int = 1,
- var temperature: Double = 0.1,
- var useScratchesSystemPath: Boolean = false,
-
-/* Model Settings */
- var smartModel: ApiChatModel? = null,
- var fastModel: ApiChatModel? = null,
- var imageChatModel: ApiChatModel? = null,
- var transcriptionModel: String? = null,
- var imageModel: ApiImageModel? = null,
- /* Embedding Model Settings */
- var embeddingModel: EmbeddingModel? = null,
- var processor: PatchProcessors = PatchProcessors.Fuzzy,
-
- /* AWS Settings */
- var awsProfile: String? = null,
- var awsRegion: String? = null,
- var awsBucket: String? = null,
-
- /* System Configuration */
- var analyticsEnabled: Boolean = false,
- var diffLoggingEnabled: Boolean = false,
- var listeningPort: Int = Random.nextInt(3000, 9000),
- var listeningEndpoint: String = "localhost",
- var apiThreads: Int = 4,
- var modalTasks: Boolean = false,
- var suppressErrors: Boolean = false,
- var devActions: Boolean = false,
- var disableAutoOpenUrls: Boolean = false,
- var showWelcomeScreen: Boolean = true,
- var greetedVersion: String = "",
- var shellCommand: String = getDefaultShell(),
- var feedbackRequested: Boolean = false,
- var feedbackOptOut: Boolean = false,
-
- /* Recent Activity Helpers */
- var savedPlanConfigs: MutableMap? = mutableMapOf(),
- val savedCommandConfigsJson: MutableMap? = mutableMapOf(),
- val recentCommandsJson: MutableMap? = mutableMapOf(),
- val recentArguments: MutableList? = mutableListOf(),
- val recentWorkingDirs: MutableList? = mutableListOf(),
+ /* Audio Settings */
+ var selectedMicLine: String? = null,
+ var talkTime: Double = 1.0,
+ var memorySeconds: Double = 10.0,
+ var lookbackSeconds: Double = 5.0,
+ var minRMS: Double = 0.5,
+ var minIEC61672: Double = 0.5,
+ var minSpectralEntropy: Double = 0.5,
+ var minimumTalkSeconds: Double = 1.0,
+ var rmsLevel: Int = 0,
+ var iec61672Level: Int = 0,
+ var spectralEntropyLevel: Int = 0,
+ var sampleRate: Int = 44100,
+ var sampleSize: Int = 16,
+ var channels: Int = 1,
+ var temperature: Double = 0.1,
+ var useScratchesSystemPath: Boolean = false,
+
+ /* Model Settings */
+ var smartModel: ApiChatModel? = null,
+ var fastModel: ApiChatModel? = null,
+ var imageChatModel: ApiChatModel? = null,
+ var transcriptionModel: String? = null,
+ var imageModel: ApiImageModel? = null,
+ /* Embedding Model Settings */
+ var embeddingModel: EmbeddingModel? = null,
+ var processor: PatchProcessors = PatchProcessors.Fuzzy,
+
+ /* AWS Settings */
+ var awsProfile: String? = null,
+ var awsRegion: String? = null,
+ var awsBucket: String? = null,
+
+ /* System Configuration */
+ var analyticsEnabled: Boolean = false,
+ var diffLoggingEnabled: Boolean = false,
+ var listeningPort: Int = Random.nextInt(3000, 9000),
+ var listeningEndpoint: String = "localhost",
+ var apiThreads: Int = 4,
+ var modalTasks: Boolean = false,
+ var suppressErrors: Boolean = false,
+ var devActions: Boolean = false,
+ var disableAutoOpenUrls: Boolean = false,
+ var showWelcomeScreen: Boolean = true,
+ var greetedVersion: String = "",
+ var shellCommand: String = getDefaultShell(),
+ var feedbackRequested: Boolean = false,
+ var feedbackOptOut: Boolean = false,
+
+ /* Recent Activity Helpers */
+ var savedPlanConfigs: MutableMap? = mutableMapOf(),
+ val savedCommandConfigsJson: MutableMap? = mutableMapOf(),
+ val recentCommandsJson: MutableMap? = mutableMapOf(),
+ val recentArguments: MutableList? = mutableListOf(),
+ val recentWorkingDirs: MutableList? = mutableListOf(),
) : PersistentStateComponent {
- @get:JsonIgnore
- val smartChatClient: ChatInterface
- get() = smartModel?.instance() ?: throw IllegalStateException("Smart model not configured")
+ @get:JsonIgnore
+ val smartChatClient: ChatInterface
+ get() = smartModel?.instance() ?: throw IllegalStateException("Smart model not configured")
-@get:JsonIgnore
- val fastChatClient: ChatInterface
- get() = fastModel?.instance() ?: throw IllegalStateException("Fast model not configured")
- @get:JsonIgnore
- val imageChatClient: ChatInterface
- get() = imageChatModel?.instance() ?: throw IllegalStateException("Image chat model not configured")
+ @get:JsonIgnore
+ val fastChatClient: ChatInterface
+ get() = fastModel?.instance() ?: throw IllegalStateException("Fast model not configured")
+ @get:JsonIgnore
+ val imageChatClient: ChatInterface
+ get() = imageChatModel?.instance() ?: throw IllegalStateException("Image chat model not configured")
- @get:JsonIgnore
- val imageClient: com.simiacryptus.cognotik.image.ImageClientInterface?
- get() = imageModel?.instance()
+ @get:JsonIgnore
+ val imageClient: com.simiacryptus.cognotik.image.ImageClientInterface?
+ get() = imageModel?.instance()
- @get:JsonIgnore
- val embeddingClient: com.simiacryptus.cognotik.embedding.Embedder? get() = embeddingModel?.instance()
- @JsonIgnore
- override fun getState() = SimpleEnvelope(toJson(this))
+ @get:JsonIgnore
+ val embeddingClient: com.simiacryptus.cognotik.embedding.Embedder? get() = embeddingModel?.instance()
- @JsonIgnore
- fun getRecentCommands(id: String) = recentCommandsJson?.get(id)?.let {
- try {
- fromJson(it, MRUItems::class.java)
- } catch (e: Exception) {
- log.warn("Error loading recent commands: $it", e)
- MRUItems()
- }
- } ?: MRUItems()
-
- @JsonIgnore
- override fun loadState(state: SimpleEnvelope) {
- state.value ?: return
- val fromJson = try {
- val jsonNode = ObjectMapper().readTree(state.value)
- try {
- fromJson(ObjectMapper().writeValueAsString(jsonNode), AppSettingsState::class.java)
- } catch (e: Exception) {
- log.warn("Error parsing settings: $jsonNode", e)
- AppSettingsState()
- }
- } catch (e: Exception) {
- log.warn("Error loading settings: ${state.value}", e)
- AppSettingsState()
- }
+ @JsonIgnore
+ override fun getState() = SimpleEnvelope(toJson(this))
+
+ @JsonIgnore
+ fun getRecentCommands(id: String) = recentCommandsJson?.get(id)?.let {
+ try {
+ fromJson(it, MRUItems::class.java)
+ } catch (e: Exception) {
+ log.warn("Error loading recent commands: $it", e)
+ MRUItems()
+ }
+ } ?: MRUItems()
- XmlSerializerUtil.copyBean(fromJson, this)
+ @JsonIgnore
+ override fun loadState(state: SimpleEnvelope) {
+ state.value ?: return
+ val fromJson = try {
+ val jsonNode = ObjectMapper().readTree(state.value)
+ try {
+ fromJson(ObjectMapper().writeValueAsString(jsonNode), AppSettingsState::class.java)
+ } catch (e: Exception) {
+ log.warn("Error parsing settings: $jsonNode", e)
+ AppSettingsState()
+ }
+ } catch (e: Exception) {
+ log.warn("Error loading settings: ${state.value}", e)
+ AppSettingsState()
+ }
+
+ XmlSerializerUtil.copyBean(fromJson, this)
- /* Copy savedCommandConfigsJson */
+ /* Copy savedCommandConfigsJson */
// savedCommandConfigsJson?.clear()
- fromJson.savedCommandConfigsJson?.forEach { (key, value) ->
- savedCommandConfigsJson?.set(key, value)
- }
- /* Copy savedPlanConfigs */
+ fromJson.savedCommandConfigsJson?.forEach { (key, value) ->
+ savedCommandConfigsJson?.set(key, value)
+ }
+ /* Copy savedPlanConfigs */
// savedPlanConfigs?.clear()
- fromJson.savedPlanConfigs?.forEach { (key, value) ->
- savedPlanConfigs?.set(key, value)
- }
- /* Copy recentCommandsJson */
+ fromJson.savedPlanConfigs?.forEach { (key, value) ->
+ savedPlanConfigs?.set(key, value)
+ }
+ /* Copy recentCommandsJson */
// recentCommandsJson?.clear()
- fromJson.recentCommandsJson?.forEach { (key, value) ->
- recentCommandsJson?.set(key, value)
- }
- /* Copy recentArguments */
+ fromJson.recentCommandsJson?.forEach { (key, value) ->
+ recentCommandsJson?.set(key, value)
+ }
+ /* Copy recentArguments */
// recentArguments?.clear()
- fromJson.recentArguments?.forEach { argument ->
- recentArguments?.add(argument)
- }
- /* Copy recentWorkingDirs */
+ fromJson.recentArguments?.forEach { argument ->
+ recentArguments?.add(argument)
+ }
+ /* Copy recentWorkingDirs */
// recentWorkingDirs?.clear()
- fromJson.recentWorkingDirs?.forEach { workingDir ->
- recentWorkingDirs?.add(workingDir)
- }
- notifySettingsLoaded()
- }
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
- other as AppSettingsState
- if (selectedMicLine != other.selectedMicLine) return false
- if (talkTime != other.talkTime) return false
- if (memorySeconds != other.memorySeconds) return false
- if (lookbackSeconds != other.lookbackSeconds) return false
- if (minRMS != other.minRMS) return false
- if (minIEC61672 != other.minIEC61672) return false
- if (minSpectralEntropy != other.minSpectralEntropy) return false
- if (minimumTalkSeconds != other.minimumTalkSeconds) return false
- if (rmsLevel != other.rmsLevel) return false
- if (iec61672Level != other.iec61672Level) return false
- if (spectralEntropyLevel != other.spectralEntropyLevel) return false
- if (sampleRate != other.sampleRate) return false
- if (sampleSize != other.sampleSize) return false
- if (channels != other.channels) return false
- if (temperature != other.temperature) return false
-if (useScratchesSystemPath != other.useScratchesSystemPath) return false
- if (smartModel != other.smartModel) return false
- if (fastModel != other.fastModel) return false
- if (imageChatModel != other.imageChatModel) return false
- if (transcriptionModel != other.transcriptionModel) return false
- if (imageModel != other.imageModel) return false
- if (embeddingModel != other.embeddingModel) return false
- if (processor != other.processor) return false
- if (awsProfile != other.awsProfile) return false
- if (awsRegion != other.awsRegion) return false
- if (awsBucket != other.awsBucket) return false
- if (analyticsEnabled != other.analyticsEnabled) return false
- if (diffLoggingEnabled != other.diffLoggingEnabled) return false
- if (listeningPort != other.listeningPort) return false
- if (listeningEndpoint != other.listeningEndpoint) return false
- if (apiThreads != other.apiThreads) return false
- if (modalTasks != other.modalTasks) return false
- if (suppressErrors != other.suppressErrors) return false
- if (devActions != other.devActions) return false
- if (disableAutoOpenUrls != other.disableAutoOpenUrls) return false
- if (showWelcomeScreen != other.showWelcomeScreen) return false
- if (greetedVersion != other.greetedVersion) return false
- if (shellCommand != other.shellCommand) return false
- if (savedPlanConfigs != other.savedPlanConfigs) return false
- if (savedCommandConfigsJson != other.savedCommandConfigsJson) return false
- if (recentCommandsJson != other.recentCommandsJson) return false
- if (recentArguments != other.recentArguments) return false
- if (recentWorkingDirs != other.recentWorkingDirs) return false
- return true
- }
-
- override fun hashCode(): Int {
- var result = selectedMicLine?.hashCode() ?: 0
- result = 31 * result + talkTime.hashCode()
- result = 31 * result + memorySeconds.hashCode()
- result = 31 * result + lookbackSeconds.hashCode()
- result = 31 * result + minRMS.hashCode()
- result = 31 * result + minIEC61672.hashCode()
- result = 31 * result + minSpectralEntropy.hashCode()
- result = 31 * result + minimumTalkSeconds.hashCode()
- result = 31 * result + rmsLevel
- result = 31 * result + iec61672Level
- result = 31 * result + spectralEntropyLevel
- result = 31 * result + sampleRate
- result = 31 * result + sampleSize
- result = 31 * result + channels
- result = 31 * result + temperature.hashCode()
-result = 31 * result + useScratchesSystemPath.hashCode()
- result = 31 * result + smartModel.hashCode()
- result = 31 * result + fastModel.hashCode()
- result = 31 * result + (imageChatModel?.hashCode() ?: 0)
- result = 31 * result + (transcriptionModel?.hashCode() ?: 0)
- result = 31 * result + (imageModel?.hashCode() ?: 0)
- result = 31 * result + (embeddingModel?.hashCode() ?: 0)
- result = 31 * result + processor.hashCode()
- result = 31 * result + (awsProfile?.hashCode() ?: 0)
- result = 31 * result + (awsRegion?.hashCode() ?: 0)
- result = 31 * result + (awsBucket?.hashCode() ?: 0)
- result = 31 * result + analyticsEnabled.hashCode()
- result = 31 * result + diffLoggingEnabled.hashCode()
- result = 31 * result + listeningPort
- result = 31 * result + listeningEndpoint.hashCode()
- result = 31 * result + apiThreads
- result = 31 * result + modalTasks.hashCode()
- result = 31 * result + suppressErrors.hashCode()
- result = 31 * result + devActions.hashCode()
- result = 31 * result + disableAutoOpenUrls.hashCode()
- result = 31 * result + showWelcomeScreen.hashCode()
- result = 31 * result + greetedVersion.hashCode()
- result = 31 * result + shellCommand.hashCode()
- result = 31 * result + (savedPlanConfigs?.hashCode() ?: 0)
- result = 31 * result + (savedCommandConfigsJson?.hashCode() ?: 0)
- result = 31 * result + (recentCommandsJson?.hashCode() ?: 0)
- result = 31 * result + (recentArguments?.hashCode() ?: 0)
- result = 31 * result + (recentWorkingDirs?.hashCode() ?: 0)
- return result
- }
-
- companion object {
- var lastEvent: AnActionEvent? = null
- val log = LoggerFactory.getLogger(AppSettingsState::class.java)
- var auxiliaryLog: File? = null
- const val WELCOME_VERSION: String = "2.0.8"
-
- @JvmStatic
- val instance: AppSettingsState by lazy {
- require(APIProvider.values().isNotEmpty()) { "No API providers registered" }
- ApplicationManager.getApplication()?.getService(AppSettingsState::class.java) ?: AppSettingsState()
+ fromJson.recentWorkingDirs?.forEach { workingDir ->
+ recentWorkingDirs?.add(workingDir)
+ }
+ notifySettingsLoaded()
}
- fun getDefaultShell() = if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ other as AppSettingsState
+ if (selectedMicLine != other.selectedMicLine) return false
+ if (talkTime != other.talkTime) return false
+ if (memorySeconds != other.memorySeconds) return false
+ if (lookbackSeconds != other.lookbackSeconds) return false
+ if (minRMS != other.minRMS) return false
+ if (minIEC61672 != other.minIEC61672) return false
+ if (minSpectralEntropy != other.minSpectralEntropy) return false
+ if (minimumTalkSeconds != other.minimumTalkSeconds) return false
+ if (rmsLevel != other.rmsLevel) return false
+ if (iec61672Level != other.iec61672Level) return false
+ if (spectralEntropyLevel != other.spectralEntropyLevel) return false
+ if (sampleRate != other.sampleRate) return false
+ if (sampleSize != other.sampleSize) return false
+ if (channels != other.channels) return false
+ if (temperature != other.temperature) return false
+ if (useScratchesSystemPath != other.useScratchesSystemPath) return false
+ if (smartModel != other.smartModel) return false
+ if (fastModel != other.fastModel) return false
+ if (imageChatModel != other.imageChatModel) return false
+ if (transcriptionModel != other.transcriptionModel) return false
+ if (imageModel != other.imageModel) return false
+ if (embeddingModel != other.embeddingModel) return false
+ if (processor != other.processor) return false
+ if (awsProfile != other.awsProfile) return false
+ if (awsRegion != other.awsRegion) return false
+ if (awsBucket != other.awsBucket) return false
+ if (analyticsEnabled != other.analyticsEnabled) return false
+ if (diffLoggingEnabled != other.diffLoggingEnabled) return false
+ if (listeningPort != other.listeningPort) return false
+ if (listeningEndpoint != other.listeningEndpoint) return false
+ if (apiThreads != other.apiThreads) return false
+ if (modalTasks != other.modalTasks) return false
+ if (suppressErrors != other.suppressErrors) return false
+ if (devActions != other.devActions) return false
+ if (disableAutoOpenUrls != other.disableAutoOpenUrls) return false
+ if (showWelcomeScreen != other.showWelcomeScreen) return false
+ if (greetedVersion != other.greetedVersion) return false
+ if (shellCommand != other.shellCommand) return false
+ if (savedPlanConfigs != other.savedPlanConfigs) return false
+ if (savedCommandConfigsJson != other.savedCommandConfigsJson) return false
+ if (recentCommandsJson != other.recentCommandsJson) return false
+ if (recentArguments != other.recentArguments) return false
+ if (recentWorkingDirs != other.recentWorkingDirs) return false
+ return true
+ }
- @JsonIgnore
- var onSettingsLoadedListeners = mutableListOf<() -> Unit>()
- fun notifySettingsLoaded() {
- onSettingsLoadedListeners.forEach { it() }
+ override fun hashCode(): Int {
+ var result = selectedMicLine?.hashCode() ?: 0
+ result = 31 * result + talkTime.hashCode()
+ result = 31 * result + memorySeconds.hashCode()
+ result = 31 * result + lookbackSeconds.hashCode()
+ result = 31 * result + minRMS.hashCode()
+ result = 31 * result + minIEC61672.hashCode()
+ result = 31 * result + minSpectralEntropy.hashCode()
+ result = 31 * result + minimumTalkSeconds.hashCode()
+ result = 31 * result + rmsLevel
+ result = 31 * result + iec61672Level
+ result = 31 * result + spectralEntropyLevel
+ result = 31 * result + sampleRate
+ result = 31 * result + sampleSize
+ result = 31 * result + channels
+ result = 31 * result + temperature.hashCode()
+ result = 31 * result + useScratchesSystemPath.hashCode()
+ result = 31 * result + smartModel.hashCode()
+ result = 31 * result + fastModel.hashCode()
+ result = 31 * result + (imageChatModel?.hashCode() ?: 0)
+ result = 31 * result + (transcriptionModel?.hashCode() ?: 0)
+ result = 31 * result + (imageModel?.hashCode() ?: 0)
+ result = 31 * result + (embeddingModel?.hashCode() ?: 0)
+ result = 31 * result + processor.hashCode()
+ result = 31 * result + (awsProfile?.hashCode() ?: 0)
+ result = 31 * result + (awsRegion?.hashCode() ?: 0)
+ result = 31 * result + (awsBucket?.hashCode() ?: 0)
+ result = 31 * result + analyticsEnabled.hashCode()
+ result = 31 * result + diffLoggingEnabled.hashCode()
+ result = 31 * result + listeningPort
+ result = 31 * result + listeningEndpoint.hashCode()
+ result = 31 * result + apiThreads
+ result = 31 * result + modalTasks.hashCode()
+ result = 31 * result + suppressErrors.hashCode()
+ result = 31 * result + devActions.hashCode()
+ result = 31 * result + disableAutoOpenUrls.hashCode()
+ result = 31 * result + showWelcomeScreen.hashCode()
+ result = 31 * result + greetedVersion.hashCode()
+ result = 31 * result + shellCommand.hashCode()
+ result = 31 * result + (savedPlanConfigs?.hashCode() ?: 0)
+ result = 31 * result + (savedCommandConfigsJson?.hashCode() ?: 0)
+ result = 31 * result + (recentCommandsJson?.hashCode() ?: 0)
+ result = 31 * result + (recentArguments?.hashCode() ?: 0)
+ result = 31 * result + (recentWorkingDirs?.hashCode() ?: 0)
+ return result
}
- val currentSession = Session.Companion.newGlobalID()
- val workPool = ApplicationServices.threadPoolManager.getPool(currentSession, UserSettingsManager.defaultUser)
- val pluginHome: File by lazy {
- run {
- var logPath: String? = null
- //if (logPath == null) logPath = System.getProperty("java.io.tmpdir")
- if (logPath == null) logPath = System.getProperty("user.home")
- if (logPath == null) logPath = System.getProperty("idea.plugins.path")
- File(logPath, ".cognotik")
- }
+ companion object {
+ var lastEvent: AnActionEvent? = null
+ val log = LoggerFactory.getLogger(AppSettingsState::class.java)
+ var auxiliaryLog: File? = null
+ const val WELCOME_VERSION: String = "2.0.8"
+
+ @JvmStatic
+ val instance: AppSettingsState by lazy {
+ require(APIProvider.values().isNotEmpty()) { "No API providers registered" }
+ ApplicationManager.getApplication()?.getService(AppSettingsState::class.java) ?: AppSettingsState()
+ }
+
+ fun getDefaultShell() = if (System.getProperty("os.name").lowercase().contains("win")) "powershell" else "bash"
+
+ @JsonIgnore
+ var onSettingsLoadedListeners = mutableListOf<() -> Unit>()
+ fun notifySettingsLoaded() {
+ onSettingsLoadedListeners.forEach { it() }
+ }
+
+ val currentSession = Session.Companion.newGlobalID()
+ val workPool = ApplicationServices.threadPoolManager.getPool(currentSession, UserSettingsManager.defaultUser)
+ val pluginHome: File by lazy {
+ run {
+ var logPath: String? = null
+ //if (logPath == null) logPath = System.getProperty("java.io.tmpdir")
+ if (logPath == null) logPath = System.getProperty("user.home")
+ if (logPath == null) logPath = System.getProperty("idea.plugins.path")
+ File(logPath, ".cognotik")
+ }
+ }
}
- }
}
fun ApiChatModel.instance(): ChatInterface? {
- val usageManager = ApplicationServices.fileApplicationServices(AppSettingsState.Companion.pluginHome).usageManager
- val model = model
- if (model == null) {
- log.warn("Model not configured for ${provider?.provider?.name}")
- return null
- }
- return model.instance(
- key = provider?.key ?: throw IllegalArgumentException("API key is not set"),
- base = provider?.provider?.base
- ?: throw IllegalArgumentException("API base for ${provider?.provider?.name} is not set"),
- workPool = AppSettingsState.workPool,
- temperature = AppSettingsState.instance.temperature,
- scheduledPool = ApplicationServices.threadPoolManager.getScheduledPool(
- AppSettingsState.currentSession,
- UserSettingsManager.defaultUser
- ),
- onUsage = { model, usage ->
- usageManager.incrementUsage(
- AppSettingsState.currentSession,
- UserSettingsManager.defaultUser, model, usage
- )
- },
- )
+ val usageManager = ApplicationServices.fileApplicationServices(AppSettingsState.Companion.pluginHome).usageManager
+ val model = model
+ if (model == null) {
+ log.warn("Model not configured for ${provider?.provider?.name}")
+ return null
+ }
+ return model.instance(
+ key = provider?.key ?: throw IllegalArgumentException("API key is not set"),
+ base = provider?.provider?.base
+ ?: throw IllegalArgumentException("API base for ${provider?.provider?.name} is not set"),
+ workPool = AppSettingsState.workPool,
+ temperature = AppSettingsState.instance.temperature,
+ scheduledPool = ApplicationServices.threadPoolManager.getScheduledPool(
+ AppSettingsState.currentSession,
+ UserSettingsManager.defaultUser
+ ),
+ onUsage = { model, usage ->
+ usageManager.incrementUsage(
+ AppSettingsState.currentSession,
+ UserSettingsManager.defaultUser, model, usage
+ )
+ },
+ )
}
data class ApiImageModel(
- val model: ImageModel,
- val provider: ApiData?
+ val model: ImageModel,
+ val provider: ApiData?
)
fun ApiImageModel.instance(): com.simiacryptus.cognotik.image.ImageClientInterface? {
- val model = model
- if (model == null) {
- log.warn("Model not configured for ${provider?.provider?.name}")
- return null
- }
- return provider?.provider?.getImageClient(
- key = provider.key ?: throw IllegalArgumentException("API key is not set"),
- base = provider.baseUrl ?: provider.provider?.base
- ?: throw IllegalArgumentException("API base for ${provider.provider?.name} is not set"),
- workPool = AppSettingsState.workPool,
- scheduledPool = ApplicationServices.threadPoolManager.getScheduledPool(
- AppSettingsState.currentSession,
- UserSettingsManager.defaultUser
- ),
- )
+ val model = model
+ if (model == null) {
+ log.warn("Model not configured for ${provider?.provider?.name}")
+ return null
+ }
+ return provider?.provider?.getImageClient(
+ key = provider.key ?: throw IllegalArgumentException("API key is not set"),
+ base = provider.baseUrl ?: provider.provider?.base
+ ?: throw IllegalArgumentException("API base for ${provider.provider?.name} is not set"),
+ workPool = AppSettingsState.workPool,
+ scheduledPool = ApplicationServices.threadPoolManager.getScheduledPool(
+ AppSettingsState.currentSession,
+ UserSettingsManager.defaultUser
+ ),
+ )
}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/StaticAppSettingsConfigurable.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/StaticAppSettingsConfigurable.kt
index 95b1f8bb3..b53e810c0 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/StaticAppSettingsConfigurable.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/StaticAppSettingsConfigurable.kt
@@ -25,585 +25,606 @@ import javax.swing.filechooser.FileNameExtensionFilter
import javax.swing.table.DefaultTableModel
class StaticAppSettingsConfigurable : AppSettingsConfigurable() {
- override fun apply() {
- super.apply()
- AppSettingsState.auxiliaryLog = null
- AppSettingsState.notifySettingsLoaded()
- }
+ override fun apply() {
+ super.apply()
+ AppSettingsState.auxiliaryLog = null
+ AppSettingsState.notifySettingsLoaded()
+ }
- private val password = JPasswordField()
+ private val password = JPasswordField()
- override fun build(component: AppSettingsComponent): JComponent {
- val tabbedPane = com.intellij.ui.components.JBTabbedPane()
- try {
- tabbedPane.addTab("Basic Settings", JPanel(BorderLayout()).apply {
- add(JPanel(BorderLayout()).apply {
- layout = BoxLayout(this, BoxLayout.Y_AXIS)
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Smart Model:"))
- add(component.smartModel)
- })
-add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Fast Model:"))
- add(component.fastModel)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Image Chat Model:"))
- add(component.imageChatModel)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Image Model:"))
- add(component.mainImageModel)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Embedding Model:"))
- add(component.embeddingModel)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Patch Processor:"))
- add(component.patchProcessor)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Temperature:"))
- add(component.temperature)
- })
- })
- })
- } catch (e: Exception) {
- log.warn("Error building Basic Settings", e)
- }
+ override fun build(component: AppSettingsComponent): JComponent {
+ val tabbedPane = com.intellij.ui.components.JBTabbedPane()
+ try {
+ tabbedPane.addTab("Basic Settings", JPanel(BorderLayout()).apply {
+ add(JPanel(BorderLayout()).apply {
+ layout = BoxLayout(this, BoxLayout.Y_AXIS)
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Smart Model:"))
+ add(component.smartModel)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Fast Model:"))
+ add(component.fastModel)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Image Chat Model:"))
+ add(component.imageChatModel)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Image Model:"))
+ add(component.mainImageModel)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Embedding Model:"))
+ add(component.embeddingModel)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Patch Processor:"))
+ add(component.patchProcessor)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Temperature:"))
+ add(component.temperature)
+ })
+ })
+ })
+ } catch (e: Exception) {
+ log.warn("Error building Basic Settings", e)
+ }
- try {
- tabbedPane.addTab("Keys", JPanel(BorderLayout()).apply {
- add(JPanel(BorderLayout()).apply {
- add(JPanel(BorderLayout()).apply {
- add(JLabel("API Configurations:"), BorderLayout.NORTH)
- add(component.apiManagementPanel, BorderLayout.CENTER)
- }, BorderLayout.CENTER)
- })
- })
- } catch (e: Exception) {
- log.warn("Error building Configuration", e)
- }
- try {
- tabbedPane.addTab("Tools", JPanel(BorderLayout()).apply {
- add(JPanel().apply {
- layout = BoxLayout(this, BoxLayout.Y_AXIS)
- add(JPanel(BorderLayout()).apply {
- add(JLabel("Configured Tools:"), BorderLayout.NORTH)
- add(component.toolManagementPanel, BorderLayout.CENTER)
- })
- }, BorderLayout.NORTH)
- })
- } catch (e: Exception) {
- log.warn("Error building Tools Settings", e)
- }
+ try {
+ tabbedPane.addTab("Keys", JPanel(BorderLayout()).apply {
+ add(JPanel(BorderLayout()).apply {
+ add(JPanel(BorderLayout()).apply {
+ add(JLabel("API Configurations:"), BorderLayout.NORTH)
+ add(component.apiManagementPanel, BorderLayout.CENTER)
+ }, BorderLayout.CENTER)
+ })
+ })
+ } catch (e: Exception) {
+ log.warn("Error building Configuration", e)
+ }
+ try {
+ tabbedPane.addTab("Tools", JPanel(BorderLayout()).apply {
+ add(JPanel().apply {
+ layout = BoxLayout(this, BoxLayout.Y_AXIS)
+ add(JPanel(BorderLayout()).apply {
+ add(JLabel("Configured Tools:"), BorderLayout.NORTH)
+ add(component.toolManagementPanel, BorderLayout.CENTER)
+ })
+ }, BorderLayout.NORTH)
+ })
+ } catch (e: Exception) {
+ log.warn("Error building Tools Settings", e)
+ }
- tabbedPane.addTab("Advanced Settings", JPanel(BorderLayout()).apply {
- try {
- add(JPanel().apply {
- layout = BoxLayout(this, BoxLayout.Y_AXIS)
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Developer Tools:"))
- add(component.devActions)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Disable Auto-Open URLs:"))
- add(component.disableAutoOpenUrls)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Enable Diff Logging:"))
- add(component.diffLoggingEnabled)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Suppress Errors:"))
- add(component.suppressErrors)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Use System Path for Scratches (instead of Config Path):"))
- add(component.useScratchesSystemPath)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Show Welcome Screen:"))
- add(component.showWelcomeScreen)
- }, BorderLayout.NORTH)
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Server Port:"))
- add(component.listeningPort)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Server Endpoint:"))
- add(component.listeningEndpoint)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Shell Command:"))
- add(component.shellCommand)
- })
- }, BorderLayout.NORTH)
+ tabbedPane.addTab("Advanced Settings", JPanel(BorderLayout()).apply {
+ try {
+ add(JPanel().apply {
+ layout = BoxLayout(this, BoxLayout.Y_AXIS)
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Developer Tools:"))
+ add(component.devActions)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Disable Auto-Open URLs:"))
+ add(component.disableAutoOpenUrls)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Enable Diff Logging:"))
+ add(component.diffLoggingEnabled)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Suppress Errors:"))
+ add(component.suppressErrors)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Use System Path for Scratches (instead of Config Path):"))
+ add(component.useScratchesSystemPath)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Show Welcome Screen:"))
+ add(component.showWelcomeScreen)
+ }, BorderLayout.NORTH)
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Server Port:"))
+ add(component.listeningPort)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Server Endpoint:"))
+ add(component.listeningEndpoint)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Shell Command:"))
+ add(component.shellCommand)
+ })
+ }, BorderLayout.NORTH)
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("Password:"))
- add(password)
- add(JLabel("Configuration:"))
- add(JButton("Export Config").apply {
- addActionListener {
- try {
- showExportConfigDialog()
- } catch (e: Exception) {
- log.error("Failed to show export config dialog", e)
- JOptionPane.showMessageDialog(
- null, "Failed to export configuration: ${e.message}", "Export Error", JOptionPane.ERROR_MESSAGE
- )
- }
- }
- })
- add(JButton("Import Config").apply {
- addActionListener {
- try {
- showImportConfigDialog()
- } catch (e: Exception) {
- log.error("Failed to show import config dialog", e)
- JOptionPane.showMessageDialog(
- null, "Failed to import configuration: ${e.message}", "Import Error", JOptionPane.ERROR_MESSAGE
- )
- }
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("Password:"))
+ add(password)
+ add(JLabel("Configuration:"))
+ add(JButton("Export Config").apply {
+ addActionListener {
+ try {
+ showExportConfigDialog()
+ } catch (e: Exception) {
+ log.error("Failed to show export config dialog", e)
+ JOptionPane.showMessageDialog(
+ null,
+ "Failed to export configuration: ${e.message}",
+ "Export Error",
+ JOptionPane.ERROR_MESSAGE
+ )
+ }
+ }
+ })
+ add(JButton("Import Config").apply {
+ addActionListener {
+ try {
+ showImportConfigDialog()
+ } catch (e: Exception) {
+ log.error("Failed to show import config dialog", e)
+ JOptionPane.showMessageDialog(
+ null,
+ "Failed to import configuration: ${e.message}",
+ "Import Error",
+ JOptionPane.ERROR_MESSAGE
+ )
+ }
+ }
+ })
+ })
+ } catch (e: Exception) {
+ log.warn("Error building Developer Tools", e)
}
- })
- })
- } catch (e: Exception) {
- log.warn("Error building Developer Tools", e)
- }
- try {
- tabbedPane.addTab("AWS", JPanel(BorderLayout()).apply {
- add(JPanel().apply {
- layout = BoxLayout(this, BoxLayout.Y_AXIS)
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("AWS Profile:"))
- add(component.awsProfile)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("AWS Region:"))
- add(component.awsRegion)
- })
- add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
- add(JLabel("AWS Bucket:"))
- add(component.awsBucket)
- })
- }, BorderLayout.NORTH)
+ try {
+ tabbedPane.addTab("AWS", JPanel(BorderLayout()).apply {
+ add(JPanel().apply {
+ layout = BoxLayout(this, BoxLayout.Y_AXIS)
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("AWS Profile:"))
+ add(component.awsProfile)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("AWS Region:"))
+ add(component.awsRegion)
+ })
+ add(JPanel(FlowLayout(FlowLayout.LEFT)).apply {
+ add(JLabel("AWS Bucket:"))
+ add(component.awsBucket)
+ })
+ }, BorderLayout.NORTH)
+ })
+ } catch (e: Exception) {
+ log.warn("Error building AWS Settings", e)
+ }
})
- } catch (e: Exception) {
- log.warn("Error building AWS Settings", e)
- }
- })
- return tabbedPane
- }
+ return tabbedPane
+ }
- private fun showExportConfigDialog() {
- log.debug("Opening export configuration dialog")
- val dialog = JDialog(null as Frame?, "Export Configuration", true)
- dialog.layout = BorderLayout()
+ private fun showExportConfigDialog() {
+ log.debug("Opening export configuration dialog")
+ val dialog = JDialog(null as Frame?, "Export Configuration", true)
+ dialog.layout = BorderLayout()
- val userSettings = ApplicationServices.fileApplicationServices(AppSettingsState.pluginHome).userSettingsManager.getUserSettings()
- val fullConfig = try {
- val encryptedSettings = AppSettingsState.instance.copy()
- // Export UserSettings with encrypted keys
- log.debug("Encrypting ${userSettings.apis.size} API configurations")
+ val userSettings =
+ ApplicationServices.fileApplicationServices(AppSettingsState.pluginHome).userSettingsManager.getUserSettings()
+ val fullConfig = try {
+ val encryptedSettings = AppSettingsState.instance.copy()
+ // Export UserSettings with encrypted keys
+ log.debug("Encrypting ${userSettings.apis.size} API configurations")
- val encryptedUserSettings = userSettings.copy(
- apis = userSettings.apis.map { api ->
- try {
- api.copy(key = api.key?.let { EncryptionUtil.encrypt(it, password.text) } ?: api.key)
- } catch (e: Exception) {
- log.error("Failed to encrypt API key for provider: ${api.provider}", e)
- api // Return original if encryption fails
- }
- }.toMutableList()
- )
- val configJson = JsonUtil.toJson(encryptedSettings)
- val userSettingsJson = JsonUtil.toJson(encryptedUserSettings)
- """
+ val encryptedUserSettings = userSettings.copy(
+ apis = userSettings.apis.map { api ->
+ try {
+ api.copy(key = api.key?.let { EncryptionUtil.encrypt(it, password.text) } ?: api.key)
+ } catch (e: Exception) {
+ log.error("Failed to encrypt API key for provider: ${api.provider}", e)
+ api // Return original if encryption fails
+ }
+ }.toMutableList()
+ )
+ val configJson = JsonUtil.toJson(encryptedSettings)
+ val userSettingsJson = JsonUtil.toJson(encryptedUserSettings)
+ """
{
"appSettings": $configJson,
"userSettings": $userSettingsJson
}
""".trimIndent()
- } catch (e: Exception) {
- log.error("Failed to prepare configuration for export", e)
- JOptionPane.showMessageDialog(
- dialog, "Failed to prepare configuration: ${e.message}", "Export Error", JOptionPane.ERROR_MESSAGE
- )
- dialog.dispose()
- return
- }
+ } catch (e: Exception) {
+ log.error("Failed to prepare configuration for export", e)
+ JOptionPane.showMessageDialog(
+ dialog, "Failed to prepare configuration: ${e.message}", "Export Error", JOptionPane.ERROR_MESSAGE
+ )
+ dialog.dispose()
+ return
+ }
- val textArea = JTextArea(fullConfig).apply {
- lineWrap = true
- wrapStyleWord = true
- font = Font(Font.MONOSPACED, Font.PLAIN, 12)
- }
- dialog.add(JScrollPane(textArea), BorderLayout.CENTER)
- val buttonPanel = JPanel(FlowLayout(FlowLayout.RIGHT))
- val copyButton = JButton("Copy to Clipboard")
- copyButton.addActionListener {
- textArea.selectAll()
- textArea.copy()
- JOptionPane.showMessageDialog(
- dialog, "Configuration copied to clipboard", "Success", JOptionPane.INFORMATION_MESSAGE
- )
- }
- val saveButton = JButton("Save to File")
- saveButton.addActionListener {
- val fileChooser = JFileChooser().apply {
- dialogTitle = "Save Configuration"
- fileFilter = FileNameExtensionFilter("JSON Files", "json")
- }
- if (fileChooser.showSaveDialog(dialog) == JFileChooser.APPROVE_OPTION) {
- val file = fileChooser.selectedFile
- val filePath = if (!file.name.lowercase().endsWith(".json")) {
- File("${file.absolutePath}.json")
- } else {
- file
+ val textArea = JTextArea(fullConfig).apply {
+ lineWrap = true
+ wrapStyleWord = true
+ font = Font(Font.MONOSPACED, Font.PLAIN, 12)
}
- try {
- FileWriter(filePath).use { writer ->
- writer.write(textArea.text)
- }
- log.info("Configuration exported successfully to: ${filePath.absolutePath}")
- JOptionPane.showMessageDialog(
- dialog, "Configuration saved to ${filePath.absolutePath}", "Success", JOptionPane.INFORMATION_MESSAGE
- )
- } catch (e: Exception) {
- log.error("Failed to save configuration to file: ${filePath.absolutePath}", e)
- JOptionPane.showMessageDialog(
- dialog, "Error saving configuration: ${e.message}", "Error", JOptionPane.ERROR_MESSAGE
- )
+ dialog.add(JScrollPane(textArea), BorderLayout.CENTER)
+ val buttonPanel = JPanel(FlowLayout(FlowLayout.RIGHT))
+ val copyButton = JButton("Copy to Clipboard")
+ copyButton.addActionListener {
+ textArea.selectAll()
+ textArea.copy()
+ JOptionPane.showMessageDialog(
+ dialog, "Configuration copied to clipboard", "Success", JOptionPane.INFORMATION_MESSAGE
+ )
}
- }
- }
- val closeButton = JButton("Close")
- closeButton.addActionListener {
- dialog.dispose()
+ val saveButton = JButton("Save to File")
+ saveButton.addActionListener {
+ val fileChooser = JFileChooser().apply {
+ dialogTitle = "Save Configuration"
+ fileFilter = FileNameExtensionFilter("JSON Files", "json")
+ }
+ if (fileChooser.showSaveDialog(dialog) == JFileChooser.APPROVE_OPTION) {
+ val file = fileChooser.selectedFile
+ val filePath = if (!file.name.lowercase().endsWith(".json")) {
+ File("${file.absolutePath}.json")
+ } else {
+ file
+ }
+ try {
+ FileWriter(filePath).use { writer ->
+ writer.write(textArea.text)
+ }
+ log.info("Configuration exported successfully to: ${filePath.absolutePath}")
+ JOptionPane.showMessageDialog(
+ dialog,
+ "Configuration saved to ${filePath.absolutePath}",
+ "Success",
+ JOptionPane.INFORMATION_MESSAGE
+ )
+ } catch (e: Exception) {
+ log.error("Failed to save configuration to file: ${filePath.absolutePath}", e)
+ JOptionPane.showMessageDialog(
+ dialog, "Error saving configuration: ${e.message}", "Error", JOptionPane.ERROR_MESSAGE
+ )
+ }
+ }
+ }
+ val closeButton = JButton("Close")
+ closeButton.addActionListener {
+ dialog.dispose()
+ }
+ buttonPanel.add(copyButton)
+ buttonPanel.add(saveButton)
+ buttonPanel.add(closeButton)
+ dialog.add(buttonPanel, BorderLayout.SOUTH)
+ dialog.preferredSize = Dimension(800, 600)
+ dialog.pack()
+ dialog.setLocationRelativeTo(null)
+ dialog.isVisible = true
}
- buttonPanel.add(copyButton)
- buttonPanel.add(saveButton)
- buttonPanel.add(closeButton)
- dialog.add(buttonPanel, BorderLayout.SOUTH)
- dialog.preferredSize = Dimension(800, 600)
- dialog.pack()
- dialog.setLocationRelativeTo(null)
- dialog.isVisible = true
- }
- private fun showImportConfigDialog() {
- log.debug("Opening import configuration dialog")
- val dialog = JDialog(null as Frame?, "Import Configuration", true)
- dialog.layout = BorderLayout()
- val textArea = JTextArea().apply {
- lineWrap = true
- wrapStyleWord = true
- font = Font(Font.MONOSPACED, Font.PLAIN, 12)
- }
- dialog.add(JScrollPane(textArea), BorderLayout.CENTER)
- val buttonPanel = JPanel(FlowLayout(FlowLayout.RIGHT))
- val pasteButton = JButton("Paste from Clipboard")
- pasteButton.addActionListener {
- textArea.paste()
- }
- val loadButton = JButton("Load from File")
- loadButton.addActionListener {
- val fileChooser = JFileChooser().apply {
- dialogTitle = "Load Configuration"
- fileFilter = FileNameExtensionFilter("JSON Files", "json")
- }
- if (fileChooser.showOpenDialog(dialog) == JFileChooser.APPROVE_OPTION) {
- try {
- val file = fileChooser.selectedFile
- log.debug("Loading configuration from file: ${file.absolutePath}")
- FileReader(file).use { reader ->
- textArea.text = reader.readText()
- }
- log.info("Configuration loaded successfully from: ${file.absolutePath}")
- } catch (e: Exception) {
- log.error("Failed to load configuration from file", e)
- JOptionPane.showMessageDialog(
- dialog, "Error loading configuration: ${e.message}", "Error", JOptionPane.ERROR_MESSAGE
- )
+ private fun showImportConfigDialog() {
+ log.debug("Opening import configuration dialog")
+ val dialog = JDialog(null as Frame?, "Import Configuration", true)
+ dialog.layout = BorderLayout()
+ val textArea = JTextArea().apply {
+ lineWrap = true
+ wrapStyleWord = true
+ font = Font(Font.MONOSPACED, Font.PLAIN, 12)
}
- }
- }
- val applyButton = JButton("Apply Configuration")
- applyButton.addActionListener {
- try {
- val confirm = JOptionPane.showConfirmDialog(
- dialog,
- "Are you sure you want to apply this configuration? This will overwrite your current settings.",
- "Confirm Import",
- JOptionPane.YES_NO_OPTION,
- JOptionPane.WARNING_MESSAGE
- )
+ dialog.add(JScrollPane(textArea), BorderLayout.CENTER)
+ val buttonPanel = JPanel(FlowLayout(FlowLayout.RIGHT))
+ val pasteButton = JButton("Paste from Clipboard")
+ pasteButton.addActionListener {
+ textArea.paste()
+ }
+ val loadButton = JButton("Load from File")
+ loadButton.addActionListener {
+ val fileChooser = JFileChooser().apply {
+ dialogTitle = "Load Configuration"
+ fileFilter = FileNameExtensionFilter("JSON Files", "json")
+ }
+ if (fileChooser.showOpenDialog(dialog) == JFileChooser.APPROVE_OPTION) {
+ try {
+ val file = fileChooser.selectedFile
+ log.debug("Loading configuration from file: ${file.absolutePath}")
+ FileReader(file).use { reader ->
+ textArea.text = reader.readText()
+ }
+ log.info("Configuration loaded successfully from: ${file.absolutePath}")
+ } catch (e: Exception) {
+ log.error("Failed to load configuration from file", e)
+ JOptionPane.showMessageDialog(
+ dialog, "Error loading configuration: ${e.message}", "Error", JOptionPane.ERROR_MESSAGE
+ )
+ }
+ }
+ }
+ val applyButton = JButton("Apply Configuration")
+ applyButton.addActionListener {
+ try {
+ val confirm = JOptionPane.showConfirmDialog(
+ dialog,
+ "Are you sure you want to apply this configuration? This will overwrite your current settings.",
+ "Confirm Import",
+ JOptionPane.YES_NO_OPTION,
+ JOptionPane.WARNING_MESSAGE
+ )
- if (confirm == JOptionPane.YES_OPTION) {
- log.info("User confirmed configuration import")
- import(textArea.text)
- write(AppSettingsState.instance, component!!)
- log.info("Configuration imported and applied successfully")
- JOptionPane.showMessageDialog(
- dialog, "Configuration applied successfully. Please restart the IDE for all changes to take effect.", "Success", JOptionPane.INFORMATION_MESSAGE
- )
- dialog.dispose()
- } else {
- log.debug("User cancelled configuration import")
+ if (confirm == JOptionPane.YES_OPTION) {
+ log.info("User confirmed configuration import")
+ import(textArea.text)
+ write(AppSettingsState.instance, component!!)
+ log.info("Configuration imported and applied successfully")
+ JOptionPane.showMessageDialog(
+ dialog,
+ "Configuration applied successfully. Please restart the IDE for all changes to take effect.",
+ "Success",
+ JOptionPane.INFORMATION_MESSAGE
+ )
+ dialog.dispose()
+ } else {
+ log.debug("User cancelled configuration import")
+ }
+ } catch (e: Exception) {
+ log.error("Failed to apply imported configuration", e)
+ JOptionPane.showMessageDialog(
+ dialog, "Error applying configuration: ${e.message}", "Error", JOptionPane.ERROR_MESSAGE
+ )
+ }
}
- } catch (e: Exception) {
- log.error("Failed to apply imported configuration", e)
- JOptionPane.showMessageDialog(
- dialog, "Error applying configuration: ${e.message}", "Error", JOptionPane.ERROR_MESSAGE
- )
- }
- }
- val closeButton = JButton("Cancel")
- closeButton.addActionListener {
- dialog.dispose()
+ val closeButton = JButton("Cancel")
+ closeButton.addActionListener {
+ dialog.dispose()
+ }
+ buttonPanel.add(pasteButton)
+ buttonPanel.add(loadButton)
+ buttonPanel.add(applyButton)
+ buttonPanel.add(closeButton)
+ dialog.add(buttonPanel, BorderLayout.SOUTH)
+ dialog.preferredSize = Dimension(800, 600)
+ dialog.pack()
+ dialog.setLocationRelativeTo(null)
+ dialog.isVisible = true
}
- buttonPanel.add(pasteButton)
- buttonPanel.add(loadButton)
- buttonPanel.add(applyButton)
- buttonPanel.add(closeButton)
- dialog.add(buttonPanel, BorderLayout.SOUTH)
- dialog.preferredSize = Dimension(800, 600)
- dialog.pack()
- dialog.setLocationRelativeTo(null)
- dialog.isVisible = true
- }
- fun import(text: String) {
- log.debug("Importing configuration, text length: ${text.length}")
- try {
- // Try to parse as new format with both appSettings and userSettings
- val fullConfig: Map = fromJson(text, Map::class.java)
- if (fullConfig.containsKey("appSettings") && fullConfig.containsKey("userSettings")) {
- log.info("Importing new format configuration with appSettings and userSettings")
- val appSettingsJson = JsonUtil.toJson(fullConfig["appSettings"])
- val userSettingsJson = JsonUtil.toJson(fullConfig["userSettings"])
- val importedSettings = fromJson(appSettingsJson, AppSettingsState::class.java)
- XmlSerializerUtil.copyBean(importedSettings, AppSettingsState.instance)
+ fun import(text: String) {
+ log.debug("Importing configuration, text length: ${text.length}")
+ try {
+ // Try to parse as new format with both appSettings and userSettings
+ val fullConfig: Map = fromJson(text, Map::class.java)
+ if (fullConfig.containsKey("appSettings") && fullConfig.containsKey("userSettings")) {
+ log.info("Importing new format configuration with appSettings and userSettings")
+ val appSettingsJson = JsonUtil.toJson(fullConfig["appSettings"])
+ val userSettingsJson = JsonUtil.toJson(fullConfig["userSettings"])
+ val importedSettings = fromJson(appSettingsJson, AppSettingsState::class.java)
+ XmlSerializerUtil.copyBean(importedSettings, AppSettingsState.instance)
- val importedUserSettings = fromJson(
- userSettingsJson, UserSettings::class.java
- )
- log.debug("Decrypting ${importedUserSettings.apis.size} API configurations")
- val decryptedUserSettings = importedUserSettings.copy(
- apis = importedUserSettings.apis.map { api ->
- try {
- api.copy(key = api.key?.let { EncryptionUtil.decrypt(it, password.text) } ?: api.key)
- } catch (e: Exception) {
- log.error("Failed to decrypt API key for provider: ${api.provider}", e)
- throw IllegalStateException(
- "Failed to decrypt API key for ${api.provider}. Please check your password.", e
- )
+ val importedUserSettings = fromJson(
+ userSettingsJson, UserSettings::class.java
+ )
+ log.debug("Decrypting ${importedUserSettings.apis.size} API configurations")
+ val decryptedUserSettings = importedUserSettings.copy(
+ apis = importedUserSettings.apis.map { api ->
+ try {
+ api.copy(key = api.key?.let { EncryptionUtil.decrypt(it, password.text) } ?: api.key)
+ } catch (e: Exception) {
+ log.error("Failed to decrypt API key for provider: ${api.provider}", e)
+ throw IllegalStateException(
+ "Failed to decrypt API key for ${api.provider}. Please check your password.", e
+ )
+ }
+ }.toMutableList()
+ )
+ ApplicationServices.fileApplicationServices(AppSettingsState.pluginHome).userSettingsManager.updateUserSettings(
+ UserSettingsManager.defaultUser, decryptedUserSettings
+ )
+ log.info("Successfully imported configuration with ${decryptedUserSettings.apis.size} API configurations")
+ } else {
+ // Fall back to old format
+ log.info("Importing legacy format configuration")
+ val importedSettings = fromJson(text, AppSettingsState::class.java)
+ XmlSerializerUtil.copyBean(importedSettings, AppSettingsState.instance)
+ log.info("Successfully imported legacy configuration")
}
- }.toMutableList()
- )
- ApplicationServices.fileApplicationServices(AppSettingsState.pluginHome).userSettingsManager.updateUserSettings(
- UserSettingsManager.defaultUser, decryptedUserSettings
- )
- log.info("Successfully imported configuration with ${decryptedUserSettings.apis.size} API configurations")
- } else {
- // Fall back to old format
- log.info("Importing legacy format configuration")
- val importedSettings = fromJson(text, AppSettingsState::class.java)
- XmlSerializerUtil.copyBean(importedSettings, AppSettingsState.instance)
- log.info("Successfully imported legacy configuration")
- }
- } catch (e: Exception) {
- log.error("Failed to import configuration", e)
- throw e
+ } catch (e: Exception) {
+ log.error("Failed to import configuration", e)
+ throw e
+ }
}
- }
- override fun write(settings: AppSettingsState, component: AppSettingsComponent) {
- log.debug("Writing settings to UI components")
- try {
- component.diffLoggingEnabled.isSelected = settings.diffLoggingEnabled
- component.awsProfile.text = settings.awsProfile ?: ""
- component.awsRegion.text = settings.awsRegion ?: ""
- component.awsBucket.text = settings.awsBucket ?: ""
- component.listeningPort.text = settings.listeningPort.toString()
- component.listeningEndpoint.text = settings.listeningEndpoint
-component.suppressErrors.isSelected = settings.suppressErrors
- component.disableAutoOpenUrls.isSelected = settings.disableAutoOpenUrls
- settings.fastModel?.model?.let { component.fastModel.selectedItem = it.modelName }
- settings.smartModel?.model?.let { component.smartModel.selectedItem = it.modelName }
- settings.imageChatModel?.model?.let { component.imageChatModel.selectedItem = it.modelName }
- settings.imageModel?.model?.let { component.mainImageModel.selectedItem = it.modelName }
- component.devActions.isSelected = settings.devActions
- component.temperature.text = settings.temperature.toString()
- component.embeddingModel.selectedItem = settings.embeddingModel
- component.shellCommand.text = settings.shellCommand
- component.showWelcomeScreen.isSelected = settings.showWelcomeScreen
- component.patchProcessor.selectedItem = settings.processor.label
- log.debug("Successfully wrote settings to UI components")
- } catch (e: Exception) {
- log.error("Failed to write settings to UI components", e)
- throw IllegalStateException("Failed to update UI with settings", e)
+ override fun write(settings: AppSettingsState, component: AppSettingsComponent) {
+ log.debug("Writing settings to UI components")
+ try {
+ component.diffLoggingEnabled.isSelected = settings.diffLoggingEnabled
+ component.awsProfile.text = settings.awsProfile ?: ""
+ component.awsRegion.text = settings.awsRegion ?: ""
+ component.awsBucket.text = settings.awsBucket ?: ""
+ component.listeningPort.text = settings.listeningPort.toString()
+ component.listeningEndpoint.text = settings.listeningEndpoint
+ component.suppressErrors.isSelected = settings.suppressErrors
+ component.disableAutoOpenUrls.isSelected = settings.disableAutoOpenUrls
+ settings.fastModel?.model?.let { component.fastModel.selectedItem = it.modelName }
+ settings.smartModel?.model?.let { component.smartModel.selectedItem = it.modelName }
+ settings.imageChatModel?.model?.let { component.imageChatModel.selectedItem = it.modelName }
+ settings.imageModel?.model?.let { component.mainImageModel.selectedItem = it.modelName }
+ component.devActions.isSelected = settings.devActions
+ component.temperature.text = settings.temperature.toString()
+ component.embeddingModel.selectedItem = settings.embeddingModel
+ component.shellCommand.text = settings.shellCommand
+ component.showWelcomeScreen.isSelected = settings.showWelcomeScreen
+ component.patchProcessor.selectedItem = settings.processor.label
+ log.debug("Successfully wrote settings to UI components")
+ } catch (e: Exception) {
+ log.error("Failed to write settings to UI components", e)
+ throw IllegalStateException("Failed to update UI with settings", e)
+ }
}
- }
- override fun read(component: AppSettingsComponent, settings: AppSettingsState) {
- log.debug("Reading settings from UI components")
- try {
- val userSettings = ApplicationServices.fileApplicationServices(
- AppSettingsState.pluginHome
- ).userSettingsManager.getUserSettings()
- log.debug("Current user has ${userSettings.apis.size} API configurations")
+ override fun read(component: AppSettingsComponent, settings: AppSettingsState) {
+ log.debug("Reading settings from UI components")
+ try {
+ val userSettings = ApplicationServices.fileApplicationServices(
+ AppSettingsState.pluginHome
+ ).userSettingsManager.getUserSettings()
+ log.debug("Current user has ${userSettings.apis.size} API configurations")
-val fastModelName = component.fastModel.selectedItem as String?
- val smartModelName = component.smartModel.selectedItem as String?
- val imageChatModelName = component.imageChatModel.selectedItem as String?
- val imageModelName = component.mainImageModel.selectedItem as String?
- log.debug("Selected models - fast: $fastModelName, smart: $smartModelName, imageChat: $imageChatModelName")
+ val fastModelName = component.fastModel.selectedItem as String?
+ val smartModelName = component.smartModel.selectedItem as String?
+ val imageChatModelName = component.imageChatModel.selectedItem as String?
+ val imageModelName = component.mainImageModel.selectedItem as String?
+ log.debug("Selected models - fast: $fastModelName, smart: $smartModelName, imageChat: $imageChatModelName")
- val chatModels = userSettings.apis.flatMap { apiData -> apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl) ?: emptyList() }
- val imageModels = userSettings.apis.flatMap { apiData -> apiData.provider?.getImageModels(apiData.key!!, apiData.baseUrl) ?: emptyList() }
- val fastChatModel = chatModels.find { model -> model.modelName == fastModelName || model.name == fastModelName }
- val fastApiData = userSettings.apis.find { it.provider == fastChatModel?.provider }
-val smartChatModel = chatModels.find { model -> model.modelName == smartModelName || model.name == smartModelName }
- val smartApiData = userSettings.apis.find { it.provider == smartChatModel?.provider }
- val imageChatModel = chatModels.find { model -> model.modelName == imageChatModelName || model.name == imageChatModelName }
- val imageChatApiData = userSettings.apis.find { it.provider == imageChatModel?.provider }
- val imageModel = imageModels.find { model -> model.modelName == imageModelName || model.name == imageModelName }
- val imageApiData = userSettings.apis.find { it.provider == imageModel?.provider }
+ val chatModels = userSettings.apis.flatMap { apiData ->
+ apiData.provider?.getChatModels(apiData.key!!, apiData.baseUrl) ?: emptyList()
+ }
+ val imageModels = userSettings.apis.flatMap { apiData ->
+ apiData.provider?.getImageModels(apiData.key!!, apiData.baseUrl) ?: emptyList()
+ }
+ val fastChatModel =
+ chatModels.find { model -> model.modelName == fastModelName || model.name == fastModelName }
+ val fastApiData = userSettings.apis.find { it.provider == fastChatModel?.provider }
+ val smartChatModel =
+ chatModels.find { model -> model.modelName == smartModelName || model.name == smartModelName }
+ val smartApiData = userSettings.apis.find { it.provider == smartChatModel?.provider }
+ val imageChatModel =
+ chatModels.find { model -> model.modelName == imageChatModelName || model.name == imageChatModelName }
+ val imageChatApiData = userSettings.apis.find { it.provider == imageChatModel?.provider }
+ val imageModel =
+ imageModels.find { model -> model.modelName == imageModelName || model.name == imageModelName }
+ val imageApiData = userSettings.apis.find { it.provider == imageModel?.provider }
-settings.fastModel = ApiChatModel(fastChatModel, fastApiData)
- settings.diffLoggingEnabled = component.diffLoggingEnabled.isSelected
- settings.imageChatModel = ApiChatModel(imageChatModel, imageChatApiData)
- settings.awsProfile = component.awsProfile.text.takeIf { it.isNotBlank() }
- settings.awsRegion = component.awsRegion.text.takeIf { it.isNotBlank() }
- settings.awsBucket = component.awsBucket.text.takeIf { it.isNotBlank() }
- settings.listeningPort = component.listeningPort.text.safeInt()
- settings.listeningEndpoint = component.listeningEndpoint.text
- settings.suppressErrors = component.suppressErrors.isSelected
- settings.smartModel = ApiChatModel(smartChatModel, smartApiData)
- settings.imageModel = imageModel?.let { ApiImageModel(it, imageApiData) }
- settings.devActions = component.devActions.isSelected
- settings.disableAutoOpenUrls = component.disableAutoOpenUrls.isSelected
- settings.temperature = component.temperature.text.safeDouble()
- settings.embeddingModel = component.embeddingModel.selectedItem?.let {
- when (it) {
- is String -> it.embeddingModel()
- is EmbeddingModel -> it
- else -> null
- }
- }
- settings.shellCommand = component.shellCommand.text
- settings.showWelcomeScreen = component.showWelcomeScreen.isSelected
- settings.processor = component.patchProcessor.selectedItem?.let {
- when (it) {
- is String -> try {
- PatchProcessors.valueOf(it)
- } catch (e: IllegalArgumentException) {
- log.warn("Unknown patch processor: $it, defaulting to Fuzzy")
- PatchProcessors.Fuzzy
- }
+ settings.fastModel = ApiChatModel(fastChatModel, fastApiData)
+ settings.diffLoggingEnabled = component.diffLoggingEnabled.isSelected
+ settings.imageChatModel = ApiChatModel(imageChatModel, imageChatApiData)
+ settings.awsProfile = component.awsProfile.text.takeIf { it.isNotBlank() }
+ settings.awsRegion = component.awsRegion.text.takeIf { it.isNotBlank() }
+ settings.awsBucket = component.awsBucket.text.takeIf { it.isNotBlank() }
+ settings.listeningPort = component.listeningPort.text.safeInt()
+ settings.listeningEndpoint = component.listeningEndpoint.text
+ settings.suppressErrors = component.suppressErrors.isSelected
+ settings.smartModel = ApiChatModel(smartChatModel, smartApiData)
+ settings.imageModel = imageModel?.let { ApiImageModel(it, imageApiData) }
+ settings.devActions = component.devActions.isSelected
+ settings.disableAutoOpenUrls = component.disableAutoOpenUrls.isSelected
+ settings.temperature = component.temperature.text.safeDouble()
+ settings.embeddingModel = component.embeddingModel.selectedItem?.let {
+ when (it) {
+ is String -> it.embeddingModel()
+ is EmbeddingModel -> it
+ else -> null
+ }
+ }
+ settings.shellCommand = component.shellCommand.text
+ settings.showWelcomeScreen = component.showWelcomeScreen.isSelected
+ settings.processor = component.patchProcessor.selectedItem?.let {
+ when (it) {
+ is String -> try {
+ PatchProcessors.valueOf(it)
+ } catch (e: IllegalArgumentException) {
+ log.warn("Unknown patch processor: $it, defaulting to Fuzzy")
+ PatchProcessors.Fuzzy
+ }
- is PatchProcessor -> it
- else -> PatchProcessors.Fuzzy
- } as? PatchProcessors ?: PatchProcessors.Fuzzy
- } ?: PatchProcessors.Fuzzy
+ is PatchProcessor -> it
+ else -> PatchProcessors.Fuzzy
+ } as? PatchProcessors ?: PatchProcessors.Fuzzy
+ } ?: PatchProcessors.Fuzzy
- val tableModel = component.apis.model as DefaultTableModel
- log.debug("Reading API keys from table with ${tableModel.rowCount} rows")
- userSettings.apis.clear()
- for (row in 0 until tableModel.rowCount) {
- try {
- val provider = (tableModel.getValueAt(row, 0) as? String) ?: ""
- val name = (tableModel.getValueAt(row, 1) as? String) ?: ""
- val key = (tableModel.getValueAt(row, 2) as? String) ?: ""
- val base = (tableModel.getValueAt(row, 3) as? String) ?: ""
- log.debug("Row $row: provider=$provider, name=$name, key=<${if (key.isNotBlank()) "hidden" else "empty"}>, base=$base")
+ val tableModel = component.apis.model as DefaultTableModel
+ log.debug("Reading API keys from table with ${tableModel.rowCount} rows")
+ userSettings.apis.clear()
+ for (row in 0 until tableModel.rowCount) {
+ try {
+ val provider = (tableModel.getValueAt(row, 0) as? String) ?: ""
+ val name = (tableModel.getValueAt(row, 1) as? String) ?: ""
+ val key = (tableModel.getValueAt(row, 2) as? String) ?: ""
+ val base = (tableModel.getValueAt(row, 3) as? String) ?: ""
+ log.debug("Row $row: provider=$provider, name=$name, key=<${if (key.isNotBlank()) "hidden" else "empty"}>, base=$base")
- if (provider.isNotBlank()) {
- try {
- val apiProvider = APIProvider.valueOf(provider)
- userSettings.apis.add(
- ApiData(
- name = name.takeIf { it.isNotBlank() },
- key = key.takeIf { it.isNotBlank() } ?: "",
- baseUrl = base,
- provider = apiProvider))
- } catch (e: Exception) {
- log.debug("Added API configuration for provider: $provider")
- } catch (e: IllegalArgumentException) {
- log.warn("Unknown provider at row $row: $provider", e)
+ if (provider.isNotBlank()) {
+ try {
+ val apiProvider = APIProvider.valueOf(provider)
+ userSettings.apis.add(
+ ApiData(
+ name = name.takeIf { it.isNotBlank() },
+ key = key.takeIf { it.isNotBlank() } ?: "",
+ baseUrl = base,
+ provider = apiProvider))
+ } catch (e: Exception) {
+ log.debug("Added API configuration for provider: $provider")
+ } catch (e: IllegalArgumentException) {
+ log.warn("Unknown provider at row $row: $provider", e)
+ }
+ }
+ } catch (e: Exception) {
+ log.error("Failed to read API configuration from row $row", e)
+ }
}
- }
- } catch (e: Exception) {
- log.error("Failed to read API configuration from row $row", e)
- }
- }
- val toolsModel = component.tools.model as DefaultTableModel
- log.debug("Reading Tools from table with ${toolsModel.rowCount} rows")
- userSettings.tools.clear()
- for (row in 0 until toolsModel.rowCount) {
- try {
- val providerName = (toolsModel.getValueAt(row, 0) as? String) ?: ""
- val path = (toolsModel.getValueAt(row, 1) as? String) ?: ""
- if (providerName.isNotBlank()) {
- try {
- val provider = ToolProvider.valueOf(providerName)
- userSettings.tools.add(ToolData(provider, path))
- } catch (e: Exception) {
- log.warn("Unknown tool provider: $providerName")
+ val toolsModel = component.tools.model as DefaultTableModel
+ log.debug("Reading Tools from table with ${toolsModel.rowCount} rows")
+ userSettings.tools.clear()
+ for (row in 0 until toolsModel.rowCount) {
+ try {
+ val providerName = (toolsModel.getValueAt(row, 0) as? String) ?: ""
+ val path = (toolsModel.getValueAt(row, 1) as? String) ?: ""
+ if (providerName.isNotBlank()) {
+ try {
+ val provider = ToolProvider.valueOf(providerName)
+ userSettings.tools.add(ToolData(provider, path))
+ } catch (e: Exception) {
+ log.warn("Unknown tool provider: $providerName")
+ }
+ }
+ } catch (e: Exception) {
+ log.error("Failed to read tool configuration from row $row", e)
+ }
}
- }
+ ApplicationServices.fileApplicationServices(AppSettingsState.pluginHome).userSettingsManager.updateUserSettings(
+ UserSettingsManager.defaultUser,
+ userSettings
+ )
+ log.info("Successfully read settings with ${userSettings.apis.size} API configurations")
+ log.debug("Settings after reading: ${settings.toJson()}")
+
} catch (e: Exception) {
- log.error("Failed to read tool configuration from row $row", e)
+ log.error("Failed to read settings from UI components", e)
+ throw IllegalStateException("Failed to read settings from UI", e)
}
- }
- ApplicationServices.fileApplicationServices(AppSettingsState.pluginHome).userSettingsManager.updateUserSettings(
- UserSettingsManager.defaultUser,
- userSettings
- )
- log.info("Successfully read settings with ${userSettings.apis.size} API configurations")
- log.debug("Settings after reading: ${settings.toJson()}")
-
- } catch (e: Exception) {
- log.error("Failed to read settings from UI components", e)
- throw IllegalStateException("Failed to read settings from UI", e)
}
- }
- companion object {
- val log = com.intellij.openapi.diagnostic.Logger.getInstance(StaticAppSettingsConfigurable::class.java)
- }
+ companion object {
+ val log = com.intellij.openapi.diagnostic.Logger.getInstance(StaticAppSettingsConfigurable::class.java)
+ }
}
fun String.embeddingModel(): EmbeddingModel? = try {
- EmbeddingModel.values()[this]
+ EmbeddingModel.values()[this]
} catch (e: Exception) {
- StaticAppSettingsConfigurable.log.warn("Failed to parse embedding model: $this", e)
- null
+ StaticAppSettingsConfigurable.log.warn("Failed to parse embedding model: $this", e)
+ null
}
fun String?.safeInt() = if (null == this) 0 else when {
- isEmpty() -> 0
- else -> try {
- toInt()
- } catch (e: NumberFormatException) {
- StaticAppSettingsConfigurable.log.debug("Failed to parse integer: $this", e)
- 0
- }
+ isEmpty() -> 0
+ else -> try {
+ toInt()
+ } catch (e: NumberFormatException) {
+ StaticAppSettingsConfigurable.log.debug("Failed to parse integer: $this", e)
+ 0
+ }
}
fun String?.safeDouble() = if (null == this) 0.0 else when {
- isEmpty() -> 0.0
- else -> try {
- toDouble()
- } catch (e: NumberFormatException) {
- StaticAppSettingsConfigurable.log.debug("Failed to parse double: $this", e)
- 0.0
- }
+ isEmpty() -> 0.0
+ else -> try {
+ toDouble()
+ } catch (e: NumberFormatException) {
+ StaticAppSettingsConfigurable.log.debug("Failed to parse double: $this", e)
+ 0.0
+ }
}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/UIAdapter.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/UIAdapter.kt
index ff530fbb1..1205bf046 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/UIAdapter.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/UIAdapter.kt
@@ -2,9 +2,9 @@ package com.simiacryptus.cognotik.config
import com.intellij.openapi.Disposable
import com.intellij.openapi.options.Configurable
+import com.simiacryptus.cognotik.util.LoggerFactory
import com.simiacryptus.cognotik.util.UITools
import com.simiacryptus.cognotik.util.buildFormViaReflection
-import com.simiacryptus.cognotik.util.LoggerFactory
import javax.swing.JComponent
abstract class UIAdapter(
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/UsageTable.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/UsageTable.kt
index a2ca69bf6..dbe0f5fcd 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/UsageTable.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/config/UsageTable.kt
@@ -1,6 +1,5 @@
package com.simiacryptus.cognotik.config
-import ai.grazie.utils.mpp.money.sumOf
import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.actionSystem.AnAction
@@ -22,224 +21,223 @@ import javax.swing.JPanel
import javax.swing.JTable
import javax.swing.table.AbstractTableModel
import javax.swing.table.DefaultTableCellRenderer
-import kotlin.collections.sumOf
class UsageTable(
- val usage: UsageInterface
+ val usage: UsageInterface
) : JPanel(BorderLayout()) {
- private val buttonPanel = JPanel()
- val columnNames = arrayOf("Model", "Prompt", "Completion", "Cost")
-
- val rowData by lazy {
- val usageData = usage.getUserUsageSummary(UserSettingsManager.defaultUser).map { entry ->
- listOf(
- entry.key,
- entry.value.prompt_tokens.toString(),
- entry.value.completion_tokens.toString(),
- String.format("%.2f", entry.value.cost)
- ).toMutableList()
+ private val buttonPanel = JPanel()
+ val columnNames = arrayOf("Model", "Prompt", "Completion", "Cost")
+
+ val rowData by lazy {
+ val usageData = usage.getUserUsageSummary(UserSettingsManager.defaultUser).map { entry ->
+ listOf(
+ entry.key,
+ entry.value.prompt_tokens.toString(),
+ entry.value.completion_tokens.toString(),
+ String.format("%.2f", entry.value.cost)
+ ).toMutableList()
+ }
+
+ val totalPromptTokens = usageData.sumOf { it[1].toInt() }
+ val totalCompletionTokens = usageData.sumOf { it[2].toInt() }
+ val totalCost = usageData.sumOf { it[3].toDouble() }
+
+ (usageData + listOf(
+ listOf(
+ "TOTAL",
+ totalPromptTokens.toString(),
+ totalCompletionTokens.toString(),
+ String.format("%.2f", totalCost)
+ ).toMutableList()
+ )).toMutableList()
}
- val totalPromptTokens = usageData.sumOf { it[1].toInt() }
- val totalCompletionTokens = usageData.sumOf { it[2].toInt() }
- val totalCost = usageData.sumOf { it[3].toDouble() }
+ private val dataModel by lazy {
+ object : AbstractTableModel() {
+ init {
+ checkUsageThreshold()
+ }
+
+ override fun getColumnName(column: Int): String {
+ return columnNames.get(column)
+ }
- (usageData + listOf(
- listOf(
- "TOTAL",
- totalPromptTokens.toString(),
- totalCompletionTokens.toString(),
- String.format("%.2f", totalCost)
- ).toMutableList()
- )).toMutableList()
- }
+ override fun getValueAt(row: Int, col: Int): Any {
+ return rowData[row][col]
+ }
- private val dataModel by lazy {
- object : AbstractTableModel() {
- init {
- checkUsageThreshold()
- }
+ override fun isCellEditable(row: Int, column: Int): Boolean {
- override fun getColumnName(column: Int): String {
- return columnNames.get(column)
- }
+ return row != rowData.size - 1
+ }
- override fun getValueAt(row: Int, col: Int): Any {
- return rowData[row][col]
- }
+ override fun getRowCount(): Int {
+ return rowData.size
+ }
- override fun isCellEditable(row: Int, column: Int): Boolean {
+ override fun getColumnCount(): Int {
+ return columnNames.size
+ }
- return row != rowData.size - 1
- }
+ override fun setValueAt(value: Any, row: Int, col: Int) {
- override fun getRowCount(): Int {
- return rowData.size
- }
+ if (row == rowData.size - 1) return
+ val strings = rowData[row]
+ strings[col] = value.toString()
+ fireTableCellUpdated(row, col)
+ checkUsageThreshold()
+ }
- override fun getColumnCount(): Int {
- return columnNames.size
- }
+ }
+ }
- override fun setValueAt(value: Any, row: Int, col: Int) {
+ private val jtable by lazy { JBTable(dataModel) }
- if (row == rowData.size - 1) return
- val strings = rowData[row]
- strings[col] = value.toString()
- fireTableCellUpdated(row, col)
- checkUsageThreshold()
- }
+ private val scrollpane by lazy { JBScrollPane(jtable) }
+ private val clearButton by lazy {
+ JButton(object : AbstractAction("Clear") {
+ override fun actionPerformed(e: ActionEvent?) {
+ rowData.clear()
+ usage.clear()
+ this@UsageTable.parent.invalidate()
+ }
+ })
}
- }
-
- private val jtable by lazy { JBTable(dataModel) }
-
- private val scrollpane by lazy { JBScrollPane(jtable) }
-
-private val clearButton by lazy {
- JButton(object : AbstractAction("Clear") {
- override fun actionPerformed(e: ActionEvent?) {
- rowData.clear()
- usage.clear()
- this@UsageTable.parent.invalidate()
- }
- })
- }
-
- init {
-
- val totalRowRenderer = object : DefaultTableCellRenderer() {
- override fun getTableCellRendererComponent(
- table: JTable?,
- value: Any?,
- isSelected: Boolean,
- hasFocus: Boolean,
- row: Int,
- column: Int
- ): Component {
- val c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column)
- if (row == table?.model?.rowCount?.minus(1)) {
- font = font.deriveFont(font.style or java.awt.Font.BOLD)
+
+ init {
+
+ val totalRowRenderer = object : DefaultTableCellRenderer() {
+ override fun getTableCellRendererComponent(
+ table: JTable?,
+ value: Any?,
+ isSelected: Boolean,
+ hasFocus: Boolean,
+ row: Int,
+ column: Int
+ ): Component {
+ val c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column)
+ if (row == table?.model?.rowCount?.minus(1)) {
+ font = font.deriveFont(font.style or java.awt.Font.BOLD)
+ }
+ return c
+ }
}
- return c
- }
- }
- jtable.columnModel.getColumn(0).cellRenderer = DefaultTableCellRenderer()
- jtable.columnModel.getColumn(1).cellRenderer = DefaultTableCellRenderer()
- jtable.columnModel.getColumn(2).cellRenderer = DefaultTableCellRenderer()
- jtable.columnModel.getColumn(3).cellRenderer = DefaultTableCellRenderer()
+ jtable.columnModel.getColumn(0).cellRenderer = DefaultTableCellRenderer()
+ jtable.columnModel.getColumn(1).cellRenderer = DefaultTableCellRenderer()
+ jtable.columnModel.getColumn(2).cellRenderer = DefaultTableCellRenderer()
+ jtable.columnModel.getColumn(3).cellRenderer = DefaultTableCellRenderer()
- for (i in 0..3) {
- val column = jtable.columnModel.getColumn(i)
- column.cellRenderer = totalRowRenderer
- }
+ for (i in 0..3) {
+ val column = jtable.columnModel.getColumn(i)
+ column.cellRenderer = totalRowRenderer
+ }
- val editor = object : JXTable.GenericEditor() {
- override fun isCellEditable(anEvent: EventObject?) = false
+ val editor = object : JXTable.GenericEditor() {
+ override fun isCellEditable(anEvent: EventObject?) = false
+ }
+ jtable.columnModel.getColumn(0).cellEditor = editor
+ jtable.columnModel.getColumn(1).cellEditor = editor
+ jtable.columnModel.getColumn(2).cellEditor = editor
+ jtable.columnModel.getColumn(3).cellEditor = editor
+
+ jtable.columnModel.getColumn(0).headerRenderer = DefaultTableCellRenderer()
+ jtable.columnModel.getColumn(1).headerRenderer = DefaultTableCellRenderer()
+ jtable.columnModel.getColumn(2).headerRenderer = DefaultTableCellRenderer()
+ jtable.columnModel.getColumn(3).headerRenderer = DefaultTableCellRenderer()
+
+ initCol(0)
+ initCol(1)
+ initCol(2)
+ initCol(3)
+
+ jtable.tableHeader.defaultRenderer = DefaultTableCellRenderer()
+
+ add(scrollpane, BorderLayout.CENTER)
+ buttonPanel.add(clearButton)
+ add(buttonPanel, BorderLayout.SOUTH)
}
- jtable.columnModel.getColumn(0).cellEditor = editor
- jtable.columnModel.getColumn(1).cellEditor = editor
- jtable.columnModel.getColumn(2).cellEditor = editor
- jtable.columnModel.getColumn(3).cellEditor = editor
-
- jtable.columnModel.getColumn(0).headerRenderer = DefaultTableCellRenderer()
- jtable.columnModel.getColumn(1).headerRenderer = DefaultTableCellRenderer()
- jtable.columnModel.getColumn(2).headerRenderer = DefaultTableCellRenderer()
- jtable.columnModel.getColumn(3).headerRenderer = DefaultTableCellRenderer()
-
- initCol(0)
- initCol(1)
- initCol(2)
- initCol(3)
-
- jtable.tableHeader.defaultRenderer = DefaultTableCellRenderer()
-
- add(scrollpane, BorderLayout.CENTER)
- buttonPanel.add(clearButton)
- add(buttonPanel, BorderLayout.SOUTH)
- }
-
- private fun checkUsageThreshold() {
- val settings = AppSettingsState.instance
- if (settings.feedbackOptOut || settings.feedbackRequested) {
- return
+
+ private fun checkUsageThreshold() {
+ val settings = AppSettingsState.instance
+ if (settings.feedbackOptOut || settings.feedbackRequested) {
+ return
+ }
+ val totalTokens = rowData.dropLast(1).sumOf {
+ (it[1].toIntOrNull() ?: 0) + (it[2].toIntOrNull() ?: 0)
+ }
+ if (totalTokens >= 1000000) {
+ settings.feedbackRequested = true
+ showFeedbackNotification()
+ }
}
- val totalTokens = rowData.dropLast(1).sumOf {
- (it[1].toIntOrNull() ?: 0) + (it[2].toIntOrNull() ?: 0)
+
+ private fun showFeedbackNotification() {
+ val notification = NotificationGroupManager.getInstance()
+ .getNotificationGroup("Cognotik Feedback")
+ .createNotification(
+ "You're Making Great Progress with Cognotik! 🚀",
+ "You've processed over 10,000 tokens! Your feedback helps shape the future of this open-source tool. Share your experience?",
+ NotificationType.INFORMATION
+ )
+ notification.addAction(object : AnAction("It's Great! 🎉") {
+ override fun actionPerformed(e: AnActionEvent) {
+ notification.expire()
+ showReviewRequest()
+ }
+ })
+ notification.addAction(object : AnAction("I Have Feedback 💭") {
+ override fun actionPerformed(e: AnActionEvent) {
+ notification.expire()
+ showFeedbackDialog()
+ }
+ })
+ notification.addAction(object : AnAction("Don't Ask Again") {
+ override fun actionPerformed(e: AnActionEvent) {
+ AppSettingsState.instance.feedbackOptOut = true
+ notification.expire()
+ }
+ })
+ notification.notify(null)
}
- if (totalTokens >= 1000000) {
- settings.feedbackRequested = true
- showFeedbackNotification()
+
+ private fun showReviewRequest() {
+ val notification = NotificationGroupManager.getInstance()
+ .getNotificationGroup("Cognotik Feedback")
+ .createNotification(
+ "Help Other Developers Discover Cognotik! 🌟",
+ "Your positive experience can help others find this tool. Would you consider leaving a quick review? It takes less than a minute and makes a huge difference!",
+ NotificationType.INFORMATION
+ )
+ notification.addAction(object : AnAction("Leave a Review ⭐") {
+ override fun actionPerformed(e: AnActionEvent) {
+ BrowseUtil.browse(URI("https://plugins.jetbrains.com/plugin/27289-cognotik/reviews"))
+ notification.expire()
+ }
+ })
+ notification.addAction(object : AnAction("Maybe Later") {
+ override fun actionPerformed(e: AnActionEvent) {
+ AppSettingsState.instance.feedbackRequested = false
+ notification.expire()
+ }
+ })
+ notification.addAction(object : AnAction("Share Feedback Instead 💬") {
+ override fun actionPerformed(e: AnActionEvent) {
+ showFeedbackDialog()
+ notification.expire()
+ }
+ })
+ notification.notify(null)
}
- }
-
- private fun showFeedbackNotification() {
- val notification = NotificationGroupManager.getInstance()
- .getNotificationGroup("Cognotik Feedback")
- .createNotification(
- "You're Making Great Progress with Cognotik! 🚀",
- "You've processed over 10,000 tokens! Your feedback helps shape the future of this open-source tool. Share your experience?",
- NotificationType.INFORMATION
- )
- notification.addAction(object : AnAction("It's Great! 🎉") {
- override fun actionPerformed(e: AnActionEvent) {
- notification.expire()
- showReviewRequest()
- }
- })
- notification.addAction(object : AnAction("I Have Feedback 💭") {
- override fun actionPerformed(e: AnActionEvent) {
- notification.expire()
- showFeedbackDialog()
- }
- })
- notification.addAction(object : AnAction("Don't Ask Again") {
- override fun actionPerformed(e: AnActionEvent) {
- AppSettingsState.instance.feedbackOptOut = true
- notification.expire()
- }
- })
- notification.notify(null)
- }
-
- private fun showReviewRequest() {
- val notification = NotificationGroupManager.getInstance()
- .getNotificationGroup("Cognotik Feedback")
- .createNotification(
- "Help Other Developers Discover Cognotik! 🌟",
- "Your positive experience can help others find this tool. Would you consider leaving a quick review? It takes less than a minute and makes a huge difference!",
- NotificationType.INFORMATION
- )
- notification.addAction(object : AnAction("Leave a Review ⭐") {
- override fun actionPerformed(e: AnActionEvent) {
- BrowseUtil.browse(URI("https://plugins.jetbrains.com/plugin/27289-cognotik/reviews"))
- notification.expire()
- }
- })
- notification.addAction(object : AnAction("Maybe Later") {
- override fun actionPerformed(e: AnActionEvent) {
- AppSettingsState.instance.feedbackRequested = false
- notification.expire()
- }
- })
- notification.addAction(object : AnAction("Share Feedback Instead 💬") {
- override fun actionPerformed(e: AnActionEvent) {
- showFeedbackDialog()
- notification.expire()
- }
- })
- notification.notify(null)
- }
-
- private fun showFeedbackDialog() {
- val notification = NotificationGroupManager.getInstance()
- .getNotificationGroup("Cognotik Feedback")
- .createNotification(
- "Your Feedback Shapes Cognotik's Future! 💡",
- """
+
+ private fun showFeedbackDialog() {
+ val notification = NotificationGroupManager.getInstance()
+ .getNotificationGroup("Cognotik Feedback")
+ .createNotification(
+ "Your Feedback Shapes Cognotik's Future! 💡",
+ """
As an open-source project, your input directly influences our development priorities. Here's how you can contribute:
💬 Share Your Thoughts:
@@ -255,40 +253,40 @@ private val clearButton by lazy {
Together, we're building better AI-powered development tools!
""".trimIndent(),
- NotificationType.INFORMATION
- )
-notification.addAction(object : AnAction("Report an Issue 🐛") {
- override fun actionPerformed(e: AnActionEvent) {
- BrowseUtil.browse(URI("https://github.com/SimiaCryptus/Cognotik/issues/new"))
- notification.expire()
- }
- })
- notification.addAction(object : AnAction("Share Ideas 💬") {
- override fun actionPerformed(e: AnActionEvent) {
- BrowseUtil.browse(URI("https://github.com/SimiaCryptus/Cognotik/discussions/new?category=feedback"))
- notification.expire()
- }
- })
- notification.addAction(object : AnAction("Browse Docs 📚") {
- override fun actionPerformed(e: AnActionEvent) {
- BrowseUtil.browse(URI("https://github.com/SimiaCryptus/Cognotik#readme"))
- notification.expire()
- }
- })
- notification.addAction(object : AnAction("Not Now") {
- override fun actionPerformed(e: AnActionEvent) {
- notification.expire()
- }
- })
- notification.notify(null)
- }
-
-
- private fun initCol(idx: Int) {
- val headerRenderer = jtable.tableHeader.defaultRenderer
- val headerValue = jtable.columnModel.getColumn(idx).headerValue
- val headerComp = headerRenderer.getTableCellRendererComponent(jtable, headerValue, false, false, 0, idx)
- jtable.columnModel.getColumn(idx).preferredWidth = headerComp.preferredSize.width
- }
+ NotificationType.INFORMATION
+ )
+ notification.addAction(object : AnAction("Report an Issue 🐛") {
+ override fun actionPerformed(e: AnActionEvent) {
+ BrowseUtil.browse(URI("https://github.com/SimiaCryptus/Cognotik/issues/new"))
+ notification.expire()
+ }
+ })
+ notification.addAction(object : AnAction("Share Ideas 💬") {
+ override fun actionPerformed(e: AnActionEvent) {
+ BrowseUtil.browse(URI("https://github.com/SimiaCryptus/Cognotik/discussions/new?category=feedback"))
+ notification.expire()
+ }
+ })
+ notification.addAction(object : AnAction("Browse Docs 📚") {
+ override fun actionPerformed(e: AnActionEvent) {
+ BrowseUtil.browse(URI("https://github.com/SimiaCryptus/Cognotik#readme"))
+ notification.expire()
+ }
+ })
+ notification.addAction(object : AnAction("Not Now") {
+ override fun actionPerformed(e: AnActionEvent) {
+ notification.expire()
+ }
+ })
+ notification.notify(null)
+ }
+
+
+ private fun initCol(idx: Int) {
+ val headerRenderer = jtable.tableHeader.defaultRenderer
+ val headerValue = jtable.columnModel.getColumn(idx).headerValue
+ val headerComp = headerRenderer.getTableCellRendererComponent(jtable, headerValue, false, false, 0, idx)
+ jtable.columnModel.getColumn(idx).preferredWidth = headerComp.preferredSize.width
+ }
}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/ControlPanel.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/ControlPanel.kt
index 26a9f7536..68a48325a 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/ControlPanel.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/ControlPanel.kt
@@ -4,10 +4,10 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComboBox
import com.intellij.ui.components.JBLabel
import com.intellij.util.ui.JBUI
+import com.simiacryptus.cognotik.audio.AudioModels
+import com.simiacryptus.cognotik.audio.DictationManager
import com.simiacryptus.cognotik.config.AppSettingsState
import com.simiacryptus.cognotik.dictation.DictationWidgetFactory.SpeechToTextWidget.Companion.toggleRecording
-import com.simiacryptus.cognotik.audio.DictationManager
-import com.simiacryptus.cognotik.audio.AudioModels
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
import java.awt.event.MouseAdapter
@@ -19,7 +19,7 @@ import javax.swing.JProgressBar
class ControlPanel(
val project: Project,
val settings: DictationState = DictationState,
- val dictationManager : DictationManager = DictationWidgetFactory.dictationManager
+ val dictationManager: DictationManager = DictationWidgetFactory.dictationManager
) : JPanel(), AutoCloseable {
companion object {
}
@@ -55,7 +55,6 @@ class ControlPanel(
private val talkTimeProgressBar = JProgressBar(0, maxTalkTimeDisplayMs).apply {
-
toolTipText = "Current consecutive talk duration"
border = JBUI.Borders.emptyRight(5)
}
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/DictationState.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/DictationState.kt
index d2947bf5c..e970c6f7f 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/DictationState.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/DictationState.kt
@@ -3,16 +3,15 @@ package com.simiacryptus.cognotik.dictation
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
-import com.simiacryptus.cognotik.config.AppSettingsState
-import com.simiacryptus.cognotik.util.LoggerFactory
+import com.simiacryptus.cognotik.audio.AudioModels
import com.simiacryptus.cognotik.audio.AudioPacket
-import com.simiacryptus.cognotik.audio.DictationManager
import com.simiacryptus.cognotik.audio.TranscriptionProcessor
-import com.simiacryptus.cognotik.audio.AudioModels
+import com.simiacryptus.cognotik.config.AppSettingsState
import com.simiacryptus.cognotik.dictation.DictationWidgetFactory.Companion.dictationManager
import com.simiacryptus.cognotik.models.APIProvider
import com.simiacryptus.cognotik.platform.ApplicationServices
import com.simiacryptus.cognotik.util.EventDispatcher
+import com.simiacryptus.cognotik.util.LoggerFactory
import javax.sound.sampled.AudioFormat
open class DictationState {
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/DictationWidgetFactory.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/DictationWidgetFactory.kt
index 33a083d97..6f8032a54 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/DictationWidgetFactory.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/DictationWidgetFactory.kt
@@ -132,8 +132,14 @@ class DictationWidgetFactory : StatusBarWidgetFactory {
apiBase = apiData.baseUrl,
logLevel = Level.INFO,
logStreams = mutableListOf(),
- workPool = ApplicationServices.threadPoolManager.getPool(currentSession, UserSettingsManager.defaultUser),
- scheduledPool = ApplicationServices.threadPoolManager.getScheduledPool(currentSession, UserSettingsManager.defaultUser),
+ workPool = ApplicationServices.threadPoolManager.getPool(
+ currentSession,
+ UserSettingsManager.defaultUser
+ ),
+ scheduledPool = ApplicationServices.threadPoolManager.getScheduledPool(
+ currentSession,
+ UserSettingsManager.defaultUser
+ ),
provider = model.provider
)
}
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/EventPanel.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/EventPanel.kt
index 22326a4cc..648e4f384 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/EventPanel.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/EventPanel.kt
@@ -14,7 +14,6 @@ class EventPanel : JPanel() {
border = BorderFactory.createEmptyBorder(15, 15, 15, 15)
-
val listModel = DefaultListModel()
val transcriptionList = JList(listModel)
transcriptionList.setCellRenderer(object : DefaultListCellRenderer() {
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/SettingsPanel.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/SettingsPanel.kt
index e5f25689f..36c0fbff1 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/SettingsPanel.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/dictation/SettingsPanel.kt
@@ -3,7 +3,6 @@ package com.simiacryptus.cognotik.dictation
import com.intellij.openapi.project.Project
import com.intellij.ui.components.JBLabel
import com.intellij.util.ui.JBUI
-import com.simiacryptus.cognotik.audio.DictationManager
import com.simiacryptus.cognotik.dictation.DictationWidgetFactory.Companion.dictationManager
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/BrowseUtil.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/BrowseUtil.kt
index 1d103189f..077079296 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/BrowseUtil.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/BrowseUtil.kt
@@ -1,7 +1,7 @@
package com.simiacryptus.cognotik.util
-import com.simiacryptus.cognotik.config.AppSettingsState
import com.simiacryptus.cognotik.SettingsWidgetFactory.SettingsWidget
+import com.simiacryptus.cognotik.config.AppSettingsState
import java.awt.Desktop
import java.net.DatagramPacket
import java.net.DatagramSocket
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/CodeChatSocketManager.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/CodeChatSocketManager.kt
index 67d50b90c..22fa712c6 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/CodeChatSocketManager.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/CodeChatSocketManager.kt
@@ -15,7 +15,7 @@ open class CodeChatSocketManager(
val codeSelection: String,
model: ChatInterface,
parsingModel: ChatInterface,
- storage: StorageInterface?,
+ storage: StorageInterface,
) : ChatSocketManager(
session = session,
smartModel = model,
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/FileUtils.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/FileUtils.kt
deleted file mode 100644
index 598417bb6..000000000
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/FileUtils.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.simiacryptus.cognotik.util
-
-import com.intellij.openapi.vfs.VirtualFile
-
-fun VirtualFile.findRecursively(
- deadline: Long = System.currentTimeMillis() + 100,
- predicate: (VirtualFile) -> Boolean
-): List {
- val results = mutableListOf()
- when {
- System.currentTimeMillis() > deadline -> return results
- this.isDirectory -> {
- val children = this.children
- children?.forEach { child ->
- when {
- System.currentTimeMillis() > deadline -> return results
- child.isDirectory -> results.addAll(child.findRecursively(deadline, predicate))
- predicate(child) -> results.add(child)
- }
- }
- }
-
- predicate(this) -> {
- results.add(this)
- }
- }
- return results
-}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/IdeaOpenAIClient.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/IdeaOpenAIClient.kt
deleted file mode 100644
index 519a0727c..000000000
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/IdeaOpenAIClient.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.simiacryptus.cognotik.util
-
-import com.simiacryptus.cognotik.OpenAIClient
-import com.simiacryptus.cognotik.config.AppSettingsState
-import com.simiacryptus.cognotik.models.AIModel
-import com.simiacryptus.cognotik.models.ModelSchema
-import com.simiacryptus.cognotik.platform.ApplicationServices
-import com.simiacryptus.cognotik.platform.file.UserSettingsManager
-import java.util.concurrent.Executors
-
-class IdeaOpenAIClient : OpenAIClient(
- key = "",
- apiBase = "",
- workPool = Executors.newCachedThreadPool(),
- scheduledPool = ApplicationServices.threadPoolManager.getScheduledPool(
- AppSettingsState.currentSession,
- UserSettingsManager.defaultUser
- ),
-) {
-
- override fun onUsage(model: AIModel?, tokens: ModelSchema.Usage) {
- ApplicationServices.fileApplicationServices(AppSettingsState.Companion.pluginHome).usageManager.incrementUsage(
- AppSettingsState.currentSession,
- UserSettingsManager.defaultUser, model!!, tokens
- )
- }
-
- companion object {
-
- val instance by lazy {
- IdeaOpenAIClient()
- }
- val log = LoggerFactory.getLogger(IdeaOpenAIClient::class.java)
- }
-}
\ No newline at end of file
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/IntelliJPsiValidator.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/IntelliJPsiValidator.kt
index 0b542a360..02371b628 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/IntelliJPsiValidator.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/IntelliJPsiValidator.kt
@@ -7,7 +7,8 @@ import com.intellij.psi.PsiErrorElement
import com.intellij.psi.PsiFileFactory
import com.intellij.testFramework.LightVirtualFile
-class IntelliJPsiValidator(private val project: Project, val extension: String, val filename: String) : GrammarValidator {
+class IntelliJPsiValidator(private val project: Project, val extension: String, val filename: String) :
+ GrammarValidator {
override fun validateGrammar(code: String): List {
var errors: List? = null
WriteCommandAction.runWriteCommandAction(project) {
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/LineComment.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/LineComment.kt
index 4a94eea3f..1bb9f51b9 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/LineComment.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/LineComment.kt
@@ -12,8 +12,10 @@ class LineComment(private val commentPrefix: CharSequence, indent: CharSequence?
override fun fromString(text: String?): LineComment {
var textVar = text
textVar = textVar!!.replace(Regex("\t"), TextBlock.TAB_REPLACEMENT.toString())
- val indent = getWhitespacePrefix(*textVar.split(TextBlock.DELIMITER.toRegex())
- .dropLastWhile { it.isEmpty() }.toTypedArray())
+ val indent = getWhitespacePrefix(
+ *textVar.split(TextBlock.DELIMITER.toRegex())
+ .dropLastWhile { it.isEmpty() }.toTypedArray()
+ )
return LineComment(
commentPrefix,
indent,
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/PsiUtil.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/PsiUtil.kt
index 9b96a9ae1..fe845ca27 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/PsiUtil.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/PsiUtil.kt
@@ -1,9 +1,6 @@
package com.simiacryptus.cognotik.util
import com.intellij.psi.PsiElement
-import java.util.Arrays
-import java.util.HashSet
-import java.util.stream.Collectors
object PsiUtil {
diff --git a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/UITools.kt b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/UITools.kt
index c8451f348..45c691a49 100644
--- a/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/UITools.kt
+++ b/intellij/src/main/kotlin/com/simiacryptus/cognotik/util/UITools.kt
@@ -1,6 +1,5 @@
package com.simiacryptus.cognotik.util
-import cognotik.actions.agent.toFile
import com.google.common.util.concurrent.*
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
@@ -656,11 +655,6 @@ fun AnActionEvent.getSelectedFolder(): VirtualFile? {
return null
}
-fun AnActionEvent.hasSelection(): Boolean {
- val caret = getData(CommonDataKeys.CARET)
- return null != caret && caret.hasSelection()
-}
-
fun AnActionEvent.getIndent() = getData(CommonDataKeys.CARET)?.getIndent() ?: ""
fun Caret?.getIndent(): CharSequence {
@@ -676,17 +670,13 @@ fun Caret?.getIndent(): CharSequence {
fun AnActionEvent.redoableTask(
request: Supplier,
) {
- UITools.log.debug("Starting redoableTask with event: {}, request: {}",this,request)
+ UITools.log.debug("Starting redoableTask with event: {}, request: {}", this, request)
Futures.addCallback(UITools.pool.submit {
request.get()
}, futureCallback(request), UITools.pool)
UITools.log.debug("Submitted redoableTask for execution")
}
-fun AnActionEvent.getRoot(): String {
- return this.getSelectedFolder()?.toFile?.absolutePath ?: this.getSelectedFile()?.toFile?.parent ?: ""
-}
-
fun Class?>.findValue(string: String): Enum<*>? {
val caseInsensitiveMatch = this.enumConstants?.firstOrNull {
diff --git a/intellij/src/main/resources/META-INF/plugin.xml b/intellij/src/main/resources/META-INF/plugin.xml
index f418573af..089c2ecef 100644
--- a/intellij/src/main/resources/META-INF/plugin.xml
+++ b/intellij/src/main/resources/META-INF/plugin.xml
@@ -47,6 +47,10 @@
text="🧠 Adaptive Chat"
description="Enhanced chat with automatic history summarization and intelligent query elevation for complex questions">
+