Add agent mode visuals (icons and color tiers) to UI components
This commit is contained in:
@@ -39,6 +39,7 @@ const MODE_COLOR_MAP: Record<string, string> = {
|
|||||||
safe: baseColors.green[500],
|
safe: baseColors.green[500],
|
||||||
moderate: baseColors.amber[500],
|
moderate: baseColors.amber[500],
|
||||||
dangerous: baseColors.red[500],
|
dangerous: baseColors.red[500],
|
||||||
|
readonly: baseColors.purple[500],
|
||||||
};
|
};
|
||||||
|
|
||||||
type DropdownTriggerRenderProps = {
|
type DropdownTriggerRenderProps = {
|
||||||
|
|||||||
@@ -720,8 +720,9 @@ export function AgentInputArea({
|
|||||||
disabled={!isConnected || voice?.isVoiceSwitching}
|
disabled={!isConnected || voice?.isVoiceSwitching}
|
||||||
accessibilityLabel="Enable Voice mode"
|
accessibilityLabel="Enable Voice mode"
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
style={[
|
style={({ hovered }) => [
|
||||||
styles.realtimeVoiceButton as any,
|
styles.realtimeVoiceButton as any,
|
||||||
|
(hovered ? styles.iconButtonHovered : undefined) as any,
|
||||||
(!isConnected || voice?.isVoiceSwitching ? styles.buttonDisabled : undefined) as any,
|
(!isConnected || voice?.isVoiceSwitching ? styles.buttonDisabled : undefined) as any,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -901,9 +902,6 @@ const styles = StyleSheet.create(((theme: Theme) => ({
|
|||||||
width: 28,
|
width: 28,
|
||||||
height: 28,
|
height: 28,
|
||||||
borderRadius: theme.borderRadius.full,
|
borderRadius: theme.borderRadius.full,
|
||||||
backgroundColor: theme.colors.surface0,
|
|
||||||
borderWidth: theme.borderWidth[1],
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
@@ -911,6 +909,9 @@ const styles = StyleSheet.create(((theme: Theme) => ({
|
|||||||
backgroundColor: theme.colors.palette.green[600],
|
backgroundColor: theme.colors.palette.green[600],
|
||||||
borderColor: theme.colors.palette.green[800],
|
borderColor: theme.colors.palette.green[800],
|
||||||
},
|
},
|
||||||
|
iconButtonHovered: {
|
||||||
|
backgroundColor: theme.colors.surface2,
|
||||||
|
},
|
||||||
tooltipRow: {
|
tooltipRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useCallback, useMemo, useRef, useState } from 'react'
|
|||||||
import { View, Text, Platform, Pressable } from 'react-native'
|
import { View, Text, Platform, Pressable } from 'react-native'
|
||||||
import { StyleSheet, useUnistyles } from 'react-native-unistyles'
|
import { StyleSheet, useUnistyles } from 'react-native-unistyles'
|
||||||
import {
|
import {
|
||||||
|
Bot,
|
||||||
Brain,
|
Brain,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ShieldAlert,
|
ShieldAlert,
|
||||||
@@ -9,6 +10,8 @@ import {
|
|||||||
ShieldOff,
|
ShieldOff,
|
||||||
SlidersHorizontal,
|
SlidersHorizontal,
|
||||||
} from 'lucide-react-native'
|
} from 'lucide-react-native'
|
||||||
|
import { ClaudeIcon } from '@/components/icons/claude-icon'
|
||||||
|
import { CodexIcon } from '@/components/icons/codex-icon'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { useSessionStore } from '@/stores/session-store'
|
import { useSessionStore } from '@/stores/session-store'
|
||||||
import {
|
import {
|
||||||
@@ -91,9 +94,18 @@ const MODE_ICONS = {
|
|||||||
ShieldOff,
|
ShieldOff,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
const PROVIDER_ICONS: Record<string, typeof Bot> = {
|
||||||
|
claude: ClaudeIcon as unknown as typeof Bot,
|
||||||
|
codex: CodexIcon as unknown as typeof Bot,
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProviderIcon(provider: string): typeof Bot {
|
||||||
|
return PROVIDER_ICONS[provider] ?? Bot
|
||||||
|
}
|
||||||
|
|
||||||
function getModeIconColor(
|
function getModeIconColor(
|
||||||
colorTier: AgentModeColorTier | undefined,
|
colorTier: AgentModeColorTier | undefined,
|
||||||
palette: { green: { 500: string }; amber: { 500: string }; red: { 500: string } }
|
palette: { green: { 500: string }; amber: { 500: string }; red: { 500: string }; purple: { 500: string } }
|
||||||
): string {
|
): string {
|
||||||
switch (colorTier) {
|
switch (colorTier) {
|
||||||
case 'safe':
|
case 'safe':
|
||||||
@@ -102,6 +114,8 @@ function getModeIconColor(
|
|||||||
return palette.amber[500]
|
return palette.amber[500]
|
||||||
case 'dangerous':
|
case 'dangerous':
|
||||||
return palette.red[500]
|
return palette.red[500]
|
||||||
|
case 'readonly':
|
||||||
|
return palette.purple[500]
|
||||||
default:
|
default:
|
||||||
return palette.green[500]
|
return palette.green[500]
|
||||||
}
|
}
|
||||||
@@ -153,6 +167,7 @@ function ControlledStatusBar({
|
|||||||
const modeVisuals = selectedModeId ? getModeVisuals(provider, selectedModeId) : undefined
|
const modeVisuals = selectedModeId ? getModeVisuals(provider, selectedModeId) : undefined
|
||||||
const ModeIconComponent = modeVisuals?.icon ? MODE_ICONS[modeVisuals.icon] : null
|
const ModeIconComponent = modeVisuals?.icon ? MODE_ICONS[modeVisuals.icon] : null
|
||||||
const modeIconColor = getModeIconColor(modeVisuals?.colorTier, theme.colors.palette)
|
const modeIconColor = getModeIconColor(modeVisuals?.colorTier, theme.colors.palette)
|
||||||
|
const ProviderIcon = getProviderIcon(provider)
|
||||||
|
|
||||||
const hasAnyControl =
|
const hasAnyControl =
|
||||||
Boolean(providerOptions?.length) ||
|
Boolean(providerOptions?.length) ||
|
||||||
@@ -210,7 +225,7 @@ function ControlledStatusBar({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, isWeb && { marginBottom: -theme.spacing[1] }]}>
|
<View style={styles.container}>
|
||||||
{isWeb ? (
|
{isWeb ? (
|
||||||
<>
|
<>
|
||||||
{providerOptions && providerOptions.length > 0 ? (
|
{providerOptions && providerOptions.length > 0 ? (
|
||||||
@@ -298,6 +313,7 @@ function ControlledStatusBar({
|
|||||||
accessibilityLabel="Select agent model"
|
accessibilityLabel="Select agent model"
|
||||||
testID="agent-model-selector"
|
testID="agent-model-selector"
|
||||||
>
|
>
|
||||||
|
<ProviderIcon size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||||
<Text style={styles.modeBadgeText}>{displayModel}</Text>
|
<Text style={styles.modeBadgeText}>{displayModel}</Text>
|
||||||
<ChevronDown size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
<ChevronDown size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
@@ -326,14 +342,10 @@ function ControlledStatusBar({
|
|||||||
(disabled || !canSelectThinking) && styles.disabledBadge,
|
(disabled || !canSelectThinking) && styles.disabledBadge,
|
||||||
]}
|
]}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel="Select thinking option"
|
accessibilityLabel={`Select thinking option (${displayThinking})`}
|
||||||
testID="agent-thinking-selector"
|
testID="agent-thinking-selector"
|
||||||
>
|
>
|
||||||
<Brain
|
<Brain size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||||
size={theme.iconSize.xs}
|
|
||||||
color={theme.colors.foregroundMuted}
|
|
||||||
style={{ marginTop: 1 }}
|
|
||||||
/>
|
|
||||||
<Text style={styles.modeBadgeText}>{displayThinking}</Text>
|
<Text style={styles.modeBadgeText}>{displayThinking}</Text>
|
||||||
<ChevronDown size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
<ChevronDown size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
@@ -362,7 +374,7 @@ function ControlledStatusBar({
|
|||||||
accessibilityLabel="Agent preferences"
|
accessibilityLabel="Agent preferences"
|
||||||
testID="agent-preferences-button"
|
testID="agent-preferences-button"
|
||||||
>
|
>
|
||||||
<SlidersHorizontal size={theme.iconSize.lg} color={theme.colors.foreground} />
|
<SlidersHorizontal size={theme.iconSize.md} color={theme.colors.foreground} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
||||||
<AdaptiveModalSheet
|
<AdaptiveModalSheet
|
||||||
@@ -688,7 +700,7 @@ export function DraftAgentStatusBar({
|
|||||||
const styles = StyleSheet.create((theme) => ({
|
const styles = StyleSheet.create((theme) => ({
|
||||||
container: {
|
container: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'flex-end',
|
||||||
gap: theme.spacing[1],
|
gap: theme.spacing[1],
|
||||||
},
|
},
|
||||||
modeBadge: {
|
modeBadge: {
|
||||||
@@ -701,10 +713,11 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
borderRadius: theme.borderRadius['2xl'],
|
borderRadius: theme.borderRadius['2xl'],
|
||||||
},
|
},
|
||||||
modeIconBadge: {
|
modeIconBadge: {
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
padding: theme.spacing[1],
|
|
||||||
borderRadius: theme.borderRadius.full,
|
borderRadius: theme.borderRadius.full,
|
||||||
},
|
},
|
||||||
modeBadgeHovered: {
|
modeBadgeHovered: {
|
||||||
@@ -722,8 +735,8 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
fontWeight: theme.fontWeight.normal,
|
fontWeight: theme.fontWeight.normal,
|
||||||
},
|
},
|
||||||
prefsButton: {
|
prefsButton: {
|
||||||
width: 34,
|
width: 28,
|
||||||
height: 34,
|
height: 28,
|
||||||
borderRadius: theme.borderRadius.full,
|
borderRadius: theme.borderRadius.full,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
|||||||
@@ -944,7 +944,11 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
|||||||
disabled={!isConnected || disabled}
|
disabled={!isConnected || disabled}
|
||||||
accessibilityLabel="Attach images"
|
accessibilityLabel="Attach images"
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
style={[styles.attachButton, (!isConnected || disabled) && styles.buttonDisabled]}
|
style={({ hovered }) => [
|
||||||
|
styles.attachButton,
|
||||||
|
hovered && styles.iconButtonHovered,
|
||||||
|
(!isConnected || disabled) && styles.buttonDisabled,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Paperclip size={theme.iconSize.md} color={theme.colors.foreground} />
|
<Paperclip size={theme.iconSize.md} color={theme.colors.foreground} />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@@ -972,8 +976,9 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
|||||||
? 'Stop dictation'
|
? 'Stop dictation'
|
||||||
: 'Start dictation'
|
: 'Start dictation'
|
||||||
}
|
}
|
||||||
style={[
|
style={({ hovered }) => [
|
||||||
styles.voiceButton,
|
styles.voiceButton,
|
||||||
|
hovered && !isDictating && styles.iconButtonHovered,
|
||||||
(!isDictationStartEnabled) && styles.buttonDisabled,
|
(!isDictationStartEnabled) && styles.buttonDisabled,
|
||||||
isDictating && styles.voiceButtonRecording,
|
isDictating && styles.voiceButtonRecording,
|
||||||
]}
|
]}
|
||||||
@@ -1010,7 +1015,11 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
|||||||
disabled={!isConnected || disabled}
|
disabled={!isConnected || disabled}
|
||||||
accessibilityLabel="Queue message"
|
accessibilityLabel="Queue message"
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
style={[styles.queueButton, (!isConnected || disabled) && styles.buttonDisabled]}
|
style={({ hovered }) => [
|
||||||
|
styles.queueButton,
|
||||||
|
hovered && styles.iconButtonHovered,
|
||||||
|
(!isConnected || disabled) && styles.buttonDisabled,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Plus size={theme.iconSize.md} color="white" />
|
<Plus size={theme.iconSize.md} color="white" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@@ -1174,7 +1183,7 @@ const styles = StyleSheet.create(((theme: any) => ({
|
|||||||
},
|
},
|
||||||
leftButtonGroup: {
|
leftButtonGroup: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'flex-end',
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
rightButtonGroup: {
|
rightButtonGroup: {
|
||||||
@@ -1215,6 +1224,9 @@ const styles = StyleSheet.create(((theme: any) => ({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
|
iconButtonHovered: {
|
||||||
|
backgroundColor: theme.colors.surface2,
|
||||||
|
},
|
||||||
tooltipRow: {
|
tooltipRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
@@ -145,9 +145,8 @@ function TabChip({
|
|||||||
style={({ hovered, pressed }) => [
|
style={({ hovered, pressed }) => [
|
||||||
styles.tab,
|
styles.tab,
|
||||||
Platform.OS === "web" &&
|
Platform.OS === "web" &&
|
||||||
({
|
isDragging &&
|
||||||
cursor: isDragging ? "grabbing" : "grab",
|
({ cursor: "grabbing" } as const),
|
||||||
} as const),
|
|
||||||
{
|
{
|
||||||
minWidth: resolvedTabWidth,
|
minWidth: resolvedTabWidth,
|
||||||
width: resolvedTabWidth,
|
width: resolvedTabWidth,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import type { AgentMode } from "./agent-sdk-types.js";
|
import type { AgentMode } from "./agent-sdk-types.js";
|
||||||
|
|
||||||
export type AgentModeColorTier = 'safe' | 'moderate' | 'dangerous';
|
export type AgentModeColorTier = 'safe' | 'moderate' | 'dangerous' | 'readonly';
|
||||||
export type AgentModeIcon = 'ShieldCheck' | 'ShieldAlert' | 'ShieldOff';
|
export type AgentModeIcon = 'ShieldCheck' | 'ShieldAlert' | 'ShieldOff';
|
||||||
|
|
||||||
export interface AgentModeVisuals {
|
export interface AgentModeVisuals {
|
||||||
@@ -27,19 +27,19 @@ export interface AgentProviderDefinition {
|
|||||||
const CLAUDE_MODES: AgentProviderModeDefinition[] = [
|
const CLAUDE_MODES: AgentProviderModeDefinition[] = [
|
||||||
{ id: "default", label: "Always Ask", description: "Prompts for permission the first time a tool is used", icon: "ShieldCheck", colorTier: "safe" },
|
{ id: "default", label: "Always Ask", description: "Prompts for permission the first time a tool is used", icon: "ShieldCheck", colorTier: "safe" },
|
||||||
{ id: "acceptEdits", label: "Accept File Edits", description: "Automatically approves edit-focused tools without prompting", icon: "ShieldAlert", colorTier: "moderate" },
|
{ id: "acceptEdits", label: "Accept File Edits", description: "Automatically approves edit-focused tools without prompting", icon: "ShieldAlert", colorTier: "moderate" },
|
||||||
{ id: "plan", label: "Plan Mode", description: "Analyze the codebase without executing tools or edits", icon: "ShieldCheck", colorTier: "safe" },
|
{ id: "plan", label: "Plan Mode", description: "Analyze the codebase without executing tools or edits", icon: "ShieldCheck", colorTier: "readonly" },
|
||||||
{ id: "bypassPermissions", label: "Bypass", description: "Skip all permission prompts (use with caution)", icon: "ShieldOff", colorTier: "dangerous" },
|
{ id: "bypassPermissions", label: "Bypass", description: "Skip all permission prompts (use with caution)", icon: "ShieldAlert", colorTier: "dangerous" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const CODEX_MODES: AgentProviderModeDefinition[] = [
|
const CODEX_MODES: AgentProviderModeDefinition[] = [
|
||||||
{ id: "read-only", label: "Read Only", description: "Read files and answer questions. Manual approval required for edits, commands, or network ops.", icon: "ShieldCheck", colorTier: "safe" },
|
{ id: "read-only", label: "Read Only", description: "Read files and answer questions. Manual approval required for edits, commands, or network ops.", icon: "ShieldCheck", colorTier: "readonly" },
|
||||||
{ id: "auto", label: "Auto", description: "Edit files and run commands but still request approval before escalating scope.", icon: "ShieldAlert", colorTier: "moderate" },
|
{ id: "auto", label: "Auto", description: "Edit files and run commands but still request approval before escalating scope.", icon: "ShieldAlert", colorTier: "moderate" },
|
||||||
{ id: "full-access", label: "Full Access", description: "Edit files, run commands, and access the network without additional prompts.", icon: "ShieldOff", colorTier: "dangerous" },
|
{ id: "full-access", label: "Full Access", description: "Edit files, run commands, and access the network without additional prompts.", icon: "ShieldAlert", colorTier: "dangerous" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const OPENCODE_MODES: AgentProviderModeDefinition[] = [
|
const OPENCODE_MODES: AgentProviderModeDefinition[] = [
|
||||||
{ id: "build", label: "Build", description: "Allows edits and tool execution for implementation work", icon: "ShieldAlert", colorTier: "moderate" },
|
{ id: "build", label: "Build", description: "Allows edits and tool execution for implementation work", icon: "ShieldAlert", colorTier: "moderate" },
|
||||||
{ id: "plan", label: "Plan", description: "Read-only planning mode that avoids file edits", icon: "ShieldCheck", colorTier: "safe" },
|
{ id: "plan", label: "Plan", description: "Read-only planning mode that avoids file edits", icon: "ShieldCheck", colorTier: "readonly" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const AGENT_PROVIDER_DEFINITIONS: AgentProviderDefinition[] = [
|
export const AGENT_PROVIDER_DEFINITIONS: AgentProviderDefinition[] = [
|
||||||
|
|||||||
Reference in New Issue
Block a user