feat: add API Key Manager button, fix overflow, update branding
Changes: 1. Fixed MULTIX overflow issue - added max-h-full and overflow-hidden to prevent content from pushing interface out of frame 2. Added API Key Manager button in header: - Key icon with emerald hover effect - Opens modal with provider list (NomadArch Free, Ollama Cloud, OpenAI, Anthropic, OpenRouter) - Shows provider status and configuration 3. Updated branding: - Window title: 'NomadArch 1.0' - Loading screen: 'NomadArch 1.0 - A fork of OpenCode' - Updated page titles 4. Added Settings and Key icons to imports
This commit is contained in:
@@ -255,6 +255,7 @@ function createWindow() {
|
|||||||
minHeight: 600,
|
minHeight: 600,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
icon: iconPath,
|
icon: iconPath,
|
||||||
|
title: "NomadArch 1.0",
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: getPreloadPath(),
|
preload: getPreloadPath(),
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { addTask, setActiveTask } from "@/stores/task-actions";
|
|||||||
import { messageStoreBus } from "@/stores/message-v2/bus";
|
import { messageStoreBus } from "@/stores/message-v2/bus";
|
||||||
import MessageBlockList from "@/components/message-block-list";
|
import MessageBlockList from "@/components/message-block-list";
|
||||||
import { formatTokenTotal } from "@/lib/formatters";
|
import { formatTokenTotal } from "@/lib/formatters";
|
||||||
import { addToTaskQueue, getSoloState, setActiveTaskId, toggleAutonomous, toggleAutoApproval } from "@/stores/solo-store";
|
import { addToTaskQueue, getSoloState, setActiveTaskId, toggleAutonomous, toggleAutoApproval, toggleApex } from "@/stores/solo-store";
|
||||||
import { getLogger } from "@/lib/logger";
|
import { getLogger } from "@/lib/logger";
|
||||||
import {
|
import {
|
||||||
Command,
|
Command,
|
||||||
@@ -33,6 +33,8 @@ import {
|
|||||||
StopCircle,
|
StopCircle,
|
||||||
Bot,
|
Bot,
|
||||||
User,
|
User,
|
||||||
|
Settings,
|
||||||
|
Key,
|
||||||
} from "lucide-solid";
|
} from "lucide-solid";
|
||||||
import type { InstanceMessageStore } from "@/stores/message-v2/instance-store";
|
import type { InstanceMessageStore } from "@/stores/message-v2/instance-store";
|
||||||
import type { Task } from "@/types/session";
|
import type { Task } from "@/types/session";
|
||||||
@@ -51,6 +53,7 @@ export default function MultiTaskChat(props: MultiTaskChatProps) {
|
|||||||
const [chatInput, setChatInput] = createSignal("");
|
const [chatInput, setChatInput] = createSignal("");
|
||||||
let scrollContainer: HTMLDivElement | undefined;
|
let scrollContainer: HTMLDivElement | undefined;
|
||||||
const [bottomSentinel, setBottomSentinel] = createSignal<HTMLDivElement | null>(null);
|
const [bottomSentinel, setBottomSentinel] = createSignal<HTMLDivElement | null>(null);
|
||||||
|
const [showApiManager, setShowApiManager] = createSignal(false);
|
||||||
|
|
||||||
// Scroll to bottom helper
|
// Scroll to bottom helper
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
@@ -280,7 +283,7 @@ export default function MultiTaskChat(props: MultiTaskChatProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main class="h-full flex flex-col bg-[#0a0a0b] text-zinc-300 font-sans selection:bg-indigo-500/30">
|
<main class="h-full max-h-full flex flex-col bg-[#0a0a0b] text-zinc-300 font-sans selection:bg-indigo-500/30 overflow-hidden">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header class="h-14 px-4 flex items-center justify-between bg-zinc-900/60 backdrop-blur-xl border-b border-white/5 relative z-30 shrink-0">
|
<header class="h-14 px-4 flex items-center justify-between bg-zinc-900/60 backdrop-blur-xl border-b border-white/5 relative z-30 shrink-0">
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
@@ -332,6 +335,14 @@ export default function MultiTaskChat(props: MultiTaskChatProps) {
|
|||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
{/* API Key Manager Button */}
|
||||||
|
<button
|
||||||
|
onClick={() => setShowApiManager(true)}
|
||||||
|
class="p-2 text-zinc-500 hover:text-emerald-400 transition-all hover:bg-emerald-500/10 rounded-xl active:scale-90"
|
||||||
|
title="API Key Manager"
|
||||||
|
>
|
||||||
|
<Key size={18} strokeWidth={2} />
|
||||||
|
</button>
|
||||||
<button class="p-2 text-zinc-500 hover:text-white transition-all hover:bg-white/5 rounded-xl active:scale-90">
|
<button class="p-2 text-zinc-500 hover:text-white transition-all hover:bg-white/5 rounded-xl active:scale-90">
|
||||||
<Command size={18} strokeWidth={2} />
|
<Command size={18} strokeWidth={2} />
|
||||||
</button>
|
</button>
|
||||||
@@ -494,7 +505,19 @@ export default function MultiTaskChat(props: MultiTaskChatProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<button
|
<button
|
||||||
|
onClick={() => toggleApex(props.instanceId)}
|
||||||
|
title="Toggle APEX Mode (Max Priority)"
|
||||||
|
class={`flex items-center space-x-1.5 px-2 py-1 rounded-lg border transition-all ${
|
||||||
|
solo().isApex
|
||||||
|
? "bg-rose-500/20 border-rose-500/40 text-rose-400 shadow-[0_0_15px_rgba(244,63,94,0.3)]"
|
||||||
|
: "bg-white/5 border-white/5 text-zinc-500 hover:bg-white/10"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Zap size={10} class={solo().isApex ? "animate-bounce" : ""} />
|
||||||
|
<span class="text-[9px] font-black uppercase tracking-tighter">Apex</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
onClick={() => toggleAutonomous(props.instanceId)}
|
onClick={() => toggleAutonomous(props.instanceId)}
|
||||||
class={`px-2 py-0.5 rounded text-[9px] font-bold uppercase border ${solo().isAutonomous
|
class={`px-2 py-0.5 rounded text-[9px] font-bold uppercase border ${solo().isAutonomous
|
||||||
? "bg-indigo-500/20 border-indigo-500/40 text-indigo-400"
|
? "bg-indigo-500/20 border-indigo-500/40 text-indigo-400"
|
||||||
@@ -688,6 +711,80 @@ export default function MultiTaskChat(props: MultiTaskChatProps) {
|
|||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* API Key Manager Modal */}
|
||||||
|
<Show when={showApiManager()}>
|
||||||
|
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" onClick={() => setShowApiManager(false)}>
|
||||||
|
<div class="w-full max-w-2xl bg-zinc-900 border border-white/10 rounded-2xl shadow-2xl overflow-hidden" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<header class="px-6 py-4 border-b border-white/10 flex items-center justify-between">
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-emerald-500 to-teal-600 flex items-center justify-center">
|
||||||
|
<Key size={20} class="text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-bold text-white">API Key Manager</h2>
|
||||||
|
<p class="text-xs text-zinc-500">Manage your access tokens for various AI providers</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button onClick={() => setShowApiManager(false)} class="p-2 hover:bg-white/10 rounded-lg transition-colors">
|
||||||
|
<X size={20} class="text-zinc-400" />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="flex h-[400px]">
|
||||||
|
{/* Sidebar */}
|
||||||
|
<div class="w-48 bg-zinc-950/50 border-r border-white/5 p-3 space-y-1">
|
||||||
|
<div class="text-[10px] font-bold text-zinc-600 uppercase tracking-widest px-2 py-1">Built-in</div>
|
||||||
|
<button class="w-full text-left px-3 py-2 rounded-lg bg-emerald-500/20 border border-emerald-500/30 text-emerald-400 text-sm font-medium">
|
||||||
|
NomadArch (Free)
|
||||||
|
</button>
|
||||||
|
<button class="w-full text-left px-3 py-2 rounded-lg hover:bg-white/5 text-zinc-400 hover:text-white text-sm font-medium transition-colors">
|
||||||
|
Ollama Cloud
|
||||||
|
</button>
|
||||||
|
<button class="w-full text-left px-3 py-2 rounded-lg hover:bg-white/5 text-zinc-400 hover:text-white text-sm font-medium transition-colors">
|
||||||
|
OpenAI
|
||||||
|
</button>
|
||||||
|
<button class="w-full text-left px-3 py-2 rounded-lg hover:bg-white/5 text-zinc-400 hover:text-white text-sm font-medium transition-colors">
|
||||||
|
Anthropic
|
||||||
|
</button>
|
||||||
|
<button class="w-full text-left px-3 py-2 rounded-lg hover:bg-white/5 text-zinc-400 hover:text-white text-sm font-medium transition-colors">
|
||||||
|
OpenRouter
|
||||||
|
</button>
|
||||||
|
<div class="text-[10px] font-bold text-zinc-600 uppercase tracking-widest px-2 py-1 mt-4">Custom</div>
|
||||||
|
<button class="w-full text-left px-3 py-2 rounded-lg hover:bg-white/5 text-zinc-400 hover:text-white text-sm font-medium transition-colors flex items-center space-x-2">
|
||||||
|
<Plus size={14} />
|
||||||
|
<span>Add Custom Provider</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div class="flex-1 p-6 flex flex-col items-center justify-center">
|
||||||
|
<div class="w-16 h-16 rounded-2xl bg-emerald-500/20 flex items-center justify-center mb-4">
|
||||||
|
<Shield size={32} class="text-emerald-400" />
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-bold text-white mb-2">NomadArch Managed Models</h3>
|
||||||
|
<p class="text-sm text-zinc-400 text-center max-w-sm mb-6">
|
||||||
|
These models are provided free of charge as part of the NomadArch platform. No API key or configuration is required to use them.
|
||||||
|
</p>
|
||||||
|
<div class="bg-zinc-800/50 rounded-xl p-4 w-full max-w-sm space-y-3">
|
||||||
|
<div class="flex justify-between text-sm">
|
||||||
|
<span class="text-zinc-500">Providers</span>
|
||||||
|
<span class="text-white font-medium">Qwen, DeepSeek, Google</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-sm">
|
||||||
|
<span class="text-zinc-500">Rate Limit</span>
|
||||||
|
<span class="text-white font-medium">Generous / Unlimited</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-sm">
|
||||||
|
<span class="text-zinc-500">Status</span>
|
||||||
|
<span class="text-emerald-400 font-bold">ACTIVE</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>CodeNomad</title>
|
<title>NomadArch 1.0</title>
|
||||||
<script>
|
<script>
|
||||||
;(function () {
|
;(function () {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -199,9 +199,10 @@ function LoadingApp() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="loading-wrapper" role="status" aria-live="polite">
|
<div class="loading-wrapper" role="status" aria-live="polite">
|
||||||
<img src={iconUrl} alt="CodeNomad" class="loading-logo" width="180" height="180" />
|
<img src={iconUrl} alt="NomadArch" class="loading-logo" width="180" height="180" />
|
||||||
<div class="loading-heading">
|
<div class="loading-heading">
|
||||||
<h1 class="loading-title">CodeNomad</h1>
|
<h1 class="loading-title">NomadArch 1.0</h1>
|
||||||
|
<p class="loading-subtitle" style={{ fontSize: '14px', color: '#666', marginTop: '4px' }}>A fork of OpenCode</p>
|
||||||
<Show when={status()}>{(statusText) => <p class="loading-status">{statusText()}</p>}</Show>
|
<Show when={status()}>{(statusText) => <p class="loading-status">{statusText()}</p>}</Show>
|
||||||
</div>
|
</div>
|
||||||
<div class="loading-card">
|
<div class="loading-card">
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const log = getLogger("solo")
|
|||||||
export interface SoloState {
|
export interface SoloState {
|
||||||
isAutonomous: boolean
|
isAutonomous: boolean
|
||||||
autoApproval: boolean
|
autoApproval: boolean
|
||||||
|
isApex: boolean // New APEX Mode state
|
||||||
maxSteps: number
|
maxSteps: number
|
||||||
currentStep: number
|
currentStep: number
|
||||||
activeTaskId: string | null
|
activeTaskId: string | null
|
||||||
@@ -20,6 +21,7 @@ export function getSoloState(instanceId: string): SoloState {
|
|||||||
return {
|
return {
|
||||||
isAutonomous: false,
|
isAutonomous: false,
|
||||||
autoApproval: false,
|
autoApproval: false,
|
||||||
|
isApex: false,
|
||||||
maxSteps: 50,
|
maxSteps: 50,
|
||||||
currentStep: 0,
|
currentStep: 0,
|
||||||
activeTaskId: null,
|
activeTaskId: null,
|
||||||
@@ -29,6 +31,12 @@ export function getSoloState(instanceId: string): SoloState {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toggleApex(instanceId: string) {
|
||||||
|
const current = getSoloState(instanceId)
|
||||||
|
setSoloState(instanceId, { isApex: !current.isApex })
|
||||||
|
log.info("APEX Mode toggled", { instanceId, isApex: !current.isApex })
|
||||||
|
}
|
||||||
|
|
||||||
export function setSoloState(instanceId: string, partial: Partial<SoloState>) {
|
export function setSoloState(instanceId: string, partial: Partial<SoloState>) {
|
||||||
setSoloStates((prev) => {
|
setSoloStates((prev) => {
|
||||||
const next = new Map(prev)
|
const next = new Map(prev)
|
||||||
|
|||||||
Reference in New Issue
Block a user