mirror of
https://github.com/musistudio/claude-code-router.git
synced 2026-01-30 06:12:06 +00:00
remove ccrsets
This commit is contained in:
@@ -169,13 +169,13 @@ Each preset contains:
|
||||
|
||||
Located in `packages/shared/src/preset/`:
|
||||
|
||||
- **export.ts**: Export current configuration as a preset (.ccrsets file)
|
||||
- `exportPreset(presetName, config, options)`: Creates ZIP archive with manifest.json
|
||||
- **export.ts**: Export current configuration as a preset directory
|
||||
- `exportPreset(presetName, config, options)`: Creates preset directory with manifest.json
|
||||
- Automatically sanitizes sensitive data (api_key fields become `{{field}}` placeholders)
|
||||
|
||||
- **install.ts**: Install and manage presets
|
||||
- `installPreset(preset, config, options)`: Install preset to config
|
||||
- `loadPreset(source)`: Load preset from file, URL, or directory
|
||||
- `loadPreset(source)`: Load preset from directory
|
||||
- `listPresets()`: List all installed presets
|
||||
- `isPresetInstalled(presetName)`: Check if preset is installed
|
||||
- `validatePreset(preset)`: Validate preset structure
|
||||
@@ -189,7 +189,7 @@ Located in `packages/shared/src/preset/`:
|
||||
|
||||
### Preset File Format
|
||||
|
||||
**manifest.json** (in ZIP archive or extracted directory):
|
||||
**manifest.json** (in preset directory):
|
||||
```json
|
||||
{
|
||||
"name": "my-preset",
|
||||
|
||||
@@ -264,9 +264,8 @@ ccr preset export my-preset
|
||||
# Export with metadata
|
||||
ccr preset export my-preset --description "My OpenAI config" --author "Your Name" --tags "openai,production"
|
||||
|
||||
# Install a preset from file, URL, or registry
|
||||
ccr preset install my-preset.ccrsets
|
||||
ccr preset install https://example.com/preset.ccrsets
|
||||
# Install a preset from local directory
|
||||
ccr preset install /path/to/preset
|
||||
|
||||
# List all installed presets
|
||||
ccr preset list
|
||||
@@ -279,8 +278,8 @@ ccr preset delete my-preset
|
||||
```
|
||||
|
||||
**Preset Features:**
|
||||
- **Export**: Save your current configuration as a `.ccrsets` file (ZIP archive with manifest.json)
|
||||
- **Install**: Install presets from local files, URLs, or the preset registry
|
||||
- **Export**: Save your current configuration as a preset directory (with manifest.json)
|
||||
- **Install**: Install presets from local directories
|
||||
- **Sensitive Data Handling**: API keys and other sensitive data are automatically sanitized during export (marked as `{{field}}` placeholders)
|
||||
- **Dynamic Configuration**: Presets can include input schemas for collecting required information during installation
|
||||
- **Version Control**: Each preset includes version metadata for tracking updates
|
||||
|
||||
@@ -239,9 +239,8 @@ ccr preset export my-preset
|
||||
# 使用元数据导出
|
||||
ccr preset export my-preset --description "我的 OpenAI 配置" --author "您的名字" --tags "openai,生产环境"
|
||||
|
||||
# 从文件、URL 或注册表安装预设
|
||||
ccr preset install my-preset.ccrsets
|
||||
ccr preset install https://example.com/preset.ccrsets
|
||||
# 从本地目录安装预设
|
||||
ccr preset install /path/to/preset
|
||||
|
||||
# 列出所有已安装的预设
|
||||
ccr preset list
|
||||
@@ -254,8 +253,8 @@ ccr preset delete my-preset
|
||||
```
|
||||
|
||||
**预设功能:**
|
||||
- **导出**:将当前配置保存为 `.ccrsets` 文件(包含 manifest.json 的 ZIP 存档)
|
||||
- **安装**:从本地文件、URL 或预设注册表安装预设
|
||||
- **导出**:将当前配置保存为预设目录(包含 manifest.json)
|
||||
- **安装**:从本地目录安装预设
|
||||
- **敏感数据处理**:导出期间自动清理 API 密钥和其他敏感数据(标记为 `{{field}}` 占位符)
|
||||
- **动态配置**:预设可以包含输入架构,用于在安装期间收集所需信息
|
||||
- **版本控制**:每个预设包含版本元数据,用于跟踪更新
|
||||
|
||||
@@ -58,7 +58,7 @@ Commands:
|
||||
-h, help Show help information
|
||||
|
||||
Presets:
|
||||
Any preset-name defined in ~/.claude-code-router/presets/*.ccrsets
|
||||
Any preset directory in ~/.claude-code-router/presets/
|
||||
|
||||
Examples:
|
||||
ccr start
|
||||
@@ -66,7 +66,7 @@ Examples:
|
||||
ccr my-preset "Write a Hello World" # Use preset configuration
|
||||
ccr model
|
||||
ccr preset export my-config # Export current config as preset
|
||||
ccr preset install my-config.ccrsets # Install a preset
|
||||
ccr preset install /path/to/preset # Install a preset from directory
|
||||
ccr preset list # List all presets
|
||||
eval "$(ccr activate)" # Set environment variables globally
|
||||
ccr ui
|
||||
|
||||
@@ -72,7 +72,7 @@ export async function exportPresetCli(
|
||||
|
||||
// 4. Display summary
|
||||
console.log(`\n${BOLDGREEN}✓ Preset exported successfully${RESET}\n`);
|
||||
console.log(`${BOLDCYAN}Location:${RESET} ${result.outputPath}\n`);
|
||||
console.log(`${BOLDCYAN}Location:${RESET} ${result.presetDir}\n`);
|
||||
console.log(`${BOLDCYAN}Summary:${RESET}`);
|
||||
console.log(` - Providers: ${result.sanitizedConfig.Providers?.length || 0}`);
|
||||
console.log(` - Router rules: ${Object.keys(result.sanitizedConfig.Router || {}).length}`);
|
||||
@@ -93,9 +93,9 @@ export async function exportPresetCli(
|
||||
|
||||
// Display sharing tips
|
||||
console.log(`\n${BOLDCYAN}To share this preset:${RESET}`);
|
||||
console.log(` 1. Share the file: ${result.outputPath}`);
|
||||
console.log(` 1. Share the directory: ${result.presetDir}`);
|
||||
console.log(` 2. Upload to GitHub Gist or your repository`);
|
||||
console.log(` 3. Others can install with: ${GREEN}ccr preset install <file>${RESET}\n`);
|
||||
console.log(` 3. Others can install with: ${GREEN}ccr preset install <directory>${RESET}\n`);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error(`\n${YELLOW}Error exporting preset:${RESET} ${error.message}`);
|
||||
|
||||
@@ -13,8 +13,6 @@ import {
|
||||
readManifestFromDir,
|
||||
manifestToPresetFile,
|
||||
saveManifest,
|
||||
extractPreset,
|
||||
findPresetFile,
|
||||
isPresetInstalled,
|
||||
ManifestFile,
|
||||
PresetFile,
|
||||
@@ -151,53 +149,39 @@ export async function installPresetCli(
|
||||
try {
|
||||
// Determine preset name
|
||||
let presetName = options.name;
|
||||
let sourceZip: string | undefined;
|
||||
let sourceDir: string | undefined;
|
||||
let isReconfigure = false; // Whether to reconfigure installed preset
|
||||
|
||||
// Determine source type and get ZIP file path
|
||||
// Determine source type and get directory path
|
||||
if (source.startsWith('http://') || source.startsWith('https://')) {
|
||||
// URL: download to temp file
|
||||
if (!presetName) {
|
||||
const urlParts = source.split('/');
|
||||
const filename = urlParts[urlParts.length - 1];
|
||||
presetName = filename.replace('.ccrsets', '');
|
||||
}
|
||||
// downloadPresetToTemp imported from shared package will return temp file
|
||||
// but we'll auto-cleanup in loadPreset, so no need to handle here
|
||||
// Re-download to temp file for extractPreset usage
|
||||
// Since loadPreset already downloaded and deleted, special handling needed here
|
||||
throw new Error('URL installation not fully implemented yet');
|
||||
// URL installation not supported
|
||||
throw new Error('URL installation is not supported. Please download the preset directory and install from local path.');
|
||||
} else if (source.includes('/') || source.includes('\\')) {
|
||||
// File path
|
||||
// Directory path
|
||||
if (!presetName) {
|
||||
const filename = path.basename(source);
|
||||
presetName = filename.replace('.ccrsets', '');
|
||||
presetName = path.basename(source);
|
||||
}
|
||||
// Verify file exists
|
||||
// Verify directory exists
|
||||
try {
|
||||
await fs.access(source);
|
||||
const stats = await fs.stat(source);
|
||||
if (!stats.isDirectory()) {
|
||||
throw new Error(`Source is not a directory: ${source}`);
|
||||
}
|
||||
} catch {
|
||||
throw new Error(`Preset file not found: ${source}`);
|
||||
throw new Error(`Preset directory not found: ${source}`);
|
||||
}
|
||||
sourceZip = source;
|
||||
sourceDir = source;
|
||||
} else {
|
||||
// Preset name (without path)
|
||||
presetName = source;
|
||||
|
||||
// Search files by priority: current directory -> presets directory
|
||||
const presetFile = await findPresetFile(source);
|
||||
|
||||
if (presetFile) {
|
||||
sourceZip = presetFile;
|
||||
// Check if already installed (directory exists)
|
||||
if (await isPresetInstalled(source)) {
|
||||
// Already installed, reconfigure
|
||||
isReconfigure = true;
|
||||
} else {
|
||||
// Check if already installed (directory exists)
|
||||
if (await isPresetInstalled(source)) {
|
||||
// Already installed, reconfigure
|
||||
isReconfigure = true;
|
||||
} else {
|
||||
// Neither exists, error
|
||||
throw new Error(`Preset '${source}' not found in current directory or presets directory.`);
|
||||
}
|
||||
// Not found, error
|
||||
throw new Error(`Preset '${source}' not found. Please provide a valid preset directory path.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,17 +196,16 @@ export async function installPresetCli(
|
||||
// Apply preset (will ask for sensitive info)
|
||||
await applyPresetCli(presetName, preset);
|
||||
} else {
|
||||
// New installation: extract to target directory
|
||||
if (!sourceZip) {
|
||||
throw new Error('Source ZIP file is required for installation');
|
||||
// New installation: read from source directory
|
||||
if (!sourceDir) {
|
||||
throw new Error('Source directory is required for installation');
|
||||
}
|
||||
const targetDir = getPresetDir(presetName);
|
||||
console.log(`${BOLDCYAN}Extracting preset to:${RESET} ${targetDir}`);
|
||||
await extractPreset(sourceZip, targetDir);
|
||||
console.log(`${GREEN}✓${RESET} Extracted successfully\n`);
|
||||
|
||||
// Read manifest from extracted directory
|
||||
const manifest = await readManifestFromDir(targetDir);
|
||||
console.log(`${BOLDCYAN}Reading preset from:${RESET} ${sourceDir}`);
|
||||
console.log(`${GREEN}✓${RESET} Read successfully\n`);
|
||||
|
||||
// Read manifest from source directory
|
||||
const manifest = await readManifestFromDir(sourceDir);
|
||||
const preset = manifestToPresetFile(manifest);
|
||||
|
||||
// Apply preset (ask user info, etc.)
|
||||
|
||||
@@ -8,14 +8,14 @@ import {
|
||||
getPresetDir,
|
||||
readManifestFromDir,
|
||||
manifestToPresetFile,
|
||||
extractPreset,
|
||||
saveManifest,
|
||||
isPresetInstalled,
|
||||
downloadPresetToTemp,
|
||||
getTempDir,
|
||||
extractPreset,
|
||||
HOME_DIR,
|
||||
extractMetadata,
|
||||
loadConfigFromManifest,
|
||||
downloadPresetToTemp,
|
||||
getTempDir,
|
||||
type PresetFile,
|
||||
type ManifestFile,
|
||||
type PresetMetadata,
|
||||
@@ -288,103 +288,6 @@ export const createServer = async (config: any): Promise<any> => {
|
||||
}
|
||||
});
|
||||
|
||||
// Upload and install preset (supports file upload)
|
||||
app.post("/api/presets/install", async (req: any, reply: any) => {
|
||||
try {
|
||||
const { source, name, url } = req.body;
|
||||
|
||||
// If URL is provided, download from URL
|
||||
if (url) {
|
||||
const tempFile = await downloadPresetToTemp(url);
|
||||
const preset = await loadPresetFromZip(tempFile);
|
||||
|
||||
// Determine preset name
|
||||
const presetName = name || preset.metadata?.name || `preset-${Date.now()}`;
|
||||
|
||||
// Check if already installed
|
||||
if (await isPresetInstalled(presetName)) {
|
||||
reply.status(409).send({ error: "Preset already installed" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract to target directory
|
||||
const targetDir = getPresetDir(presetName);
|
||||
await extractPreset(tempFile, targetDir);
|
||||
|
||||
// Clean up temp file
|
||||
unlinkSync(tempFile);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
presetName,
|
||||
preset: {
|
||||
...preset.metadata,
|
||||
installed: true,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// If no URL, need to handle file upload (using multipart/form-data)
|
||||
// This part requires FormData upload on client side
|
||||
reply.status(400).send({ error: "Please provide a URL or upload a file" });
|
||||
} catch (error: any) {
|
||||
console.error("Failed to install preset:", error);
|
||||
reply.status(500).send({ error: error.message || "Failed to install preset" });
|
||||
}
|
||||
});
|
||||
|
||||
// Upload preset file (multipart/form-data)
|
||||
app.post("/api/presets/upload", async (req: any, reply: any) => {
|
||||
try {
|
||||
const data = await req.file();
|
||||
if (!data) {
|
||||
reply.status(400).send({ error: "No file uploaded" });
|
||||
return;
|
||||
}
|
||||
|
||||
const tempDir = getTempDir();
|
||||
mkdirSync(tempDir, { recursive: true });
|
||||
|
||||
const tempFile = join(tempDir, `preset-${Date.now()}.ccrsets`);
|
||||
|
||||
// Save uploaded file to temp location
|
||||
const buffer = await data.toBuffer();
|
||||
writeFileSync(tempFile, buffer);
|
||||
|
||||
// Load preset
|
||||
const preset = await loadPresetFromZip(tempFile);
|
||||
|
||||
// Determine preset name
|
||||
const presetName = data.fields.name?.value || preset.metadata?.name || `preset-${Date.now()}`;
|
||||
|
||||
// Check if already installed
|
||||
if (await isPresetInstalled(presetName)) {
|
||||
unlinkSync(tempFile);
|
||||
reply.status(409).send({ error: "Preset already installed" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract to target directory
|
||||
const targetDir = getPresetDir(presetName);
|
||||
await extractPreset(tempFile, targetDir);
|
||||
|
||||
// Clean up temp file
|
||||
unlinkSync(tempFile);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
presetName,
|
||||
preset: {
|
||||
...preset.metadata,
|
||||
installed: true,
|
||||
}
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error("Failed to upload preset:", error);
|
||||
reply.status(500).send({ error: error.message || "Failed to upload preset" });
|
||||
}
|
||||
});
|
||||
|
||||
// Apply preset (configure sensitive information)
|
||||
app.post("/api/presets/:name/apply", async (req: any, reply: any) => {
|
||||
try {
|
||||
|
||||
@@ -4,18 +4,15 @@
|
||||
*/
|
||||
|
||||
import * as fs from 'fs/promises';
|
||||
import * as fsSync from 'fs';
|
||||
import * as path from 'path';
|
||||
import archiver from 'archiver';
|
||||
import { sanitizeConfig } from './sensitiveFields';
|
||||
import { PresetFile, PresetMetadata, ManifestFile } from './types';
|
||||
import { PresetMetadata, ManifestFile } from './types';
|
||||
import { HOME_DIR } from '../constants';
|
||||
|
||||
/**
|
||||
* Export options
|
||||
*/
|
||||
export interface ExportOptions {
|
||||
output?: string;
|
||||
includeSensitive?: boolean;
|
||||
description?: string;
|
||||
author?: string;
|
||||
@@ -26,7 +23,7 @@ export interface ExportOptions {
|
||||
* Export result
|
||||
*/
|
||||
export interface ExportResult {
|
||||
outputPath: string;
|
||||
presetDir: string;
|
||||
sanitizedConfig: any;
|
||||
metadata: PresetMetadata;
|
||||
requiredInputs: any[];
|
||||
@@ -93,42 +90,31 @@ export async function exportPreset(
|
||||
requiredInputs: options.includeSensitive ? undefined : requiredInputs,
|
||||
};
|
||||
|
||||
// 4. Determine output path
|
||||
const presetsDir = path.join(HOME_DIR, 'presets');
|
||||
// 4. Create preset directory
|
||||
const presetDir = path.join(HOME_DIR, 'presets', presetName);
|
||||
|
||||
// Ensure presets directory exists
|
||||
await fs.mkdir(presetsDir, { recursive: true });
|
||||
// Check if preset directory already exists
|
||||
try {
|
||||
await fs.access(presetDir);
|
||||
throw new Error(`Preset directory already exists: ${presetName}`);
|
||||
} catch (error: any) {
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const outputPath = options.output || path.join(presetsDir, `${presetName}.ccrsets`);
|
||||
// Create preset directory
|
||||
await fs.mkdir(presetDir, { recursive: true });
|
||||
|
||||
// 5. Create archive
|
||||
const output = fsSync.createWriteStream(outputPath);
|
||||
const archive = archiver('zip', {
|
||||
zlib: { level: 9 } // Highest compression level
|
||||
});
|
||||
// 5. Write manifest.json to preset directory
|
||||
const manifestPath = path.join(presetDir, 'manifest.json');
|
||||
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
||||
|
||||
return new Promise<ExportResult>((resolve, reject) => {
|
||||
output.on('close', () => {
|
||||
resolve({
|
||||
outputPath,
|
||||
sanitizedConfig,
|
||||
metadata,
|
||||
requiredInputs,
|
||||
sanitizedCount,
|
||||
});
|
||||
});
|
||||
|
||||
archive.on('error', (err: Error) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
// Connect output stream
|
||||
archive.pipe(output);
|
||||
|
||||
// Add manifest.json to archive
|
||||
archive.append(JSON.stringify(manifest, null, 2), { name: 'manifest.json' });
|
||||
|
||||
// Finalize archive
|
||||
archive.finalize();
|
||||
});
|
||||
return {
|
||||
presetDir,
|
||||
sanitizedConfig,
|
||||
metadata,
|
||||
requiredInputs,
|
||||
sanitizedCount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import JSON5 from 'json5';
|
||||
import AdmZip from 'adm-zip';
|
||||
import { PresetFile, MergeStrategy, RequiredInput, ManifestFile, PresetInfo, PresetMetadata } from './types';
|
||||
import { PresetFile, ManifestFile, PresetInfo, PresetMetadata } from './types';
|
||||
import { HOME_DIR, PRESETS_DIR } from '../constants';
|
||||
import { loadConfigFromManifest } from './schema';
|
||||
|
||||
@@ -236,48 +236,27 @@ export async function downloadPresetToTemp(url: string): Promise<string> {
|
||||
const tempDir = getTempDir();
|
||||
await fs.mkdir(tempDir, { recursive: true });
|
||||
|
||||
const tempFile = path.join(tempDir, `preset-${Date.now()}.ccrsets`);
|
||||
const tempFile = path.join(tempDir, `preset-${Date.now()}.zip`);
|
||||
await fs.writeFile(tempFile, Buffer.from(buffer));
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load preset from local ZIP file
|
||||
* @param zipFile ZIP file path
|
||||
* @returns PresetFile
|
||||
*/
|
||||
export async function loadPresetFromZip(zipFile: string): Promise<PresetFile> {
|
||||
const zip = new AdmZip(zipFile);
|
||||
const entry = zip.getEntry('manifest.json');
|
||||
if (!entry) {
|
||||
throw new Error('Invalid preset file: manifest.json not found');
|
||||
}
|
||||
const manifest = JSON5.parse(entry.getData().toString('utf-8')) as ManifestFile;
|
||||
return manifestToPresetFile(manifest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load preset file
|
||||
* @param source Preset source (file path, URL, or preset name)
|
||||
* @param source Preset source (preset name or directory path)
|
||||
*/
|
||||
export async function loadPreset(source: string): Promise<PresetFile> {
|
||||
// Check if it's a URL
|
||||
if (source.startsWith('http://') || source.startsWith('https://')) {
|
||||
const tempFile = await downloadPresetToTemp(source);
|
||||
const preset = await loadPresetFromZip(tempFile);
|
||||
// Delete temp file
|
||||
await fs.unlink(tempFile).catch(() => {});
|
||||
return preset;
|
||||
}
|
||||
|
||||
// Check if it's absolute or relative path (contains / or \)
|
||||
if (source.includes('/') || source.includes('\\')) {
|
||||
// File path
|
||||
return await loadPresetFromZip(source);
|
||||
// Directory path - read manifest from directory
|
||||
const manifestPath = path.join(source, 'manifest.json');
|
||||
const content = await fs.readFile(manifestPath, 'utf-8');
|
||||
const manifest = JSON5.parse(content) as ManifestFile;
|
||||
return manifestToPresetFile(manifest);
|
||||
}
|
||||
|
||||
// Otherwise treat as preset name (read from extracted directory)
|
||||
// Otherwise treat as preset name (read from presets directory)
|
||||
const presetDir = getPresetDir(source);
|
||||
const manifest = await readManifestFromDir(presetDir);
|
||||
return manifestToPresetFile(manifest);
|
||||
@@ -370,33 +349,6 @@ export async function saveManifest(presetName: string, manifest: ManifestFile):
|
||||
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find preset file
|
||||
* @param source Preset source
|
||||
* @returns File path or null
|
||||
*/
|
||||
export async function findPresetFile(source: string): Promise<string | null> {
|
||||
// Current directory file
|
||||
const currentDirFile = path.join(process.cwd(), `${source}.ccrsets`);
|
||||
|
||||
// presets directory file
|
||||
const presetsDirFile = path.join(HOME_DIR, 'presets', `${source}.ccrsets`);
|
||||
|
||||
// Check current directory
|
||||
try {
|
||||
await fs.access(currentDirFile);
|
||||
return currentDirFile;
|
||||
} catch {
|
||||
// Check presets directory
|
||||
try {
|
||||
await fs.access(presetsDirFile);
|
||||
return presetsDirFile;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if preset is already installed
|
||||
* @param presetName Preset name
|
||||
|
||||
@@ -274,19 +274,15 @@ export function Presets() {
|
||||
}
|
||||
|
||||
// 确定预设名称
|
||||
const presetName = installName || (
|
||||
installMethod === 'file'
|
||||
? installFile!.name.replace('.ccrsets', '')
|
||||
: installUrl!.split('/').pop()!.replace('.ccrsets', '')
|
||||
);
|
||||
const presetName = installName || '';
|
||||
|
||||
// Step 1: Install preset (extract to directory)
|
||||
// Step 1: Install preset from GitHub repository
|
||||
let installResult;
|
||||
if (installMethod === 'url' && installUrl) {
|
||||
installResult = await api.installPresetFromUrl(installUrl, presetName);
|
||||
} else if (installMethod === 'file' && installFile) {
|
||||
installResult = await api.uploadPresetFile(installFile, presetName);
|
||||
// Install from GitHub repository
|
||||
installResult = await api.installPresetFromGitHub(installUrl, presetName);
|
||||
} else {
|
||||
setToast({ message: t('presets.please_provide_url'), type: 'warning' });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -488,15 +484,7 @@ export function Presets() {
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant={installMethod === 'file' ? 'default' : 'outline'}
|
||||
onClick={() => setInstallMethod('file')}
|
||||
className="flex-1"
|
||||
>
|
||||
<Upload className="mr-2 h-4 w-4" />
|
||||
{t('presets.upload_file')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={installMethod === 'url' ? 'default' : 'outline'}
|
||||
variant="default"
|
||||
onClick={() => setInstallMethod('url')}
|
||||
className="flex-1"
|
||||
>
|
||||
@@ -505,28 +493,17 @@ export function Presets() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{installMethod === 'file' ? (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="preset-file">{t('presets.preset_file')}</Label>
|
||||
<Input
|
||||
id="preset-file"
|
||||
type="file"
|
||||
accept=".ccrsets"
|
||||
onChange={(e) => setInstallFile(e.target.files?.[0] || null)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="preset-url">{t('presets.preset_url')}</Label>
|
||||
<Input
|
||||
id="preset-url"
|
||||
type="url"
|
||||
placeholder={t('presets.preset_url_placeholder')}
|
||||
value={installUrl}
|
||||
onChange={(e) => setInstallUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="preset-url">{t('presets.github_repository')}</Label>
|
||||
<Input
|
||||
id="preset-url"
|
||||
type="url"
|
||||
placeholder={t('presets.preset_url_placeholder')}
|
||||
value={installUrl}
|
||||
onChange={(e) => setInstallUrl(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-gray-500">{t('presets.github_url_hint')}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="preset-name">{t('presets.preset_name')}</Label>
|
||||
|
||||
@@ -260,15 +260,14 @@
|
||||
"close": "Close",
|
||||
"delete": "Delete",
|
||||
"install_dialog_title": "Install Preset",
|
||||
"install_dialog_description": "Install a preset from a file or URL",
|
||||
"upload_file": "Upload File",
|
||||
"from_url": "From URL",
|
||||
"preset_file": "Preset File (.ccrsets)",
|
||||
"preset_url": "Preset URL",
|
||||
"preset_url_placeholder": "https://example.com/preset.ccrsets",
|
||||
"install_dialog_description": "Install a preset from a GitHub repository",
|
||||
"from_url": "From GitHub",
|
||||
"github_repository": "GitHub Repository",
|
||||
"preset_url": "Repository URL",
|
||||
"preset_url_placeholder": "https://github.com/owner/repo",
|
||||
"preset_name": "Preset Name (Optional)",
|
||||
"preset_name_placeholder": "Auto-generated from file",
|
||||
"please_provide_file_or_url": "Please provide a file or URL",
|
||||
"preset_name_placeholder": "Auto-generated from repository",
|
||||
"github_url_hint": "Enter GitHub repository URL (e.g., https://github.com/owner/repo)",
|
||||
"detail_dialog_title": "Preset Details",
|
||||
"required_information": "Required Information",
|
||||
"delete_dialog_title": "Delete Preset",
|
||||
@@ -284,8 +283,8 @@
|
||||
"please_fill_field": "Please fill in {{field}}",
|
||||
"load_market_failed": "Failed to load market presets",
|
||||
"preset_installed_config_required": "Preset installed, please complete configuration",
|
||||
"please_provide_file": "Please provide a preset file",
|
||||
"please_provide_url": "Please provide a preset URL",
|
||||
"please_provide_file": "Please provide a preset directory",
|
||||
"please_provide_url": "Please provide a valid GitHub repository URL",
|
||||
"form": {
|
||||
"field_required": "{{field}} is required",
|
||||
"must_be_number": "{{field}} must be a number",
|
||||
|
||||
@@ -260,15 +260,14 @@
|
||||
"close": "关闭",
|
||||
"delete": "删除",
|
||||
"install_dialog_title": "安装预设",
|
||||
"install_dialog_description": "从文件或URL安装预设",
|
||||
"upload_file": "上传文件",
|
||||
"from_url": "从 URL",
|
||||
"preset_file": "预设文件 (.ccrsets)",
|
||||
"preset_url": "预设 URL",
|
||||
"preset_url_placeholder": "https://example.com/preset.ccrsets",
|
||||
"install_dialog_description": "从 GitHub 仓库安装预设",
|
||||
"from_url": "从 GitHub",
|
||||
"github_repository": "GitHub 仓库",
|
||||
"preset_url": "仓库 URL",
|
||||
"preset_url_placeholder": "https://github.com/owner/repo",
|
||||
"preset_name": "预设名称 (可选)",
|
||||
"preset_name_placeholder": "从文件自动生成",
|
||||
"please_provide_file_or_url": "请提供文件或 URL",
|
||||
"preset_name_placeholder": "从仓库自动生成",
|
||||
"github_url_hint": "输入 GitHub 仓库 URL(例如:https://github.com/owner/repo)",
|
||||
"detail_dialog_title": "预设详情",
|
||||
"required_information": "必需信息",
|
||||
"delete_dialog_title": "删除预设",
|
||||
@@ -284,8 +283,8 @@
|
||||
"please_fill_field": "请填写 {{field}}",
|
||||
"load_market_failed": "加载市场预设失败",
|
||||
"preset_installed_config_required": "预设已安装,请完成配置",
|
||||
"please_provide_file": "请提供预设文件",
|
||||
"please_provide_url": "请提供预设 URL",
|
||||
"please_provide_file": "请提供预设目录",
|
||||
"please_provide_url": "请提供有效的 GitHub 仓库 URL",
|
||||
"form": {
|
||||
"field_required": "{{field}} 为必填项",
|
||||
"must_be_number": "{{field}} 必须是数字",
|
||||
|
||||
Reference in New Issue
Block a user