Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/cli/src/cli/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
8 changes: 8 additions & 0 deletions crates/cli/src/desktop/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = agent.approval_required_tools().to_vec();

Expand Down
13 changes: 12 additions & 1 deletion crates/core/src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
17 changes: 17 additions & 0 deletions crates/mobile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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
// ---------------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions crates/server/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ struct StatusResponse {
model: String,
memory_chunks: usize,
active_sessions: usize,
is_brand_new: bool,
}

async fn status(State(state): State<Arc<AppState>>) -> Json<StatusResponse> {
Expand All @@ -398,6 +399,7 @@ async fn status(State(state): State<Arc<AppState>>) -> Json<StatusResponse> {
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(),
})
}

Expand Down
11 changes: 11 additions & 0 deletions crates/server/src/telegram.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
31 changes: 31 additions & 0 deletions crates/server/ui/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,32 @@ function showEmptyState() {
}
}

function showFirstRunWelcome() {
const messages = document.getElementById('messages');
if (messages.children.length === 0) {
messages.innerHTML = `
<div class="empty-state welcome">
<h1>Welcome to LocalGPT</h1>
<p>This is your first session. I've set up a fresh workspace for you.</p>
<h3>Quick Start</h3>
<ol>
<li><strong>Just chat</strong> - I'm ready to help with coding, writing, research, or anything else</li>
<li><strong>Your memory files</strong> are in the workspace:
<ul>
<li><code>MEMORY.md</code> - I'll remember important things here</li>
<li><code>SOUL.md</code> - Customize my personality and behavior</li>
<li><code>HEARTBEAT.md</code> - Tasks for autonomous mode</li>
</ul>
</li>
</ol>
<h3>Tell Me About Yourself</h3>
<p>What's your name? What kind of projects do you work on? Any preferences for how I should communicate?</p>
<p><em>I'll save what I learn to MEMORY.md so I remember it next time.</em></p>
</div>
`;
}
}

function clearEmptyState() {
const emptyState = document.querySelector('.empty-state');
if (emptyState) {
Expand Down Expand Up @@ -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);
}
Expand Down