diff --git a/crates/cli/src/cli/chat.rs b/crates/cli/src/cli/chat.rs index eb50550d..daa952d4 100644 --- a/crates/cli/src/cli/chat.rs +++ b/crates/cli/src/cli/chat.rs @@ -191,6 +191,11 @@ pub async fn run(args: ChatArgs, agent_id: &str) -> Result<()> { println!("{}\n", notice); } + // Display welcome message on first run + if agent.is_brand_new() { + println!("{}\n", localgpt_core::agent::FIRST_RUN_WELCOME); + } + // Store agent_id for command handling let agent_id = agent_id.to_string(); diff --git a/crates/cli/src/desktop/worker.rs b/crates/cli/src/desktop/worker.rs index 87460de0..be90d92a 100644 --- a/crates/cli/src/desktop/worker.rs +++ b/crates/cli/src/desktop/worker.rs @@ -104,6 +104,14 @@ async fn worker_loop( // Send initial status let _ = tx.send(WorkerMessage::Status(agent.session_status())); + // Send welcome message on first run + let is_brand_new = agent.is_brand_new(); + if is_brand_new { + let _ = tx.send(WorkerMessage::SystemMessage( + localgpt_core::agent::FIRST_RUN_WELCOME.to_string(), + )); + } + // Track tools requiring approval let approval_tools: Vec = agent.approval_required_tools().to_vec(); diff --git a/crates/core/src/agent/mod.rs b/crates/core/src/agent/mod.rs index ccad7666..00dea5b4 100644 --- a/crates/core/src/agent/mod.rs +++ b/crates/core/src/agent/mod.rs @@ -294,6 +294,11 @@ impl Agent { self.memory.has_embeddings() } + /// Check if this is a brand new workspace (first run) + pub fn is_brand_new(&self) -> bool { + self.memory.is_brand_new() + } + /// Get context window configuration pub fn context_window(&self) -> usize { self.config.context_window @@ -1495,10 +1500,16 @@ impl AgentHandle { let agent = self.inner.lock().await; agent.export_markdown() } + + /// Check if this is a brand new workspace (first run). + pub async fn is_brand_new(&self) -> bool { + let agent = self.inner.lock().await; + agent.is_brand_new() + } } /// Welcome message shown on first run (brand new workspace) -const FIRST_RUN_WELCOME: &str = r#"# Welcome to LocalGPT +pub const FIRST_RUN_WELCOME: &str = r#"# Welcome to LocalGPT This is your first session. I've set up a fresh workspace for you. diff --git a/crates/mobile/src/lib.rs b/crates/mobile/src/lib.rs index f6ae700d..34c2bd4d 100644 --- a/crates/mobile/src/lib.rs +++ b/crates/mobile/src/lib.rs @@ -205,6 +205,12 @@ impl LocalGPTClient { self.runtime.block_on(self.handle.clear_session()); } + /// Check if this is a brand new workspace (first run). + /// Mobile apps can use this to display a custom welcome message. + pub fn is_brand_new(&self) -> bool { + self.runtime.block_on(self.handle.is_brand_new()) + } + /// Configure an API key for a provider. pub fn configure_provider(&self, provider: String, api_key: String) -> Result<(), MobileError> { let workspace = self.config.workspace_path(); @@ -232,6 +238,17 @@ impl LocalGPTClient { } } +// --------------------------------------------------------------------------- +// Standalone functions +// --------------------------------------------------------------------------- + +/// Get the first-run welcome message text. +/// Mobile apps can display this when `is_brand_new()` returns true. +#[uniffi::export] +pub fn get_welcome_message() -> String { + localgpt_core::agent::FIRST_RUN_WELCOME.to_string() +} + // --------------------------------------------------------------------------- // Error type // --------------------------------------------------------------------------- diff --git a/crates/server/src/http.rs b/crates/server/src/http.rs index cd4c127a..ebee9d19 100644 --- a/crates/server/src/http.rs +++ b/crates/server/src/http.rs @@ -388,6 +388,7 @@ struct StatusResponse { model: String, memory_chunks: usize, active_sessions: usize, + is_brand_new: bool, } async fn status(State(state): State>) -> Json { @@ -398,6 +399,7 @@ async fn status(State(state): State>) -> Json { model: state.config.agent.default_model.clone(), memory_chunks: state.memory.chunk_count().unwrap_or(0), active_sessions: sessions.len(), + is_brand_new: state.memory.is_brand_new(), }) } diff --git a/crates/server/src/telegram.rs b/crates/server/src/telegram.rs index 31951883..2e2f4b1e 100644 --- a/crates/server/src/telegram.rs +++ b/crates/server/src/telegram.rs @@ -696,6 +696,17 @@ async fn handle_chat( .await; return Ok(()); } + + // Send welcome message on first run + let is_brand_new = agent.is_brand_new(); + if is_brand_new { + let html = markdown_to_html(localgpt_core::agent::FIRST_RUN_WELCOME); + let _ = bot + .send_message(chat_id, html) + .parse_mode(ParseMode::Html) + .await; + } + e.insert(SessionEntry { agent, last_accessed: Instant::now(), diff --git a/crates/server/ui/app.js b/crates/server/ui/app.js index 55dee5c7..79404224 100644 --- a/crates/server/ui/app.js +++ b/crates/server/ui/app.js @@ -73,6 +73,32 @@ function showEmptyState() { } } +function showFirstRunWelcome() { + const messages = document.getElementById('messages'); + if (messages.children.length === 0) { + messages.innerHTML = ` +
+

Welcome to LocalGPT

+

This is your first session. I've set up a fresh workspace for you.

+

Quick Start

+
    +
  1. Just chat - I'm ready to help with coding, writing, research, or anything else
  2. +
  3. Your memory files are in the workspace: +
      +
    • MEMORY.md - I'll remember important things here
    • +
    • SOUL.md - Customize my personality and behavior
    • +
    • HEARTBEAT.md - Tasks for autonomous mode
    • +
    +
  4. +
+

Tell Me About Yourself

+

What's your name? What kind of projects do you work on? Any preferences for how I should communicate?

+

I'll save what I learn to MEMORY.md so I remember it next time.

+
+ `; + } +} + function clearEmptyState() { const emptyState = document.querySelector('.empty-state'); if (emptyState) { @@ -440,6 +466,11 @@ async function loadStatus() { const heartbeat = await heartbeatRes.json(); updateStatusPanel(status, heartbeat); + + // Show detailed welcome message on first run + if (status.is_brand_new) { + showFirstRunWelcome(); + } } catch (err) { console.error('Failed to load status:', err); }