File size: 6,856 Bytes
88c4c60 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | "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 getDroidDir = () => path.join(os.homedir(), ".factory");
const getDroidSettingsPath = () => path.join(getDroidDir(), "settings.json");
// Check if droid CLI is installed (via which/where or config file exists)
const checkDroidInstalled = async () => {
try {
const isWindows = os.platform() === "win32";
const command = isWindows ? "where droid" : "which droid";
const env = isWindows
? { ...process.env, PATH: `${process.env.APPDATA}\\npm;${process.env.PATH}` }
: process.env;
await execAsync(command, { windowsHide: true, env });
return true;
} catch {
try {
await fs.access(getDroidSettingsPath());
return true;
} catch {
return false;
}
}
};
// Read current settings.json
const readSettings = async () => {
try {
const settingsPath = getDroidSettingsPath();
const content = await fs.readFile(settingsPath, "utf-8");
return JSON.parse(content);
} catch (error) {
if (error.code === "ENOENT") return null;
throw error;
}
};
// Check if settings has 9Router customModels
const has9RouterConfig = (settings) => {
if (!settings || !settings.customModels) return false;
return settings.customModels.some(m => m.id?.startsWith("custom:9Router"));
};
// GET - Check droid CLI and read current settings
export async function GET() {
try {
const isInstalled = await checkDroidInstalled();
if (!isInstalled) {
return NextResponse.json({
installed: false,
settings: null,
message: "Factory Droid CLI is not installed",
});
}
const settings = await readSettings();
return NextResponse.json({
installed: true,
settings,
has9Router: has9RouterConfig(settings),
settingsPath: getDroidSettingsPath(),
});
} catch (error) {
console.log("Error checking droid settings:", error);
return NextResponse.json({ error: "Failed to check droid settings" }, { status: 500 });
}
}
// POST - Update 9Router customModels (merge with existing settings)
// Accepts either `model` (string, legacy single-model) or `models` (array of strings, multi-model)
// Also accepts `activeModel` to set which model is active/primary
export async function POST(request) {
try {
const { baseUrl, apiKey, model, models, activeModel } = await request.json();
// Accept either `models` (array) or `model` (string, legacy)
const modelsArray = Array.isArray(models) ? models.slice() : (typeof model === "string" ? [model] : []);
if (!baseUrl || modelsArray.length === 0) {
return NextResponse.json({ error: "baseUrl and at least one model are required" }, { status: 400 });
}
const droidDir = getDroidDir();
const settingsPath = getDroidSettingsPath();
// Ensure directory exists
await fs.mkdir(droidDir, { recursive: true });
// Read existing settings or create new
let settings = {};
try {
const existingSettings = await fs.readFile(settingsPath, "utf-8");
settings = JSON.parse(existingSettings);
} catch { /* No existing settings */ }
// Ensure customModels array exists
if (!settings.customModels) {
settings.customModels = [];
}
// Remove all existing 9Router configs
settings.customModels = settings.customModels.filter(m => !m.id?.startsWith("custom:9Router"));
// Normalize baseUrl to ensure /v1 suffix
const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
const keyToUse = apiKey || "your_api_key";
// Determine active model: prefer explicit activeModel, else first of modelsArray
// If activeModel is explicitly empty string, no model will be set as default
let defaultIndex = 0;
if (typeof activeModel === "string") {
if (activeModel === "") {
defaultIndex = -1; // signal: don't set a default
} else {
const idx = modelsArray.indexOf(activeModel);
defaultIndex = idx >= 0 ? idx : 0;
}
}
// Add entries for all requested models
// The first one (index 0) will be the default if defaultIndex >= 0
for (let i = 0; i < modelsArray.length; i++) {
const m = modelsArray[i];
if (!m || typeof m !== "string") continue;
settings.customModels.push({
model: m,
id: `custom:9Router-${i}`,
index: i,
baseUrl: normalizedBaseUrl,
apiKey: keyToUse,
displayName: m,
maxOutputTokens: 131072,
noImageSupport: false,
provider: "openai",
});
}
// Set default model if applicable
if (defaultIndex >= 0 && settings.customModels[defaultIndex]) {
// Reorder so the default comes first
const [defaultEntry] = settings.customModels.splice(defaultIndex, 1);
settings.customModels.unshift({ ...defaultEntry, index: 0 });
// Re-index the rest
settings.customModels.forEach((m, i) => { m.index = i; });
}
// Write settings
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
return NextResponse.json({
success: true,
message: "Factory Droid settings applied successfully!",
settingsPath,
});
} catch (error) {
console.log("Error updating droid settings:", error);
return NextResponse.json({ error: "Failed to update droid settings" }, { status: 500 });
}
}
// DELETE - Remove 9Router customModels only (keep other settings)
export async function DELETE() {
try {
const settingsPath = getDroidSettingsPath();
// Read existing settings
let settings = {};
try {
const existingSettings = await fs.readFile(settingsPath, "utf-8");
settings = JSON.parse(existingSettings);
} catch (error) {
if (error.code === "ENOENT") {
return NextResponse.json({
success: true,
message: "No settings file to reset",
});
}
throw error;
}
// Remove 9Router customModels
if (settings.customModels) {
settings.customModels = settings.customModels.filter(m => !m.id?.startsWith("custom:9Router"));
// Remove customModels array if empty
if (settings.customModels.length === 0) {
delete settings.customModels;
}
}
// Write updated settings
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
return NextResponse.json({
success: true,
message: "9Router settings removed successfully",
});
} catch (error) {
console.log("Error resetting droid settings:", error);
return NextResponse.json({ error: "Failed to reset droid settings" }, { status: 500 });
}
} |