mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-30 14:23:37 +00:00
refactor(cliproxy): remove model alias functionality
- delete model-alias-config.ts and cliproxy-alias-handler.ts - simplify sync to use ANTHROPIC_MODEL directly - remove alias routes, hooks, and UI components
This commit is contained in:
@@ -180,7 +180,7 @@ export type {
|
|||||||
export { ManagementApiClient, createManagementClient } from './management-api-client';
|
export { ManagementApiClient, createManagementClient } from './management-api-client';
|
||||||
|
|
||||||
// Sync module (profile sync to remote CLIProxy)
|
// Sync module (profile sync to remote CLIProxy)
|
||||||
export type { SyncableProfile, SyncPreviewItem, ModelAlias, ModelAliasConfig } from './sync';
|
export type { SyncableProfile, SyncPreviewItem } from './sync';
|
||||||
export {
|
export {
|
||||||
loadSyncableProfiles,
|
loadSyncableProfiles,
|
||||||
mapProfileToClaudeKey,
|
mapProfileToClaudeKey,
|
||||||
@@ -188,12 +188,4 @@ export {
|
|||||||
generateSyncPreview,
|
generateSyncPreview,
|
||||||
getSyncableProfileCount,
|
getSyncableProfileCount,
|
||||||
isProfileSyncable,
|
isProfileSyncable,
|
||||||
getModelAliasesPath,
|
|
||||||
loadModelAliases,
|
|
||||||
saveModelAliases,
|
|
||||||
getProfileAliases,
|
|
||||||
addProfileAlias,
|
|
||||||
removeProfileAlias,
|
|
||||||
listAllAliases,
|
|
||||||
DEFAULT_MODEL_ALIASES,
|
|
||||||
} from './sync';
|
} from './sync';
|
||||||
|
|||||||
@@ -15,19 +15,6 @@ export {
|
|||||||
isProfileSyncable,
|
isProfileSyncable,
|
||||||
} from './profile-mapper';
|
} from './profile-mapper';
|
||||||
|
|
||||||
// Model alias config
|
|
||||||
export type { ModelAlias, ModelAliasConfig } from './model-alias-config';
|
|
||||||
export {
|
|
||||||
getModelAliasesPath,
|
|
||||||
loadModelAliases,
|
|
||||||
saveModelAliases,
|
|
||||||
getProfileAliases,
|
|
||||||
addProfileAlias,
|
|
||||||
removeProfileAlias,
|
|
||||||
listAllAliases,
|
|
||||||
DEFAULT_MODEL_ALIASES,
|
|
||||||
} from './model-alias-config';
|
|
||||||
|
|
||||||
// Local config sync
|
// Local config sync
|
||||||
export { syncToLocalConfig, getLocalSyncStatus } from './local-config-sync';
|
export { syncToLocalConfig, getLocalSyncStatus } from './local-config-sync';
|
||||||
|
|
||||||
|
|||||||
@@ -122,10 +122,11 @@ function transformToConfigFormat(key: ClaudeKey): Record<string, unknown> {
|
|||||||
// Add empty proxy-url (required by CLIProxyAPI)
|
// Add empty proxy-url (required by CLIProxyAPI)
|
||||||
entry['proxy-url'] = '';
|
entry['proxy-url'] = '';
|
||||||
|
|
||||||
|
// Use model name directly (no alias mapping)
|
||||||
if (key.models && key.models.length > 0) {
|
if (key.models && key.models.length > 0) {
|
||||||
entry.models = key.models.map((m) => ({
|
entry.models = key.models.map((m) => ({
|
||||||
name: m.name,
|
name: m.name,
|
||||||
alias: m.alias || '',
|
alias: '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
/**
|
|
||||||
* Model Alias Configuration
|
|
||||||
*
|
|
||||||
* Manages model alias mappings for CLIProxy sync.
|
|
||||||
* Aliases map Claude model names to provider-specific models.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
import { getCcsDir } from '../../utils/config-manager';
|
|
||||||
|
|
||||||
/** Model alias mapping */
|
|
||||||
export interface ModelAlias {
|
|
||||||
/** Claude model name (e.g., "claude-3-5-sonnet") */
|
|
||||||
from: string;
|
|
||||||
/** Target model (e.g., "glm-4.7-airx-thinking") */
|
|
||||||
to: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Model alias configuration file structure */
|
|
||||||
export interface ModelAliasConfig {
|
|
||||||
/** Version for future schema changes */
|
|
||||||
version: number;
|
|
||||||
/** Aliases for each profile name */
|
|
||||||
aliases: Record<string, ModelAlias[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Default model aliases (common mappings) */
|
|
||||||
export const DEFAULT_MODEL_ALIASES: Record<string, ModelAlias[]> = {
|
|
||||||
glm: [
|
|
||||||
{ from: 'claude-sonnet-4-20250514', to: 'glm-4.7-thinking' },
|
|
||||||
{ from: 'claude-3-5-sonnet-20241022', to: 'glm-4.7' },
|
|
||||||
{ from: 'claude-3-5-haiku-20241022', to: 'glm-4.7-flash' },
|
|
||||||
],
|
|
||||||
kimi: [
|
|
||||||
{ from: 'claude-sonnet-4-20250514', to: 'moonshot-v1-auto' },
|
|
||||||
{ from: 'claude-3-5-sonnet-20241022', to: 'moonshot-v1-128k' },
|
|
||||||
{ from: 'claude-3-5-haiku-20241022', to: 'moonshot-v1-32k' },
|
|
||||||
],
|
|
||||||
qwen: [
|
|
||||||
{ from: 'claude-sonnet-4-20250514', to: 'qwen-coder-plus' },
|
|
||||||
{ from: 'claude-3-5-sonnet-20241022', to: 'qwen-plus' },
|
|
||||||
{ from: 'claude-3-5-haiku-20241022', to: 'qwen-turbo' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get path to model aliases config file.
|
|
||||||
*/
|
|
||||||
export function getModelAliasesPath(): string {
|
|
||||||
return path.join(getCcsDir(), 'cliproxy', 'model-aliases.json');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load model alias configuration.
|
|
||||||
* Returns defaults if file doesn't exist.
|
|
||||||
*/
|
|
||||||
export function loadModelAliases(): ModelAliasConfig {
|
|
||||||
const aliasPath = getModelAliasesPath();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (fs.existsSync(aliasPath)) {
|
|
||||||
const content = fs.readFileSync(aliasPath, 'utf8');
|
|
||||||
const config = JSON.parse(content) as ModelAliasConfig;
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Fall through to defaults
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return defaults
|
|
||||||
return {
|
|
||||||
version: 1,
|
|
||||||
aliases: { ...DEFAULT_MODEL_ALIASES },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save model alias configuration.
|
|
||||||
* @returns Success status with optional error message
|
|
||||||
*/
|
|
||||||
export function saveModelAliases(config: ModelAliasConfig): { success: boolean; error?: string } {
|
|
||||||
try {
|
|
||||||
const aliasPath = getModelAliasesPath();
|
|
||||||
const dir = path.dirname(aliasPath);
|
|
||||||
|
|
||||||
if (!fs.existsSync(dir)) {
|
|
||||||
fs.mkdirSync(dir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(aliasPath, JSON.stringify(config, null, 2), 'utf8');
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get aliases for a specific profile.
|
|
||||||
*/
|
|
||||||
export function getProfileAliases(profileName: string): ModelAlias[] {
|
|
||||||
const config = loadModelAliases();
|
|
||||||
return config.aliases[profileName] ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an alias for a profile.
|
|
||||||
*/
|
|
||||||
export function addProfileAlias(profileName: string, from: string, to: string): void {
|
|
||||||
// Validate inputs
|
|
||||||
if (!from || from.trim() === '') {
|
|
||||||
throw new Error('Model alias "from" cannot be empty');
|
|
||||||
}
|
|
||||||
if (!to || to.trim() === '') {
|
|
||||||
throw new Error('Model alias "to" cannot be empty');
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = loadModelAliases();
|
|
||||||
|
|
||||||
if (!config.aliases[profileName]) {
|
|
||||||
config.aliases[profileName] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if alias already exists (update if so)
|
|
||||||
const existingIdx = config.aliases[profileName].findIndex((a) => a.from === from);
|
|
||||||
if (existingIdx >= 0) {
|
|
||||||
config.aliases[profileName][existingIdx].to = to;
|
|
||||||
} else {
|
|
||||||
config.aliases[profileName].push({ from, to });
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = saveModelAliases(config);
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(`Failed to save model aliases: ${result.error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an alias for a profile.
|
|
||||||
*/
|
|
||||||
export function removeProfileAlias(profileName: string, from: string): boolean {
|
|
||||||
const config = loadModelAliases();
|
|
||||||
|
|
||||||
if (!config.aliases[profileName]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialLen = config.aliases[profileName].length;
|
|
||||||
config.aliases[profileName] = config.aliases[profileName].filter((a) => a.from !== from);
|
|
||||||
|
|
||||||
if (config.aliases[profileName].length === initialLen) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove empty profile entry
|
|
||||||
if (config.aliases[profileName].length === 0) {
|
|
||||||
delete config.aliases[profileName];
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = saveModelAliases(config);
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(`Failed to save model aliases: ${result.error}`);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all aliases.
|
|
||||||
*/
|
|
||||||
export function listAllAliases(): Record<string, ModelAlias[]> {
|
|
||||||
const config = loadModelAliases();
|
|
||||||
return config.aliases;
|
|
||||||
}
|
|
||||||
@@ -2,15 +2,13 @@
|
|||||||
* Profile Mapper for CLIProxy Sync
|
* Profile Mapper for CLIProxy Sync
|
||||||
*
|
*
|
||||||
* Transforms CCS settings-based profiles into CLIProxy ClaudeKey format.
|
* Transforms CCS settings-based profiles into CLIProxy ClaudeKey format.
|
||||||
* Handles model alias mapping and settings normalization.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { getCcsDir } from '../../utils/config-manager';
|
import { getCcsDir } from '../../utils/config-manager';
|
||||||
import { listApiProfiles, isApiProfileConfigured } from '../../api/services/profile-reader';
|
import { listApiProfiles, isApiProfileConfigured } from '../../api/services/profile-reader';
|
||||||
import type { ClaudeKey, ClaudeModel } from '../management-api-types';
|
import type { ClaudeKey } from '../management-api-types';
|
||||||
import { getProfileAliases, type ModelAlias } from './model-alias-config';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profile info with settings for sync.
|
* Profile info with settings for sync.
|
||||||
@@ -80,16 +78,6 @@ export function loadSyncableProfiles(): SyncableProfile[] {
|
|||||||
return syncable;
|
return syncable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Map model aliases to ClaudeModel format.
|
|
||||||
*/
|
|
||||||
function mapAliasesToClaudeModels(aliases: ModelAlias[]): ClaudeModel[] {
|
|
||||||
return aliases.map((alias) => ({
|
|
||||||
name: alias.from,
|
|
||||||
alias: alias.to,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitize profile name for YAML safety.
|
* Sanitize profile name for YAML safety.
|
||||||
* Replaces non-alphanumeric chars (except - and _) with hyphens.
|
* Replaces non-alphanumeric chars (except - and _) with hyphens.
|
||||||
@@ -109,6 +97,7 @@ export function mapProfileToClaudeKey(profile: SyncableProfile): ClaudeKey | nul
|
|||||||
if (!apiKey) return null;
|
if (!apiKey) return null;
|
||||||
|
|
||||||
const baseUrl = env.ANTHROPIC_BASE_URL;
|
const baseUrl = env.ANTHROPIC_BASE_URL;
|
||||||
|
const modelName = env.ANTHROPIC_MODEL;
|
||||||
|
|
||||||
// Generate prefix from profile name (e.g., "glm" -> "glm-")
|
// Generate prefix from profile name (e.g., "glm" -> "glm-")
|
||||||
const sanitizedName = sanitizeProfileName(profile.name);
|
const sanitizedName = sanitizeProfileName(profile.name);
|
||||||
@@ -117,10 +106,6 @@ export function mapProfileToClaudeKey(profile: SyncableProfile): ClaudeKey | nul
|
|||||||
}
|
}
|
||||||
const prefix = `${sanitizedName}-`;
|
const prefix = `${sanitizedName}-`;
|
||||||
|
|
||||||
// Load model aliases for this profile
|
|
||||||
const aliases = getProfileAliases(profile.name);
|
|
||||||
const models = aliases.length > 0 ? mapAliasesToClaudeModels(aliases) : undefined;
|
|
||||||
|
|
||||||
const claudeKey: ClaudeKey = {
|
const claudeKey: ClaudeKey = {
|
||||||
'api-key': apiKey,
|
'api-key': apiKey,
|
||||||
prefix,
|
prefix,
|
||||||
@@ -130,8 +115,14 @@ export function mapProfileToClaudeKey(profile: SyncableProfile): ClaudeKey | nul
|
|||||||
claudeKey['base-url'] = baseUrl;
|
claudeKey['base-url'] = baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (models && models.length > 0) {
|
// Use model name directly from profile (no alias mapping)
|
||||||
claudeKey.models = models;
|
if (modelName) {
|
||||||
|
claudeKey.models = [
|
||||||
|
{
|
||||||
|
name: modelName,
|
||||||
|
alias: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return claudeKey;
|
return claudeKey;
|
||||||
@@ -164,10 +155,8 @@ export interface SyncPreviewItem {
|
|||||||
name: string;
|
name: string;
|
||||||
/** Base URL (masked) */
|
/** Base URL (masked) */
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
/** Whether profile has model aliases */
|
/** Model name */
|
||||||
hasAliases: boolean;
|
modelName?: string;
|
||||||
/** Number of model aliases */
|
|
||||||
aliasCount: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateSyncPreview(): SyncPreviewItem[] {
|
export function generateSyncPreview(): SyncPreviewItem[] {
|
||||||
@@ -175,13 +164,10 @@ export function generateSyncPreview(): SyncPreviewItem[] {
|
|||||||
const preview: SyncPreviewItem[] = [];
|
const preview: SyncPreviewItem[] = [];
|
||||||
|
|
||||||
for (const profile of profiles) {
|
for (const profile of profiles) {
|
||||||
const aliases = getProfileAliases(profile.name);
|
|
||||||
|
|
||||||
preview.push({
|
preview.push({
|
||||||
name: profile.name,
|
name: profile.name,
|
||||||
baseUrl: profile.env?.ANTHROPIC_BASE_URL,
|
baseUrl: profile.env?.ANTHROPIC_BASE_URL,
|
||||||
hasAliases: aliases.length > 0,
|
modelName: profile.env?.ANTHROPIC_MODEL,
|
||||||
aliasCount: aliases.length,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,226 +0,0 @@
|
|||||||
/**
|
|
||||||
* CLIProxy Alias Command Handler
|
|
||||||
*
|
|
||||||
* Handles `ccs cliproxy alias` commands for managing model alias mappings.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { addProfileAlias, removeProfileAlias, listAllAliases } from '../cliproxy/sync';
|
|
||||||
import { initUI, header, subheader, color, dim, ok, fail, warn, info, table } from '../utils/ui';
|
|
||||||
|
|
||||||
interface AliasArgs {
|
|
||||||
subcommand: 'add' | 'remove' | 'list' | 'help' | null;
|
|
||||||
profile?: string;
|
|
||||||
from?: string;
|
|
||||||
to?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse alias command arguments.
|
|
||||||
*/
|
|
||||||
export function parseAliasArgs(args: string[]): AliasArgs {
|
|
||||||
const rawCommand = args[0];
|
|
||||||
|
|
||||||
if (!rawCommand || rawCommand === 'help' || args.includes('--help')) {
|
|
||||||
return { subcommand: 'help' };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawCommand === 'list' || rawCommand === 'ls') {
|
|
||||||
return { subcommand: 'list', profile: args[1] };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawCommand === 'add') {
|
|
||||||
// ccs cliproxy alias add <profile> <from> <to>
|
|
||||||
return {
|
|
||||||
subcommand: 'add',
|
|
||||||
profile: args[1],
|
|
||||||
from: args[2],
|
|
||||||
to: args[3],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawCommand === 'remove' || rawCommand === 'rm' || rawCommand === 'delete') {
|
|
||||||
// ccs cliproxy alias remove <profile> <from>
|
|
||||||
return {
|
|
||||||
subcommand: 'remove',
|
|
||||||
profile: args[1],
|
|
||||||
from: args[2],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { subcommand: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show alias command help.
|
|
||||||
*/
|
|
||||||
async function showAliasHelp(): Promise<void> {
|
|
||||||
await initUI();
|
|
||||||
console.log('');
|
|
||||||
console.log(header('Model Alias Management'));
|
|
||||||
console.log('');
|
|
||||||
console.log(subheader('Usage:'));
|
|
||||||
console.log(` ${color('ccs cliproxy alias', 'command')} <command> [args]`);
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
console.log(subheader('Commands:'));
|
|
||||||
const commands: [string, string][] = [
|
|
||||||
['list [profile]', 'List all aliases (or for specific profile)'],
|
|
||||||
['add <profile> <from> <to>', 'Add model alias mapping'],
|
|
||||||
['remove <profile> <from>', 'Remove model alias mapping'],
|
|
||||||
];
|
|
||||||
|
|
||||||
const maxLen = Math.max(...commands.map(([cmd]) => cmd.length));
|
|
||||||
for (const [cmd, desc] of commands) {
|
|
||||||
console.log(` ${color(cmd.padEnd(maxLen + 2), 'command')} ${desc}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('');
|
|
||||||
console.log(subheader('Examples:'));
|
|
||||||
console.log(` ${dim('# List all aliases')}`);
|
|
||||||
console.log(` ${color('ccs cliproxy alias list', 'command')}`);
|
|
||||||
console.log('');
|
|
||||||
console.log(` ${dim('# Add alias for GLM profile')}`);
|
|
||||||
console.log(
|
|
||||||
` ${color('ccs cliproxy alias add glm claude-sonnet-4-20250514 glm-4.7-thinking', 'command')}`
|
|
||||||
);
|
|
||||||
console.log('');
|
|
||||||
console.log(` ${dim('# Remove alias')}`);
|
|
||||||
console.log(` ${color('ccs cliproxy alias remove glm claude-sonnet-4-20250514', 'command')}`);
|
|
||||||
console.log('');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle `ccs cliproxy alias list` command.
|
|
||||||
*/
|
|
||||||
async function handleAliasList(profile?: string): Promise<void> {
|
|
||||||
await initUI();
|
|
||||||
const allAliases = listAllAliases();
|
|
||||||
|
|
||||||
if (Object.keys(allAliases).length === 0) {
|
|
||||||
console.log(info('No model aliases configured'));
|
|
||||||
console.log('');
|
|
||||||
console.log('Add aliases with:');
|
|
||||||
console.log(` ${color('ccs cliproxy alias add <profile> <from> <to>', 'command')}`);
|
|
||||||
console.log('');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter to specific profile if provided
|
|
||||||
const profiles = profile ? { [profile]: allAliases[profile] } : allAliases;
|
|
||||||
|
|
||||||
if (profile && !allAliases[profile]) {
|
|
||||||
console.log(warn(`No aliases found for profile: ${profile}`));
|
|
||||||
console.log('');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(header('Model Aliases'));
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
for (const [profileName, aliases] of Object.entries(profiles)) {
|
|
||||||
if (!aliases || aliases.length === 0) continue;
|
|
||||||
|
|
||||||
console.log(subheader(profileName));
|
|
||||||
|
|
||||||
const rows = aliases.map((a) => [a.from, color('->', 'info'), a.to]);
|
|
||||||
console.log(
|
|
||||||
table(rows, { head: ['Claude Model', '', 'Target Model'], colWidths: [35, 4, 30] })
|
|
||||||
);
|
|
||||||
console.log('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle `ccs cliproxy alias add` command.
|
|
||||||
*/
|
|
||||||
async function handleAliasAdd(profile: string, from: string, to: string): Promise<void> {
|
|
||||||
await initUI();
|
|
||||||
|
|
||||||
if (!profile || !from || !to) {
|
|
||||||
console.log(fail('Missing required arguments'));
|
|
||||||
console.log('');
|
|
||||||
console.log('Usage:');
|
|
||||||
console.log(` ${color('ccs cliproxy alias add <profile> <from> <to>', 'command')}`);
|
|
||||||
console.log('');
|
|
||||||
console.log('Example:');
|
|
||||||
console.log(
|
|
||||||
` ${color('ccs cliproxy alias add glm claude-sonnet-4-20250514 glm-4.7-thinking', 'command')}`
|
|
||||||
);
|
|
||||||
console.log('');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
addProfileAlias(profile, from, to);
|
|
||||||
|
|
||||||
console.log(ok(`Added alias for ${profile}`));
|
|
||||||
console.log(` ${from} -> ${to}`);
|
|
||||||
console.log('');
|
|
||||||
console.log(info('Run sync to apply changes:'));
|
|
||||||
console.log(` ${color('ccs cliproxy sync', 'command')}`);
|
|
||||||
console.log('');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle `ccs cliproxy alias remove` command.
|
|
||||||
*/
|
|
||||||
async function handleAliasRemove(profile: string, from: string): Promise<void> {
|
|
||||||
await initUI();
|
|
||||||
|
|
||||||
if (!profile || !from) {
|
|
||||||
console.log(fail('Missing required arguments'));
|
|
||||||
console.log('');
|
|
||||||
console.log('Usage:');
|
|
||||||
console.log(` ${color('ccs cliproxy alias remove <profile> <from>', 'command')}`);
|
|
||||||
console.log('');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const removed = removeProfileAlias(profile, from);
|
|
||||||
|
|
||||||
if (!removed) {
|
|
||||||
console.log(warn(`Alias not found: ${profile}/${from}`));
|
|
||||||
console.log('');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(ok(`Removed alias from ${profile}`));
|
|
||||||
console.log(` ${from}`);
|
|
||||||
console.log('');
|
|
||||||
console.log(info('Run sync to apply changes:'));
|
|
||||||
console.log(` ${color('ccs cliproxy sync', 'command')}`);
|
|
||||||
console.log('');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle `ccs cliproxy alias` command router.
|
|
||||||
*/
|
|
||||||
export async function handleAlias(args: string[]): Promise<void> {
|
|
||||||
const parsed = parseAliasArgs(args);
|
|
||||||
|
|
||||||
switch (parsed.subcommand) {
|
|
||||||
case 'list':
|
|
||||||
await handleAliasList(parsed.profile);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'add':
|
|
||||||
if (parsed.profile && parsed.from && parsed.to) {
|
|
||||||
await handleAliasAdd(parsed.profile, parsed.from, parsed.to);
|
|
||||||
} else {
|
|
||||||
await handleAliasAdd('', '', ''); // Will show error message
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'remove':
|
|
||||||
if (parsed.profile && parsed.from) {
|
|
||||||
await handleAliasRemove(parsed.profile, parsed.from);
|
|
||||||
} else {
|
|
||||||
await handleAliasRemove('', ''); // Will show error message
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'help':
|
|
||||||
default:
|
|
||||||
await showAliasHelp();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -64,9 +64,8 @@ import {
|
|||||||
installLatest,
|
installLatest,
|
||||||
} from '../cliproxy/services';
|
} from '../cliproxy/services';
|
||||||
|
|
||||||
// Import sync and alias handlers
|
// Import sync handler
|
||||||
import { handleSync } from './cliproxy-sync-handler';
|
import { handleSync } from './cliproxy-sync-handler';
|
||||||
import { handleAlias } from './cliproxy-alias-handler';
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// ARGUMENT PARSING
|
// ARGUMENT PARSING
|
||||||
@@ -623,9 +622,6 @@ async function showHelp(): Promise<void> {
|
|||||||
['sync', 'Sync API profiles to remote CLIProxy'],
|
['sync', 'Sync API profiles to remote CLIProxy'],
|
||||||
['sync --dry-run', 'Preview sync without applying'],
|
['sync --dry-run', 'Preview sync without applying'],
|
||||||
['sync --force', 'Sync without confirmation prompt'],
|
['sync --force', 'Sync without confirmation prompt'],
|
||||||
['alias list', 'List model alias mappings'],
|
|
||||||
['alias add <profile> <from> <to>', 'Add model alias'],
|
|
||||||
['alias remove <profile> <from>', 'Remove model alias'],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -1024,11 +1020,6 @@ export async function handleCliproxyCommand(args: string[]): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command === 'alias') {
|
|
||||||
await handleAlias(remainingArgs.slice(1));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command === 'stop') {
|
if (command === 'stop') {
|
||||||
await handleStop();
|
await handleStop();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -61,12 +61,12 @@ export async function handleSync(args: string[]): Promise<void> {
|
|||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
const rows = preview.map((p) => {
|
const rows = preview.map((p) => {
|
||||||
const aliases = p.hasAliases ? color(`${p.aliasCount} aliases`, 'info') : dim('none');
|
const model = p.modelName ? color(p.modelName, 'info') : dim('default');
|
||||||
const url = p.baseUrl ? dim(p.baseUrl) : dim('-');
|
const url = p.baseUrl ? dim(p.baseUrl) : dim('-');
|
||||||
return [p.name, url, aliases];
|
return [p.name, url, model];
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(table(rows, { head: ['Profile', 'Base URL', 'Aliases'], colWidths: [15, 40, 15] }));
|
console.log(table(rows, { head: ['Profile', 'Base URL', 'Model'], colWidths: [15, 40, 20] }));
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
if (parsed.verbose) {
|
if (parsed.verbose) {
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
|||||||
import {
|
import {
|
||||||
generateSyncPayload,
|
generateSyncPayload,
|
||||||
generateSyncPreview,
|
generateSyncPreview,
|
||||||
listAllAliases,
|
|
||||||
addProfileAlias,
|
|
||||||
removeProfileAlias,
|
|
||||||
getAutoSyncStatus,
|
getAutoSyncStatus,
|
||||||
restartAutoSyncWatcher,
|
restartAutoSyncWatcher,
|
||||||
syncToLocalConfig,
|
syncToLocalConfig,
|
||||||
@@ -104,68 +101,6 @@ router.post('/', async (_req: Request, res: Response): Promise<void> => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ==================== Model Aliases ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/cliproxy/sync/aliases - List all model aliases
|
|
||||||
* Returns: { aliases: Record<string, ModelAlias[]> }
|
|
||||||
*/
|
|
||||||
router.get('/aliases', async (_req: Request, res: Response): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const aliases = listAllAliases();
|
|
||||||
res.json({ aliases });
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({ error: (error as Error).message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST /api/cliproxy/sync/aliases - Add a model alias
|
|
||||||
* Body: { profile: string, from: string, to: string }
|
|
||||||
* Returns: { success: true }
|
|
||||||
*/
|
|
||||||
router.post('/aliases', async (req: Request, res: Response): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const { profile, from, to } = req.body;
|
|
||||||
const trimmedProfile = typeof profile === 'string' ? profile.trim() : '';
|
|
||||||
const trimmedFrom = typeof from === 'string' ? from.trim() : '';
|
|
||||||
const trimmedTo = typeof to === 'string' ? to.trim() : '';
|
|
||||||
|
|
||||||
if (!trimmedProfile || !trimmedFrom || !trimmedTo) {
|
|
||||||
res.status(400).json({ error: 'Missing required fields: profile, from, to' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
addProfileAlias(trimmedProfile, trimmedFrom, trimmedTo);
|
|
||||||
res.json({ success: true, profile: trimmedProfile, from: trimmedFrom, to: trimmedTo });
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({ error: (error as Error).message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DELETE /api/cliproxy/sync/aliases - Remove a model alias
|
|
||||||
* Body: { profile: string, from: string }
|
|
||||||
* Returns: { success: boolean }
|
|
||||||
*/
|
|
||||||
router.delete('/aliases', async (req: Request, res: Response): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const { profile, from } = req.body;
|
|
||||||
const trimmedProfile = typeof profile === 'string' ? profile.trim() : '';
|
|
||||||
const trimmedFrom = typeof from === 'string' ? from.trim() : '';
|
|
||||||
|
|
||||||
if (!trimmedProfile || !trimmedFrom) {
|
|
||||||
res.status(400).json({ error: 'Missing required fields: profile, from' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const removed = removeProfileAlias(trimmedProfile, trimmedFrom);
|
|
||||||
res.json({ success: removed, profile: trimmedProfile, from: trimmedFrom });
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({ error: (error as Error).message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ==================== Auto-Sync ====================
|
// ==================== Auto-Sync ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
* Dialog for managing sync configuration, preview, and execution
|
* Dialog for managing sync configuration, preview, and execution
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -11,13 +10,12 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { Loader2, Upload, CheckCircle, AlertCircle, ArrowRight } from 'lucide-react';
|
import { Loader2, Upload, CheckCircle } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useSyncPreview, useExecuteSync, useSyncAliases } from '@/hooks/use-cliproxy-sync';
|
import { useSyncPreview, useExecuteSync } from '@/hooks/use-cliproxy-sync';
|
||||||
|
|
||||||
interface SyncDialogProps {
|
interface SyncDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -25,9 +23,7 @@ interface SyncDialogProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SyncDialog({ open, onOpenChange }: SyncDialogProps) {
|
export function SyncDialog({ open, onOpenChange }: SyncDialogProps) {
|
||||||
const [activeTab, setActiveTab] = useState('preview');
|
|
||||||
const { data: preview, isLoading: previewLoading } = useSyncPreview();
|
const { data: preview, isLoading: previewLoading } = useSyncPreview();
|
||||||
const { data: aliasData } = useSyncAliases();
|
|
||||||
const { mutate: executeSync, isPending: isSyncing, isSuccess, reset } = useExecuteSync();
|
const { mutate: executeSync, isPending: isSyncing, isSuccess, reset } = useExecuteSync();
|
||||||
|
|
||||||
const handleSync = () => {
|
const handleSync = () => {
|
||||||
@@ -56,132 +52,79 @@ export function SyncDialog({ open, onOpenChange }: SyncDialogProps) {
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="mt-4">
|
<div className="mt-4">
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
{previewLoading ? (
|
||||||
<TabsTrigger value="preview">Preview</TabsTrigger>
|
<div className="flex items-center justify-center py-8">
|
||||||
<TabsTrigger value="aliases">Model Aliases</TabsTrigger>
|
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsContent value="preview" className="mt-4">
|
|
||||||
{previewLoading ? (
|
|
||||||
<div className="flex items-center justify-center py-8">
|
|
||||||
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
) : preview?.count === 0 ? (
|
|
||||||
<div className="text-center py-8 text-muted-foreground">
|
|
||||||
<p>No profiles configured to sync.</p>
|
|
||||||
<p className="text-sm mt-2">Create API profiles first using the Profiles tab.</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<ScrollArea className="h-[300px] pr-4">
|
|
||||||
<div className="space-y-3">
|
|
||||||
{preview?.profiles.map((profile) => (
|
|
||||||
<div
|
|
||||||
key={profile.name}
|
|
||||||
className="flex items-center justify-between p-3 rounded-lg border bg-muted/30"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div className="font-medium">{profile.name}</div>
|
|
||||||
{profile.baseUrl && (
|
|
||||||
<div className="text-xs text-muted-foreground truncate max-w-[300px]">
|
|
||||||
{profile.baseUrl}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{profile.hasAliases && (
|
|
||||||
<Badge variant="secondary" className="text-xs">
|
|
||||||
{profile.aliasCount} alias{profile.aliasCount !== 1 ? 'es' : ''}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
Ready
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between mt-6 pt-4 border-t">
|
|
||||||
<div className="text-sm text-muted-foreground">
|
|
||||||
{preview?.count ?? 0} profile{(preview?.count ?? 0) !== 1 ? 's' : ''} to sync
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button variant="outline" onClick={handleClose}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleSync}
|
|
||||||
disabled={isSyncing || (preview?.count ?? 0) === 0}
|
|
||||||
className={cn('gap-2', isSuccess && 'bg-green-600 hover:bg-green-700')}
|
|
||||||
>
|
|
||||||
{isSyncing ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="w-4 h-4 animate-spin" />
|
|
||||||
Syncing...
|
|
||||||
</>
|
|
||||||
) : isSuccess ? (
|
|
||||||
<>
|
|
||||||
<CheckCircle className="w-4 h-4" />
|
|
||||||
Synced!
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Upload className="w-4 h-4" />
|
|
||||||
Sync Now
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
) : preview?.count === 0 ? (
|
||||||
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
<TabsContent value="aliases" className="mt-4">
|
<p>No profiles configured to sync.</p>
|
||||||
|
<p className="text-sm mt-2">Create API profiles first using the Profiles tab.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<ScrollArea className="h-[300px] pr-4">
|
<ScrollArea className="h-[300px] pr-4">
|
||||||
{!aliasData?.aliases || Object.keys(aliasData.aliases).length === 0 ? (
|
<div className="space-y-3">
|
||||||
<div className="text-center py-8 text-muted-foreground">
|
{preview?.profiles.map((profile) => (
|
||||||
<p>No model aliases configured.</p>
|
<div
|
||||||
<p className="text-sm mt-2">
|
key={profile.name}
|
||||||
Add aliases via CLI:{' '}
|
className="flex items-center justify-between p-3 rounded-lg border bg-muted/30"
|
||||||
<code className="bg-muted px-1 rounded">ccs cliproxy alias add</code>
|
>
|
||||||
</p>
|
<div className="flex-1">
|
||||||
</div>
|
<div className="font-medium">{profile.name}</div>
|
||||||
) : (
|
{profile.modelName && (
|
||||||
<div className="space-y-4">
|
<div className="text-xs text-muted-foreground truncate max-w-[300px]">
|
||||||
{Object.entries(aliasData.aliases).map(([profileName, aliases]) => (
|
Model: {profile.modelName}
|
||||||
<div key={profileName} className="space-y-2">
|
</div>
|
||||||
<div className="text-sm font-medium text-muted-foreground">{profileName}</div>
|
)}
|
||||||
<div className="space-y-1">
|
{profile.baseUrl && (
|
||||||
{aliases.map((alias) => (
|
<div className="text-xs text-muted-foreground truncate max-w-[300px]">
|
||||||
<div
|
{profile.baseUrl}
|
||||||
key={`${profileName}-${alias.from}`}
|
</div>
|
||||||
className="flex items-center gap-2 p-2 rounded border bg-muted/30 text-sm"
|
)}
|
||||||
>
|
|
||||||
<code className="flex-1 truncate">{alias.from}</code>
|
|
||||||
<ArrowRight className="w-4 h-4 text-muted-foreground" />
|
|
||||||
<code className="flex-1 truncate text-right">{alias.to}</code>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<Badge variant="outline" className="text-xs">
|
||||||
</div>
|
Ready
|
||||||
)}
|
</Badge>
|
||||||
</ScrollArea>
|
</div>
|
||||||
|
))}
|
||||||
<div className="mt-6 pt-4 border-t">
|
|
||||||
<div className="flex items-start gap-2 text-xs text-muted-foreground">
|
|
||||||
<AlertCircle className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
|
||||||
<p>
|
|
||||||
Model aliases map Claude model names to your provider's model names. Manage
|
|
||||||
aliases via CLI for now. UI editor coming soon.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between mt-6 pt-4 border-t">
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{preview?.count ?? 0} profile{(preview?.count ?? 0) !== 1 ? 's' : ''} to sync
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
<div className="flex gap-2">
|
||||||
</Tabs>
|
<Button variant="outline" onClick={handleClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSync}
|
||||||
|
disabled={isSyncing || (preview?.count ?? 0) === 0}
|
||||||
|
className={cn('gap-2', isSuccess && 'bg-green-600 hover:bg-green-700')}
|
||||||
|
>
|
||||||
|
{isSyncing ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
Syncing...
|
||||||
|
</>
|
||||||
|
) : isSuccess ? (
|
||||||
|
<>
|
||||||
|
<CheckCircle className="w-4 h-4" />
|
||||||
|
Synced!
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Upload className="w-4 h-4" />
|
||||||
|
Sync Now
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export function SyncStatusCard() {
|
|||||||
className="flex-1"
|
className="flex-1"
|
||||||
onClick={() => setDialogOpen(true)}
|
onClick={() => setDialogOpen(true)}
|
||||||
>
|
>
|
||||||
Aliases
|
Details
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ export interface SyncStatus {
|
|||||||
export interface SyncPreviewItem {
|
export interface SyncPreviewItem {
|
||||||
name: string;
|
name: string;
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
hasAliases: boolean;
|
modelName?: string;
|
||||||
aliasCount: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Masked payload item for preview */
|
/** Masked payload item for preview */
|
||||||
@@ -119,79 +118,6 @@ async function executeSync(): Promise<SyncResult> {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch model aliases from API
|
|
||||||
*/
|
|
||||||
async function fetchAliases(): Promise<AliasesResponse> {
|
|
||||||
const response = await fetch('/api/cliproxy/sync/aliases');
|
|
||||||
if (!response.ok) {
|
|
||||||
let message = 'Failed to fetch aliases';
|
|
||||||
try {
|
|
||||||
const error = await response.json();
|
|
||||||
message = error.error || error.message || message;
|
|
||||||
} catch {
|
|
||||||
// Non-JSON response (e.g., 502 Bad Gateway)
|
|
||||||
}
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a model alias
|
|
||||||
*/
|
|
||||||
async function addAlias(params: {
|
|
||||||
profile: string;
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
}): Promise<{ success: boolean }> {
|
|
||||||
const response = await fetch('/api/cliproxy/sync/aliases', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(params),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
let message = 'Failed to add alias';
|
|
||||||
try {
|
|
||||||
const error = await response.json();
|
|
||||||
message = error.error || error.message || message;
|
|
||||||
} catch {
|
|
||||||
// Non-JSON response (e.g., 502 Bad Gateway)
|
|
||||||
}
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a model alias
|
|
||||||
*/
|
|
||||||
async function removeAlias(params: {
|
|
||||||
profile: string;
|
|
||||||
from: string;
|
|
||||||
}): Promise<{ success: boolean }> {
|
|
||||||
const response = await fetch('/api/cliproxy/sync/aliases', {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(params),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
let message = 'Failed to remove alias';
|
|
||||||
try {
|
|
||||||
const error = await response.json();
|
|
||||||
message = error.error || error.message || message;
|
|
||||||
} catch {
|
|
||||||
// Non-JSON response (e.g., 502 Bad Gateway)
|
|
||||||
}
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to get sync status
|
* Hook to get sync status
|
||||||
*/
|
*/
|
||||||
@@ -232,45 +158,3 @@ export function useExecuteSync() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to get model aliases
|
|
||||||
*/
|
|
||||||
export function useSyncAliases() {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['cliproxy-sync-aliases'],
|
|
||||||
queryFn: fetchAliases,
|
|
||||||
staleTime: 30000,
|
|
||||||
retry: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to add a model alias
|
|
||||||
*/
|
|
||||||
export function useAddAlias() {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: addAlias,
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['cliproxy-sync-aliases'] });
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['cliproxy-sync-preview'] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to remove a model alias
|
|
||||||
*/
|
|
||||||
export function useRemoveAlias() {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: removeAlias,
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['cliproxy-sync-aliases'] });
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['cliproxy-sync-preview'] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user