diff --git a/README.md b/README.md index f50a4ae8..e0ccef86 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # mu -A personal app platform — blog, chat, news, mail, video and more. +Your personal AI — news, mail, markets, weather, search and more, all through one interface. No ads. No tracking. No algorithm. ## Overview -The current tech ecosystem is totally broken. All the app platform are filled with ads and addictive content. I'm tired of it. You probably are too. -So here's a personal app platform. Blog, chat, news, mail, video and more. No ads, no tracking, no algorithms. Built in the open. It's called Mu for short. +Mu is a personal AI platform. Ask it anything — it checks your mail, looks up prices, searches the web, reads the news, and gives you a personalised answer. Every service is a tool the AI can use on your behalf. + +Built in the open. Pay for the tools, not with your attention. ### What's included diff --git a/agent/run.go b/agent/run.go index b511bc5b..bcb4f281 100644 --- a/agent/run.go +++ b/agent/run.go @@ -13,7 +13,11 @@ import ( "mu/internal/auth" ) -// RunRequest is the input for the synchronous agent endpoint. +// UserContextFunc is set by main.go to provide personalised context +// for the agent's responses. Returns a string with the user's current +// state (unread mail, market prices, etc.) that gets injected into the +// synthesis prompt. +var UserContextFunc func(accountID string) string type RunRequest struct { Prompt string `json:"prompt"` Model string `json:"model"` @@ -130,11 +134,19 @@ func RunHandler(w http.ResponseWriter, r *http.Request) { toolsUsed = append(toolsUsed, ToolUsed{Name: tc.Tool, Status: "ok"}) } - // Step 3: Synthesise + // Step 3: Synthesise with user context. today := time.Now().UTC().Format("Monday, 2 January 2006 (UTC)") + userCtx := "" + if UserContextFunc != nil { + userCtx = UserContextFunc(acc.ID) + } + synthSystem := "You are Micro, a personal AI assistant. Today is " + today + ". " + + "Answer concisely using the tool results below. Use markdown." + if userCtx != "" { + synthSystem += "\n\nUser context:\n" + userCtx + } answer, err := ai.Ask(&ai.Prompt{ - System: "You are a helpful assistant. Today's date is " + today + ". " + - "Answer using ONLY the tool results below. Use markdown.", + System: synthSystem, Rag: ragParts, Question: req.Prompt, Priority: ai.PriorityHigh, diff --git a/home/home.go b/home/home.go index c5cfd399..4d2f925c 100644 --- a/home/home.go +++ b/home/home.go @@ -446,22 +446,22 @@ function fetchW(la,lo){ // ── Cards (always visible) ── b.WriteString(`
`) - b.WriteString(dateHTML) - // Console prompt — inline at the top, before cards. Claude-style - // rounded textarea with send button inside. + // AI prompt — the primary interface. First thing on screen. if viewerID != "" { b.WriteString(fmt.Sprintf(` -
+
- - + +
- +
`, stream.MaxContentLength)) b.WriteString(consoleScript) } + b.WriteString(dateHTML) + // Inline card preferences panel if viewerAcc != nil { allCardDefs := []struct{ id, label string }{ diff --git a/internal/ai/providers.go b/internal/ai/providers.go index f18fddfa..421be11f 100644 --- a/internal/ai/providers.go +++ b/internal/ai/providers.go @@ -28,9 +28,20 @@ var ( cacheReadTokens int cacheCreationTokens int - // Atlas Cloud config - atlasAPIKey = os.Getenv("ATLAS_API_KEY") - atlasBaseURL = "https://api.atlascloud.ai/v1" + // Atlas Cloud / OpenAI-compatible config. + // Set OPENAI_BASE_URL to use a local model server (Ollama, llama.cpp, etc.) + atlasAPIKey = func() string { + if v := os.Getenv("ATLAS_API_KEY"); v != "" { + return v + } + return os.Getenv("OPENAI_API_KEY") + }() + atlasBaseURL = func() string { + if v := os.Getenv("OPENAI_BASE_URL"); v != "" { + return strings.TrimRight(v, "/") + } + return "https://api.atlascloud.ai/v1" + }() ) // Atlas Cloud model aliases — used to route requests to Atlas Cloud diff --git a/main.go b/main.go index ad11219b..b55e47ad 100644 --- a/main.go +++ b/main.go @@ -119,6 +119,24 @@ func main() { // load agent agent.Load() + // Wire user context into the agent — personalises responses. + agent.UserContextFunc = func(accountID string) string { + var parts []string + // Unread mail count. + if unread := mail.GetUnreadCount(accountID); unread > 0 { + parts = append(parts, fmt.Sprintf("- %d unread email(s)", unread)) + } + // Wallet balance. + bal := wallet.GetBalance(accountID) + if bal > 0 { + parts = append(parts, fmt.Sprintf("- Wallet: %d credits", bal)) + } + if len(parts) == 0 { + return "" + } + return strings.Join(parts, "\n") + } + // Wire digest → blog callbacks (digest publishes as blog post) digest.PublishBlogPost = func(title, content, author, authorID, tags string) (string, error) { err := blog.CreatePost(title, content, author, authorID, tags, false)