Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fcc943c955 | |||
| aea0d2d0be | |||
| d7396b53e3 | |||
| bf48644640 | |||
| 44cdb3e376 |
@@ -56,7 +56,7 @@ they appear in the UI.
|
||||
| Dynamic Window Title | `ui.dynamicWindowTitle` | Update the terminal window title with current status icons (Ready: ◇, Action Required: ✋, Working: ✦) | `true` |
|
||||
| Show Home Directory Warning | `ui.showHomeDirectoryWarning` | Show a warning when running Gemini CLI in the home directory. | `true` |
|
||||
| Show Compatibility Warnings | `ui.showCompatibilityWarnings` | Show warnings about terminal or OS compatibility issues. | `true` |
|
||||
| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI | `false` |
|
||||
| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI | `true` |
|
||||
| Escape Pasted @ Symbols | `ui.escapePastedAtSymbols` | When enabled, @ symbols in pasted text are escaped to prevent unintended @path expansion. | `false` |
|
||||
| Show Shortcuts Hint | `ui.showShortcutsHint` | Show the "? for shortcuts" hint above the input. | `true` |
|
||||
| Hide Banner | `ui.hideBanner` | Hide the application banner | `false` |
|
||||
|
||||
@@ -246,7 +246,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
|
||||
- **`ui.hideTips`** (boolean):
|
||||
- **Description:** Hide helpful tips in the UI
|
||||
- **Default:** `false`
|
||||
- **Default:** `true`
|
||||
|
||||
- **`ui.escapePastedAtSymbols`** (boolean):
|
||||
- **Description:** When enabled, @ symbols in pasted text are escaped to
|
||||
@@ -1933,7 +1933,7 @@ of v0.3.0:
|
||||
"ui": {
|
||||
"theme": "GitHub",
|
||||
"hideBanner": true,
|
||||
"hideTips": false,
|
||||
"hideTips": true,
|
||||
"customWittyPhrases": [
|
||||
"You forget a thousand things every day. Make sure this is one of ’em",
|
||||
"Connecting to AGI"
|
||||
|
||||
@@ -538,7 +538,7 @@ const SETTINGS_SCHEMA = {
|
||||
label: 'Hide Tips',
|
||||
category: 'UI',
|
||||
requiresRestart: false,
|
||||
default: false,
|
||||
default: true,
|
||||
description: 'Hide helpful tips in the UI',
|
||||
showInDialog: true,
|
||||
},
|
||||
|
||||
@@ -93,6 +93,7 @@ import { setupTerminalAndTheme } from './utils/terminalTheme.js';
|
||||
import { runDeferredCommand } from './deferred.js';
|
||||
import { cleanupBackgroundLogs } from './utils/logCleanup.js';
|
||||
import { SlashCommandConflictHandler } from './services/SlashCommandConflictHandler.js';
|
||||
import { initializeConsoleStore } from './ui/hooks/useConsoleMessages.js';
|
||||
|
||||
export function validateDnsResolutionOrder(
|
||||
order: string | undefined,
|
||||
@@ -294,6 +295,7 @@ export async function main() {
|
||||
process.exit(ExitCodes.FATAL_INPUT_ERROR);
|
||||
}
|
||||
|
||||
initializeConsoleStore();
|
||||
const isDebugMode = cliConfig.isDebugMode(argv);
|
||||
const consolePatcher = new ConsolePatcher({
|
||||
stderr: true,
|
||||
|
||||
@@ -91,18 +91,40 @@ describe('App', () => {
|
||||
backgroundTasks: new Map(),
|
||||
};
|
||||
|
||||
it('should render main content and composer when not quitting', async () => {
|
||||
const { lastFrame, unmount } = await renderWithProviders(<App />, {
|
||||
uiState: mockUIState,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
|
||||
});
|
||||
it('should render main content and composer without tips by default', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
|
||||
<App />,
|
||||
{
|
||||
uiState: mockUIState,
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: false },
|
||||
}),
|
||||
},
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
expect(lastFrame()).toContain('Tips for getting started');
|
||||
expect(lastFrame()).not.toContain('Tips for getting started');
|
||||
expect(lastFrame()).toContain('Notifications');
|
||||
expect(lastFrame()).toContain('Composer');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should render tips when hideTips is explicitly set to false', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
|
||||
<App />,
|
||||
{
|
||||
uiState: mockUIState,
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: false, hideTips: false },
|
||||
}),
|
||||
},
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
expect(lastFrame()).toContain('Tips for getting started');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should render quitting display when quittingMessages is set', async () => {
|
||||
const quittingUIState = {
|
||||
...mockUIState,
|
||||
@@ -147,7 +169,7 @@ describe('App', () => {
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
});
|
||||
|
||||
expect(lastFrame()).toContain('Tips for getting started');
|
||||
expect(lastFrame()).not.toContain('Tips for getting started');
|
||||
expect(lastFrame()).toContain('Notifications');
|
||||
expect(lastFrame()).toContain('DialogManager');
|
||||
unmount();
|
||||
@@ -185,7 +207,7 @@ describe('App', () => {
|
||||
|
||||
expect(lastFrame()).toContain('Notifications');
|
||||
expect(lastFrame()).toContain('Footer');
|
||||
expect(lastFrame()).toContain('Tips for getting started');
|
||||
expect(lastFrame()).not.toContain('Tips for getting started');
|
||||
expect(lastFrame()).toContain('Composer');
|
||||
unmount();
|
||||
});
|
||||
@@ -198,7 +220,7 @@ describe('App', () => {
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
});
|
||||
|
||||
expect(lastFrame()).toContain('Tips for getting started');
|
||||
expect(lastFrame()).not.toContain('Tips for getting started');
|
||||
expect(lastFrame()).toContain('Notifications');
|
||||
expect(lastFrame()).toContain('Composer');
|
||||
unmount();
|
||||
@@ -250,7 +272,7 @@ describe('App', () => {
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
});
|
||||
|
||||
expect(lastFrame()).toContain('Tips for getting started');
|
||||
expect(lastFrame()).not.toContain('Tips for getting started');
|
||||
expect(lastFrame()).toContain('Notifications');
|
||||
expect(lastFrame()).toContain('Action Required'); // From ToolConfirmationQueue
|
||||
expect(lastFrame()).toContain('Composer');
|
||||
|
||||
@@ -11,11 +11,11 @@ exports[`App > Snapshots > renders default layout correctly 1`] = `
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -55,12 +55,6 @@ Footer
|
||||
Gemini CLI v1.2.3
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
Composer
|
||||
"
|
||||
`;
|
||||
@@ -118,12 +112,6 @@ exports[`App > should render ToolConfirmationQueue along with Composer when tool
|
||||
Gemini CLI v1.2.3
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
HistoryItemDisplay
|
||||
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Action Required │
|
||||
@@ -145,6 +133,12 @@ HistoryItemDisplay
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Notifications
|
||||
|
||||
Composer
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
<text x="882" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">▄</text>
|
||||
<text x="0" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="138" fill="#afafaf" textLength="414" lengthAdjust="spacingAndGlyphs">... first 44 lines hidden (Ctrl+O to show) ...</text>
|
||||
<text x="882" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="155" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">45</text>
|
||||
<text x="63" y="155" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
@@ -39,6 +41,7 @@
|
||||
<text x="198" y="155" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="155" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="172" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">46</text>
|
||||
<text x="63" y="172" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
@@ -47,6 +50,7 @@
|
||||
<text x="198" y="172" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="172" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="189" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">47</text>
|
||||
<text x="63" y="189" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
@@ -8,10 +8,10 @@ exports[`Full Terminal Tool Confirmation Snapshot > renders tool confirmation bo
|
||||
│ Action Required │
|
||||
│ │
|
||||
│ ? Edit packages/.../InputPrompt.tsx: return kittyProtocolSupporte... => return kittyProto… │
|
||||
│ │
|
||||
│ ... first 44 lines hidden (Ctrl+O to show) ... │
|
||||
│ 45 const line45 = true; │
|
||||
│ 46 const line46 = true; │
|
||||
│ │▄
|
||||
│ ... first 44 lines hidden (Ctrl+O to show) ... │█
|
||||
│ 45 const line45 = true; │█
|
||||
│ 46 const line46 = true; │█
|
||||
│ 47 const line47 = true; │█
|
||||
│ 48 const line48 = true; │█
|
||||
│ 49 const line49 = true; │█
|
||||
|
||||
@@ -8,9 +8,10 @@ import {
|
||||
renderWithProviders,
|
||||
persistentStateMock,
|
||||
} from '../../test-utils/render.js';
|
||||
import { createMockSettings } from '../../test-utils/settings.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import { AppHeader } from './AppHeader.js';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { makeFakeConfig } from '@google/gemini-cli-core';
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
@@ -19,6 +20,11 @@ vi.mock('../utils/terminalSetup.js', () => ({
|
||||
}));
|
||||
|
||||
describe('<AppHeader />', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
persistentStateMock.setData({ tipsShown: undefined });
|
||||
});
|
||||
|
||||
it('should render the banner with default text', async () => {
|
||||
const uiState = {
|
||||
history: [],
|
||||
@@ -37,10 +43,32 @@ describe('<AppHeader />', () => {
|
||||
);
|
||||
|
||||
expect(lastFrame()).toContain('This is the default banner');
|
||||
expect(lastFrame()).not.toContain('Tips for getting started');
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should render tips when hideTips is explicitly set to false', async () => {
|
||||
const uiState = {
|
||||
history: [],
|
||||
bannerData: {
|
||||
defaultText: 'This is the default banner',
|
||||
warningText: '',
|
||||
},
|
||||
bannerVisible: true,
|
||||
};
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<AppHeader version="1.0.0" />,
|
||||
{
|
||||
uiState,
|
||||
},
|
||||
);
|
||||
|
||||
expect(lastFrame()).not.toContain('Tips for getting started');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should render the banner with warning text', async () => {
|
||||
const uiState = {
|
||||
history: [],
|
||||
@@ -59,6 +87,7 @@ describe('<AppHeader />', () => {
|
||||
);
|
||||
|
||||
expect(lastFrame()).toContain('There are capacity issues');
|
||||
expect(lastFrame()).not.toContain('Tips for getting started');
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
@@ -80,6 +109,7 @@ describe('<AppHeader />', () => {
|
||||
);
|
||||
|
||||
expect(lastFrame()).not.toContain('Banner');
|
||||
expect(lastFrame()).not.toContain('Tips for getting started');
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
@@ -110,6 +140,7 @@ describe('<AppHeader />', () => {
|
||||
);
|
||||
|
||||
expect(lastFrame()).not.toContain('This is the default banner');
|
||||
expect(lastFrame()).not.toContain('Tips for getting started');
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
@@ -123,10 +154,6 @@ describe('<AppHeader />', () => {
|
||||
},
|
||||
};
|
||||
|
||||
// Set tipsShown to 10 or more to prevent Tips from incrementing its count
|
||||
// and interfering with the expected persistentState.set call.
|
||||
persistentStateMock.setData({ tipsShown: 10 });
|
||||
|
||||
const { unmount } = await renderWithProviders(
|
||||
<AppHeader version="1.0.0" />,
|
||||
{
|
||||
@@ -167,7 +194,32 @@ describe('<AppHeader />', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should render Tips when tipsShown is less than 10', async () => {
|
||||
it('should render Tips when tipsShown is less than 10 and hideTips is false', async () => {
|
||||
const uiState = {
|
||||
history: [],
|
||||
bannerData: {
|
||||
defaultText: 'First line\\nSecond line',
|
||||
warningText: '',
|
||||
},
|
||||
bannerVisible: true,
|
||||
};
|
||||
|
||||
persistentStateMock.setData({ tipsShown: 5 });
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<AppHeader version="1.0.0" />,
|
||||
{
|
||||
uiState,
|
||||
settings: createMockSettings({ ui: { hideTips: false } }),
|
||||
},
|
||||
);
|
||||
|
||||
expect(lastFrame()).toContain('Tips');
|
||||
expect(persistentStateMock.set).toHaveBeenCalledWith('tipsShown', 6);
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should NOT render Tips when tipsShown is less than 10 but hideTips is true (default)', async () => {
|
||||
const uiState = {
|
||||
history: [],
|
||||
bannerData: {
|
||||
@@ -186,8 +238,8 @@ describe('<AppHeader />', () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(lastFrame()).toContain('Tips');
|
||||
expect(persistentStateMock.set).toHaveBeenCalledWith('tipsShown', 6);
|
||||
expect(lastFrame()).not.toContain('Tips');
|
||||
expect(persistentStateMock.set).not.toHaveBeenCalledWith('tipsShown', 6);
|
||||
unmount();
|
||||
});
|
||||
|
||||
@@ -213,7 +265,7 @@ describe('<AppHeader />', () => {
|
||||
});
|
||||
|
||||
it('should show tips until they have been shown 10 times (persistence flow)', async () => {
|
||||
persistentStateMock.setData({ tipsShown: 9 });
|
||||
persistentStateMock.set('tipsShown', 0);
|
||||
|
||||
const uiState = {
|
||||
history: [],
|
||||
@@ -227,19 +279,20 @@ describe('<AppHeader />', () => {
|
||||
// First session
|
||||
const session1 = await renderWithProviders(<AppHeader version="1.0.0" />, {
|
||||
uiState,
|
||||
settings: createMockSettings({ ui: { hideTips: false } }),
|
||||
});
|
||||
|
||||
expect(session1.lastFrame()).toContain('Tips');
|
||||
expect(persistentStateMock.get('tipsShown')).toBe(10);
|
||||
expect(persistentStateMock.get('tipsShown')).toBe(1);
|
||||
session1.unmount();
|
||||
|
||||
// Second session - state is persisted in the fake
|
||||
const session2 = await renderWithProviders(
|
||||
<AppHeader version="1.0.0" />,
|
||||
{},
|
||||
);
|
||||
const session2 = await renderWithProviders(<AppHeader version="1.0.0" />, {
|
||||
settings: createMockSettings({ ui: { hideTips: false } }),
|
||||
});
|
||||
|
||||
expect(session2.lastFrame()).not.toContain('Tips');
|
||||
expect(session2.lastFrame()).toContain('Tips');
|
||||
expect(persistentStateMock.get('tipsShown')).toBe(2);
|
||||
session2.unmount();
|
||||
});
|
||||
|
||||
|
||||
@@ -62,7 +62,10 @@ export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
|
||||
const { terminalWidth, bannerData, bannerVisible, updateInfo } = useUIState();
|
||||
|
||||
const { bannerText } = useBanner(bannerData);
|
||||
const { showTips } = useTips();
|
||||
const isTipsVisible = !(
|
||||
settings.merged.ui.hideTips || config.getScreenReader()
|
||||
);
|
||||
const { showTips } = useTips({ isVisible: isTipsVisible });
|
||||
|
||||
const authType = config.getContentGeneratorConfig()?.authType;
|
||||
const loggedOut = !authType;
|
||||
@@ -159,8 +162,7 @@ export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{!(settings.merged.ui.hideTips || config.getScreenReader()) &&
|
||||
showTips && <Tips config={config} />}
|
||||
{isTipsVisible && showTips && <Tips config={config} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -35,10 +35,7 @@ vi.mock('./shared/ScrollableList.js', () => ({
|
||||
|
||||
describe('DetailedMessagesDisplay', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(useConsoleMessages).mockReturnValue({
|
||||
consoleMessages: [],
|
||||
clearConsoleMessages: vi.fn(),
|
||||
});
|
||||
vi.mocked(useConsoleMessages).mockReturnValue([]);
|
||||
});
|
||||
it('renders nothing when messages are empty', async () => {
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
@@ -58,10 +55,7 @@ describe('DetailedMessagesDisplay', () => {
|
||||
{ type: 'error', content: 'Error message', count: 1 },
|
||||
{ type: 'debug', content: 'Debug message', count: 1 },
|
||||
];
|
||||
vi.mocked(useConsoleMessages).mockReturnValue({
|
||||
consoleMessages: messages,
|
||||
clearConsoleMessages: vi.fn(),
|
||||
});
|
||||
vi.mocked(useConsoleMessages).mockReturnValue(messages);
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<DetailedMessagesDisplay maxHeight={20} width={80} hasFocus={true} />,
|
||||
@@ -79,10 +73,7 @@ describe('DetailedMessagesDisplay', () => {
|
||||
const messages: ConsoleMessageItem[] = [
|
||||
{ type: 'error', content: 'Error message', count: 1 },
|
||||
];
|
||||
vi.mocked(useConsoleMessages).mockReturnValue({
|
||||
consoleMessages: messages,
|
||||
clearConsoleMessages: vi.fn(),
|
||||
});
|
||||
vi.mocked(useConsoleMessages).mockReturnValue(messages);
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<DetailedMessagesDisplay maxHeight={20} width={80} hasFocus={true} />,
|
||||
@@ -98,10 +89,7 @@ describe('DetailedMessagesDisplay', () => {
|
||||
const messages: ConsoleMessageItem[] = [
|
||||
{ type: 'error', content: 'Error message', count: 1 },
|
||||
];
|
||||
vi.mocked(useConsoleMessages).mockReturnValue({
|
||||
consoleMessages: messages,
|
||||
clearConsoleMessages: vi.fn(),
|
||||
});
|
||||
vi.mocked(useConsoleMessages).mockReturnValue(messages);
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<DetailedMessagesDisplay maxHeight={20} width={80} hasFocus={true} />,
|
||||
@@ -117,10 +105,7 @@ describe('DetailedMessagesDisplay', () => {
|
||||
const messages: ConsoleMessageItem[] = [
|
||||
{ type: 'log', content: 'Repeated message', count: 5 },
|
||||
];
|
||||
vi.mocked(useConsoleMessages).mockReturnValue({
|
||||
consoleMessages: messages,
|
||||
clearConsoleMessages: vi.fn(),
|
||||
});
|
||||
vi.mocked(useConsoleMessages).mockReturnValue(messages);
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<DetailedMessagesDisplay maxHeight={10} width={80} hasFocus={false} />,
|
||||
|
||||
@@ -29,7 +29,7 @@ export const DetailedMessagesDisplay: React.FC<
|
||||
> = ({ maxHeight, width, hasFocus }) => {
|
||||
const scrollableListRef = useRef<ScrollableListRef<ConsoleMessageItem>>(null);
|
||||
|
||||
const { consoleMessages } = useConsoleMessages();
|
||||
const consoleMessages = useConsoleMessages();
|
||||
const config = useConfig();
|
||||
|
||||
const messages = useMemo(() => {
|
||||
|
||||
@@ -11,12 +11,6 @@ exports[`AlternateBufferQuittingDisplay > renders with a tool awaiting confirmat
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
|
||||
Action Required (was prompted):
|
||||
|
||||
? confirming_tool Confirming tool description
|
||||
@@ -33,12 +27,6 @@ exports[`AlternateBufferQuittingDisplay > renders with active and pending tool m
|
||||
Gemini CLI v0.10.0
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
╭──────────────────────────────────────────────────────────────────────────╮
|
||||
│ ✓ tool1 Description for tool 1 │
|
||||
│ │
|
||||
@@ -62,14 +50,6 @@ exports[`AlternateBufferQuittingDisplay > renders with empty history and no pend
|
||||
▝▀ ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀
|
||||
|
||||
Gemini CLI v0.10.0
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -83,12 +63,6 @@ exports[`AlternateBufferQuittingDisplay > renders with history but no pending it
|
||||
Gemini CLI v0.10.0
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
╭──────────────────────────────────────────────────────────────────────────╮
|
||||
│ ✓ tool1 Description for tool 1 │
|
||||
│ │
|
||||
@@ -110,12 +84,6 @@ exports[`AlternateBufferQuittingDisplay > renders with pending items but no hist
|
||||
Gemini CLI v0.10.0
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
╭──────────────────────────────────────────────────────────────────────────╮
|
||||
│ o tool3 Description for tool 3 │
|
||||
│ │
|
||||
@@ -133,12 +101,6 @@ exports[`AlternateBufferQuittingDisplay > renders with user and gemini messages
|
||||
Gemini CLI v0.10.0
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> Hello Gemini
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
|
||||
@@ -8,14 +8,6 @@ exports[`<AppHeader /> > should not render the banner when no flags are set 1`]
|
||||
▝▀ ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀
|
||||
|
||||
Gemini CLI v1.0.0
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -27,14 +19,6 @@ exports[`<AppHeader /> > should not render the default banner if shown count is
|
||||
▝▀ ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀
|
||||
|
||||
Gemini CLI v1.0.0
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -51,12 +35,6 @@ exports[`<AppHeader /> > should render the banner with default text 1`] = `
|
||||
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ This is the default banner │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -73,12 +51,6 @@ exports[`<AppHeader /> > should render the banner with warning text 1`] = `
|
||||
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ There are capacity issues │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
"
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="275" viewBox="0 0 920 275">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="139" viewBox="0 0 920 139">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="920" height="275" fill="#000000" />
|
||||
<rect width="920" height="139" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="9" y="19" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="18" y="19" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▜</text>
|
||||
@@ -21,14 +21,5 @@
|
||||
<text x="90" y="70" fill="#ffffff" textLength="297" lengthAdjust="spacingAndGlyphs"> ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀</text>
|
||||
<text x="9" y="104" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">Gemini CLI</text>
|
||||
<text x="99" y="104" fill="#afafaf" textLength="63" lengthAdjust="spacingAndGlyphs"> v1.0.0</text>
|
||||
<text x="0" y="172" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">Tips for getting started:</text>
|
||||
<text x="0" y="189" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">1. Create </text>
|
||||
<text x="90" y="189" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">GEMINI.md</text>
|
||||
<text x="171" y="189" fill="#ffffff" textLength="333" lengthAdjust="spacingAndGlyphs"> files to customize your interactions</text>
|
||||
<text x="0" y="206" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">2. </text>
|
||||
<text x="27" y="206" fill="#afafaf" textLength="45" lengthAdjust="spacingAndGlyphs">/help</text>
|
||||
<text x="72" y="206" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs"> for more information</text>
|
||||
<text x="0" y="223" fill="#ffffff" textLength="450" lengthAdjust="spacingAndGlyphs">3. Ask coding questions, edit code or run commands</text>
|
||||
<text x="0" y="240" fill="#ffffff" textLength="315" lengthAdjust="spacingAndGlyphs">4. Be specific for the best results</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -1,8 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="275" viewBox="0 0 920 275">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="139" viewBox="0 0 920 139">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="920" height="275" fill="#000000" />
|
||||
<rect width="920" height="139" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="9" y="19" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="18" y="19" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▜</text>
|
||||
@@ -22,14 +22,5 @@
|
||||
<text x="81" y="70" fill="#ffffff" textLength="297" lengthAdjust="spacingAndGlyphs"> ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀</text>
|
||||
<text x="9" y="104" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">Gemini CLI</text>
|
||||
<text x="99" y="104" fill="#afafaf" textLength="63" lengthAdjust="spacingAndGlyphs"> v1.0.0</text>
|
||||
<text x="0" y="172" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">Tips for getting started:</text>
|
||||
<text x="0" y="189" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">1. Create </text>
|
||||
<text x="90" y="189" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">GEMINI.md</text>
|
||||
<text x="171" y="189" fill="#ffffff" textLength="333" lengthAdjust="spacingAndGlyphs"> files to customize your interactions</text>
|
||||
<text x="0" y="206" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">2. </text>
|
||||
<text x="27" y="206" fill="#afafaf" textLength="45" lengthAdjust="spacingAndGlyphs">/help</text>
|
||||
<text x="72" y="206" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs"> for more information</text>
|
||||
<text x="0" y="223" fill="#ffffff" textLength="450" lengthAdjust="spacingAndGlyphs">3. Ask coding questions, edit code or run commands</text>
|
||||
<text x="0" y="240" fill="#ffffff" textLength="315" lengthAdjust="spacingAndGlyphs">4. Be specific for the best results</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -9,13 +9,7 @@ exports[`AppHeader Icon Rendering > renders the default icon in standard termina
|
||||
|
||||
Gemini CLI v1.0.0
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results"
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`AppHeader Icon Rendering > renders the symmetric icon in Apple Terminal 1`] = `
|
||||
@@ -27,11 +21,5 @@ exports[`AppHeader Icon Rendering > renders the symmetric icon in Apple Terminal
|
||||
|
||||
Gemini CLI v1.0.0
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results"
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -7,76 +7,93 @@
|
||||
import { act, useCallback } from 'react';
|
||||
import { vi } from 'vitest';
|
||||
import { render } from '../../test-utils/render.js';
|
||||
import { useConsoleMessages } from './useConsoleMessages.js';
|
||||
import { CoreEvent, type ConsoleLogPayload } from '@google/gemini-cli-core';
|
||||
|
||||
// Mock coreEvents
|
||||
let consoleLogHandler: ((payload: ConsoleLogPayload) => void) | undefined;
|
||||
import {
|
||||
useConsoleMessages,
|
||||
useErrorCount,
|
||||
initializeConsoleStore,
|
||||
} from './useConsoleMessages.js';
|
||||
import { coreEvents } from '@google/gemini-cli-core';
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const actual = (await importOriginal()) as any;
|
||||
const actual = await importOriginal();
|
||||
const handlers = new Map<string, (payload: unknown) => void>();
|
||||
|
||||
return {
|
||||
...actual,
|
||||
...(actual as Record<string, unknown>),
|
||||
coreEvents: {
|
||||
on: vi.fn((event, handler) => {
|
||||
if (event === CoreEvent.ConsoleLog) {
|
||||
consoleLogHandler = handler;
|
||||
}
|
||||
...((actual as Record<string, unknown>)['coreEvents'] as Record<
|
||||
string,
|
||||
unknown
|
||||
>),
|
||||
on: vi.fn((event: string, handler: (payload: unknown) => void) => {
|
||||
handlers.set(event, handler);
|
||||
}),
|
||||
off: vi.fn((event) => {
|
||||
if (event === CoreEvent.ConsoleLog) {
|
||||
consoleLogHandler = undefined;
|
||||
}
|
||||
off: vi.fn((event: string) => {
|
||||
handlers.delete(event);
|
||||
}),
|
||||
emitConsoleLog: vi.fn(),
|
||||
// Helper for testing to trigger the handlers
|
||||
_trigger: (event: string, payload: unknown) => {
|
||||
handlers.get(event)?.(payload);
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('useConsoleMessages', () => {
|
||||
let unmounts: Array<() => void> = [];
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
consoleLogHandler = undefined;
|
||||
initializeConsoleStore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
for (const unmount of unmounts) {
|
||||
try {
|
||||
unmount();
|
||||
} catch (_e) {
|
||||
// Ignore unmount errors
|
||||
}
|
||||
}
|
||||
unmounts = [];
|
||||
vi.runOnlyPendingTimers();
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
const useTestableConsoleMessages = () => {
|
||||
const { ...rest } = useConsoleMessages();
|
||||
const consoleMessages = useConsoleMessages();
|
||||
const log = useCallback((content: string) => {
|
||||
if (consoleLogHandler) {
|
||||
consoleLogHandler({ type: 'log', content });
|
||||
}
|
||||
// @ts-expect-error - internal testing helper
|
||||
coreEvents._trigger('console-log', { type: 'log', content });
|
||||
}, []);
|
||||
const error = useCallback((content: string) => {
|
||||
if (consoleLogHandler) {
|
||||
consoleLogHandler({ type: 'error', content });
|
||||
}
|
||||
// @ts-expect-error - internal testing helper
|
||||
coreEvents._trigger('console-log', { type: 'error', content });
|
||||
}, []);
|
||||
const clearConsoleMessages = useCallback(() => {
|
||||
initializeConsoleStore();
|
||||
}, []);
|
||||
return {
|
||||
...rest,
|
||||
consoleMessages,
|
||||
log,
|
||||
error,
|
||||
clearConsoleMessages: rest.clearConsoleMessages,
|
||||
clearConsoleMessages,
|
||||
};
|
||||
};
|
||||
|
||||
const renderConsoleMessagesHook = async () => {
|
||||
let hookResult: ReturnType<typeof useTestableConsoleMessages>;
|
||||
let hookResult: ReturnType<typeof useTestableConsoleMessages> | undefined;
|
||||
function TestComponent() {
|
||||
hookResult = useTestableConsoleMessages();
|
||||
return null;
|
||||
}
|
||||
const { unmount } = await render(<TestComponent />);
|
||||
unmounts.push(unmount);
|
||||
return {
|
||||
result: {
|
||||
get current() {
|
||||
return hookResult;
|
||||
return hookResult!;
|
||||
},
|
||||
},
|
||||
unmount,
|
||||
@@ -93,10 +110,7 @@ describe('useConsoleMessages', () => {
|
||||
|
||||
act(() => {
|
||||
result.current.log('Test message');
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(60);
|
||||
vi.runAllTimers();
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toEqual([
|
||||
@@ -111,10 +125,7 @@ describe('useConsoleMessages', () => {
|
||||
result.current.log('Test message');
|
||||
result.current.log('Test message');
|
||||
result.current.log('Test message');
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(60);
|
||||
vi.runAllTimers();
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toEqual([
|
||||
@@ -128,10 +139,7 @@ describe('useConsoleMessages', () => {
|
||||
act(() => {
|
||||
result.current.log('First message');
|
||||
result.current.error('Second message');
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(60);
|
||||
vi.runAllTimers();
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toEqual([
|
||||
@@ -139,53 +147,85 @@ describe('useConsoleMessages', () => {
|
||||
{ type: 'error', content: 'Second message', count: 1 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should clear all messages when clearConsoleMessages is called', async () => {
|
||||
const { result } = await renderConsoleMessagesHook();
|
||||
describe('useErrorCount', () => {
|
||||
let unmounts: Array<() => void> = [];
|
||||
|
||||
act(() => {
|
||||
result.current.log('A message');
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(60);
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toHaveLength(1);
|
||||
|
||||
act(() => {
|
||||
result.current.clearConsoleMessages();
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toHaveLength(0);
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
initializeConsoleStore();
|
||||
});
|
||||
|
||||
it('should clear the pending timeout when clearConsoleMessages is called', async () => {
|
||||
const { result } = await renderConsoleMessagesHook();
|
||||
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
||||
|
||||
act(() => {
|
||||
result.current.log('A message');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.clearConsoleMessages();
|
||||
});
|
||||
|
||||
expect(clearTimeoutSpy).toHaveBeenCalled();
|
||||
// clearTimeoutSpy.mockRestore() is handled by afterEach restoreAllMocks
|
||||
afterEach(() => {
|
||||
for (const unmount of unmounts) {
|
||||
try {
|
||||
unmount();
|
||||
} catch (_e) {
|
||||
// Ignore unmount errors
|
||||
}
|
||||
}
|
||||
unmounts = [];
|
||||
vi.runOnlyPendingTimers();
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should clean up the timeout on unmount', async () => {
|
||||
const { result, unmount } = await renderConsoleMessagesHook();
|
||||
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
||||
const renderErrorCountHook = async () => {
|
||||
let hookResult: ReturnType<typeof useErrorCount>;
|
||||
function TestComponent() {
|
||||
hookResult = useErrorCount();
|
||||
return null;
|
||||
}
|
||||
const { unmount } = await render(<TestComponent />);
|
||||
unmounts.push(unmount);
|
||||
return {
|
||||
result: {
|
||||
get current() {
|
||||
return hookResult;
|
||||
},
|
||||
},
|
||||
unmount,
|
||||
};
|
||||
};
|
||||
|
||||
it('should initialize with an error count of 0', async () => {
|
||||
const { result } = await renderErrorCountHook();
|
||||
expect(result.current.errorCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should increment error count when an error is logged', async () => {
|
||||
const { result } = await renderErrorCountHook();
|
||||
act(() => {
|
||||
// @ts-expect-error - internal testing helper
|
||||
coreEvents._trigger('console-log', { type: 'error', content: 'error' });
|
||||
vi.runAllTimers();
|
||||
});
|
||||
expect(result.current.errorCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should not increment error count for non-error logs', async () => {
|
||||
const { result } = await renderErrorCountHook();
|
||||
act(() => {
|
||||
// @ts-expect-error - internal testing helper
|
||||
coreEvents._trigger('console-log', { type: 'log', content: 'log' });
|
||||
vi.runAllTimers();
|
||||
});
|
||||
expect(result.current.errorCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should clear the error count', async () => {
|
||||
const { result } = await renderErrorCountHook();
|
||||
act(() => {
|
||||
// @ts-expect-error - internal testing helper
|
||||
coreEvents._trigger('console-log', { type: 'error', content: 'error' });
|
||||
vi.runAllTimers();
|
||||
});
|
||||
expect(result.current.errorCount).toBe(1);
|
||||
|
||||
act(() => {
|
||||
result.current.log('A message');
|
||||
result.current.clearErrorCount();
|
||||
});
|
||||
|
||||
unmount();
|
||||
|
||||
expect(clearTimeoutSpy).toHaveBeenCalled();
|
||||
expect(result.current.errorCount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,13 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useReducer,
|
||||
useRef,
|
||||
startTransition,
|
||||
} from 'react';
|
||||
import { useCallback, useSyncExternalStore } from 'react';
|
||||
import type { ConsoleMessageItem } from '../types.js';
|
||||
import {
|
||||
coreEvents,
|
||||
@@ -18,207 +12,170 @@ import {
|
||||
type ConsoleLogPayload,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
export interface UseConsoleMessagesReturn {
|
||||
consoleMessages: ConsoleMessageItem[];
|
||||
clearConsoleMessages: () => void;
|
||||
}
|
||||
|
||||
type Action =
|
||||
| { type: 'ADD_MESSAGES'; payload: ConsoleMessageItem[] }
|
||||
| { type: 'CLEAR' };
|
||||
|
||||
function consoleMessagesReducer(
|
||||
state: ConsoleMessageItem[],
|
||||
action: Action,
|
||||
): ConsoleMessageItem[] {
|
||||
const MAX_CONSOLE_MESSAGES = 1000;
|
||||
switch (action.type) {
|
||||
case 'ADD_MESSAGES': {
|
||||
const newMessages = [...state];
|
||||
for (const queuedMessage of action.payload) {
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (
|
||||
lastMessage &&
|
||||
lastMessage.type === queuedMessage.type &&
|
||||
lastMessage.content === queuedMessage.content
|
||||
) {
|
||||
// Create a new object for the last message to ensure React detects
|
||||
// the change, preventing mutation of the existing state object.
|
||||
newMessages[newMessages.length - 1] = {
|
||||
...lastMessage,
|
||||
count: lastMessage.count + 1,
|
||||
};
|
||||
} else {
|
||||
newMessages.push({ ...queuedMessage, count: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
// Limit the number of messages to prevent memory issues
|
||||
if (newMessages.length > MAX_CONSOLE_MESSAGES) {
|
||||
return newMessages.slice(newMessages.length - MAX_CONSOLE_MESSAGES);
|
||||
}
|
||||
|
||||
return newMessages;
|
||||
}
|
||||
case 'CLEAR':
|
||||
return [];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export function useConsoleMessages(): UseConsoleMessagesReturn {
|
||||
const [consoleMessages, dispatch] = useReducer(consoleMessagesReducer, []);
|
||||
const messageQueueRef = useRef<ConsoleMessageItem[]>([]);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const isProcessingRef = useRef(false);
|
||||
|
||||
const processQueue = useCallback(() => {
|
||||
if (messageQueueRef.current.length > 0) {
|
||||
isProcessingRef.current = true;
|
||||
const messagesToProcess = messageQueueRef.current;
|
||||
messageQueueRef.current = [];
|
||||
startTransition(() => {
|
||||
dispatch({ type: 'ADD_MESSAGES', payload: messagesToProcess });
|
||||
});
|
||||
}
|
||||
timeoutRef.current = null;
|
||||
}, []);
|
||||
|
||||
const handleNewMessage = useCallback(
|
||||
(message: ConsoleMessageItem) => {
|
||||
messageQueueRef.current.push(message);
|
||||
if (!isProcessingRef.current && !timeoutRef.current) {
|
||||
// Batch updates using a timeout. 50ms is a reasonable delay to batch
|
||||
// rapid-fire messages without noticeable lag while avoiding React update
|
||||
// queue flooding.
|
||||
timeoutRef.current = setTimeout(processQueue, 50);
|
||||
}
|
||||
},
|
||||
[processQueue],
|
||||
);
|
||||
|
||||
// Once the updated consoleMessages have been committed to the screen,
|
||||
// we can safely process the next batch of queued messages if any exist.
|
||||
// This completely eliminates overlapping concurrent updates to this state.
|
||||
useEffect(() => {
|
||||
isProcessingRef.current = false;
|
||||
if (messageQueueRef.current.length > 0 && !timeoutRef.current) {
|
||||
timeoutRef.current = setTimeout(processQueue, 50);
|
||||
}
|
||||
}, [consoleMessages, processQueue]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleConsoleLog = (payload: ConsoleLogPayload) => {
|
||||
let content = payload.content;
|
||||
const MAX_CONSOLE_MSG_LENGTH = 10000;
|
||||
if (content.length > MAX_CONSOLE_MSG_LENGTH) {
|
||||
content =
|
||||
content.slice(0, MAX_CONSOLE_MSG_LENGTH) +
|
||||
`... [Truncated ${content.length - MAX_CONSOLE_MSG_LENGTH} characters]`;
|
||||
}
|
||||
|
||||
handleNewMessage({
|
||||
type: payload.type,
|
||||
content,
|
||||
count: 1,
|
||||
});
|
||||
};
|
||||
|
||||
const handleOutput = (payload: {
|
||||
isStderr: boolean;
|
||||
chunk: Uint8Array | string;
|
||||
}) => {
|
||||
let content =
|
||||
typeof payload.chunk === 'string'
|
||||
? payload.chunk
|
||||
: new TextDecoder().decode(payload.chunk);
|
||||
|
||||
const MAX_OUTPUT_CHUNK_LENGTH = 10000;
|
||||
if (content.length > MAX_OUTPUT_CHUNK_LENGTH) {
|
||||
content =
|
||||
content.slice(0, MAX_OUTPUT_CHUNK_LENGTH) +
|
||||
`... [Truncated ${content.length - MAX_OUTPUT_CHUNK_LENGTH} characters]`;
|
||||
}
|
||||
|
||||
// It would be nice if we could show stderr as 'warn' but unfortunately
|
||||
// we log non warning info to stderr before the app starts so that would
|
||||
// be misleading.
|
||||
handleNewMessage({ type: 'log', content, count: 1 });
|
||||
};
|
||||
|
||||
coreEvents.on(CoreEvent.ConsoleLog, handleConsoleLog);
|
||||
coreEvents.on(CoreEvent.Output, handleOutput);
|
||||
return () => {
|
||||
coreEvents.off(CoreEvent.ConsoleLog, handleConsoleLog);
|
||||
coreEvents.off(CoreEvent.Output, handleOutput);
|
||||
};
|
||||
}, [handleNewMessage]);
|
||||
|
||||
const clearConsoleMessages = useCallback(() => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = null;
|
||||
}
|
||||
messageQueueRef.current = [];
|
||||
isProcessingRef.current = true;
|
||||
startTransition(() => {
|
||||
dispatch({ type: 'CLEAR' });
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return { consoleMessages, clearConsoleMessages };
|
||||
}
|
||||
|
||||
export interface UseErrorCountReturn {
|
||||
errorCount: number;
|
||||
clearErrorCount: () => void;
|
||||
}
|
||||
|
||||
// --- Global Console Store ---
|
||||
|
||||
const MAX_CONSOLE_MESSAGES = 1000;
|
||||
let globalConsoleMessages: ConsoleMessageItem[] = [];
|
||||
let globalErrorCount = 0;
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
let messageQueue: ConsoleMessageItem[] = [];
|
||||
let timeoutId: NodeJS.Timeout | null = null;
|
||||
|
||||
/**
|
||||
* Initializes the global console store and subscribes to coreEvents.
|
||||
* Acts as a safe reset function, making it idempotent and useful for test isolation.
|
||||
* Must be called during application startup.
|
||||
*/
|
||||
export function initializeConsoleStore() {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
}
|
||||
messageQueue = [];
|
||||
globalConsoleMessages = [];
|
||||
globalErrorCount = 0;
|
||||
notifyListeners();
|
||||
|
||||
// Safely detach first to ensure idempotency and prevent listener leaks
|
||||
coreEvents.off(CoreEvent.ConsoleLog, handleConsoleLog);
|
||||
coreEvents.off(CoreEvent.Output, handleOutput);
|
||||
|
||||
coreEvents.on(CoreEvent.ConsoleLog, handleConsoleLog);
|
||||
coreEvents.on(CoreEvent.Output, handleOutput);
|
||||
}
|
||||
|
||||
function notifyListeners() {
|
||||
for (const listener of listeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
function processQueue() {
|
||||
if (messageQueue.length === 0) return;
|
||||
|
||||
// Create a new array to trigger React updates
|
||||
const newMessages = [...globalConsoleMessages];
|
||||
|
||||
for (const queuedMessage of messageQueue) {
|
||||
if (queuedMessage.type === 'error') {
|
||||
globalErrorCount++;
|
||||
}
|
||||
|
||||
// Coalesce consecutive identical messages
|
||||
const prev = newMessages[newMessages.length - 1];
|
||||
if (
|
||||
prev &&
|
||||
prev.type === queuedMessage.type &&
|
||||
prev.content === queuedMessage.content
|
||||
) {
|
||||
newMessages[newMessages.length - 1] = {
|
||||
...prev,
|
||||
count: prev.count + 1,
|
||||
};
|
||||
} else {
|
||||
newMessages.push({ ...queuedMessage, count: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
globalConsoleMessages =
|
||||
newMessages.length > MAX_CONSOLE_MESSAGES
|
||||
? newMessages.slice(-MAX_CONSOLE_MESSAGES)
|
||||
: newMessages;
|
||||
|
||||
messageQueue = [];
|
||||
timeoutId = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
function handleNewMessage(message: ConsoleMessageItem) {
|
||||
messageQueue.push(message);
|
||||
if (!timeoutId) {
|
||||
// Batch updates using a timeout. 50ms is a reasonable delay to batch
|
||||
// rapid-fire messages without noticeable lag while avoiding React update
|
||||
// queue flooding.
|
||||
timeoutId = setTimeout(processQueue, 50);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Subscription API for useSyncExternalStore ---
|
||||
|
||||
function subscribe(listener: () => void) {
|
||||
listeners.add(listener);
|
||||
return () => {
|
||||
listeners.delete(listener);
|
||||
};
|
||||
}
|
||||
|
||||
function getConsoleMessagesSnapshot() {
|
||||
return globalConsoleMessages;
|
||||
}
|
||||
|
||||
function getErrorCountSnapshot() {
|
||||
return globalErrorCount;
|
||||
}
|
||||
|
||||
// --- Core Event Listeners (Always active at module level) ---
|
||||
|
||||
const handleConsoleLog = (payload: ConsoleLogPayload) => {
|
||||
let content = payload.content;
|
||||
const MAX_CONSOLE_MSG_LENGTH = 10000;
|
||||
if (content.length > MAX_CONSOLE_MSG_LENGTH) {
|
||||
content =
|
||||
content.slice(0, MAX_CONSOLE_MSG_LENGTH) +
|
||||
`... [Truncated ${content.length - MAX_CONSOLE_MSG_LENGTH} characters]`;
|
||||
}
|
||||
|
||||
handleNewMessage({
|
||||
type: payload.type,
|
||||
content,
|
||||
count: 1,
|
||||
});
|
||||
};
|
||||
|
||||
const handleOutput = (payload: {
|
||||
isStderr: boolean;
|
||||
chunk: Uint8Array | string;
|
||||
}) => {
|
||||
let content =
|
||||
typeof payload.chunk === 'string'
|
||||
? payload.chunk
|
||||
: new TextDecoder().decode(payload.chunk);
|
||||
|
||||
const MAX_OUTPUT_CHUNK_LENGTH = 10000;
|
||||
if (content.length > MAX_OUTPUT_CHUNK_LENGTH) {
|
||||
content =
|
||||
content.slice(0, MAX_OUTPUT_CHUNK_LENGTH) +
|
||||
`... [Truncated ${content.length - MAX_OUTPUT_CHUNK_LENGTH} characters]`;
|
||||
}
|
||||
|
||||
handleNewMessage({ type: 'log', content, count: 1 });
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to access the global console message history.
|
||||
* Decoupled from any component lifecycle to ensure history is preserved even
|
||||
* when the UI is unmounted.
|
||||
*/
|
||||
export function useConsoleMessages(): ConsoleMessageItem[] {
|
||||
return useSyncExternalStore(subscribe, getConsoleMessagesSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to access the global error count.
|
||||
* Uses the same external store as useConsoleMessages for consistency.
|
||||
*/
|
||||
export function useErrorCount(): UseErrorCountReturn {
|
||||
const [errorCount, dispatch] = useReducer(
|
||||
(state: number, action: 'INCREMENT' | 'CLEAR') => {
|
||||
switch (action) {
|
||||
case 'INCREMENT':
|
||||
return state + 1;
|
||||
case 'CLEAR':
|
||||
return 0;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
},
|
||||
0,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleConsoleLog = (payload: ConsoleLogPayload) => {
|
||||
if (payload.type === 'error') {
|
||||
startTransition(() => {
|
||||
dispatch('INCREMENT');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
coreEvents.on(CoreEvent.ConsoleLog, handleConsoleLog);
|
||||
return () => {
|
||||
coreEvents.off(CoreEvent.ConsoleLog, handleConsoleLog);
|
||||
};
|
||||
}, []);
|
||||
const errorCount = useSyncExternalStore(subscribe, getErrorCountSnapshot);
|
||||
|
||||
const clearErrorCount = useCallback(() => {
|
||||
startTransition(() => {
|
||||
dispatch('CLEAR');
|
||||
});
|
||||
globalErrorCount = 0;
|
||||
notifyListeners();
|
||||
}, []);
|
||||
|
||||
return { errorCount, clearErrorCount };
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useTips } from './useTips.js';
|
||||
describe('useTips()', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
persistentStateMock.setData({ tipsShown: undefined });
|
||||
});
|
||||
|
||||
it('should return false and call set(1) if state is undefined', async () => {
|
||||
@@ -44,4 +45,13 @@ describe('useTips()', () => {
|
||||
expect(persistentStateMock.set).not.toHaveBeenCalled();
|
||||
expect(persistentStateMock.get('tipsShown')).toBe(10);
|
||||
});
|
||||
|
||||
it('should NOT increment if isVisible is false', async () => {
|
||||
const { result } = await renderHookWithProviders(() =>
|
||||
useTips({ isVisible: false }),
|
||||
);
|
||||
|
||||
expect(result.current.showTips).toBe(true);
|
||||
expect(persistentStateMock.set).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,16 +11,21 @@ interface UseTipsResult {
|
||||
showTips: boolean;
|
||||
}
|
||||
|
||||
export function useTips(): UseTipsResult {
|
||||
interface UseTipsOptions {
|
||||
isVisible?: boolean;
|
||||
}
|
||||
|
||||
export function useTips(options: UseTipsOptions = {}): UseTipsResult {
|
||||
const { isVisible = true } = options;
|
||||
const [tipsCount] = useState(() => persistentState.get('tipsShown') ?? 0);
|
||||
|
||||
const showTips = tipsCount < 10;
|
||||
|
||||
useEffect(() => {
|
||||
if (showTips) {
|
||||
if (showTips && isVisible) {
|
||||
persistentState.set('tipsShown', tipsCount + 1);
|
||||
}
|
||||
}, [tipsCount, showTips]);
|
||||
}, [tipsCount, showTips, isVisible]);
|
||||
|
||||
return { showTips };
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="343" viewBox="0 0 920 343">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="241" viewBox="0 0 920 241">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="920" height="343" fill="#000000" />
|
||||
<rect width="920" height="241" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="9" y="19" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="18" y="19" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▜</text>
|
||||
@@ -21,25 +21,16 @@
|
||||
<text x="90" y="70" fill="#ffffff" textLength="297" lengthAdjust="spacingAndGlyphs"> ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀</text>
|
||||
<text x="9" y="104" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">Gemini CLI</text>
|
||||
<text x="99" y="104" fill="#afafaf" textLength="63" lengthAdjust="spacingAndGlyphs"> v1.2.3</text>
|
||||
<text x="0" y="155" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">Tips for getting started:</text>
|
||||
<text x="0" y="172" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">1. Create </text>
|
||||
<text x="90" y="172" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">GEMINI.md</text>
|
||||
<text x="171" y="172" fill="#ffffff" textLength="333" lengthAdjust="spacingAndGlyphs"> files to customize your interactions</text>
|
||||
<text x="0" y="189" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">2. </text>
|
||||
<text x="27" y="189" fill="#afafaf" textLength="45" lengthAdjust="spacingAndGlyphs">/help</text>
|
||||
<text x="72" y="189" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs"> for more information</text>
|
||||
<text x="0" y="206" fill="#ffffff" textLength="450" lengthAdjust="spacingAndGlyphs">3. Ask coding questions, edit code or run commands</text>
|
||||
<text x="0" y="223" fill="#ffffff" textLength="315" lengthAdjust="spacingAndGlyphs">4. Be specific for the best results</text>
|
||||
<text x="0" y="240" fill="#ffffaf" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">⊶</text>
|
||||
<text x="45" y="257" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs" font-weight="bold">google_web_search</text>
|
||||
<text x="855" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="855" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="291" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Searching...</text>
|
||||
<text x="855" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="308" fill="#ffffaf" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="0" y="138" fill="#ffffaf" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">⊶</text>
|
||||
<text x="45" y="155" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs" font-weight="bold">google_web_search</text>
|
||||
<text x="855" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="855" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="189" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Searching...</text>
|
||||
<text x="855" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="206" fill="#ffffaf" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 4.0 KiB |
@@ -1,8 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="343" viewBox="0 0 920 343">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="241" viewBox="0 0 920 241">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="920" height="343" fill="#000000" />
|
||||
<rect width="920" height="241" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="9" y="19" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="18" y="19" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▜</text>
|
||||
@@ -21,25 +21,16 @@
|
||||
<text x="90" y="70" fill="#ffffff" textLength="297" lengthAdjust="spacingAndGlyphs"> ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀</text>
|
||||
<text x="9" y="104" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">Gemini CLI</text>
|
||||
<text x="99" y="104" fill="#afafaf" textLength="63" lengthAdjust="spacingAndGlyphs"> v1.2.3</text>
|
||||
<text x="0" y="155" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">Tips for getting started:</text>
|
||||
<text x="0" y="172" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">1. Create </text>
|
||||
<text x="90" y="172" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">GEMINI.md</text>
|
||||
<text x="171" y="172" fill="#ffffff" textLength="333" lengthAdjust="spacingAndGlyphs"> files to customize your interactions</text>
|
||||
<text x="0" y="189" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">2. </text>
|
||||
<text x="27" y="189" fill="#afafaf" textLength="45" lengthAdjust="spacingAndGlyphs">/help</text>
|
||||
<text x="72" y="189" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs"> for more information</text>
|
||||
<text x="0" y="206" fill="#ffffff" textLength="450" lengthAdjust="spacingAndGlyphs">3. Ask coding questions, edit code or run commands</text>
|
||||
<text x="0" y="223" fill="#ffffff" textLength="315" lengthAdjust="spacingAndGlyphs">4. Be specific for the best results</text>
|
||||
<text x="0" y="240" fill="#87afff" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="257" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="257" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">⊶</text>
|
||||
<text x="45" y="257" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs" font-weight="bold">run_shell_command</text>
|
||||
<text x="855" y="257" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="274" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="855" y="274" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="291" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="291" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Running command...</text>
|
||||
<text x="855" y="291" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="308" fill="#87afff" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="0" y="138" fill="#87afff" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="155" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="155" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">⊶</text>
|
||||
<text x="45" y="155" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs" font-weight="bold">run_shell_command</text>
|
||||
<text x="855" y="155" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="855" y="172" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="189" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Running command...</text>
|
||||
<text x="855" y="189" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="206" fill="#87afff" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 4.0 KiB |
@@ -1,8 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="343" viewBox="0 0 920 343">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="241" viewBox="0 0 920 241">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="920" height="343" fill="#000000" />
|
||||
<rect width="920" height="241" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="9" y="19" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="18" y="19" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▜</text>
|
||||
@@ -21,25 +21,16 @@
|
||||
<text x="90" y="70" fill="#ffffff" textLength="297" lengthAdjust="spacingAndGlyphs"> ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀</text>
|
||||
<text x="9" y="104" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">Gemini CLI</text>
|
||||
<text x="99" y="104" fill="#afafaf" textLength="63" lengthAdjust="spacingAndGlyphs"> v1.2.3</text>
|
||||
<text x="0" y="155" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">Tips for getting started:</text>
|
||||
<text x="0" y="172" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">1. Create </text>
|
||||
<text x="90" y="172" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">GEMINI.md</text>
|
||||
<text x="171" y="172" fill="#ffffff" textLength="333" lengthAdjust="spacingAndGlyphs"> files to customize your interactions</text>
|
||||
<text x="0" y="189" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">2. </text>
|
||||
<text x="27" y="189" fill="#afafaf" textLength="45" lengthAdjust="spacingAndGlyphs">/help</text>
|
||||
<text x="72" y="189" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs"> for more information</text>
|
||||
<text x="0" y="206" fill="#ffffff" textLength="450" lengthAdjust="spacingAndGlyphs">3. Ask coding questions, edit code or run commands</text>
|
||||
<text x="0" y="223" fill="#ffffff" textLength="315" lengthAdjust="spacingAndGlyphs">4. Be specific for the best results</text>
|
||||
<text x="0" y="240" fill="#ffffaf" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">⊶</text>
|
||||
<text x="45" y="257" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs" font-weight="bold">google_web_search</text>
|
||||
<text x="855" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="855" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="291" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Searching...</text>
|
||||
<text x="855" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="308" fill="#ffffaf" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="0" y="138" fill="#ffffaf" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">⊶</text>
|
||||
<text x="45" y="155" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs" font-weight="bold">google_web_search</text>
|
||||
<text x="855" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="855" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="189" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Searching...</text>
|
||||
<text x="855" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="206" fill="#ffffaf" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 4.0 KiB |
@@ -9,12 +9,6 @@ exports[`MainContent tool group border SVG snapshots > should render SVG snapsho
|
||||
|
||||
Gemini CLI v1.2.3
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ⊶ google_web_search │
|
||||
│ │
|
||||
@@ -31,12 +25,6 @@ exports[`MainContent tool group border SVG snapshots > should render SVG snapsho
|
||||
|
||||
Gemini CLI v1.2.3
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ⊶ run_shell_command │
|
||||
│ │
|
||||
@@ -53,12 +41,6 @@ exports[`MainContent tool group border SVG snapshots > should render SVG snapsho
|
||||
|
||||
Gemini CLI v1.2.3
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ⊶ google_web_search │
|
||||
│ │
|
||||
|
||||
@@ -299,8 +299,8 @@
|
||||
"hideTips": {
|
||||
"title": "Hide Tips",
|
||||
"description": "Hide helpful tips in the UI",
|
||||
"markdownDescription": "Hide helpful tips in the UI\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `false`",
|
||||
"default": false,
|
||||
"markdownDescription": "Hide helpful tips in the UI\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `true`",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"escapePastedAtSymbols": {
|
||||
|
||||