mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 02:15:36 +00:00
refactor(cliproxy): use centralized ui system for model config display
Replace custom formatting with ui.ts imports (color, bold, dim, ok, info, header, initUI) to maintain consistent styling with ccs doctor. Add model descriptions to catalog and clarify [Paid API] tier labeling. Update showCurrentConfig to async for UI initialization. Improves code reuse and visual consistency across CLI commands.
This commit is contained in:
1 parent
e3a18358e9
commit
20b7bae046
3 files changed
+86
-32
No files matched your search
@@ -15,8 +15,10 @@ export interface ModelEntry {
|
||||
id: string;
|
||||
/** Human-readable name for display */
|
||||
name: string;
|
||||
/** Access tier indicator */
|
||||
/** Access tier indicator - 'paid' means requires paid API tier (not model pricing) */
|
||||
tier?: 'free' | 'paid';
|
||||
/** Optional description for the model */
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,10 +42,27 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
displayName: 'Antigravity',
|
||||
defaultModel: 'gemini-3-pro-preview',
|
||||
models: [
|
||||
{ id: 'gemini-claude-opus-4-5-thinking', name: 'Claude Opus 4.5 Thinking' },
|
||||
{ id: 'gemini-claude-sonnet-4-5-thinking', name: 'Claude Sonnet 4.5 Thinking' },
|
||||
{ id: 'gemini-claude-sonnet-4-5', name: 'Claude Sonnet 4.5' },
|
||||
{ id: 'gemini-3-pro-preview', name: 'Gemini 3 Pro', tier: 'paid' },
|
||||
{
|
||||
id: 'gemini-claude-opus-4-5-thinking',
|
||||
name: 'Claude Opus 4.5 Thinking',
|
||||
description: 'Most capable, extended thinking',
|
||||
},
|
||||
{
|
||||
id: 'gemini-claude-sonnet-4-5-thinking',
|
||||
name: 'Claude Sonnet 4.5 Thinking',
|
||||
description: 'Balanced with extended thinking',
|
||||
},
|
||||
{
|
||||
id: 'gemini-claude-sonnet-4-5',
|
||||
name: 'Claude Sonnet 4.5',
|
||||
description: 'Fast and capable',
|
||||
},
|
||||
{
|
||||
id: 'gemini-3-pro-preview',
|
||||
name: 'Gemini 3 Pro',
|
||||
tier: 'paid',
|
||||
description: 'Google latest, requires Paid API tier',
|
||||
},
|
||||
],
|
||||
},
|
||||
gemini: {
|
||||
@@ -51,8 +70,17 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
|
||||
displayName: 'Gemini',
|
||||
defaultModel: 'gemini-2.5-pro',
|
||||
models: [
|
||||
{ id: 'gemini-3-pro-preview', name: 'Gemini 3 Pro', tier: 'paid' },
|
||||
{ id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro' },
|
||||
{
|
||||
id: 'gemini-3-pro-preview',
|
||||
name: 'Gemini 3 Pro',
|
||||
tier: 'paid',
|
||||
description: 'Latest model, requires Paid API tier',
|
||||
},
|
||||
{
|
||||
id: 'gemini-2.5-pro',
|
||||
name: 'Gemini 2.5 Pro',
|
||||
description: 'Stable release, works with free tier',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ import { InteractivePrompt } from '../utils/prompt';
|
||||
import { getProviderCatalog, supportsModelConfig, ModelEntry } from './model-catalog';
|
||||
import { getProviderSettingsPath, getClaudeEnvVars } from './config-generator';
|
||||
import { CLIProxyProvider } from './types';
|
||||
import { initUI, color, bold, dim, ok, info, header } from '../utils/ui';
|
||||
|
||||
/** CCS directory */
|
||||
const CCS_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '', '.ccs');
|
||||
@@ -39,11 +40,23 @@ export function getCurrentModel(provider: CLIProxyProvider): string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Format model entry for display
|
||||
* Format model entry for display in selection list
|
||||
*/
|
||||
function formatModelOption(model: ModelEntry): string {
|
||||
const tierLabel = model.tier === 'paid' ? ' [paid]' : '';
|
||||
return `${model.name} (${model.id})${tierLabel}`;
|
||||
// Tier badge: clarify that "paid" means API tier, not model pricing
|
||||
const tierBadge = model.tier === 'paid' ? color(' [Paid API]', 'warning') : '';
|
||||
return `${model.name}${tierBadge}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format model entry for detailed display (with description)
|
||||
*/
|
||||
function formatModelDetailed(model: ModelEntry, isCurrent: boolean): string {
|
||||
const marker = isCurrent ? color('>', 'success') : ' ';
|
||||
const name = isCurrent ? bold(model.name) : model.name;
|
||||
const tierBadge = model.tier === 'paid' ? color(' [Paid API]', 'warning') : '';
|
||||
const desc = model.description ? dim(` - ${model.description}`) : '';
|
||||
return ` ${marker} ${name}${tierBadge}${desc}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,6 +85,9 @@ export async function configureProviderModel(
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize UI for colors/gradient
|
||||
await initUI();
|
||||
|
||||
// Build options list
|
||||
const options = catalog.models.map((m) => ({
|
||||
id: m.id,
|
||||
@@ -82,9 +98,13 @@ export async function configureProviderModel(
|
||||
const defaultIdx = catalog.models.findIndex((m) => m.id === catalog.defaultModel);
|
||||
const safeDefaultIdx = defaultIdx >= 0 ? defaultIdx : 0;
|
||||
|
||||
// Show header
|
||||
// Show header with context (gradient like ccs doctor)
|
||||
console.error('');
|
||||
console.error(header(`Configure ${catalog.displayName} Model`));
|
||||
console.error('');
|
||||
console.error(dim(' Select which model to use for this provider.'));
|
||||
console.error(dim(' Models marked [Paid API] require a paid Google AI Studio API key.'));
|
||||
console.error('');
|
||||
console.error(`[i] Configure ${catalog.displayName} model`);
|
||||
|
||||
// Interactive selection
|
||||
const selectedModel = await InteractivePrompt.selectFromList('Select model:', options, {
|
||||
@@ -118,8 +138,8 @@ export async function configureProviderModel(
|
||||
const displayName = selectedEntry?.name || selectedModel;
|
||||
|
||||
console.error('');
|
||||
console.error(`[OK] Model set to: ${displayName} (${selectedModel})`);
|
||||
console.error(`[i] Saved to: ${settingsPath}`);
|
||||
console.error(ok(`Model set to: ${bold(displayName)}`));
|
||||
console.error(dim(` Config saved: ${settingsPath}`));
|
||||
console.error('');
|
||||
|
||||
return true;
|
||||
@@ -128,41 +148,47 @@ export async function configureProviderModel(
|
||||
/**
|
||||
* Show current model configuration
|
||||
*/
|
||||
export function showCurrentConfig(provider: CLIProxyProvider): void {
|
||||
export async function showCurrentConfig(provider: CLIProxyProvider): Promise<void> {
|
||||
if (!supportsModelConfig(provider)) {
|
||||
console.error(`[i] Provider ${provider} does not support model configuration`);
|
||||
console.error(info(`Provider ${provider} does not support model configuration`));
|
||||
return;
|
||||
}
|
||||
|
||||
const catalog = getProviderCatalog(provider);
|
||||
if (!catalog) return;
|
||||
|
||||
// Initialize UI for colors/gradient
|
||||
await initUI();
|
||||
|
||||
const currentModel = getCurrentModel(provider);
|
||||
const settingsPath = getProviderSettingsPath(provider);
|
||||
|
||||
console.error('');
|
||||
console.error(`[i] ${catalog.displayName} Model Configuration`);
|
||||
console.error(header(`${catalog.displayName} Model Configuration`));
|
||||
console.error('');
|
||||
|
||||
if (currentModel) {
|
||||
const entry = catalog.models.find((m) => m.id === currentModel);
|
||||
const displayName = entry?.name || 'Unknown';
|
||||
console.error(` Current: ${displayName} (${currentModel})`);
|
||||
console.error(` Config: ${settingsPath}`);
|
||||
console.error(
|
||||
` ${bold('Current:')} ${color(displayName, 'success')} ${dim(`(${currentModel})`)}`
|
||||
);
|
||||
console.error(` ${bold('Config:')} ${dim(settingsPath)}`);
|
||||
} else {
|
||||
console.error(` Current: (using defaults)`);
|
||||
console.error(` Default: ${catalog.defaultModel}`);
|
||||
console.error(` ${bold('Current:')} ${dim('(using defaults)')}`);
|
||||
console.error(` ${bold('Default:')} ${catalog.defaultModel}`);
|
||||
}
|
||||
|
||||
console.error('');
|
||||
console.error('Available models:');
|
||||
console.error(bold('Available models:'));
|
||||
console.error(dim(' [Paid API] = Requires paid Google AI Studio API key'));
|
||||
console.error('');
|
||||
catalog.models.forEach((m) => {
|
||||
const isCurrent = m.id === currentModel;
|
||||
const marker = isCurrent ? '>' : ' ';
|
||||
console.error(` ${marker} ${formatModelOption(m)}`);
|
||||
console.error(formatModelDetailed(m, isCurrent));
|
||||
});
|
||||
|
||||
console.error('');
|
||||
console.error(`Run "ccs ${provider} --config" to change`);
|
||||
console.error(dim(`Run "ccs ${provider} --config" to change`));
|
||||
console.error('');
|
||||
}
|
||||
@@ -55,20 +55,20 @@ describe('Model Config', () => {
|
||||
});
|
||||
|
||||
describe('showCurrentConfig', () => {
|
||||
it('does not throw for agy provider', () => {
|
||||
it('does not throw for agy provider', async () => {
|
||||
const { showCurrentConfig } = modelConfig;
|
||||
// Just verify it doesn't throw
|
||||
assert.doesNotThrow(() => showCurrentConfig('agy'));
|
||||
// Just verify it doesn't throw (now async)
|
||||
await assert.doesNotReject(async () => showCurrentConfig('agy'));
|
||||
});
|
||||
|
||||
it('does not throw for gemini provider', () => {
|
||||
it('does not throw for gemini provider', async () => {
|
||||
const { showCurrentConfig } = modelConfig;
|
||||
assert.doesNotThrow(() => showCurrentConfig('gemini'));
|
||||
await assert.doesNotReject(async () => showCurrentConfig('gemini'));
|
||||
});
|
||||
|
||||
it('does not throw for unsupported provider', () => {
|
||||
it('does not throw for unsupported provider', async () => {
|
||||
const { showCurrentConfig } = modelConfig;
|
||||
assert.doesNotThrow(() => showCurrentConfig('codex'));
|
||||
await assert.doesNotReject(async () => showCurrentConfig('codex'));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user