feat(iced): implement AI script generator with working LLM calls

This commit is contained in:
2026-07-15 17:02:06 +03:00
parent 827d9b1742
commit 3bc5966630
7 changed files with 406 additions and 38 deletions
@@ -163,6 +163,20 @@ pub struct HcieIcedApp {
pub script_error_line: Option<usize>,
/// Whether a script is currently executing.
pub script_is_running: bool,
/// AI script generator — natural language prompt input.
pub ai_script_prompt: String,
/// AI script generator — generated DSL script output.
pub ai_script_output: String,
/// AI script generator — last error message (if any).
pub ai_script_error: Option<String>,
/// AI script generator — whether a generation request is in progress.
pub ai_script_is_running: bool,
/// AI script generator — selected provider type ("ollama", "claude", "openai").
pub ai_script_provider: String,
/// AI script generator — model name for the selected provider.
pub ai_script_model: String,
/// AI script generator — base URL for the selected provider.
pub ai_script_url: String,
}
/// A recently opened file entry.
@@ -438,6 +452,17 @@ pub enum Message {
AiChatStreamChunk(String),
AiChatStreamFinished(Result<String, String>),
// ── AI Script Generator ─────────────────────────────
AiScriptPromptChanged(String),
AiScriptProviderChanged(String),
AiScriptModelChanged(String),
AiScriptUrlChanged(String),
AiScriptGenerate,
AiScriptRun,
AiScriptCopy,
AiScriptClear,
AiScriptResult(Result<String, String>),
// ── Clipboard ───────────────────────────────────────
CopyImage,
PasteImage,
@@ -642,6 +667,13 @@ impl HcieIcedApp {
script_error: None,
script_error_line: None,
script_is_running: false,
ai_script_prompt: String::new(),
ai_script_output: String::new(),
ai_script_error: None,
ai_script_is_running: false,
ai_script_provider: "ollama".to_string(),
ai_script_model: "qwen2.5:7b".to_string(),
ai_script_url: "http://localhost:11434".to_string(),
};
// Apply saved settings to tool state
@@ -2452,6 +2484,104 @@ impl HcieIcedApp {
}
}
// ── AI Script Generator ───────────────────────
Message::AiScriptPromptChanged(text) => {
self.ai_script_prompt = text;
}
Message::AiScriptProviderChanged(provider) => {
self.ai_script_provider = provider.clone();
// Set sensible defaults when switching providers
match provider.as_str() {
"ollama" => {
self.ai_script_url = "http://localhost:11434".to_string();
self.ai_script_model = "qwen2.5:7b".to_string();
}
"claude" => {
self.ai_script_url = "https://api.anthropic.com".to_string();
self.ai_script_model = "claude-sonnet-4-20250514".to_string();
}
"openai" => {
self.ai_script_url = "https://api.openai.com/v1".to_string();
self.ai_script_model = "gpt-4o".to_string();
}
_ => {}
}
}
Message::AiScriptModelChanged(model) => {
self.ai_script_model = model;
}
Message::AiScriptUrlChanged(url) => {
self.ai_script_url = url;
}
Message::AiScriptGenerate => {
if self.ai_script_prompt.trim().is_empty() || self.ai_script_is_running {
return Task::none();
}
self.ai_script_is_running = true;
self.ai_script_error = None;
self.ai_script_output.clear();
let prompt = self.ai_script_prompt.clone();
let provider_type = self.ai_script_provider.clone();
let provider = crate::ai_script::Provider {
base_url: self.ai_script_url.clone(),
model: self.ai_script_model.clone(),
};
return Task::perform(
async move {
crate::ai_script::generate_script(&provider_type, &provider, &prompt).await
},
Message::AiScriptResult,
);
}
Message::AiScriptRun => {
if self.ai_script_output.is_empty() {
return Task::none();
}
match crate::script::parser::parse_dsl_script(&self.ai_script_output) {
Ok(actions) => {
self.ai_script_error = None;
crate::script::executor::execute_actions(
&mut self.documents[self.active_doc].engine,
&actions,
);
self.documents[self.active_doc].modified = true;
self.refresh_composite_if_needed();
return Task::perform(async {}, |_| Message::CompositeRefresh);
}
Err(e) => {
self.ai_script_error = Some(e);
}
}
}
Message::AiScriptCopy => {
if !self.ai_script_output.is_empty() {
// Copy script to the script editor panel
self.script_text = self.ai_script_output.clone();
self.script_content = iced::widget::text_editor::Content::with_text(
&self.ai_script_output,
);
}
}
Message::AiScriptClear => {
self.ai_script_prompt.clear();
self.ai_script_output.clear();
self.ai_script_error = None;
self.ai_script_is_running = false;
}
Message::AiScriptResult(result) => {
self.ai_script_is_running = false;
match result {
Ok(script) => {
self.ai_script_output = script;
}
Err(e) => {
self.ai_script_error = Some(e);
}
}
}
// ── Clipboard ─────────────────────────────────
Message::CopyImage => {
let doc = &self.documents[self.active_doc];
@@ -3064,6 +3194,7 @@ impl HcieIcedApp {
(7, 7) => self.dock.reopen_pane(PaneType::Geometry),
(7, 8) => self.dock.reopen_pane(PaneType::Script),
(7, 9) => self.dock.reopen_pane(PaneType::LayerDetails),
(7, 10) => self.dock.reopen_pane(PaneType::AiScript),
// ══════════════════════════════════════════
// More menu (8) — Photopea-style