mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-06 15:26:26 +00:00
fix(tui): show WebBridge setup steps after install (#2692)
* fix(tui): show WebBridge setup steps after install * fix(tui): scope WebBridge hint to capability install * fix(tui): format WebBridge setup links * fix(tui): align WebBridge setup with live reload * fix(tui): retain WebBridge session activation step * fix(tui): compact WebBridge setup links * fix(tui): list WebBridge setup links clearly * fix(tui): show clickable WebBridge URLs * Revert "fix(tui): show clickable WebBridge URLs" This reverts commit 373ffae4b8cf4256131002b990f9a1869e9ee156. * fix(tui): restore WebBridge setup heading
This commit is contained in:
parent
335588e259
commit
03aa66ca0c
3 changed files with 144 additions and 3 deletions
5
.changeset/webbridge-install-next-steps.md
Normal file
5
.changeset/webbridge-install-next-steps.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Show browser extension links and activation steps after installing Kimi WebBridge.
|
||||
|
|
@ -8,6 +8,7 @@ import {
|
|||
type PluginSummary,
|
||||
type Session,
|
||||
} from '@moonshot-ai/kimi-code-sdk';
|
||||
import { Markdown, Spacer } from '@moonshot-ai/pi-tui';
|
||||
|
||||
import { NO_ACTIVE_SESSION_MESSAGE } from '../constant/kimi-tui';
|
||||
import {
|
||||
|
|
@ -26,6 +27,7 @@ import {
|
|||
buildPluginsListLines,
|
||||
} from '../components/messages/plugins-status-panel';
|
||||
import { UsagePanelComponent } from '../components/messages/usage-panel';
|
||||
import { createMarkdownTheme } from '../theme/pi-tui-theme';
|
||||
import { formatErrorMessage } from '../utils/event-payload';
|
||||
import {
|
||||
formatPluginSourceLabel,
|
||||
|
|
@ -561,6 +563,15 @@ async function installCapabilityFromPanel(
|
|||
host.showStatus(pluginReloadHint(), 'warning');
|
||||
return;
|
||||
}
|
||||
if (entry.id === 'kimi-webbridge') {
|
||||
host.showNotice(`${label} is installed.`);
|
||||
host.state.transcriptContainer.addChild(new Spacer(1));
|
||||
host.state.transcriptContainer.addChild(
|
||||
new Markdown(WEBBRIDGE_POST_INSTALL_MARKDOWN, 2, 0, createMarkdownTheme()),
|
||||
);
|
||||
host.state.ui.requestRender();
|
||||
return;
|
||||
}
|
||||
host.showStatus(`${label} is installed.`);
|
||||
host.showStatus(pluginReloadHint(), 'warning');
|
||||
}
|
||||
|
|
@ -778,6 +789,17 @@ function pluginReloadHint(): string {
|
|||
return isKimiV2Enabled() ? PLUGIN_RELOAD_HINT_V2 : PLUGIN_RELOAD_HINT;
|
||||
}
|
||||
|
||||
const WEBBRIDGE_POST_INSTALL_MARKDOWN = [
|
||||
'*Two steps left to use Kimi WebBridge:*',
|
||||
'1. Install the browser extension',
|
||||
'',
|
||||
' - [Chrome Web Store](https://chromewebstore.google.com/detail/kimi-webbridge/fldmhceldgbpfpkbgopacenieobmligc)',
|
||||
' - [Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/kimi-webbridge/bnlffdbcfnanfbknnlaflhlhkocccckg)',
|
||||
' - [Manual installation guide](https://www.kimi.com/code/docs/kimi-code-cli/customization/plugins.html#install-the-browser-extension)',
|
||||
'',
|
||||
'2. Run `/reload` or `/new` to apply it.',
|
||||
].join('\n');
|
||||
|
||||
const PLUGIN_QUOTA_NOTE = 'Note: This plugin consumes your quota.';
|
||||
|
||||
function showPluginInstallResult(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { log } from '@moonshot-ai/kimi-code-sdk';
|
||||
import { resetCapabilitiesCache, setCapabilities, type Component } from '@moonshot-ai/pi-tui';
|
||||
|
||||
import { __pluginsCommandInternals } from '#/tui/commands/plugins';
|
||||
import { NoticeMessageComponent } from '#/tui/components/messages/status-message';
|
||||
|
||||
const {
|
||||
isCapabilityEntry,
|
||||
|
|
@ -20,7 +22,9 @@ function fakeHost(overrides: {
|
|||
}>;
|
||||
}) {
|
||||
const statuses: string[] = [];
|
||||
const notices: { title: string; detail?: string }[] = [];
|
||||
const renders: number[] = [];
|
||||
const transcriptEntries: Component[] = [];
|
||||
const installCapability = vi.fn(() => Promise.resolve());
|
||||
const getCapability =
|
||||
overrides.capabilityStatus ??
|
||||
|
|
@ -46,10 +50,19 @@ function fakeHost(overrides: {
|
|||
showError: (text: string) => {
|
||||
statuses.push(text);
|
||||
},
|
||||
showNotice: (title: string, detail?: string) => {
|
||||
notices.push({ title, detail });
|
||||
transcriptEntries.push(new NoticeMessageComponent(title, detail));
|
||||
},
|
||||
restoreEditor: () => undefined,
|
||||
state: { ui: { requestRender: () => renders.push(1) } },
|
||||
state: {
|
||||
ui: { requestRender: () => renders.push(1) },
|
||||
transcriptContainer: {
|
||||
addChild: (entry: Component) => transcriptEntries.push(entry),
|
||||
},
|
||||
},
|
||||
};
|
||||
return { host: host as never, statuses, renders, installCapability };
|
||||
return { host: host as never, statuses, notices, renders, transcriptEntries, installCapability };
|
||||
}
|
||||
|
||||
function fakePanel() {
|
||||
|
|
@ -67,6 +80,23 @@ function fakePanel() {
|
|||
};
|
||||
}
|
||||
|
||||
function visibleLines(entries: readonly Component[], width = 100): string[] {
|
||||
return entries
|
||||
.flatMap((entry) => entry.render(width))
|
||||
.map((line) =>
|
||||
line
|
||||
.replaceAll(/\u001B]8;;[^\u001B]*\u001B\\/g, '')
|
||||
.replaceAll(/\u001B\[[0-9;]*m/g, '')
|
||||
.trimEnd(),
|
||||
);
|
||||
}
|
||||
|
||||
function unwrappedVisibleText(entries: readonly Component[]): string {
|
||||
return visibleLines(entries)
|
||||
.join('\n')
|
||||
.replaceAll(/\s+/g, '');
|
||||
}
|
||||
|
||||
describe('plugins command capability surface', () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
|
|
@ -74,6 +104,10 @@ describe('plugins command capability surface', () => {
|
|||
vi.spyOn(log, 'warn').mockImplementation(() => undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetCapabilitiesCache();
|
||||
});
|
||||
|
||||
it('routes built-in entries through capabilities only on v2', () => {
|
||||
const v2 = fakeHost({ engineV2: true });
|
||||
expect(
|
||||
|
|
@ -201,6 +235,86 @@ describe('plugins command capability surface', () => {
|
|||
expect(statuses.some((s) => s.includes('is installed'))).toBe(true);
|
||||
});
|
||||
|
||||
it('renders visible clickable store URLs after WebBridge installs in a hyperlink-capable terminal', async () => {
|
||||
setCapabilities({ images: null, trueColor: true, hyperlinks: true });
|
||||
const { host, statuses, notices, transcriptEntries } = fakeHost({ engineV2: true });
|
||||
|
||||
await installCapabilityFromPanel(
|
||||
host,
|
||||
fakePanel().panel,
|
||||
{
|
||||
id: 'kimi-webbridge',
|
||||
displayName: 'Kimi WebBridge',
|
||||
source: 'capability:kimi-webbridge',
|
||||
} as never,
|
||||
);
|
||||
|
||||
expect(notices).toContainEqual({ title: 'Kimi WebBridge is installed.', detail: undefined });
|
||||
expect(statuses).not.toContain('Run /new or /reload to apply plugin changes.');
|
||||
const rendered = transcriptEntries.flatMap((entry) => entry.render(100)).join('\n');
|
||||
expect(rendered).toContain(
|
||||
'\u001B]8;;https://chromewebstore.google.com/detail/kimi-webbridge/fldmhceldgbpfpkbgopacenieobmligc\u001B\\',
|
||||
);
|
||||
expect(rendered).toContain('Chrome Web Store');
|
||||
expect(rendered).toContain('Edge Add-ons');
|
||||
expect(rendered).toContain('Manual installation guide');
|
||||
expect(rendered).toContain('/reload');
|
||||
expect(rendered).toContain('/new');
|
||||
});
|
||||
|
||||
it('renders full store URLs after WebBridge installs in a terminal without hyperlinks', async () => {
|
||||
setCapabilities({ images: null, trueColor: true, hyperlinks: false });
|
||||
const { host, transcriptEntries } = fakeHost({ engineV2: true });
|
||||
|
||||
await installCapabilityFromPanel(
|
||||
host,
|
||||
fakePanel().panel,
|
||||
{
|
||||
id: 'kimi-webbridge',
|
||||
displayName: 'Kimi WebBridge',
|
||||
source: 'capability:kimi-webbridge',
|
||||
} as never,
|
||||
);
|
||||
|
||||
const rendered = transcriptEntries.flatMap((entry) => entry.render(100)).join('\n');
|
||||
expect(rendered).not.toContain('\u001B]8;;');
|
||||
expect(unwrappedVisibleText(transcriptEntries)).toContain(
|
||||
'https://chromewebstore.google.com/detail/kimi-webbridge/fldmhceldgbpfpkbgopacenieobmligc',
|
||||
);
|
||||
});
|
||||
|
||||
it('separates the WebBridge install result from its setup steps with one blank line', async () => {
|
||||
setCapabilities({ images: null, trueColor: true, hyperlinks: true });
|
||||
const { host, transcriptEntries } = fakeHost({ engineV2: true });
|
||||
|
||||
await installCapabilityFromPanel(
|
||||
host,
|
||||
fakePanel().panel,
|
||||
{
|
||||
id: 'kimi-webbridge',
|
||||
displayName: 'Kimi WebBridge',
|
||||
source: 'capability:kimi-webbridge',
|
||||
} as never,
|
||||
);
|
||||
|
||||
const lines = visibleLines(transcriptEntries, 180);
|
||||
const installed = lines.findIndex((line) => line.includes('Kimi WebBridge is installed.'));
|
||||
const intro = lines.findIndex((line) =>
|
||||
line.includes('Two steps left to use Kimi WebBridge:'),
|
||||
);
|
||||
const firstStep = lines.findIndex((line) =>
|
||||
line.includes('Install the browser extension'),
|
||||
);
|
||||
const secondStep = lines.findIndex((line) => line.includes('Run /reload or /new to apply it.'));
|
||||
expect(lines.slice(installed + 1, intro)).toEqual(['']);
|
||||
expect(firstStep).toBe(intro + 1);
|
||||
expect(lines[firstStep]).toContain('1.');
|
||||
expect(lines.find((line) => line.includes('Chrome Web Store'))).toContain('•');
|
||||
expect(lines.find((line) => line.includes('Edge Add-ons'))).toContain('•');
|
||||
expect(lines.find((line) => line.includes('Manual installation guide'))).toContain('•');
|
||||
expect(lines[secondStep]).toContain('2.');
|
||||
});
|
||||
|
||||
it('shows the engine error when a background capability install fails', async () => {
|
||||
const { host, statuses } = fakeHost({
|
||||
engineV2: true,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue