From bc71a30bfa9c09dfe46a468d5a5ffecf9ac90f2f Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sun, 27 Jul 2025 19:15:30 +0100 Subject: [PATCH] fixed ai integration --- Cargo.lock | 20 +- project/context.json | 8 +- .../83592caa-f97d-427e-9d6a-50a586c30e6e.json | 2 +- src/editors/content_editor.rs | 199 ++++++++++++++++-- src/editors/context_editor.rs | 70 +++++- src/llm_integration/content_llm.rs | 15 +- src/main.rs | 21 +- 7 files changed, 292 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d43d7e1..0c4676c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1761,7 +1761,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.0", + "socket2", "system-configuration", "tokio", "tower-service", @@ -3572,16 +3572,6 @@ dependencies = [ "serde", ] -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.0" @@ -3819,9 +3809,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.46.1" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" dependencies = [ "backtrace", "bytes", @@ -3830,8 +3820,8 @@ dependencies = [ "mio", "pin-project-lite", "slab", - "socket2 0.5.10", - "windows-sys 0.52.0", + "socket2", + "windows-sys 0.59.0", ] [[package]] diff --git a/project/context.json b/project/context.json index 552aae1..a5300ed 100644 --- a/project/context.json +++ b/project/context.json @@ -1,6 +1,10 @@ { - "date": "2025-07-17", + "date": "2025-07-27", "project_name": "New Project", "project_author": "Your Name", - "project_description": "Description of your project" + "project_description": "Description of your project", + "enable_ai": true, + "llm_api_uri": "http://localhost:1234", + "llm_api_key": "", + "ai_context_prompt": "" } \ No newline at end of file diff --git a/project/documents/83592caa-f97d-427e-9d6a-50a586c30e6e.json b/project/documents/83592caa-f97d-427e-9d6a-50a586c30e6e.json index ac8f1a5..4c42bd2 100644 --- a/project/documents/83592caa-f97d-427e-9d6a-50a586c30e6e.json +++ b/project/documents/83592caa-f97d-427e-9d6a-50a586c30e6e.json @@ -3,6 +3,6 @@ "id": "83592caa-f97d-427e-9d6a-50a586c30e6e", "description": "ee", "tags": [], - "content": "# Test project\n\n- this project is a test to ensure that this tool can be integrated with AI models correctly\n- I’m testing various prompts and parameters to evaluate its capabilities. The initial focus is on simple tasks like list generation, text summarization, and question answering. More complex scenarios involving code generation and creative writing will follow in subsequent phases. A key aspect of this test project involves documenting all interactions – both the prompts used and the AI’s responses – for later analysis. This allows us to identify patterns, biases, and areas where the tool can be improved. ", + "content": "# Test project\n\n- this project is a test to ensure that this tool can be integrated with AI models correctly\n- I’m testing various prompts and parameters to evaluate its capabilities. The initial focus is on simple tasks like list generation, text summarization, and question answering. More complex scenarios involving code generation and creative writing will follow in subsequent phases. A key aspect of this test project involves documenting all interactions – both the prompts used and the AI’s responses – for later analysis. This allows us to identify patterns, biases, and areas where the tool can be improved. \n\n- \nAs a large language model, I'm essentially the engine powering these tests. My role here is to receive the prompts generated by the tool and produce responses based on my training data and the instructions provided within those prompts. The beauty of this setup lies in my flexibility; I can adapt to different tasks – from condensing lengthy articles into concise summaries to generating lists based on specific criteria, or even answering questions drawn from a given text. My ability to understand nuances in language allows for more sophisticated interactions as the project progresses beyond these initial simple tasks. The detailed documentation you're creating is particularly valuable because it provides feedback that can potentially be used to refine my responses and improve overall performance – essentially closing a loop between testing, analysis, and future model iterations.", "parent": null } \ No newline at end of file diff --git a/src/editors/content_editor.rs b/src/editors/content_editor.rs index 4b1ba8f..cd333b3 100644 --- a/src/editors/content_editor.rs +++ b/src/editors/content_editor.rs @@ -2,13 +2,18 @@ use egui::TextEdit; use egui_commonmark::{CommonMarkCache, CommonMarkViewer}; use serde::{self, Deserialize, Serialize}; -use crate::{PROJECT_FOLDER, editors::tags::Tag, llm_integration::content_llm::ai_enabled, util}; +use crate::{ + PROJECT_FOLDER, + editors::{context_editor::ProjectContext, tags::Tag}, + util, +}; pub struct MainEditor { pub content: ContentSection, pub show_editor: bool, pub show_preview: bool, preview_cache: CommonMarkCache, + dialog: Option, } impl Clone for MainEditor { @@ -19,6 +24,7 @@ impl Clone for MainEditor { show_editor: self.show_editor, show_preview: self.show_preview, preview_cache: CommonMarkCache::default(), + dialog: self.dialog.clone(), } } } @@ -48,6 +54,143 @@ pub struct ContentSection { pub saved: bool, } +#[derive(Clone)] +pub enum ContentAI { + Summarise { + open: bool, + content: String, + result: String, + ready: bool, + }, + Continue { + open: bool, + content: String, + instruction: String, + max_tokens: usize, + context_override: String, + result: String, + ready: bool, + }, +} + +impl ContentAI { + pub fn ui(&mut self, ui: &mut egui::Ui, project: &mut ProjectContext) { + let mut is_open = *match self { + ContentAI::Summarise { open, .. } => open, + ContentAI::Continue { open, .. } => open, + }; + + if is_open { + egui::Window::new("ContentAI") + .open(&mut is_open) + .show(ui.ctx(), |ui| match self { + ContentAI::Summarise { + content, + result, + ready, + .. + } => { + egui::ScrollArea::vertical() + .auto_shrink([false, false]) + .max_height(200.0) + .max_width(ui.available_width()) + .show(ui, |ui| { + ui.add( + egui::TextEdit::multiline(content) + .frame(false) + .interactive(false), + ); + }); + ui.add( + egui::TextEdit::multiline(result) + .font(egui::TextStyle::Monospace) + .interactive(false) + .frame(false) + .lock_focus(true) + .hint_text("Summary will appear here..."), + ); + if ui.button("Summarise").clicked() { + *result = Self::summarise(content).unwrap(); + *ready = true; + } + } + ContentAI::Continue { + content, + instruction, + max_tokens, + context_override, + result, + ready, + .. + } => { + ui.label("Continue"); + ui.add( + egui::TextEdit::multiline(instruction) + .frame(false) + .hint_text("Instruction"), + ); + ui.add( + egui::TextEdit::multiline(context_override) + .frame(false) + .hint_text("Any additional context?"), + ); + + ui.label("Max Tokens"); + ui.add(egui::Slider::new(max_tokens, 1000..=1000000)); + + if ui.button("Continue").clicked() { + match Self::continue_content( + instruction, + *max_tokens, + context_override, + project, + content, + ) { + Ok(str) => { + *result = str; + *ready = true; + } + Err(err) => { + *result = format!("Error: {err}"); + *ready = true; + } + } + } + } + }); + } + + match self { + ContentAI::Summarise { open, .. } => *open = is_open, + ContentAI::Continue { open, .. } => *open = is_open, + }; + } + + fn summarise(_content: &str) -> Result> { + // crate::llm_integration::content_llm::summarise_content(content, result) + Ok(String::new()) + } + + fn continue_content( + instruction: &str, + max_tokens: usize, + context_override: &str, + project: &mut ProjectContext, + content: &mut String, + ) -> Result> { + crate::llm_integration::content_llm::continue_content( + if context_override.is_empty() { + &project.ai_context_prompt + } else { + context_override + }, + content, + instruction, + max_tokens, + ) + } +} + impl ContentSection { pub fn new() -> Self { Self { @@ -97,6 +240,7 @@ impl MainEditor { show_editor: false, // Start with editor hidden show_preview: false, preview_cache: CommonMarkCache::default(), + dialog: None, } } @@ -106,10 +250,11 @@ impl MainEditor { show_editor: true, show_preview: false, preview_cache: CommonMarkCache::default(), + dialog: None, } } - pub fn ui(&mut self, ctx: &egui::Context) { + pub fn ui(&mut self, ctx: &egui::Context, project: &mut ProjectContext) { // Show the editor window if enabled let mut show = self.show_editor; if show { @@ -119,6 +264,27 @@ impl MainEditor { .default_height(800.0) .open(&mut show) .show(ctx, |ui| { + if let Some(dialog) = &mut self.dialog { + dialog.ui(ui, project); + + match dialog { + ContentAI::Summarise { ready, result, .. } => { + if *ready { + self.content.content.push_str(result.as_str()); + self.content.saved = false; + *ready = false; + } + } + ContentAI::Continue { ready, result, .. } => { + if *ready { + self.content.content.push_str(result.as_str()); + self.content.saved = false; + *ready = false; + } + } + } + } + ui.vertical(|ui| { // check for Ctrl+S to save if ui.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) { @@ -222,7 +388,7 @@ impl MainEditor { self.preview_ui(ui); } - self.editor_ui(ui); + self.editor_ui(ui, project); }); } @@ -263,7 +429,7 @@ impl MainEditor { }); } - fn editor_ui(&mut self, ui: &mut egui::Ui) { + fn editor_ui(&mut self, ui: &mut egui::Ui, project: &mut ProjectContext) { let _response = egui::ScrollArea::both() .auto_shrink([false, false]) .id_salt("editor_scroll") @@ -301,19 +467,26 @@ impl MainEditor { ctx_menu = true; ui.menu_button("AI Actions", |ui| { - ui.add_enabled_ui(ai_enabled(), |ui| { + ui.add_enabled_ui(project.ai_enabled(), |ui| { if ui.button("Summarise").clicked() { - println!("Summarise"); + self.dialog = Some(ContentAI::Summarise { + result: String::new(), + content: self.content.content.clone(), + open: true, + ready: false, + }); } if ui.button("Continue").clicked() { - let content = self.content.content.clone(); - let response = - crate::llm_integration::content_llm::continue_content( - &content, "", 1024, - ) - .unwrap(); - self.content.content.push_str(&response); + self.dialog = Some(ContentAI::Continue { + content: self.content.content.clone(), + instruction: String::new(), + max_tokens: 1024, + context_override: "".to_string(), + result: String::new(), + open: true, + ready: false, + }); } }); }); diff --git a/src/editors/context_editor.rs b/src/editors/context_editor.rs index 498a9e6..7681066 100644 --- a/src/editors/context_editor.rs +++ b/src/editors/context_editor.rs @@ -1,17 +1,27 @@ use std::io::Read; use chrono::NaiveDate; +use egui::TextEdit; use egui_extras::DatePickerButton; use serde::{Deserialize, Serialize}; use crate::PROJECT_FOLDER; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct ProjectContext { date: NaiveDate, project_name: String, project_author: String, project_description: String, + + // settings + enable_ai: bool, + llm_api_uri: String, + llm_api_key: String, + pub ai_context_prompt: String, + + #[serde(skip)] + pub open: bool, } impl ProjectContext { @@ -64,8 +74,59 @@ impl ProjectContext { ui.add(DatePickerButton::new(&mut self.date)); ui.end_row(); + + ui.label("Enable AI"); + ui.checkbox(&mut self.enable_ai, "Enable AI"); + + ui.end_row(); + + ui.label("LLM API URI"); + ui.text_edit_singleline(&mut self.llm_api_uri); + + ui.end_row(); + + ui.label("LLM API Key"); + ui.text_edit_singleline(&mut self.llm_api_key); + + ui.end_row(); + + ui.label("AI Context Prompt"); + ui.add(TextEdit::multiline(&mut self.ai_context_prompt) + .font(egui::TextStyle::Monospace) + .interactive(true) + .frame(false) + .lock_focus(true) + .hint_text("What is this project about? what should the LLM know when generating content for this project?")); + ui.end_row(); }); } + + pub fn ai_enabled(&mut self) -> bool { + let client = reqwest::blocking::Client::new(); + + if self.enable_ai { + return true; + } + + if client + .get(self.llm_api_uri.clone() + "/v1/models") + .send() + .is_ok() + { + self.enable_ai = true; + return true; + } + + false + } + + pub fn open(&mut self) { + self.open = true; + } + + pub fn close(&mut self) { + self.open = false; + } } impl Default for ProjectContext { @@ -75,6 +136,13 @@ impl Default for ProjectContext { project_name: "New Project".to_string(), project_author: "Your Name".to_string(), project_description: "Description of your project".to_string(), + + enable_ai: true, + llm_api_uri: "http://localhost:1234".to_string(), + llm_api_key: "".to_string(), + ai_context_prompt: "".to_string(), + + open: false, } } } diff --git a/src/llm_integration/content_llm.rs b/src/llm_integration/content_llm.rs index 136bbc5..80e1fe7 100644 --- a/src/llm_integration/content_llm.rs +++ b/src/llm_integration/content_llm.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; pub fn continue_content( context: &str, + previous_content: &str, instruction: &str, _max_tokens: usize, ) -> Result> { @@ -19,11 +20,15 @@ pub fn continue_content( }, Message { role: "user".to_string(), - content: context.to_string(), + content: format!("Context / General instructions: {context}"), }, Message { role: "user".to_string(), - content: format!("Instructions: {instruction}"), + content: format!("Previous content: {previous_content}"), + }, + Message { + role: "user".to_string(), + content: format!("Specific instructions: {instruction}"), }, ]; @@ -49,12 +54,6 @@ pub fn continue_content( Err("No response from model".into()) } } - -pub fn ai_enabled() -> bool { - let client = reqwest::blocking::Client::new(); - client.get("http://localhost:1234/v1/models").send().is_ok() -} - // Simple request structure #[derive(Serialize)] struct ChatRequest { diff --git a/src/main.rs b/src/main.rs index 9457cbd..0142c8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,7 +90,7 @@ impl Interface { } } - fn render_top_panel(&self, ctx: &egui::Context) { + fn render_top_panel(&mut self, ctx: &egui::Context) { // Top bar with actions egui::TopBottomPanel::top("top").show(ctx, |ui| { ui.horizontal(|ui| { @@ -99,9 +99,24 @@ impl Interface { ui.separator(); // version - ui.label(VERSION) + ui.label(VERSION); + + // Settings + if ui.button("Settings").clicked() { + self.project.open(); + } }); }); + + if self.project.open { + let mut open = self.project.open; + egui::Window::new("Settings") + .open(&mut open) + .show(ctx, |ui| { + self.project.ui(ui); + }); + self.project.open = open; + } } fn render_left_panel(&mut self, ctx: &egui::Context) { @@ -188,7 +203,7 @@ impl Interface { // render main content area fn render_main_content(&mut self, ctx: &egui::Context) { - self.editor.ui(ctx); + self.editor.ui(ctx, &mut self.project); self.scene.ui(ctx, &mut self.explorer.objects()); }