-
-
Notifications
You must be signed in to change notification settings - Fork 18
fix: CORS + Vite host filter + Claude API Key labels for Pi LAN access #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| # Pi Profile — low-intensity AEVP mode | ||
|
|
||
| The ArgentOS dashboard's AEVP (Argent Emotional Visualization Presence) | ||
| renders the orb and particles via WebGL at the browser's native refresh | ||
| rate. On a Raspberry Pi 5 with software rendering (or a Hailo-free | ||
| display), the 180-particle default + 60+ fps loop can drive the CPU | ||
| above 70% and saturate the GPU queue. | ||
|
|
||
| **Pi Profile** is an opt-in rendering mode that reduces the per-frame | ||
| cost without changing any identity preset or personality modulation. | ||
| The orb still breathes, still colour-shifts, still emits particles on | ||
| tool events — it just costs ~60% less to draw. | ||
|
|
||
| ## What it changes | ||
|
|
||
| | Lever | Default | Pi profile | Source of truth | | ||
| |---|---|---|---| | ||
| | Particle cap (runtime) | up to `180` | `60` | `aevp/colorMapping.ts` via `getMaxParticlesCap()` | | ||
| | Density scale | `1.0 ×` | `0.5 ×` | `aevp/colorMapping.ts` via `getDensityScale()` | | ||
| | Render tick interval | `0 ms` (rAF) | `33 ms` (~30 fps) | `aevp/renderer.ts` via `getFrameIntervalMs()` | | ||
|
|
||
| The hard allocation (`MAX_PARTICLES = 180` in `aevp/particles.ts`) is | ||
| unchanged — the buffer still holds room for 180 — so the profile can | ||
| be flipped on and off without a reload or re-alloc. | ||
|
Comment on lines
+22
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Docs conflict on reload behavior — tighten this up. Lines 22-24 say no reload is needed, but Lines 28-29 and 38 say activation is computed once and requires reload. Pick one behavior and state it consistently. Also applies to: 28-29, 38-38 🤖 Prompt for AI Agents |
||
|
|
||
| ## How to enable | ||
|
|
||
| Any one of these signals activates the profile. The check runs once | ||
| at module load. | ||
|
|
||
| | Method | Where | When to use | | ||
| |---|---|---| | ||
| | `PI_PROFILE=1` env var | Node / SSR | Build-time or Electron wrappers | | ||
| | `VITE_PI_PROFILE=1` env var | Vite dev/build | `.env.local` in dashboard dev | | ||
| | `localStorage.setItem("argent.piProfile", "1")` | Browser console | Runtime toggle, survives reload | | ||
| | `?piProfile=1` URL param | Any | One-shot test without touching storage | | ||
|
|
||
| To disable, unset / remove / set to `"0"`, and reload. | ||
|
|
||
| ## How to verify | ||
|
|
||
| 1. Open the dashboard with the profile active. | ||
| 2. Open dev tools → Performance → record ~5 seconds. | ||
| 3. Expect: ~30 fps main thread, GPU frame time below 8 ms, particle | ||
| count ≤60 in the `drawArrays(POINTS, ...)` call in `renderer.ts`. | ||
| 4. Compare against a baseline tab without the profile. | ||
|
|
||
| ## What it does NOT change | ||
|
|
||
| - Identity presets in `identityPresets.ts` — still emitted as-is. | ||
| - Personality modulation (warmth, energy, openness, formality) — still | ||
| applied in full. | ||
| - Morning particles and evening fireflies (separate components) — mount | ||
| them conditionally in app code if those also need to drop on Pi. | ||
| - Orb bloom / glow post-processing — future cut, currently always on. | ||
|
|
||
| ## Future cuts | ||
|
|
||
| - Gate bloom FBO pass behind the profile (skip `bloomFBO` render). | ||
| - Lower the shader precision on the particle fragment shader to | ||
| `mediump` when the profile is active. | ||
| - Expose an in-dashboard toggle under a "Performance" tab. | ||
|
|
||
| ## References | ||
|
|
||
| - `dashboard/src/aevp/pi-profile.ts` — single source of truth. | ||
| - `dashboard/src/aevp/colorMapping.ts:448` — cap + scale call sites. | ||
| - `dashboard/src/aevp/renderer.ts:269` — frame gate. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| import type { EmotionalState, ActivityStateName } from "../types/agentState"; | ||
| import type { AgentVisualIdentity, AEVPRenderState } from "./types"; | ||
| import { classifyTool, getResonanceTargets } from "./toolCategories"; | ||
| import { getMaxParticlesCap, getDensityScale } from "./pi-profile"; | ||
|
|
||
| // ── Helpers ──────────────────────────────────────────────────────────────── | ||
|
|
||
|
|
@@ -445,7 +446,13 @@ export function computeRenderState( | |
| 2.5, | ||
| ); | ||
|
|
||
| const maxParticles = Math.round(100 * presence.particleDensity); | ||
| // Pi profile: scale density + apply a hard cap on top of identity preset. | ||
| const piCap = getMaxParticlesCap(); | ||
| const piScale = getDensityScale(); | ||
| const maxParticles = Math.min( | ||
| piCap, | ||
| Math.round(100 * presence.particleDensity * piScale), | ||
| ); | ||
|
Comment on lines
+449
to
+455
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pi hard cap can still be exceeded after this block. Nice cap calculation here, but Line 486 later clamps to 🔧 Cap-preserving fix- particleCount = Math.round(clamp(particleCount * (0.7 + p.energy * 0.6), 3, 100));
+ particleCount = Math.round(clamp(particleCount * (0.7 + p.energy * 0.6), 3, maxParticles));🤖 Prompt for AI Agents |
||
| let particleCount = Math.round( | ||
| clamp( | ||
| maxParticles * (0.3 + moodVis.brightness * 0.4 + arousal * 0.3) * energyMul, | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,95 @@ | ||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * Pi Profile — low-intensity rendering mode for the Raspberry Pi or any | ||||||||||||||||||||||||||||||||||
| * underpowered host running the ArgentOS dashboard. | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * Activation: set any ONE of | ||||||||||||||||||||||||||||||||||
| * 1. env var PI_PROFILE=1 (SSR / build time via Vite) | ||||||||||||||||||||||||||||||||||
| * 2. env var VITE_PI_PROFILE=1 (Vite convention) | ||||||||||||||||||||||||||||||||||
| * 3. localStorage "argent.piProfile" = "1" (runtime toggle, no rebuild) | ||||||||||||||||||||||||||||||||||
| * 4. URL param ?piProfile=1 (one-shot try) | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * Effect: the three AEVP perf levers all get tightened: | ||||||||||||||||||||||||||||||||||
| * - MAX particle count cap 180 → 60 (colorMapping runtime) | ||||||||||||||||||||||||||||||||||
| * - Runtime density multiplier 1.0 → 0.5 (colorMapping runtime) | ||||||||||||||||||||||||||||||||||
| * - Render tick frame interval 0 → 33ms (renderer ~30fps) | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * This keeps identity presets and personality modulation intact — the | ||||||||||||||||||||||||||||||||||
| * character of the orb is preserved, only the per-frame cost drops. | ||||||||||||||||||||||||||||||||||
| * Expected reduction: ~65% fewer GPU particle updates, ~50% fewer draw | ||||||||||||||||||||||||||||||||||
| * calls on a display running at 60+ Hz. | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| function envFlag(name: string): boolean { | ||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| // Node / SSR | ||||||||||||||||||||||||||||||||||
| const p = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process; | ||||||||||||||||||||||||||||||||||
| if (p?.env?.[name] && p.env[name] !== "0" && p.env[name] !== "") return true; | ||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||
| /* ignore */ | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| // Vite (import.meta.env is statically available only inside Vite-processed | ||||||||||||||||||||||||||||||||||
| // modules; protect with a typeof check so this file compiles under raw tsc) | ||||||||||||||||||||||||||||||||||
| const meta = (globalThis as { __ARGENT_VITE_ENV__?: Record<string, string | undefined> }) | ||||||||||||||||||||||||||||||||||
| .__ARGENT_VITE_ENV__; | ||||||||||||||||||||||||||||||||||
| if (meta?.[name] && meta[name] !== "0" && meta[name] !== "") return true; | ||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Searching for __ARGENT_VITE_ENV__ definitions/usages..."
rg -n --hidden -C 2 '__ARGENT_VITE_ENV__|defineProperty\(.+__ARGENT_VITE_ENV__|window\.__ARGENT_VITE_ENV__|globalThis\.__ARGENT_VITE_ENV__'
echo
echo "Searching for VITE_PI_PROFILE usage patterns..."
rg -n --hidden -C 2 'VITE_PI_PROFILE|import\.meta\.env'Repository: ArgentAIOS/argentos-core Length of output: 5494
Lines 33–35 reach for Fix it to use 🔧 Correct env read- const meta = (globalThis as { __ARGENT_VITE_ENV__?: Record<string, string | undefined> })
- .__ARGENT_VITE_ENV__;
- if (meta?.[name] && meta[name] !== "0" && meta[name] !== "") return true;
+ const viteEnv =
+ typeof import.meta !== "undefined"
+ ? ((import.meta as { env?: Record<string, string | undefined> }).env ?? undefined)
+ : undefined;
+ if (viteEnv?.[name] && viteEnv[name] !== "0" && viteEnv[name] !== "") return true;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| /* ignore */ | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| function localStorageFlag(key: string): boolean { | ||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| const ls = (globalThis as { localStorage?: { getItem(k: string): string | null } }).localStorage; | ||||||||||||||||||||||||||||||||||
| const v = ls?.getItem(key); | ||||||||||||||||||||||||||||||||||
| return v === "1" || v === "true"; | ||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| function urlParamFlag(key: string): boolean { | ||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| const loc = (globalThis as { location?: { search?: string } }).location; | ||||||||||||||||||||||||||||||||||
| if (!loc?.search) return false; | ||||||||||||||||||||||||||||||||||
| const params = new URLSearchParams(loc.search); | ||||||||||||||||||||||||||||||||||
| const v = params.get(key); | ||||||||||||||||||||||||||||||||||
| return v === "1" || v === "true"; | ||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** True if any Pi-profile signal is active. Computed once at module load. */ | ||||||||||||||||||||||||||||||||||
| export const PI_PROFILE_ACTIVE: boolean = | ||||||||||||||||||||||||||||||||||
| envFlag("PI_PROFILE") || | ||||||||||||||||||||||||||||||||||
| envFlag("VITE_PI_PROFILE") || | ||||||||||||||||||||||||||||||||||
| localStorageFlag("argent.piProfile") || | ||||||||||||||||||||||||||||||||||
| urlParamFlag("piProfile"); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * Hard runtime cap on active particle count. Colour mapping applies this | ||||||||||||||||||||||||||||||||||
| * in addition to identity preset density so low-end hardware gets a | ||||||||||||||||||||||||||||||||||
| * predictable ceiling regardless of mood. | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| export function getMaxParticlesCap(): number { | ||||||||||||||||||||||||||||||||||
| return PI_PROFILE_ACTIVE ? 60 : 180; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * Multiplicative scale applied to presence.particleDensity at runtime. | ||||||||||||||||||||||||||||||||||
| * Identity presets still express themselves (relative density is preserved), | ||||||||||||||||||||||||||||||||||
| * but the absolute count lands lower. | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| export function getDensityScale(): number { | ||||||||||||||||||||||||||||||||||
| return PI_PROFILE_ACTIVE ? 0.5 : 1.0; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * Minimum milliseconds between tick renders. Zero means no gate (use | ||||||||||||||||||||||||||||||||||
| * requestAnimationFrame cadence). 33 ≈ 30 fps, 50 = 20 fps. | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| export function getFrameIntervalMs(): number { | ||||||||||||||||||||||||||||||||||
| return PI_PROFILE_ACTIVE ? 33 : 0; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -357,13 +357,12 @@ export function SetupWizard({ isOpen, onComplete }: SetupWizardProps) { | |
| className={`w-5 h-5 ${authType === "setup-token" ? "text-amber-400" : "text-white/40"}`} | ||
| /> | ||
| <div> | ||
| <div className="text-white font-medium">Claude Setup Token</div> | ||
| <div className="text-white font-medium">Claude API Key</div> | ||
| <div className="text-white/40 text-xs mt-0.5"> | ||
| From Anthropic Max subscription. Run{" "} | ||
| From{" "} | ||
| <code className="text-amber-400/70 bg-white/5 px-1 rounded"> | ||
| claude setup-token | ||
| </code>{" "} | ||
| in terminal. | ||
| console.anthropic.com/settings/keys | ||
| </code> | ||
|
Comment on lines
+360
to
+365
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UI says “API Key,” but this path still behaves like legacy setup-token flow. Lines 360/490/504/669 rebrand 🔧 Minimal alignment fix- setAuthType("setup-token");
+ setAuthType("api-key");- {authType === "setup-token"
- ? "Claude API Key"
+ {authType === "api-key"
+ ? "Claude API Key"
: authType === "minimax-key"Also applies to: 489-505, 669-670 🤖 Prompt for AI Agents |
||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
@@ -488,7 +487,7 @@ export function SetupWizard({ isOpen, onComplete }: SetupWizardProps) { | |
| <div> | ||
| <label className="text-white/60 text-xs font-medium block mb-1"> | ||
| {authType === "setup-token" | ||
| ? "Setup Token" | ||
| ? "Claude API Key" | ||
| : authType === "minimax-key" | ||
| ? "MiniMax API Key" | ||
| : authType === "glm-key" | ||
|
|
@@ -502,7 +501,7 @@ export function SetupWizard({ isOpen, onComplete }: SetupWizardProps) { | |
| onChange={(e) => setToken(e.target.value)} | ||
| placeholder={ | ||
| authType === "setup-token" | ||
| ? "sk-ant-oat01-..." | ||
| ? "sk-ant-api03-..." | ||
| : authType === "minimax-key" | ||
| ? "sk-cp-... or eyJ..." | ||
| : authType === "glm-key" | ||
|
|
@@ -667,7 +666,7 @@ export function SetupWizard({ isOpen, onComplete }: SetupWizardProps) { | |
| authType === "skip" | ||
| ? "Skipped (configure in Settings)" | ||
| : authType === "setup-token" | ||
| ? `Setup token saved as ${profileName}` | ||
| ? `Claude API key saved as ${profileName}` | ||
| : `API key saved as ${profileName}`; | ||
|
|
||
| const modelName = | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add
.localhostname aliases so Pi hostname access is truly covered.Very strong direction overall, but right now only
os.hostname()is allowlisted. On many Pi/LAN setups the browser origin ishttp://<hostname>.local:<port>, which can still fail CORS.Proposed patch
📝 Committable suggestion
🤖 Prompt for AI Agents