mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-14 02:17:27 +00:00
* feat: integrate Gemini AI agent provider - Core Backend: Ported gemini-cli.js and gemini-response-handler.js to establish the CLI bridge. Registered 'gemini' as an active provider within index.js. - Core Frontend: Extended QuickSettingsPanel.jsx, Settings.jsx, and AgentListItem.jsx to render the Gemini provider option, models (gemini-pro, gemini-flash, etc.), and handle OAuth states. - WebSocket Pipeline: Added support for gemini-command executions in backend and payload processing of gemini-response and gemini-error streams in useChatRealtimeHandlers.ts. Resolved JSON double-stringification and sessionId stripping issues in the transmission handler. - Platform Compatibility: Added scripts/fix-node-pty.js postinstall script and modified posix_spawnp calls with sh -c wrapper to prevent ENOEXEC and MacOS permission errors when spawning the gemini headless binary. - UX & Design: Imported official Google Gemini branding via GeminiLogo.jsx and gemini-ai-icon.svg. Updated translations (chat.json) for en, zh-CN, and ko locales. * fix: propagate gemini permission mode from settings to cli - Added Gemini Permissions UI in Settings to toggle Auto Edit and YOLO modes - Synced gemini permission mode to localStorage - Passed permissionMode in useChatComposerState for Gemini commands - Mapped frontend permission modes to --yolo and --approval-mode options in gemini-cli.js * feat(gemini): Refactor Gemini CLI integration to use stream-json - Replaced regex buffering text-system with NDJSON stream parsing - Added fallback for restricted models like gemini-3.1-pro-preview * feat(gemini): Render tool_use and tool_result UI bubbles - Forwarded gemini tool NDJSON objects to the websocket - Added React state handlers in useChatRealtimeHandlers to match Claude's tool UI behavior * feat(gemini): Add native session resumption and UI token tracking - Captured cliSessionId from init events to map ClaudeCodeUI's chat sessionId directly into Gemini's internal session manager. - Updated gemini-cli.js spawn arguments to append the --resume proxy flag instead of naively dumping the accumulated chat history into the command prompt. - Handled result stream objects by proxying total_tokens back into the frontend's claude-status tracker to natively populate the UI label. - Eliminated gemini-3 model proxy filter entirely. * fix(gemini): Fix static 'Claude' name rendering in chat UI header - Added "gemini": "Gemini" translation strings to messageTypes across English, Korean, and Chinese loc dictionaries. - Updated AssistantThinkingIndicator and MessageComponent ternary checks to identify provider === 'gemini' and render the appropriate brand label instead of statically defaulting to Claude. * feat: Add Gemini session persistence API mapping and Sidebar UI * fix(gemini): Watch ~/.gemini/sessions for live UI updates Added the .gemini/sessions directory to PROVIDER_WATCH_PATHS so that Chokidar emits projects_updated websocket events when new Gemini sessions are created or modified, fixing live sidebar updates. * fix(gemini): Fix Gemini authentication status display in Settings UI - Injected 'checkGeminiAuthStatus' into the Settings.jsx React effect hook so that the UI can poll and render the 'geminiAuthStatus' state. - Updated 'checkGeminiCredentials()' inside server/routes/cli-auth.js to read from '~/.gemini/oauth_creds.json' and '~/.gemini/google_accounts.json', resolving the email address correctly. * Use logo-only icon for gemini * feat(gemini): Add Gemini 3 preview models to UI selection list * Fix Gemini CLI session resume bug and PR #422 review nitpicks * Fix Gemini tool calls disappearing from UI after completion * fix(gemini): resolve outstanding PR #422 feedback and stabilize gemini CLI timeouts * fix(gemini): resolve resume flag and shell session initialization issues This commit addresses the remaining PR comments for the Gemini CLI integration: - Moves the `--resume` flag logic outside the prompt command block, ensuring Gemini sessions correctly resume even when a new prompt isn't passed. - Updates `handleShellConnection` to correctly lookup the native `cliSessionId` from the internal `sessionId` when spawning Gemini sessions in a plain shell. - Refactors dynamic import of `sessionManager.js` back to a native static import for code consistency. * chore: fix TypeScript errors and remove gemini CLI dependency * fix: use cross-spawn on Windows to resolve gemini.cmd correctly --------- Co-authored-by: Haileyesus <118998054+blackmammoth@users.noreply.github.com>
127 lines
3.7 KiB
TypeScript
127 lines
3.7 KiB
TypeScript
import { useTranslation } from 'react-i18next';
|
|
import SessionProviderLogo from '../../../../llm-logo-provider/SessionProviderLogo';
|
|
import type { AgentProvider, AuthStatus } from '../../../types/types';
|
|
|
|
type AgentListItemProps = {
|
|
agentId: AgentProvider;
|
|
authStatus: AuthStatus;
|
|
isSelected: boolean;
|
|
onClick: () => void;
|
|
isMobile?: boolean;
|
|
};
|
|
|
|
type AgentConfig = {
|
|
name: string;
|
|
color: 'blue' | 'purple' | 'gray' | 'indigo';
|
|
};
|
|
|
|
const agentConfig: Record<AgentProvider, AgentConfig> = {
|
|
claude: {
|
|
name: 'Claude',
|
|
color: 'blue',
|
|
},
|
|
cursor: {
|
|
name: 'Cursor',
|
|
color: 'purple',
|
|
},
|
|
codex: {
|
|
name: 'Codex',
|
|
color: 'gray',
|
|
},
|
|
gemini: {
|
|
name: 'Gemini',
|
|
color: 'indigo',
|
|
}
|
|
};
|
|
|
|
const colorClasses = {
|
|
blue: {
|
|
border: 'border-l-blue-500 md:border-l-blue-500',
|
|
borderBottom: 'border-b-blue-500',
|
|
bg: 'bg-blue-50 dark:bg-blue-900/20',
|
|
dot: 'bg-blue-500',
|
|
},
|
|
purple: {
|
|
border: 'border-l-purple-500 md:border-l-purple-500',
|
|
borderBottom: 'border-b-purple-500',
|
|
bg: 'bg-purple-50 dark:bg-purple-900/20',
|
|
dot: 'bg-purple-500',
|
|
},
|
|
gray: {
|
|
border: 'border-l-gray-700 dark:border-l-gray-300',
|
|
borderBottom: 'border-b-gray-700 dark:border-b-gray-300',
|
|
bg: 'bg-gray-100 dark:bg-gray-800/50',
|
|
dot: 'bg-gray-700 dark:bg-gray-300',
|
|
},
|
|
indigo: {
|
|
border: 'border-l-indigo-500 md:border-l-indigo-500',
|
|
borderBottom: 'border-b-indigo-500',
|
|
bg: 'bg-indigo-50 dark:bg-indigo-900/20',
|
|
dot: 'bg-indigo-500',
|
|
},
|
|
} as const;
|
|
|
|
export default function AgentListItem({
|
|
agentId,
|
|
authStatus,
|
|
isSelected,
|
|
onClick,
|
|
isMobile = false,
|
|
}: AgentListItemProps) {
|
|
const { t } = useTranslation('settings');
|
|
const config = agentConfig[agentId];
|
|
const colors = colorClasses[config.color];
|
|
|
|
if (isMobile) {
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
className={`flex-1 text-center py-3 px-2 border-b-2 transition-colors ${isSelected
|
|
? `${colors.borderBottom} ${colors.bg}`
|
|
: 'border-transparent hover:bg-gray-50 dark:hover:bg-gray-800'
|
|
}`}
|
|
>
|
|
<div className="flex flex-col items-center gap-1">
|
|
<SessionProviderLogo provider={agentId} className="w-5 h-5" />
|
|
<span className="text-xs font-medium text-foreground">{config.name}</span>
|
|
{authStatus.authenticated && (
|
|
<span className={`w-1.5 h-1.5 rounded-full ${colors.dot}`} />
|
|
)}
|
|
</div>
|
|
</button>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
className={`w-full text-left p-3 border-l-4 transition-colors ${isSelected
|
|
? `${colors.border} ${colors.bg}`
|
|
: 'border-transparent hover:bg-gray-50 dark:hover:bg-gray-800'
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<SessionProviderLogo provider={agentId} className="w-4 h-4" />
|
|
<span className="font-medium text-foreground">{config.name}</span>
|
|
</div>
|
|
<div className="text-xs text-muted-foreground pl-6">
|
|
{authStatus.loading ? (
|
|
<span className="text-gray-400">{t('agents.authStatus.checking')}</span>
|
|
) : authStatus.authenticated ? (
|
|
<div className="flex items-center gap-1">
|
|
<span className={`w-1.5 h-1.5 rounded-full ${colors.dot}`} />
|
|
<span className="truncate max-w-[120px]" title={authStatus.email ?? undefined}>
|
|
{authStatus.email || t('agents.authStatus.connected')}
|
|
</span>
|
|
</div>
|
|
) : (
|
|
<div className="flex items-center gap-1">
|
|
<span className="w-1.5 h-1.5 rounded-full bg-gray-400" />
|
|
<span>{t('agents.authStatus.notConnected')}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</button>
|
|
);
|
|
}
|