[teamai] Push 87 resource(s) from XingfenD

This commit is contained in:
2026-09-10 16:10:45 +08:00
parent 425c9c078a
commit 65c04def51
1314 changed files with 211681 additions and 0 deletions
@@ -0,0 +1,64 @@
#!/usr/bin/env node
import process from "node:process";
import { loadAmbientEnv, DEFAULT_MODEL } from "./shared.js";
await loadAmbientEnv();
const TRUTHY = new Set(["1", "true", "yes", "on", "y"]);
const rawFlag = String(process.env.ENABLE_GARDEN_IMAGEGEN || "").trim().toLowerCase();
const gardenEnabled = TRUTHY.has(rawFlag);
const apiKey = process.env.OPENAI_API_KEY || "";
const baseUrl = process.env.OPENAI_BASE_URL || "https://api.openai.com/v1";
const model = process.env.OPENAI_IMAGE_MODEL || DEFAULT_MODEL;
let recommendation;
let mode;
let summary;
if (gardenEnabled && apiKey) {
mode = "A";
recommendation = "garden";
summary =
"MODE A · Garden 本地生图:用 scripts/generate.js / scripts/edit.js 直接出图并落盘。";
} else if (gardenEnabled && !apiKey) {
mode = "A?";
recommendation = "garden-missing-key";
summary =
"ENABLE_GARDEN_IMAGEGEN 已开,但缺 OPENAI_API_KEY。先向用户索要 key,或临时降级到 MODE B / C。";
} else {
mode = "B-or-C";
recommendation = "host-or-advisor";
summary =
"MODE B / C · 未启用 Garden。若宿主 Agent 自带图像工具(image_generation / dalle / mcp__*image* 等)→ MODE B:把 prompt 交给宿主出图。若宿主无图像工具 → MODE C:仅产出高质量 prompt 给用户。";
}
const result = {
mode,
recommendation,
garden_mode_enabled: gardenEnabled,
has_api_key: Boolean(apiKey),
base_url: baseUrl,
model,
env_flag_value: rawFlag || "(unset)",
summary,
};
const wantJson = process.argv.includes("--json");
if (wantJson) {
console.log(JSON.stringify(result, null, 2));
} else {
const pad = (s) => s.padEnd(24, " ");
console.log("--- gpt-image-2 runtime mode ---");
console.log(`${pad("mode")}: ${result.mode}`);
console.log(`${pad("recommendation")}: ${result.recommendation}`);
console.log(`${pad("garden_mode_enabled")}: ${result.garden_mode_enabled}`);
console.log(`${pad("has_api_key")}: ${result.has_api_key}`);
console.log(`${pad("base_url")}: ${result.base_url}`);
console.log(`${pad("model")}: ${result.model}`);
console.log(`${pad("env_flag_value")}: ${result.env_flag_value}`);
console.log("");
console.log(result.summary);
}
@@ -0,0 +1,225 @@
import process from "node:process";
import { readFile } from "node:fs/promises";
import {
DEFAULT_IMAGE_DIR,
DEFAULT_MODEL,
appendIfPresent,
buildBaseUrl,
buildDefaultImagePath,
ensureFilesExist,
extractGeneratedBytes,
loadAmbientEnv,
mimeFor,
postMultipart,
printJson,
readPromptInput,
resolveOutput,
saveImage,
savePrompt,
slugify,
} from "./shared.js";
function printHelp() {
console.log(`Usage:
node scripts/edit.js --image source.png --prompt "Replace the background with a studio set" --output out/edit.png
Options:
--image <path> Source image path (required)
--mask <path> Optional mask image path
--prompt <text> Edit prompt
--promptfile <path> Load prompt from a file
--prompt-output <path> Save the final prompt to a specific file
--output <path> Output image path (default: ${DEFAULT_IMAGE_DIR}/<slug>-<timestamp>.png)
--model <name> Model override (default: ${DEFAULT_MODEL})
--size <WxH|auto> Output size
--n <count> Number of images
--quality <level> auto | high | medium | low
--background <mode> transparent | opaque | auto
--input-fidelity <level> low | high
--output-format <format> png | jpeg | webp
--output-compression <0-100> Compression for jpeg/webp
--moderation <level> low | auto
--json Print structured output
-h, --help Show help`);
}
function parseCli(argv) {
const cfg = {
image: null,
mask: null,
prompt: null,
promptFile: null,
promptOutput: null,
output: null,
model: null,
size: null,
n: null,
quality: null,
background: null,
inputFidelity: null,
outputFormat: null,
outputCompression: null,
moderation: null,
json: false,
help: false,
};
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === "-h" || arg === "--help") {
cfg.help = true;
continue;
}
if (arg === "--json") {
cfg.json = true;
continue;
}
if (arg === "--image") {
cfg.image = argv[++i] || null;
if (!cfg.image) throw new Error("Missing value for --image");
continue;
}
if (arg === "--mask") {
cfg.mask = argv[++i] || null;
if (!cfg.mask) throw new Error("Missing value for --mask");
continue;
}
if (arg === "--prompt") {
cfg.prompt = argv[++i] || null;
if (!cfg.prompt) throw new Error("Missing value for --prompt");
continue;
}
if (arg === "--promptfile") {
cfg.promptFile = argv[++i] || null;
if (!cfg.promptFile) throw new Error("Missing value for --promptfile");
continue;
}
if (arg === "--prompt-output") {
cfg.promptOutput = argv[++i] || null;
if (!cfg.promptOutput) throw new Error("Missing value for --prompt-output");
continue;
}
if (arg === "--output") {
cfg.output = argv[++i] || null;
if (!cfg.output) throw new Error("Missing value for --output");
continue;
}
if (arg === "--model") {
cfg.model = argv[++i] || null;
if (!cfg.model) throw new Error("Missing value for --model");
continue;
}
if (arg === "--size") {
cfg.size = argv[++i] || null;
if (!cfg.size) throw new Error("Missing value for --size");
continue;
}
if (arg === "--n") {
cfg.n = argv[++i] || null;
if (!cfg.n) throw new Error("Missing value for --n");
continue;
}
if (arg === "--quality") {
cfg.quality = argv[++i] || null;
if (!cfg.quality) throw new Error("Missing value for --quality");
continue;
}
if (arg === "--background") {
cfg.background = argv[++i] || null;
if (!cfg.background) throw new Error("Missing value for --background");
continue;
}
if (arg === "--input-fidelity") {
cfg.inputFidelity = argv[++i] || null;
if (!cfg.inputFidelity) throw new Error("Missing value for --input-fidelity");
continue;
}
if (arg === "--output-format") {
cfg.outputFormat = argv[++i] || null;
if (!cfg.outputFormat) throw new Error("Missing value for --output-format");
continue;
}
if (arg === "--output-compression") {
cfg.outputCompression = argv[++i] || null;
if (!cfg.outputCompression) throw new Error("Missing value for --output-compression");
continue;
}
if (arg === "--moderation") {
cfg.moderation = argv[++i] || null;
if (!cfg.moderation) throw new Error("Missing value for --moderation");
continue;
}
throw new Error(`Unknown option: ${arg}`);
}
return cfg;
}
function buildRequestUrl() {
return `${buildBaseUrl()}/images/edits`;
}
async function buildForm(cfg, prompt) {
const form = new FormData();
const imagePath = cfg.image;
const imageBytes = await readFile(imagePath);
form.append("image", new Blob([imageBytes], { type: mimeFor(imagePath) }), imagePath.split(/[\\/]/).pop());
if (cfg.mask) {
const maskBytes = await readFile(cfg.mask);
form.append("mask", new Blob([maskBytes], { type: mimeFor(cfg.mask) }), cfg.mask.split(/[\\/]/).pop());
}
form.append("prompt", prompt);
form.append("model", cfg.model || process.env.OPENAI_IMAGE_MODEL || DEFAULT_MODEL);
appendIfPresent(form, "size", cfg.size);
appendIfPresent(form, "n", cfg.n);
appendIfPresent(form, "quality", cfg.quality);
appendIfPresent(form, "background", cfg.background);
appendIfPresent(form, "input_fidelity", cfg.inputFidelity);
appendIfPresent(form, "output_format", cfg.outputFormat);
appendIfPresent(form, "output_compression", cfg.outputCompression);
appendIfPresent(form, "moderation", cfg.moderation);
return form;
}
async function run() {
const cfg = parseCli(process.argv.slice(2));
if (cfg.help) {
printHelp();
return;
}
if (!cfg.image) throw new Error("--image is required");
await loadAmbientEnv();
await ensureFilesExist([cfg.image, ...(cfg.mask ? [cfg.mask] : [])], "Image file");
const prompt = await readPromptInput(cfg.prompt, cfg.promptFile);
const nameHint = slugify(prompt.split(/\s+/).slice(0, 8).join(" "), "edited-image");
const promptPath = await savePrompt(prompt, cfg.promptOutput, nameHint);
const outputPath = resolveOutput(cfg.output, buildDefaultImagePath("edit", nameHint));
const form = await buildForm(cfg, prompt);
const url = buildRequestUrl();
const json = await postMultipart(url, form);
const bytes = await extractGeneratedBytes(json);
await saveImage(outputPath, bytes);
if (cfg.json) {
printJson({
savedImage: outputPath,
savedPrompt: promptPath,
model: cfg.model || process.env.OPENAI_IMAGE_MODEL || DEFAULT_MODEL,
requestUrl: url,
apiResponse: json,
});
return;
}
console.log(outputPath);
}
run().catch((error) => {
const message = error instanceof Error ? error.message : String(error);
console.error(message);
process.exit(1);
});
@@ -0,0 +1,191 @@
import process from "node:process";
import {
DEFAULT_IMAGE_DIR,
DEFAULT_MODEL,
buildBaseUrl,
buildDefaultImagePath,
ensureFilesExist,
extractGeneratedBytes,
loadAmbientEnv,
printJson,
readPromptInput,
resolveOutput,
saveImage,
savePrompt,
postJson,
slugify,
} from "./shared.js";
function printHelp() {
console.log(`Usage:
node scripts/generate.js --prompt "A cute baby sea otter" --image out/otter.png
Options:
--prompt <text> Prompt text
--promptfile <path> Load prompt from a file
--prompt-output <path> Save the final prompt to a specific file
--image <path> Output image path (default: ${DEFAULT_IMAGE_DIR}/<slug>-<timestamp>.png)
--model <name> Model override (default: ${DEFAULT_MODEL})
--size <WxH> Output size
--n <count> Number of images
--quality <level> auto | high | medium | low
--background <mode> transparent | opaque | auto
--moderation <level> low | auto
--output-format <format> png | jpeg | webp
--output-compression <0-100> Compression for jpeg/webp
--json Print structured output
-h, --help Show help`);
}
function parseCli(argv) {
const cfg = {
prompt: null,
promptFile: null,
promptOutput: null,
imagePath: null,
model: null,
size: null,
n: null,
quality: null,
background: null,
moderation: null,
outputFormat: null,
outputCompression: null,
json: false,
help: false,
};
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === "-h" || arg === "--help") {
cfg.help = true;
continue;
}
if (arg === "--json") {
cfg.json = true;
continue;
}
if (arg === "--prompt") {
cfg.prompt = argv[++i] || null;
if (!cfg.prompt) throw new Error("Missing value for --prompt");
continue;
}
if (arg === "--promptfile") {
cfg.promptFile = argv[++i] || null;
if (!cfg.promptFile) throw new Error("Missing value for --promptfile");
continue;
}
if (arg === "--prompt-output") {
cfg.promptOutput = argv[++i] || null;
if (!cfg.promptOutput) throw new Error("Missing value for --prompt-output");
continue;
}
if (arg === "--image") {
cfg.imagePath = argv[++i] || null;
if (!cfg.imagePath) throw new Error("Missing value for --image");
continue;
}
if (arg === "--model") {
cfg.model = argv[++i] || null;
if (!cfg.model) throw new Error("Missing value for --model");
continue;
}
if (arg === "--size") {
cfg.size = argv[++i] || null;
if (!cfg.size) throw new Error("Missing value for --size");
continue;
}
if (arg === "--n") {
cfg.n = argv[++i] || null;
if (!cfg.n) throw new Error("Missing value for --n");
continue;
}
if (arg === "--quality") {
cfg.quality = argv[++i] || null;
if (!cfg.quality) throw new Error("Missing value for --quality");
continue;
}
if (arg === "--background") {
cfg.background = argv[++i] || null;
if (!cfg.background) throw new Error("Missing value for --background");
continue;
}
if (arg === "--moderation") {
cfg.moderation = argv[++i] || null;
if (!cfg.moderation) throw new Error("Missing value for --moderation");
continue;
}
if (arg === "--output-format") {
cfg.outputFormat = argv[++i] || null;
if (!cfg.outputFormat) throw new Error("Missing value for --output-format");
continue;
}
if (arg === "--output-compression") {
cfg.outputCompression = argv[++i] || null;
if (!cfg.outputCompression) throw new Error("Missing value for --output-compression");
continue;
}
throw new Error(`Unknown option: ${arg}`);
}
return cfg;
}
function buildPayload(cfg, prompt) {
const payload = {
prompt,
model: cfg.model || process.env.OPENAI_IMAGE_MODEL || DEFAULT_MODEL,
};
if (cfg.size) payload.size = cfg.size;
if (cfg.n) payload.n = Number(cfg.n);
if (cfg.quality) payload.quality = cfg.quality;
if (cfg.background) payload.background = cfg.background;
if (cfg.moderation) payload.moderation = cfg.moderation;
if (cfg.outputFormat) payload.output_format = cfg.outputFormat;
if (cfg.outputCompression) payload.output_compression = Number(cfg.outputCompression);
return payload;
}
function buildRequestUrl() {
return `${buildBaseUrl()}/images/generations`;
}
async function run() {
const cfg = parseCli(process.argv.slice(2));
if (cfg.help) {
printHelp();
return;
}
await loadAmbientEnv();
const prompt = await readPromptInput(cfg.prompt, cfg.promptFile);
const nameHint = slugify(prompt.split(/\s+/).slice(0, 8).join(" "), "generated-image");
const promptPath = await savePrompt(prompt, cfg.promptOutput, nameHint);
const outputPath = resolveOutput(cfg.imagePath, buildDefaultImagePath("generate", nameHint));
await ensureFilesExist([], "input");
const payload = buildPayload(cfg, prompt);
const url = buildRequestUrl();
const json = await postJson(url, payload);
const bytes = await extractGeneratedBytes(json);
await saveImage(outputPath, bytes);
if (cfg.json) {
printJson({
savedImage: outputPath,
savedPrompt: promptPath,
model: payload.model,
requestUrl: url,
apiResponse: json,
});
return;
}
console.log(outputPath);
}
run().catch((error) => {
const message = error instanceof Error ? error.message : String(error);
console.error(message);
process.exit(1);
});
@@ -0,0 +1,5 @@
{
"name": "gpt-image-2",
"private": true,
"type": "module"
}
@@ -0,0 +1,213 @@
import path from "node:path";
import process from "node:process";
import { homedir } from "node:os";
import { mkdir, readFile, writeFile } from "node:fs/promises";
export const DEFAULT_IMAGE_DIR = "garden-gpt-image-2/image";
export const DEFAULT_PROMPT_DIR = "garden-gpt-image-2/prompt";
export const DEFAULT_MODEL = "gpt-image-2";
export async function readEnvFile(filePath) {
try {
const text = await readFile(filePath, "utf8");
const result = {};
for (const line of text.split("\n")) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) continue;
const pivot = trimmed.indexOf("=");
if (pivot === -1) continue;
const key = trimmed.slice(0, pivot).trim();
let value = trimmed.slice(pivot + 1).trim();
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1);
}
result[key] = value;
}
return result;
} catch {
return {};
}
}
export async function loadAmbientEnv() {
const places = [
path.join(process.cwd(), ".env"),
path.join(process.cwd(), ".gateway.env"),
path.join(homedir(), ".gateway.env"),
];
for (const filePath of places) {
const pairs = await readEnvFile(filePath);
for (const [key, value] of Object.entries(pairs)) {
if (!process.env[key]) process.env[key] = value;
}
}
}
export async function readPromptInput(prompt, promptFile) {
if (prompt) return prompt.trim();
if (promptFile) {
const text = await readFile(path.resolve(promptFile), "utf8");
return text.trim();
}
throw new Error("Prompt is required. Use --prompt or --promptfile.");
}
export function slugify(value, fallback = "image-task") {
const base = String(value || "").trim().toLowerCase();
const ascii = base
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 48);
return ascii || fallback;
}
export function makeTimestamp() {
const now = new Date();
const yyyy = String(now.getFullYear());
const mm = String(now.getMonth() + 1).padStart(2, "0");
const dd = String(now.getDate()).padStart(2, "0");
const hh = String(now.getHours()).padStart(2, "0");
const mi = String(now.getMinutes()).padStart(2, "0");
const ss = String(now.getSeconds()).padStart(2, "0");
return `${yyyy}${mm}${dd}-${hh}${mi}${ss}`;
}
export function buildDefaultImagePath(kind, hint, ext = ".png") {
const stamp = makeTimestamp();
const slug = slugify(hint, kind === "edit" ? "edited-image" : "generated-image");
const file = `${slug}-${stamp}${ext}`;
return path.join(DEFAULT_IMAGE_DIR, file);
}
export function buildDefaultPromptPath(hint) {
const stamp = makeTimestamp();
const slug = slugify(hint, "prompt");
return path.join(DEFAULT_PROMPT_DIR, `${slug}-${stamp}.md`);
}
export function resolveOutput(raw, fallbackPath) {
const target = raw || fallbackPath;
const full = path.resolve(target);
return path.extname(full) ? full : `${full}.png`;
}
export async function savePrompt(promptText, rawPath, hint) {
const finalPath = path.resolve(rawPath || buildDefaultPromptPath(hint));
await mkdir(path.dirname(finalPath), { recursive: true });
await writeFile(finalPath, `${promptText.trim()}\n`, "utf8");
return finalPath;
}
export function mimeFor(filePath) {
const ext = path.extname(filePath).toLowerCase();
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
if (ext === ".webp") return "image/webp";
if (ext === ".gif") return "image/gif";
return "image/png";
}
export async function ensureFilesExist(files, label) {
for (const item of files) {
try {
await readFile(path.resolve(item));
} catch {
throw new Error(`${label} not found: ${path.resolve(item)}`);
}
}
}
export async function encodeImages(files) {
const images = [];
for (const file of files) {
const absolute = path.resolve(file);
const bytes = await readFile(absolute);
images.push({
name: path.basename(absolute),
mime_type: mimeFor(absolute),
data: Buffer.from(bytes).toString("base64"),
absolute,
});
}
return images;
}
export function buildBaseUrl() {
return (process.env.OPENAI_BASE_URL || "https://api.openai.com/v1").replace(/\/$/, "");
}
export function requireApiKey() {
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) throw new Error("OPENAI_API_KEY is required.");
return apiKey;
}
export async function postJson(url, payload) {
const apiKey = requireApiKey();
const res = await fetch(url, {
method: "POST",
headers: {
authorization: `Bearer ${apiKey}`,
"content-type": "application/json",
},
body: JSON.stringify(payload),
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Image API error (${res.status}): ${text}`);
}
return res.json();
}
export async function postMultipart(url, form) {
const apiKey = requireApiKey();
const res = await fetch(url, {
method: "POST",
headers: {
authorization: `Bearer ${apiKey}`,
},
body: form,
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Image API error (${res.status}): ${text}`);
}
return res.json();
}
export async function fetchBytesFromUrl(url) {
const res = await fetch(url);
if (!res.ok) {
const text = await res.text();
throw new Error(`Failed to download generated image (${res.status}): ${text}`);
}
return Buffer.from(await res.arrayBuffer());
}
export async function extractGeneratedBytes(json) {
const first = json?.data?.[0];
if (!first) throw new Error("API response did not include data[0].");
if (first.b64_json) return Buffer.from(first.b64_json, "base64");
if (first.url) return fetchBytesFromUrl(first.url);
throw new Error("API response did not include b64_json or url.");
}
export async function saveImage(outputPath, bytes) {
await mkdir(path.dirname(outputPath), { recursive: true });
await writeFile(outputPath, bytes);
}
export function printJson(data) {
console.log(JSON.stringify(data, null, 2));
}
export function appendIfPresent(target, key, value) {
if (value === undefined || value === null || value === "") return;
target.append(key, String(value));
}