mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 20:00:47 +00:00
feat: add DeepSeek TUI as CLI tool in dashboard (#1088)
Co-authored-by: Ansh7473 <your-github-email@example.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import { GET as coworkGet } from "../cowork-settings/route";
|
||||
import { GET as copilotGet } from "../copilot-settings/route";
|
||||
import { GET as clineGet } from "../cline-settings/route";
|
||||
import { GET as kiloGet } from "../kilo-settings/route";
|
||||
import { GET as deepseekTuiGet } from "../deepseek-tui-settings/route";
|
||||
|
||||
const STATUS_GETTERS = {
|
||||
claude: claudeGet,
|
||||
@@ -23,6 +24,7 @@ const STATUS_GETTERS = {
|
||||
copilot: copilotGet,
|
||||
cline: clineGet,
|
||||
kilo: kiloGet,
|
||||
"deepseek-tui": deepseekTuiGet,
|
||||
};
|
||||
|
||||
// Batch endpoint: gather all CLI tool statuses in one round-trip
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
"use server";
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
const PROVIDER_NAME = "9router";
|
||||
|
||||
const getDeepSeekDir = () => path.join(os.homedir(), ".deepseek");
|
||||
const getDeepSeekConfigPath = () => path.join(getDeepSeekDir(), "config.toml");
|
||||
|
||||
// Simple TOML parser for key = "value" and [section] patterns
|
||||
const parseToml = (content) => {
|
||||
const result = {};
|
||||
let currentSection = result;
|
||||
|
||||
const lines = content.split(/\r?\n/);
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
// Skip empty lines and comments
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
|
||||
// Section header: [section] or [section.subsection]
|
||||
const sectionMatch = trimmed.match(/^\[([^\]]+)\]$/);
|
||||
if (sectionMatch) {
|
||||
const sectionName = sectionMatch[1];
|
||||
if (!result[sectionName]) result[sectionName] = {};
|
||||
currentSection = result[sectionName];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Key = "value" or key = value
|
||||
const keyValueMatch = trimmed.match(/^(\w+)\s*=\s*"([^"]*)"$/);
|
||||
if (keyValueMatch) {
|
||||
currentSection[keyValueMatch[1]] = keyValueMatch[2];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Key = value (unquoted)
|
||||
const unquotedMatch = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
|
||||
if (unquotedMatch) {
|
||||
currentSection[unquotedMatch[1]] = unquotedMatch[2].trim();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Build TOML config for 9Router (openai provider mode)
|
||||
const build9RouterConfig = (baseUrl, apiKey, model) => {
|
||||
const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
|
||||
return `provider = "openai"
|
||||
|
||||
[providers.openai]
|
||||
base_url = "${normalizedBaseUrl}"
|
||||
api_key = "${apiKey}"
|
||||
model = "${model}"
|
||||
`;
|
||||
};
|
||||
|
||||
// Default DeepSeek config (reset state)
|
||||
const DEFAULT_CONFIG = `provider = "deepseek"
|
||||
`;
|
||||
|
||||
const checkDeepSeekInstalled = async () => {
|
||||
try {
|
||||
const isWindows = os.platform() === "win32";
|
||||
const command = isWindows ? "where deepseek" : "which deepseek";
|
||||
await execAsync(command, { windowsHide: true });
|
||||
return true;
|
||||
} catch {
|
||||
try {
|
||||
await fs.access(getDeepSeekConfigPath());
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const readConfigToml = async () => {
|
||||
try {
|
||||
return await fs.readFile(getDeepSeekConfigPath(), "utf-8");
|
||||
} catch (error) {
|
||||
if (error.code === "ENOENT") return "";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Detect 9Router by checking if provider is "openai" and base_url points to localhost/127.0.0.1
|
||||
const has9RouterConfig = (config) => {
|
||||
if (!config) return false;
|
||||
const provider = config.provider;
|
||||
if (provider !== "openai") return false;
|
||||
const openaiSection = config["providers.openai"];
|
||||
if (!openaiSection?.base_url) return false;
|
||||
return /localhost|127\.0\.0\.1|0\.0\.0\.0/.test(openaiSection.base_url);
|
||||
};
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const installed = await checkDeepSeekInstalled();
|
||||
if (!installed) {
|
||||
return NextResponse.json({ installed: false, settings: null, message: "DeepSeek TUI is not installed" });
|
||||
}
|
||||
const toml = await readConfigToml();
|
||||
const config = parseToml(toml);
|
||||
return NextResponse.json({
|
||||
installed: true,
|
||||
settings: config,
|
||||
has9Router: has9RouterConfig(config),
|
||||
configPath: getDeepSeekConfigPath(),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error checking deepseek-tui settings:", error);
|
||||
return NextResponse.json({ error: "Failed to check deepseek-tui settings" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request) {
|
||||
try {
|
||||
const { baseUrl, apiKey, model } = await request.json();
|
||||
if (!baseUrl || !model) {
|
||||
return NextResponse.json({ error: "baseUrl and model are required" }, { status: 400 });
|
||||
}
|
||||
|
||||
const dir = getDeepSeekDir();
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
|
||||
const newConfig = build9RouterConfig(baseUrl, apiKey || "sk_9router", model);
|
||||
await fs.writeFile(getDeepSeekConfigPath(), newConfig);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "DeepSeek TUI settings applied successfully!",
|
||||
configPath: getDeepSeekConfigPath(),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error updating deepseek-tui settings:", error);
|
||||
return NextResponse.json({ error: "Failed to update deepseek-tui settings" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE() {
|
||||
try {
|
||||
const configPath = getDeepSeekConfigPath();
|
||||
try {
|
||||
await fs.access(configPath);
|
||||
} catch {
|
||||
return NextResponse.json({ success: true, message: "No config file to reset" });
|
||||
}
|
||||
|
||||
await fs.writeFile(configPath, DEFAULT_CONFIG);
|
||||
return NextResponse.json({ success: true, message: `${PROVIDER_NAME} config reset to DeepSeek defaults` });
|
||||
} catch (error) {
|
||||
console.log("Error resetting deepseek-tui settings:", error);
|
||||
return NextResponse.json({ error: "Failed to reset deepseek-tui settings" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user