mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-16 09:01:17 -07:00
148 lines
3.7 KiB
TypeScript
148 lines
3.7 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import type React from 'react';
|
|
import { useRef, useEffect } from 'react';
|
|
import { Box, Text } from 'ink';
|
|
import { theme } from '../semantic-colors.js';
|
|
import { TextInput } from '../components/shared/TextInput.js';
|
|
import { useTextBuffer } from '../components/shared/text-buffer.js';
|
|
import { useUIState } from '../contexts/UIStateContext.js';
|
|
import { clearApiKey, debugLogger } from '@google/gemini-cli-core';
|
|
import { useKeypress } from '../hooks/useKeypress.js';
|
|
import { keyMatchers, Command } from '../keyMatchers.js';
|
|
|
|
interface ApiAuthDialogProps {
|
|
onSubmit: (apiKey: string) => void;
|
|
onCancel: () => void;
|
|
error?: string | null;
|
|
defaultValue?: string;
|
|
}
|
|
|
|
export function ApiAuthDialog({
|
|
onSubmit,
|
|
onCancel,
|
|
error,
|
|
defaultValue = '',
|
|
}: ApiAuthDialogProps): React.JSX.Element {
|
|
const { mainAreaWidth } = useUIState();
|
|
const viewportWidth = mainAreaWidth - 8;
|
|
|
|
const pendingPromise = useRef<{ cancel: () => void } | null>(null);
|
|
|
|
useEffect(
|
|
() => () => {
|
|
pendingPromise.current?.cancel();
|
|
},
|
|
[],
|
|
);
|
|
|
|
const initialApiKey = defaultValue;
|
|
|
|
const buffer = useTextBuffer({
|
|
initialText: initialApiKey || '',
|
|
initialCursorOffset: initialApiKey?.length || 0,
|
|
viewport: {
|
|
width: viewportWidth,
|
|
height: 4,
|
|
},
|
|
isValidPath: () => false, // No path validation needed for API key
|
|
inputFilter: (text) =>
|
|
text.replace(/[^a-zA-Z0-9_-]/g, '').replace(/[\r\n]/g, ''),
|
|
singleLine: true,
|
|
});
|
|
|
|
const handleSubmit = (value: string) => {
|
|
onSubmit(value);
|
|
};
|
|
|
|
const handleClear = () => {
|
|
pendingPromise.current?.cancel();
|
|
|
|
let isCancelled = false;
|
|
const wrappedPromise = new Promise<void>((resolve, reject) => {
|
|
clearApiKey().then(
|
|
() => !isCancelled && resolve(),
|
|
(error) => !isCancelled && reject(error),
|
|
);
|
|
});
|
|
|
|
pendingPromise.current = {
|
|
cancel: () => {
|
|
isCancelled = true;
|
|
},
|
|
};
|
|
|
|
return wrappedPromise
|
|
.then(() => {
|
|
buffer.setText('');
|
|
})
|
|
.catch((err) => {
|
|
debugLogger.debug('Failed to clear API key:', err);
|
|
});
|
|
};
|
|
|
|
useKeypress(
|
|
async (key) => {
|
|
if (keyMatchers[Command.CLEAR_INPUT](key)) {
|
|
await handleClear();
|
|
}
|
|
},
|
|
{ isActive: true },
|
|
);
|
|
|
|
return (
|
|
<Box
|
|
borderStyle="round"
|
|
borderColor={theme.border.focused}
|
|
flexDirection="column"
|
|
padding={1}
|
|
width="100%"
|
|
>
|
|
<Text bold color={theme.text.primary}>
|
|
Enter Gemini API Key
|
|
</Text>
|
|
<Box marginTop={1} flexDirection="column">
|
|
<Text color={theme.text.primary}>
|
|
Please enter your Gemini API key. It will be securely stored in your
|
|
system keychain.
|
|
</Text>
|
|
<Text color={theme.text.secondary}>
|
|
You can get an API key from{' '}
|
|
<Text color={theme.text.link}>
|
|
https://aistudio.google.com/app/apikey
|
|
</Text>
|
|
</Text>
|
|
</Box>
|
|
<Box marginTop={1} flexDirection="row">
|
|
<Box
|
|
borderStyle="round"
|
|
borderColor={theme.border.default}
|
|
paddingX={1}
|
|
flexGrow={1}
|
|
>
|
|
<TextInput
|
|
buffer={buffer}
|
|
onSubmit={handleSubmit}
|
|
onCancel={onCancel}
|
|
placeholder="Paste your API key here"
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
{error && (
|
|
<Box marginTop={1}>
|
|
<Text color={theme.status.error}>{error}</Text>
|
|
</Box>
|
|
)}
|
|
<Box marginTop={1}>
|
|
<Text color={theme.text.secondary}>
|
|
(Press Enter to submit, Esc to cancel, Ctrl+C to clear stored key)
|
|
</Text>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|