mirror of
https://github.com/techforces-ai/Cial.git
synced 2026-05-15 20:14:11 +00:00
feat(core-ui): Claude Code icon in harness dropdown, drop credentials badge
- ConfigPicker harness dropdown rows now render the Claude Code mark as a leading icon (PickerRow gained an optional `icon` prop). - Removed the Anthropic credentials status pill from the composer chrome per request. Deleted the AnthropicStatusBadge component and its now-dead plumbing: the anthropicState/anthropicStatus props threaded ConfigPicker ← SessionView ← AppShell, and AppShell's useAnthropicStatus consumption. The hook + anthropic-status lib stay (still used by the harness settings page). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
2d64e526f7
commit
bde7a06d6d
3 changed files with 13 additions and 104 deletions
|
|
@ -21,7 +21,6 @@ import { Sidebar } from '../Sidebar/Sidebar';
|
||||||
import { SessionView } from '../SessionView/SessionView';
|
import { SessionView } from '../SessionView/SessionView';
|
||||||
import { useSessions } from '../../lib/store';
|
import { useSessions } from '../../lib/store';
|
||||||
import { useDeployStatus } from '../../lib/deploy-status';
|
import { useDeployStatus } from '../../lib/deploy-status';
|
||||||
import { useAnthropicStatus } from '../../lib/anthropic-status';
|
|
||||||
import type { SessionUser } from '../../lib/types';
|
import type { SessionUser } from '../../lib/types';
|
||||||
|
|
||||||
interface AppShellProps {
|
interface AppShellProps {
|
||||||
|
|
@ -75,7 +74,6 @@ export function AppShell({
|
||||||
});
|
});
|
||||||
|
|
||||||
const deploy = useDeployStatus(client);
|
const deploy = useDeployStatus(client);
|
||||||
const anthropic = useAnthropicStatus(client);
|
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(true);
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -135,8 +133,6 @@ export function AppShell({
|
||||||
onCancel={cancelStream}
|
onCancel={cancelStream}
|
||||||
harnessVersions={deploy.version?.harnesses ?? null}
|
harnessVersions={deploy.version?.harnesses ?? null}
|
||||||
onAnswerQuestion={answerQuestion}
|
onAnswerQuestion={answerQuestion}
|
||||||
anthropicState={anthropic.state}
|
|
||||||
anthropicStatus={anthropic.status}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import { useEffect, useMemo, useRef, type ReactNode } from 'react';
|
||||||
import { AlertTriangle, Check, ChevronDown, Cpu, Gauge } from 'lucide-react';
|
import { AlertTriangle, Check, ChevronDown, Cpu, Gauge } from 'lucide-react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { HarnessVersionInfo, RateLimitInfo } from '@cial/sdk';
|
import type { HarnessVersionInfo, RateLimitInfo } from '@cial/sdk';
|
||||||
import type { AnthropicConnectionState, AnthropicStatus } from '../../lib/anthropic-status';
|
|
||||||
import {
|
import {
|
||||||
EFFORT_OPTIONS,
|
EFFORT_OPTIONS,
|
||||||
MODEL_OPTIONS,
|
MODEL_OPTIONS,
|
||||||
|
|
@ -68,16 +67,9 @@ interface ConfigPickerProps {
|
||||||
/** Live version per registered harness (server-broadcast). Replaces the
|
/** Live version per registered harness (server-broadcast). Replaces the
|
||||||
* static `version` field in HARNESS_OPTIONS when present. */
|
* static `version` field in HARNESS_OPTIONS when present. */
|
||||||
readonly harnessVersions?: ReadonlyArray<HarnessVersionInfo> | null;
|
readonly harnessVersions?: ReadonlyArray<HarnessVersionInfo> | null;
|
||||||
/** Connection state of the calling user's Anthropic credential.
|
|
||||||
* Renders as a small pill next to the harness pill — `'unknown'`
|
|
||||||
* while the first /status fetch is in flight. */
|
|
||||||
readonly anthropicState?: AnthropicConnectionState;
|
|
||||||
/** Full status payload — lets the pill show the actual Anthropic
|
|
||||||
* account email instead of "Your account". */
|
|
||||||
readonly anthropicStatus?: AnthropicStatus | null;
|
|
||||||
/** Most recent rate-limit verdict for the active session's account.
|
/** Most recent rate-limit verdict for the active session's account.
|
||||||
* When throttled or out of overage credits, a small amber pill appears
|
* When throttled or out of overage credits, a small amber pill appears
|
||||||
* next to the Anthropic badge. */
|
* next to the harness pill. */
|
||||||
readonly rateLimit?: RateLimitInfo | null;
|
readonly rateLimit?: RateLimitInfo | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,8 +83,6 @@ export function ConfigPicker({
|
||||||
disabled,
|
disabled,
|
||||||
harnessLocked,
|
harnessLocked,
|
||||||
harnessVersions,
|
harnessVersions,
|
||||||
anthropicState,
|
|
||||||
anthropicStatus,
|
|
||||||
rateLimit,
|
rateLimit,
|
||||||
}: ConfigPickerProps) {
|
}: ConfigPickerProps) {
|
||||||
const liveVersions = useMemo(() => {
|
const liveVersions = useMemo(() => {
|
||||||
|
|
@ -124,6 +114,7 @@ export function ConfigPicker({
|
||||||
key={opt.id}
|
key={opt.id}
|
||||||
label={opt.label}
|
label={opt.label}
|
||||||
subtitle={opt.available ? v : `${v} · coming soon`}
|
subtitle={opt.available ? v : `${v} · coming soon`}
|
||||||
|
icon={<ClaudeCodeIcon className="h-3.5 w-3.5" />}
|
||||||
selected={opt.id === harness}
|
selected={opt.id === harness}
|
||||||
disabled={!opt.available || lockedOut}
|
disabled={!opt.available || lockedOut}
|
||||||
onSelect={() => onHarnessChange(opt.id)}
|
onSelect={() => onHarnessChange(opt.id)}
|
||||||
|
|
@ -131,9 +122,6 @@ export function ConfigPicker({
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</PickerMenu>
|
</PickerMenu>
|
||||||
{anthropicState ? (
|
|
||||||
<AnthropicStatusBadge state={anthropicState} status={anthropicStatus ?? null} />
|
|
||||||
) : null}
|
|
||||||
<RateLimitBadge info={rateLimit ?? null} />
|
<RateLimitBadge info={rateLimit ?? null} />
|
||||||
<PickerMenu
|
<PickerMenu
|
||||||
icon={<Cpu className="h-3 w-3" aria-hidden />}
|
icon={<Cpu className="h-3 w-3" aria-hidden />}
|
||||||
|
|
@ -234,12 +222,14 @@ function PickerMenu({ icon, label, meta, ariaLabel, disabled, title, children }:
|
||||||
interface PickerRowProps {
|
interface PickerRowProps {
|
||||||
readonly label: string;
|
readonly label: string;
|
||||||
readonly subtitle?: string;
|
readonly subtitle?: string;
|
||||||
|
/** Optional leading icon (e.g. the Claude Code mark on harness rows). */
|
||||||
|
readonly icon?: ReactNode;
|
||||||
readonly selected: boolean;
|
readonly selected: boolean;
|
||||||
readonly disabled?: boolean;
|
readonly disabled?: boolean;
|
||||||
readonly onSelect: () => void;
|
readonly onSelect: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PickerRow({ label, subtitle, selected, disabled, onSelect }: PickerRowProps) {
|
function PickerRow({ label, subtitle, icon, selected, disabled, onSelect }: PickerRowProps) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -261,11 +251,14 @@ function PickerRow({ label, subtitle, selected, disabled, onSelect }: PickerRowP
|
||||||
: 'text-[var(--t-text-primary)] hover:bg-[var(--t-surface-hover)]',
|
: 'text-[var(--t-text-primary)] hover:bg-[var(--t-surface-hover)]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="flex flex-col">
|
<span className="flex items-center gap-2">
|
||||||
<span className="font-medium leading-tight">{label}</span>
|
{icon ? <span className="shrink-0">{icon}</span> : null}
|
||||||
{subtitle ? (
|
<span className="flex flex-col">
|
||||||
<span className="text-[10.5px] text-[var(--t-text-faint)]">{subtitle}</span>
|
<span className="font-medium leading-tight">{label}</span>
|
||||||
) : null}
|
{subtitle ? (
|
||||||
|
<span className="text-[10.5px] text-[var(--t-text-faint)]">{subtitle}</span>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<Check
|
<Check
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
|
@ -278,73 +271,6 @@ function PickerRow({ label, subtitle, selected, disabled, onSelect }: PickerRowP
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────── status pill ───────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compact status pill showing whether the user has Anthropic credentials
|
|
||||||
* connected. Click → opens the harness settings page in a new tab so
|
|
||||||
* the user can connect / disconnect without losing the chat.
|
|
||||||
*
|
|
||||||
* When the active credential is OAuth, the label becomes the actual
|
|
||||||
* account email — much more informative than "Your account".
|
|
||||||
*/
|
|
||||||
function AnthropicStatusBadge({
|
|
||||||
state,
|
|
||||||
status,
|
|
||||||
}: {
|
|
||||||
state: AnthropicConnectionState;
|
|
||||||
status: AnthropicStatus | null;
|
|
||||||
}) {
|
|
||||||
if (state === 'unknown') return null;
|
|
||||||
|
|
||||||
const active =
|
|
||||||
state === 'user' ? status?.user ?? null
|
|
||||||
: state === 'instance' ? status?.instance ?? null
|
|
||||||
: null;
|
|
||||||
// Prefer the email when it's available (OAuth records carry one).
|
|
||||||
// Fall back to the masked hint, then a generic label.
|
|
||||||
const dynamicLabel =
|
|
||||||
active?.email
|
|
||||||
|| (active && active.kind === 'api_key' ? active.maskedHint : null);
|
|
||||||
|
|
||||||
const palette = {
|
|
||||||
user: {
|
|
||||||
dot: 'bg-emerald-400 shadow-[0_0_4px_rgba(52,211,153,0.6)]',
|
|
||||||
label: dynamicLabel ?? 'Your account',
|
|
||||||
title: active?.email
|
|
||||||
? `Connected as ${active.email} — used by your sessions.`
|
|
||||||
: 'Connected via your Anthropic account — used by your sessions.',
|
|
||||||
},
|
|
||||||
instance: {
|
|
||||||
dot: 'bg-emerald-400/70 shadow-[0_0_4px_rgba(52,211,153,0.45)]',
|
|
||||||
label: dynamicLabel
|
|
||||||
? `${dynamicLabel} (instance)`
|
|
||||||
: 'Instance',
|
|
||||||
title: active?.email
|
|
||||||
? `Falling back to the instance default (${active.email}) — connect your own to bill yourself.`
|
|
||||||
: 'Falling back to the instance default — connect your own to bill yourself.',
|
|
||||||
},
|
|
||||||
disconnected: {
|
|
||||||
dot: 'bg-rose-400 shadow-[0_0_4px_rgba(251,113,133,0.55)]',
|
|
||||||
label: 'Connect',
|
|
||||||
title: 'No Anthropic account connected — click to set one up.',
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
const c = palette[state];
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
href="/.cial/settings/harnesses/anthropic"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
title={c.title}
|
|
||||||
className="flex max-w-[16rem] cursor-pointer items-center gap-1 rounded-full border border-[var(--t-border)] bg-[var(--t-input-bg)] px-2.5 py-1 text-[11px] tabular-nums text-[var(--t-text-muted)] transition-colors hover:border-[var(--t-border-medium)] hover:text-[var(--t-text-primary)]"
|
|
||||||
>
|
|
||||||
<span className={`inline-flex h-1.5 w-1.5 rounded-full ${c.dot}`} aria-hidden />
|
|
||||||
<span className="truncate font-medium">{c.label}</span>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────── rate-limit pill ───────────────────────────
|
// ─────────────────────────── rate-limit pill ───────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import type { ChatSession } from '../../lib/types';
|
||||||
import type { SendOptions, NewSessionOptions } from '../../lib/store';
|
import type { SendOptions, NewSessionOptions } from '../../lib/store';
|
||||||
import type { DeployStage } from '../../lib/deploy-status';
|
import type { DeployStage } from '../../lib/deploy-status';
|
||||||
import type { HarnessVersionInfo } from '@cial/sdk';
|
import type { HarnessVersionInfo } from '@cial/sdk';
|
||||||
import type { AnthropicConnectionState, AnthropicStatus } from '../../lib/anthropic-status';
|
|
||||||
import { DEFAULT_EFFORT, DEFAULT_MODEL, type Effort } from '../../lib/models';
|
import { DEFAULT_EFFORT, DEFAULT_MODEL, type Effort } from '../../lib/models';
|
||||||
import { DEFAULT_HARNESS, type SessionProvider } from '../../lib/harnesses';
|
import { DEFAULT_HARNESS, type SessionProvider } from '../../lib/harnesses';
|
||||||
import { MessageList } from '../MessageList/MessageList';
|
import { MessageList } from '../MessageList/MessageList';
|
||||||
|
|
@ -39,14 +38,6 @@ interface SessionViewProps {
|
||||||
readonly harnessVersions: ReadonlyArray<HarnessVersionInfo> | null;
|
readonly harnessVersions: ReadonlyArray<HarnessVersionInfo> | null;
|
||||||
/** Reply to the active session's pending harness question. */
|
/** Reply to the active session's pending harness question. */
|
||||||
readonly onAnswerQuestion: (content: string, questionId?: string) => void;
|
readonly onAnswerQuestion: (content: string, questionId?: string) => void;
|
||||||
/** Whether the calling user has Anthropic credentials connected (and
|
|
||||||
* via what scope). Drives a small status pill next to the harness
|
|
||||||
* picker so the user can see at a glance whether their next message
|
|
||||||
* will succeed. */
|
|
||||||
readonly anthropicState: AnthropicConnectionState;
|
|
||||||
/** Full Anthropic status payload — lets the pill show the actual
|
|
||||||
* account email when available instead of a generic "Your account". */
|
|
||||||
readonly anthropicStatus: AnthropicStatus | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SessionView({
|
export function SessionView({
|
||||||
|
|
@ -60,8 +51,6 @@ export function SessionView({
|
||||||
onCancel,
|
onCancel,
|
||||||
harnessVersions,
|
harnessVersions,
|
||||||
onAnswerQuestion,
|
onAnswerQuestion,
|
||||||
anthropicState,
|
|
||||||
anthropicStatus,
|
|
||||||
}: SessionViewProps) {
|
}: SessionViewProps) {
|
||||||
const [draft, setDraft] = useState('');
|
const [draft, setDraft] = useState('');
|
||||||
const [harness, setHarness] = useState<SessionProvider>(DEFAULT_HARNESS);
|
const [harness, setHarness] = useState<SessionProvider>(DEFAULT_HARNESS);
|
||||||
|
|
@ -216,8 +205,6 @@ export function SessionView({
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
harnessLocked={!!active}
|
harnessLocked={!!active}
|
||||||
harnessVersions={harnessVersions}
|
harnessVersions={harnessVersions}
|
||||||
anthropicState={anthropicState}
|
|
||||||
anthropicStatus={anthropicStatus}
|
|
||||||
rateLimit={active?.rateLimit ?? null}
|
rateLimit={active?.rateLimit ?? null}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue