@telepat/ideon 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ideon.js +442 -1070
- package/package.json +2 -1
package/dist/ideon.js
CHANGED
|
@@ -111,7 +111,7 @@ var modelSettingsSchema = z.object({
|
|
|
111
111
|
topP: z.number().min(0).max(1).default(1)
|
|
112
112
|
});
|
|
113
113
|
var baseT2ISettingsSchema = z.object({
|
|
114
|
-
modelId: z.string().default("
|
|
114
|
+
modelId: z.string().default("flux"),
|
|
115
115
|
inputOverrides: z.record(z.string(), z.unknown()).default({})
|
|
116
116
|
});
|
|
117
117
|
var notificationsSettingsSchema = z.object({
|
|
@@ -1362,7 +1362,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
1362
1362
|
// package.json
|
|
1363
1363
|
var package_default = {
|
|
1364
1364
|
name: "@telepat/ideon",
|
|
1365
|
-
version: "0.1.
|
|
1365
|
+
version: "0.1.20",
|
|
1366
1366
|
description: "CLI for generating rich articles and images from ideas.",
|
|
1367
1367
|
type: "module",
|
|
1368
1368
|
repository: {
|
|
@@ -1420,6 +1420,7 @@ var package_default = {
|
|
|
1420
1420
|
dependencies: {
|
|
1421
1421
|
"@ant-design/icons": "^6.1.1",
|
|
1422
1422
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
1423
|
+
"@telepat/limn": "^0.1.5",
|
|
1423
1424
|
antd: "^6.3.4",
|
|
1424
1425
|
commander: "^14.0.1",
|
|
1425
1426
|
"env-paths": "^3.0.0",
|
|
@@ -2283,9 +2284,10 @@ function buildArticlePlanJsonSchema(targetLengthWords) {
|
|
|
2283
2284
|
items: {
|
|
2284
2285
|
type: "object",
|
|
2285
2286
|
additionalProperties: false,
|
|
2286
|
-
required: ["description"],
|
|
2287
|
+
required: ["description", "anchorAfterSection"],
|
|
2287
2288
|
properties: {
|
|
2288
|
-
description: { type: "string" }
|
|
2289
|
+
description: { type: "string" },
|
|
2290
|
+
anchorAfterSection: { type: "number", minimum: 1 }
|
|
2289
2291
|
}
|
|
2290
2292
|
}
|
|
2291
2293
|
}
|
|
@@ -2322,8 +2324,9 @@ function buildArticlePlanMessages(idea, options) {
|
|
|
2322
2324
|
"- Each section description should name the mechanism, evidence type, or practical action that makes the section useful.",
|
|
2323
2325
|
"- Sections are article-only structure and must not be treated as requirements for non-article channels.",
|
|
2324
2326
|
"- Include a cover image description and 2 to 3 inline image descriptions.",
|
|
2327
|
+
"- Each inline image must specify which section it follows (anchorAfterSection, 1-based index). Choose sections where visual reinforcement adds the most value.",
|
|
2328
|
+
"- Image descriptions should capture the general concept and mood \u2014 the exact text-to-image prompt will be refined later using the actual section content.",
|
|
2325
2329
|
"- Image descriptions must be concrete and contextual, not generic stock-photo phrasing.",
|
|
2326
|
-
"- Do not include section placement for inline images \u2014 placement is determined automatically after writing.",
|
|
2327
2330
|
"",
|
|
2328
2331
|
"Shared content brief context:",
|
|
2329
2332
|
`- description: ${options.contentBrief.description}`,
|
|
@@ -2342,7 +2345,7 @@ function buildArticlePlanMessages(idea, options) {
|
|
|
2342
2345
|
"- outroBrief: string",
|
|
2343
2346
|
`- sections: array of ${sectionCounts.label} objects, each with title and description strings`,
|
|
2344
2347
|
"- coverImageDescription: string",
|
|
2345
|
-
"- inlineImages: array of 2 to 3 objects, each with a description string
|
|
2348
|
+
"- inlineImages: array of 2 to 3 objects, each with a description string and an anchorAfterSection number (1-based section index)",
|
|
2346
2349
|
"",
|
|
2347
2350
|
"Do not omit any required fields. Return strict JSON only."
|
|
2348
2351
|
].join("\n")
|
|
@@ -2357,7 +2360,8 @@ var articleSectionPlanSchema = z5.object({
|
|
|
2357
2360
|
description: z5.string().min(1)
|
|
2358
2361
|
});
|
|
2359
2362
|
var inlineImagePlanSchema = z5.object({
|
|
2360
|
-
description: z5.string().min(1)
|
|
2363
|
+
description: z5.string().min(1),
|
|
2364
|
+
anchorAfterSection: z5.number().int().min(1)
|
|
2361
2365
|
});
|
|
2362
2366
|
var articlePlanSchema = z5.object({
|
|
2363
2367
|
title: z5.string().min(1),
|
|
@@ -2408,11 +2412,15 @@ async function planArticle({
|
|
|
2408
2412
|
});
|
|
2409
2413
|
const normalizedSlug = slugify(basePlan.slug || basePlan.title);
|
|
2410
2414
|
const uniqueSlug = await resolveUniqueSlug(markdownOutputDir, normalizedSlug);
|
|
2415
|
+
const sectionCount = basePlan.sections.length;
|
|
2411
2416
|
return {
|
|
2412
2417
|
...basePlan,
|
|
2413
2418
|
slug: uniqueSlug,
|
|
2414
2419
|
keywords: basePlan.keywords.slice(0, 8),
|
|
2415
|
-
inlineImages: basePlan.inlineImages.slice(0, 3)
|
|
2420
|
+
inlineImages: basePlan.inlineImages.slice(0, 3).map((img) => ({
|
|
2421
|
+
...img,
|
|
2422
|
+
anchorAfterSection: Math.max(1, Math.min(sectionCount, img.anchorAfterSection))
|
|
2423
|
+
}))
|
|
2416
2424
|
};
|
|
2417
2425
|
}
|
|
2418
2426
|
function buildDryRunPlan(idea, contentBrief) {
|
|
@@ -2450,10 +2458,12 @@ function buildDryRunPlan(idea, contentBrief) {
|
|
|
2450
2458
|
coverImageDescription: "A refined editorial workspace with notebooks, sketches, and glowing structured outlines, cinematic but minimal.",
|
|
2451
2459
|
inlineImages: [
|
|
2452
2460
|
{
|
|
2453
|
-
description: "A rough idea evolving into a structured article outline on a desk full of notes."
|
|
2461
|
+
description: "A rough idea evolving into a structured article outline on a desk full of notes.",
|
|
2462
|
+
anchorAfterSection: 2
|
|
2454
2463
|
},
|
|
2455
2464
|
{
|
|
2456
|
-
description: "A collaborative editorial scene where human judgment and AI suggestions coexist."
|
|
2465
|
+
description: "A collaborative editorial scene where human judgment and AI suggestions coexist.",
|
|
2466
|
+
anchorAfterSection: 4
|
|
2457
2467
|
}
|
|
2458
2468
|
]
|
|
2459
2469
|
};
|
|
@@ -2874,65 +2884,8 @@ function normalizeGeneratedSection(content, label2) {
|
|
|
2874
2884
|
return normalized.replace(/^```(?:markdown)?\s*/i, "").replace(/```\s*$/i, "").trim();
|
|
2875
2885
|
}
|
|
2876
2886
|
|
|
2877
|
-
// src/
|
|
2878
|
-
import
|
|
2879
|
-
var ReplicateClient = class {
|
|
2880
|
-
client;
|
|
2881
|
-
constructor(apiToken) {
|
|
2882
|
-
this.client = new Replicate({ auth: apiToken });
|
|
2883
|
-
}
|
|
2884
|
-
async runModel(model, input, options = {}) {
|
|
2885
|
-
let lastError = null;
|
|
2886
|
-
const startedAtMs = Date.now();
|
|
2887
|
-
let attempts = 0;
|
|
2888
|
-
let retries = 0;
|
|
2889
|
-
let retryBackoffMs = 0;
|
|
2890
|
-
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
2891
|
-
attempts = attempt + 1;
|
|
2892
|
-
try {
|
|
2893
|
-
const output = await this.client.run(model, { input });
|
|
2894
|
-
options.onMetrics?.({
|
|
2895
|
-
durationMs: Date.now() - startedAtMs,
|
|
2896
|
-
attempts,
|
|
2897
|
-
retries,
|
|
2898
|
-
retryBackoffMs,
|
|
2899
|
-
modelId: model
|
|
2900
|
-
});
|
|
2901
|
-
return output;
|
|
2902
|
-
} catch (error) {
|
|
2903
|
-
lastError = error instanceof Error ? error : new Error("Unknown Replicate client error.");
|
|
2904
|
-
if (attempt < 2 && shouldRetryError(lastError)) {
|
|
2905
|
-
const backoff = backoffMs(attempt);
|
|
2906
|
-
retries += 1;
|
|
2907
|
-
retryBackoffMs += backoff;
|
|
2908
|
-
options.onRetry?.({
|
|
2909
|
-
attempts,
|
|
2910
|
-
retries,
|
|
2911
|
-
retryBackoffMs,
|
|
2912
|
-
backoffMs: backoff,
|
|
2913
|
-
errorMessage: lastError.message,
|
|
2914
|
-
modelId: model
|
|
2915
|
-
});
|
|
2916
|
-
await wait(backoff);
|
|
2917
|
-
continue;
|
|
2918
|
-
}
|
|
2919
|
-
break;
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
|
-
throw lastError ?? new Error("Replicate request failed.");
|
|
2923
|
-
}
|
|
2924
|
-
};
|
|
2925
|
-
function shouldRetryError(error) {
|
|
2926
|
-
return /timeout|network|fetch|temporarily|rate|429|500|502|503|504/i.test(error.message);
|
|
2927
|
-
}
|
|
2928
|
-
function backoffMs(attempt) {
|
|
2929
|
-
return 500 * (attempt + 1);
|
|
2930
|
-
}
|
|
2931
|
-
function wait(ms) {
|
|
2932
|
-
return new Promise((resolve) => {
|
|
2933
|
-
setTimeout(resolve, ms);
|
|
2934
|
-
});
|
|
2935
|
-
}
|
|
2887
|
+
// src/pipeline/runner.ts
|
|
2888
|
+
import { Limn } from "@telepat/limn";
|
|
2936
2889
|
|
|
2937
2890
|
// src/images/renderImages.ts
|
|
2938
2891
|
import { writeFile as writeFile4 } from "fs/promises";
|
|
@@ -2961,11 +2914,11 @@ function buildImagePromptMessages(plan, image, section) {
|
|
|
2961
2914
|
`Section excerpt: ${section.body.slice(0, 500)}`
|
|
2962
2915
|
);
|
|
2963
2916
|
}
|
|
2964
|
-
userLines.push("Write one strong prompt
|
|
2917
|
+
userLines.push("Write one strong visual prompt describing the image in natural language.");
|
|
2965
2918
|
return [
|
|
2966
2919
|
{
|
|
2967
2920
|
role: "system",
|
|
2968
|
-
content: "You write concise, high-signal prompts for text-to-image models. The prompt should be vivid
|
|
2921
|
+
content: "You write concise, high-signal prompts for text-to-image models. The prompt should be vivid and compositionally clear. Do not include any words, letters, numbers, logos, watermarks, or signage in the image, unless text in the image was explicitly requested. Return only the requested JSON."
|
|
2969
2922
|
},
|
|
2970
2923
|
{
|
|
2971
2924
|
role: "user",
|
|
@@ -2974,468 +2927,6 @@ function buildImagePromptMessages(plan, image, section) {
|
|
|
2974
2927
|
];
|
|
2975
2928
|
}
|
|
2976
2929
|
|
|
2977
|
-
// src/models/t2i/definitions/bytedance__seedream-4.json
|
|
2978
|
-
var bytedance_seedream_4_default = {
|
|
2979
|
-
modelId: "bytedance/seedream-4",
|
|
2980
|
-
provider: "replicate",
|
|
2981
|
-
displayName: "Seedream 4",
|
|
2982
|
-
category: "image-generation",
|
|
2983
|
-
pricingSourceUrl: "https://replicate.com/bytedance/seedream-4",
|
|
2984
|
-
pricingNotes: "Replicate pricing: $0.03 per output image.",
|
|
2985
|
-
pricing: {
|
|
2986
|
-
usdPerSecond: null,
|
|
2987
|
-
usdPer1kInputTokens: null,
|
|
2988
|
-
usdPer1kOutputTokens: null
|
|
2989
|
-
},
|
|
2990
|
-
pricingRules: {
|
|
2991
|
-
basis: "output_image_count",
|
|
2992
|
-
usdPerImage: 0.03
|
|
2993
|
-
},
|
|
2994
|
-
inputOptions: {
|
|
2995
|
-
userConfigurable: ["size", "sequential_image_generation", "max_images", "enhance_prompt"],
|
|
2996
|
-
pipelineManaged: ["prompt", "aspect_ratio"],
|
|
2997
|
-
fields: {
|
|
2998
|
-
size: {
|
|
2999
|
-
type: "string",
|
|
3000
|
-
default: "2K",
|
|
3001
|
-
enum: ["1K", "2K", "4K"]
|
|
3002
|
-
},
|
|
3003
|
-
sequential_image_generation: {
|
|
3004
|
-
type: "string",
|
|
3005
|
-
default: "disabled",
|
|
3006
|
-
enum: ["disabled", "auto"]
|
|
3007
|
-
},
|
|
3008
|
-
max_images: {
|
|
3009
|
-
type: "integer",
|
|
3010
|
-
default: 1,
|
|
3011
|
-
minimum: 1,
|
|
3012
|
-
maximum: 15
|
|
3013
|
-
},
|
|
3014
|
-
enhance_prompt: {
|
|
3015
|
-
type: "boolean",
|
|
3016
|
-
default: true
|
|
3017
|
-
},
|
|
3018
|
-
prompt: {
|
|
3019
|
-
type: "string",
|
|
3020
|
-
required: true
|
|
3021
|
-
},
|
|
3022
|
-
aspect_ratio: {
|
|
3023
|
-
type: "string",
|
|
3024
|
-
required: true,
|
|
3025
|
-
enum: ["1:1", "16:9", "9:16"]
|
|
3026
|
-
}
|
|
3027
|
-
}
|
|
3028
|
-
}
|
|
3029
|
-
};
|
|
3030
|
-
|
|
3031
|
-
// src/models/t2i/definitions/black-forest-labs__flux-2-pro.json
|
|
3032
|
-
var black_forest_labs_flux_2_pro_default = {
|
|
3033
|
-
modelId: "black-forest-labs/flux-2-pro",
|
|
3034
|
-
provider: "replicate",
|
|
3035
|
-
displayName: "FLUX 2 Pro",
|
|
3036
|
-
category: "image-generation",
|
|
3037
|
-
pricingSourceUrl: "https://replicate.com/black-forest-labs/flux-2-pro",
|
|
3038
|
-
pricingNotes: "Replicate lists multi-property pricing for run and input/output megapixels; this model currently uses best-effort cost fallback when exact rule mapping is unavailable.",
|
|
3039
|
-
pricing: {
|
|
3040
|
-
usdPerSecond: null,
|
|
3041
|
-
usdPer1kInputTokens: null,
|
|
3042
|
-
usdPer1kOutputTokens: null
|
|
3043
|
-
},
|
|
3044
|
-
inputOptions: {
|
|
3045
|
-
userConfigurable: ["safety_tolerance", "seed", "output_format", "output_quality"],
|
|
3046
|
-
pipelineManaged: ["prompt", "aspect_ratio", "width", "height"],
|
|
3047
|
-
fields: {
|
|
3048
|
-
safety_tolerance: {
|
|
3049
|
-
type: "integer",
|
|
3050
|
-
default: 2,
|
|
3051
|
-
minimum: 1,
|
|
3052
|
-
maximum: 5
|
|
3053
|
-
},
|
|
3054
|
-
seed: {
|
|
3055
|
-
type: "integer",
|
|
3056
|
-
nullable: true
|
|
3057
|
-
},
|
|
3058
|
-
output_format: {
|
|
3059
|
-
type: "string",
|
|
3060
|
-
default: "webp",
|
|
3061
|
-
enum: ["webp", "png", "jpg", "jpeg"]
|
|
3062
|
-
},
|
|
3063
|
-
output_quality: {
|
|
3064
|
-
type: "integer",
|
|
3065
|
-
default: 80,
|
|
3066
|
-
minimum: 0,
|
|
3067
|
-
maximum: 100
|
|
3068
|
-
},
|
|
3069
|
-
prompt: {
|
|
3070
|
-
type: "string",
|
|
3071
|
-
required: true
|
|
3072
|
-
},
|
|
3073
|
-
aspect_ratio: {
|
|
3074
|
-
type: "string",
|
|
3075
|
-
required: true,
|
|
3076
|
-
enum: ["custom"]
|
|
3077
|
-
},
|
|
3078
|
-
width: {
|
|
3079
|
-
type: "integer",
|
|
3080
|
-
required: true,
|
|
3081
|
-
minimum: 256,
|
|
3082
|
-
maximum: 2048
|
|
3083
|
-
},
|
|
3084
|
-
height: {
|
|
3085
|
-
type: "integer",
|
|
3086
|
-
required: true,
|
|
3087
|
-
minimum: 256,
|
|
3088
|
-
maximum: 2048
|
|
3089
|
-
}
|
|
3090
|
-
}
|
|
3091
|
-
}
|
|
3092
|
-
};
|
|
3093
|
-
|
|
3094
|
-
// src/models/t2i/definitions/black-forest-labs__flux-schnell.json
|
|
3095
|
-
var black_forest_labs_flux_schnell_default = {
|
|
3096
|
-
modelId: "black-forest-labs/flux-schnell",
|
|
3097
|
-
provider: "replicate",
|
|
3098
|
-
displayName: "FLUX Schnell",
|
|
3099
|
-
category: "image-generation",
|
|
3100
|
-
pricingSourceUrl: "https://replicate.com/black-forest-labs/flux-schnell",
|
|
3101
|
-
pricingNotes: "Replicate pricing: $3 per 1000 output images.",
|
|
3102
|
-
pricing: {
|
|
3103
|
-
usdPerSecond: null,
|
|
3104
|
-
usdPer1kInputTokens: null,
|
|
3105
|
-
usdPer1kOutputTokens: null
|
|
3106
|
-
},
|
|
3107
|
-
pricingRules: {
|
|
3108
|
-
basis: "output_image_count",
|
|
3109
|
-
usdPerImage: 3e-3
|
|
3110
|
-
},
|
|
3111
|
-
inputOptions: {
|
|
3112
|
-
userConfigurable: ["num_outputs", "num_inference_steps", "seed", "output_format", "output_quality", "disable_safety_checker", "go_fast", "megapixels"],
|
|
3113
|
-
pipelineManaged: ["prompt", "aspect_ratio"],
|
|
3114
|
-
fields: {
|
|
3115
|
-
num_outputs: {
|
|
3116
|
-
type: "integer",
|
|
3117
|
-
default: 1,
|
|
3118
|
-
minimum: 1,
|
|
3119
|
-
maximum: 4
|
|
3120
|
-
},
|
|
3121
|
-
num_inference_steps: {
|
|
3122
|
-
type: "integer",
|
|
3123
|
-
default: 4,
|
|
3124
|
-
minimum: 1,
|
|
3125
|
-
maximum: 4
|
|
3126
|
-
},
|
|
3127
|
-
seed: {
|
|
3128
|
-
type: "integer",
|
|
3129
|
-
nullable: true
|
|
3130
|
-
},
|
|
3131
|
-
output_format: {
|
|
3132
|
-
type: "string",
|
|
3133
|
-
default: "webp",
|
|
3134
|
-
enum: ["webp", "jpg", "png"]
|
|
3135
|
-
},
|
|
3136
|
-
output_quality: {
|
|
3137
|
-
type: "integer",
|
|
3138
|
-
default: 80,
|
|
3139
|
-
minimum: 0,
|
|
3140
|
-
maximum: 100
|
|
3141
|
-
},
|
|
3142
|
-
disable_safety_checker: {
|
|
3143
|
-
type: "boolean",
|
|
3144
|
-
default: false
|
|
3145
|
-
},
|
|
3146
|
-
go_fast: {
|
|
3147
|
-
type: "boolean",
|
|
3148
|
-
default: true
|
|
3149
|
-
},
|
|
3150
|
-
megapixels: {
|
|
3151
|
-
type: "string",
|
|
3152
|
-
default: "1",
|
|
3153
|
-
allowAnyString: true,
|
|
3154
|
-
recommendedValues: ["1"]
|
|
3155
|
-
},
|
|
3156
|
-
prompt: {
|
|
3157
|
-
type: "string",
|
|
3158
|
-
required: true
|
|
3159
|
-
},
|
|
3160
|
-
aspect_ratio: {
|
|
3161
|
-
type: "string",
|
|
3162
|
-
required: true,
|
|
3163
|
-
enum: ["1:1", "16:9", "9:16"]
|
|
3164
|
-
}
|
|
3165
|
-
}
|
|
3166
|
-
}
|
|
3167
|
-
};
|
|
3168
|
-
|
|
3169
|
-
// src/models/t2i/definitions/google__nano-banana-pro.json
|
|
3170
|
-
var google_nano_banana_pro_default = {
|
|
3171
|
-
modelId: "google/nano-banana-pro",
|
|
3172
|
-
provider: "replicate",
|
|
3173
|
-
displayName: "Nano Banana Pro",
|
|
3174
|
-
category: "image-generation",
|
|
3175
|
-
pricingSourceUrl: "https://replicate.com/google/nano-banana-pro",
|
|
3176
|
-
pricingNotes: "Replicate pricing by target resolution: 1K/2K=$0.15 per output image, 4K=$0.30 per output image, fallback tier=$0.035 per output image.",
|
|
3177
|
-
pricing: {
|
|
3178
|
-
usdPerSecond: null,
|
|
3179
|
-
usdPer1kInputTokens: null,
|
|
3180
|
-
usdPer1kOutputTokens: null
|
|
3181
|
-
},
|
|
3182
|
-
pricingRules: {
|
|
3183
|
-
basis: "output_image_resolution",
|
|
3184
|
-
tiers: [
|
|
3185
|
-
{
|
|
3186
|
-
resolution: "1K",
|
|
3187
|
-
usdPerImage: 0.15
|
|
3188
|
-
},
|
|
3189
|
-
{
|
|
3190
|
-
resolution: "2K",
|
|
3191
|
-
usdPerImage: 0.15
|
|
3192
|
-
},
|
|
3193
|
-
{
|
|
3194
|
-
resolution: "4K",
|
|
3195
|
-
usdPerImage: 0.3
|
|
3196
|
-
},
|
|
3197
|
-
{
|
|
3198
|
-
resolution: "fallback",
|
|
3199
|
-
usdPerImage: 0.035
|
|
3200
|
-
}
|
|
3201
|
-
]
|
|
3202
|
-
},
|
|
3203
|
-
inputOptions: {
|
|
3204
|
-
userConfigurable: ["resolution", "output_format", "safety_filter_level", "allow_fallback_model"],
|
|
3205
|
-
pipelineManaged: ["prompt", "aspect_ratio"],
|
|
3206
|
-
fields: {
|
|
3207
|
-
resolution: {
|
|
3208
|
-
type: "string",
|
|
3209
|
-
default: "2K",
|
|
3210
|
-
enum: ["1K", "2K", "4K"]
|
|
3211
|
-
},
|
|
3212
|
-
output_format: {
|
|
3213
|
-
type: "string",
|
|
3214
|
-
default: "jpg",
|
|
3215
|
-
enum: ["jpg", "png", "webp"]
|
|
3216
|
-
},
|
|
3217
|
-
safety_filter_level: {
|
|
3218
|
-
type: "string",
|
|
3219
|
-
default: "block_only_high",
|
|
3220
|
-
enum: ["block_low_and_above", "block_medium_and_above", "block_only_high"]
|
|
3221
|
-
},
|
|
3222
|
-
allow_fallback_model: {
|
|
3223
|
-
type: "boolean",
|
|
3224
|
-
default: false
|
|
3225
|
-
},
|
|
3226
|
-
prompt: {
|
|
3227
|
-
type: "string",
|
|
3228
|
-
required: true
|
|
3229
|
-
},
|
|
3230
|
-
aspect_ratio: {
|
|
3231
|
-
type: "string",
|
|
3232
|
-
required: true,
|
|
3233
|
-
enum: ["1:1", "16:9", "9:16"]
|
|
3234
|
-
}
|
|
3235
|
-
}
|
|
3236
|
-
}
|
|
3237
|
-
};
|
|
3238
|
-
|
|
3239
|
-
// src/models/t2i/definitions/prunaai__z-image-turbo.json
|
|
3240
|
-
var prunaai_z_image_turbo_default = {
|
|
3241
|
-
modelId: "prunaai/z-image-turbo",
|
|
3242
|
-
provider: "replicate",
|
|
3243
|
-
displayName: "Z Image Turbo",
|
|
3244
|
-
category: "image-generation",
|
|
3245
|
-
pricingSourceUrl: "https://replicate.com/prunaai/z-image-turbo",
|
|
3246
|
-
pricingNotes: "Replicate pricing is tiered by output image resolution (megapixels).",
|
|
3247
|
-
pricing: {
|
|
3248
|
-
usdPerSecond: null,
|
|
3249
|
-
usdPer1kInputTokens: null,
|
|
3250
|
-
usdPer1kOutputTokens: null
|
|
3251
|
-
},
|
|
3252
|
-
pricingRules: {
|
|
3253
|
-
basis: "output_image_megapixels",
|
|
3254
|
-
tiers: [
|
|
3255
|
-
{
|
|
3256
|
-
maxMegapixels: 0.5,
|
|
3257
|
-
usdPerImage: 25e-4
|
|
3258
|
-
},
|
|
3259
|
-
{
|
|
3260
|
-
maxMegapixels: 1,
|
|
3261
|
-
usdPerImage: 5e-3
|
|
3262
|
-
},
|
|
3263
|
-
{
|
|
3264
|
-
maxMegapixels: 2,
|
|
3265
|
-
usdPerImage: 0.01
|
|
3266
|
-
},
|
|
3267
|
-
{
|
|
3268
|
-
maxMegapixels: 3,
|
|
3269
|
-
usdPerImage: 0.015
|
|
3270
|
-
},
|
|
3271
|
-
{
|
|
3272
|
-
maxMegapixels: 4,
|
|
3273
|
-
usdPerImage: 0.02
|
|
3274
|
-
}
|
|
3275
|
-
]
|
|
3276
|
-
},
|
|
3277
|
-
inputOptions: {
|
|
3278
|
-
userConfigurable: ["num_inference_steps", "guidance_scale", "seed", "go_fast", "output_format", "output_quality"],
|
|
3279
|
-
pipelineManaged: ["prompt", "width", "height"],
|
|
3280
|
-
fields: {
|
|
3281
|
-
num_inference_steps: {
|
|
3282
|
-
type: "integer",
|
|
3283
|
-
default: 8,
|
|
3284
|
-
minimum: 1,
|
|
3285
|
-
maximum: 50
|
|
3286
|
-
},
|
|
3287
|
-
guidance_scale: {
|
|
3288
|
-
type: "number",
|
|
3289
|
-
default: 0,
|
|
3290
|
-
minimum: 0,
|
|
3291
|
-
maximum: 20
|
|
3292
|
-
},
|
|
3293
|
-
seed: {
|
|
3294
|
-
type: "integer",
|
|
3295
|
-
nullable: true
|
|
3296
|
-
},
|
|
3297
|
-
go_fast: {
|
|
3298
|
-
type: "boolean",
|
|
3299
|
-
default: false
|
|
3300
|
-
},
|
|
3301
|
-
output_format: {
|
|
3302
|
-
type: "string",
|
|
3303
|
-
default: "jpg",
|
|
3304
|
-
enum: ["jpg", "jpeg", "png", "webp"]
|
|
3305
|
-
},
|
|
3306
|
-
output_quality: {
|
|
3307
|
-
type: "integer",
|
|
3308
|
-
default: 80,
|
|
3309
|
-
minimum: 0,
|
|
3310
|
-
maximum: 100
|
|
3311
|
-
},
|
|
3312
|
-
prompt: {
|
|
3313
|
-
type: "string",
|
|
3314
|
-
required: true
|
|
3315
|
-
},
|
|
3316
|
-
width: {
|
|
3317
|
-
type: "integer",
|
|
3318
|
-
required: true,
|
|
3319
|
-
minimum: 64,
|
|
3320
|
-
maximum: 2048
|
|
3321
|
-
},
|
|
3322
|
-
height: {
|
|
3323
|
-
type: "integer",
|
|
3324
|
-
required: true,
|
|
3325
|
-
minimum: 64,
|
|
3326
|
-
maximum: 2048
|
|
3327
|
-
}
|
|
3328
|
-
}
|
|
3329
|
-
}
|
|
3330
|
-
};
|
|
3331
|
-
|
|
3332
|
-
// src/models/t2i/registry.ts
|
|
3333
|
-
var modelDefinitions = [
|
|
3334
|
-
black_forest_labs_flux_schnell_default,
|
|
3335
|
-
black_forest_labs_flux_2_pro_default,
|
|
3336
|
-
bytedance_seedream_4_default,
|
|
3337
|
-
google_nano_banana_pro_default,
|
|
3338
|
-
prunaai_z_image_turbo_default
|
|
3339
|
-
];
|
|
3340
|
-
function getSupportedT2IModels() {
|
|
3341
|
-
return modelDefinitions;
|
|
3342
|
-
}
|
|
3343
|
-
function getT2IModel(modelId) {
|
|
3344
|
-
const model = modelDefinitions.find((entry) => entry.modelId === modelId);
|
|
3345
|
-
if (!model) {
|
|
3346
|
-
throw new Error(`Unsupported T2I model: ${modelId}`);
|
|
3347
|
-
}
|
|
3348
|
-
return model;
|
|
3349
|
-
}
|
|
3350
|
-
|
|
3351
|
-
// src/models/t2i/options.ts
|
|
3352
|
-
function getT2IFieldDefault(modelId, fieldName) {
|
|
3353
|
-
const field = getFieldDefinition(modelId, fieldName);
|
|
3354
|
-
return field?.default;
|
|
3355
|
-
}
|
|
3356
|
-
function sanitizeT2IOverrides(modelId, overrides) {
|
|
3357
|
-
const model = getT2IModel(modelId);
|
|
3358
|
-
return Object.fromEntries(
|
|
3359
|
-
Object.entries(overrides).flatMap(([fieldName, value2]) => {
|
|
3360
|
-
if (!model.inputOptions.userConfigurable.includes(fieldName)) {
|
|
3361
|
-
return [];
|
|
3362
|
-
}
|
|
3363
|
-
const sanitized = coerceT2IFieldValue(modelId, fieldName, value2);
|
|
3364
|
-
return sanitized === void 0 ? [] : [[fieldName, sanitized]];
|
|
3365
|
-
})
|
|
3366
|
-
);
|
|
3367
|
-
}
|
|
3368
|
-
function coerceT2IFieldValue(modelId, fieldName, value2) {
|
|
3369
|
-
const field = getFieldDefinition(modelId, fieldName);
|
|
3370
|
-
if (!field) {
|
|
3371
|
-
return void 0;
|
|
3372
|
-
}
|
|
3373
|
-
if (value2 === null) {
|
|
3374
|
-
return field.nullable ? null : void 0;
|
|
3375
|
-
}
|
|
3376
|
-
if (value2 === void 0) {
|
|
3377
|
-
return void 0;
|
|
3378
|
-
}
|
|
3379
|
-
if (field.type === "boolean") {
|
|
3380
|
-
return coerceBoolean(value2);
|
|
3381
|
-
}
|
|
3382
|
-
if (field.type === "integer") {
|
|
3383
|
-
return clampNumber(parseFiniteNumber(value2, true), field.minimum, field.maximum, true, field.nullable);
|
|
3384
|
-
}
|
|
3385
|
-
if (field.type === "number") {
|
|
3386
|
-
return clampNumber(parseFiniteNumber(value2, false), field.minimum, field.maximum, false, field.nullable);
|
|
3387
|
-
}
|
|
3388
|
-
if (field.type === "string" || typeof value2 === "string") {
|
|
3389
|
-
const normalized = String(value2).trim();
|
|
3390
|
-
if (!normalized) {
|
|
3391
|
-
return field.nullable ? null : void 0;
|
|
3392
|
-
}
|
|
3393
|
-
if (field.enum && field.enum.length > 0 && !field.allowAnyString && !field.enum.includes(normalized)) {
|
|
3394
|
-
return void 0;
|
|
3395
|
-
}
|
|
3396
|
-
return normalized;
|
|
3397
|
-
}
|
|
3398
|
-
return value2;
|
|
3399
|
-
}
|
|
3400
|
-
function getFieldDefinition(modelId, fieldName) {
|
|
3401
|
-
const model = getT2IModel(modelId);
|
|
3402
|
-
return model.inputOptions.fields[fieldName];
|
|
3403
|
-
}
|
|
3404
|
-
function coerceBoolean(value2) {
|
|
3405
|
-
if (typeof value2 === "boolean") {
|
|
3406
|
-
return value2;
|
|
3407
|
-
}
|
|
3408
|
-
if (typeof value2 === "string") {
|
|
3409
|
-
if (value2.trim() === "true") {
|
|
3410
|
-
return true;
|
|
3411
|
-
}
|
|
3412
|
-
if (value2.trim() === "false") {
|
|
3413
|
-
return false;
|
|
3414
|
-
}
|
|
3415
|
-
}
|
|
3416
|
-
return void 0;
|
|
3417
|
-
}
|
|
3418
|
-
function parseFiniteNumber(value2, integer) {
|
|
3419
|
-
const parsed = typeof value2 === "number" ? value2 : Number(String(value2).trim());
|
|
3420
|
-
if (!Number.isFinite(parsed)) {
|
|
3421
|
-
return void 0;
|
|
3422
|
-
}
|
|
3423
|
-
return integer ? Math.round(parsed) : parsed;
|
|
3424
|
-
}
|
|
3425
|
-
function clampNumber(value2, minimum, maximum, integer, nullable) {
|
|
3426
|
-
if (value2 === void 0) {
|
|
3427
|
-
return nullable ? null : void 0;
|
|
3428
|
-
}
|
|
3429
|
-
let next = value2;
|
|
3430
|
-
if (minimum !== void 0) {
|
|
3431
|
-
next = Math.max(minimum, next);
|
|
3432
|
-
}
|
|
3433
|
-
if (maximum !== void 0) {
|
|
3434
|
-
next = Math.min(maximum, next);
|
|
3435
|
-
}
|
|
3436
|
-
return integer ? Math.round(next) : next;
|
|
3437
|
-
}
|
|
3438
|
-
|
|
3439
2930
|
// src/pipeline/analytics.ts
|
|
3440
2931
|
var LLM_USD_PER_1K_TOKENS = {
|
|
3441
2932
|
// AUTO-GENERATED:OPENROUTER_PRICING_START
|
|
@@ -3461,64 +2952,6 @@ function estimateLlmCostUsd(modelId, usage) {
|
|
|
3461
2952
|
const usd = promptTokens / 1e3 * pricing.input + completionTokens / 1e3 * pricing.output;
|
|
3462
2953
|
return { usd, source: "estimated" };
|
|
3463
2954
|
}
|
|
3464
|
-
function estimateImageCostUsd(modelId, input, imageCount) {
|
|
3465
|
-
const model = getT2IModel(modelId);
|
|
3466
|
-
const pricingRules = "pricingRules" in model ? model.pricingRules : void 0;
|
|
3467
|
-
if (!pricingRules) {
|
|
3468
|
-
return { usd: null, source: "unavailable" };
|
|
3469
|
-
}
|
|
3470
|
-
if (pricingRules.basis === "output_image_count" && "usdPerImage" in pricingRules && typeof pricingRules.usdPerImage === "number") {
|
|
3471
|
-
return {
|
|
3472
|
-
usd: pricingRules.usdPerImage * imageCount,
|
|
3473
|
-
source: "estimated"
|
|
3474
|
-
};
|
|
3475
|
-
}
|
|
3476
|
-
if (pricingRules.basis === "output_image_resolution" && "tiers" in pricingRules && Array.isArray(pricingRules.tiers)) {
|
|
3477
|
-
const resolution = typeof input.resolution === "string" ? input.resolution : "fallback";
|
|
3478
|
-
const tiers = pricingRules.tiers;
|
|
3479
|
-
const tier = tiers.find((entry) => entry.resolution === resolution) ?? tiers.find((entry) => entry.resolution === "fallback");
|
|
3480
|
-
if (tier?.usdPerImage !== void 0) {
|
|
3481
|
-
return {
|
|
3482
|
-
usd: tier.usdPerImage * imageCount,
|
|
3483
|
-
source: "estimated"
|
|
3484
|
-
};
|
|
3485
|
-
}
|
|
3486
|
-
}
|
|
3487
|
-
if (pricingRules.basis === "output_image_megapixels" && "tiers" in pricingRules && Array.isArray(pricingRules.tiers)) {
|
|
3488
|
-
const megapixels = resolveMegapixels(input);
|
|
3489
|
-
const tiers = pricingRules.tiers;
|
|
3490
|
-
const tier = tiers.find((entry) => megapixels <= entry.maxMegapixels);
|
|
3491
|
-
if (tier?.usdPerImage !== void 0) {
|
|
3492
|
-
return {
|
|
3493
|
-
usd: tier.usdPerImage * imageCount,
|
|
3494
|
-
source: "estimated"
|
|
3495
|
-
};
|
|
3496
|
-
}
|
|
3497
|
-
}
|
|
3498
|
-
return { usd: null, source: "unavailable" };
|
|
3499
|
-
}
|
|
3500
|
-
function resolveMegapixels(input) {
|
|
3501
|
-
const explicit = toNumber(input.megapixels);
|
|
3502
|
-
if (explicit !== null) {
|
|
3503
|
-
return explicit;
|
|
3504
|
-
}
|
|
3505
|
-
const width = toNumber(input.width);
|
|
3506
|
-
const height = toNumber(input.height);
|
|
3507
|
-
if (width !== null && height !== null && width > 0 && height > 0) {
|
|
3508
|
-
return width * height / 1e6;
|
|
3509
|
-
}
|
|
3510
|
-
return 1;
|
|
3511
|
-
}
|
|
3512
|
-
function toNumber(value2) {
|
|
3513
|
-
if (typeof value2 === "number" && Number.isFinite(value2)) {
|
|
3514
|
-
return value2;
|
|
3515
|
-
}
|
|
3516
|
-
if (typeof value2 === "string" && value2.trim() !== "") {
|
|
3517
|
-
const parsed = Number(value2);
|
|
3518
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
3519
|
-
}
|
|
3520
|
-
return null;
|
|
3521
|
-
}
|
|
3522
2955
|
function sumKnownCosts(values) {
|
|
3523
2956
|
const known = values.filter((value2) => typeof value2 === "number");
|
|
3524
2957
|
if (known.length !== values.length) {
|
|
@@ -3532,24 +2965,8 @@ function sumKnownCosts(values) {
|
|
|
3532
2965
|
|
|
3533
2966
|
// src/images/renderImages.ts
|
|
3534
2967
|
var MIN_IMAGE_BYTES = 1024;
|
|
3535
|
-
function
|
|
2968
|
+
function buildImageSlots(plan, sections, options) {
|
|
3536
2969
|
const sectionCount = sections.length;
|
|
3537
|
-
let defaultInlineCount;
|
|
3538
|
-
if (sectionCount <= 3) {
|
|
3539
|
-
defaultInlineCount = 0;
|
|
3540
|
-
} else if (sectionCount <= 6) {
|
|
3541
|
-
defaultInlineCount = 1;
|
|
3542
|
-
} else {
|
|
3543
|
-
defaultInlineCount = 2;
|
|
3544
|
-
}
|
|
3545
|
-
const availableInlineCount = Math.min(defaultInlineCount, plan.inlineImages.length, sectionCount);
|
|
3546
|
-
let inlineCount;
|
|
3547
|
-
const maxImages = options?.maxImages;
|
|
3548
|
-
if (maxImages !== void 0 && maxImages >= 1) {
|
|
3549
|
-
inlineCount = Math.min(availableInlineCount, Math.max(0, maxImages - 1));
|
|
3550
|
-
} else {
|
|
3551
|
-
inlineCount = availableInlineCount;
|
|
3552
|
-
}
|
|
3553
2970
|
const slots = [
|
|
3554
2971
|
{
|
|
3555
2972
|
id: "cover",
|
|
@@ -3559,17 +2976,22 @@ function selectImageSlots(plan, sections, options) {
|
|
|
3559
2976
|
anchorAfterSection: null
|
|
3560
2977
|
}
|
|
3561
2978
|
];
|
|
2979
|
+
let inlineCount = plan.inlineImages.length;
|
|
2980
|
+
const maxImages = options?.maxImages;
|
|
2981
|
+
if (maxImages !== void 0 && maxImages >= 1) {
|
|
2982
|
+
inlineCount = Math.min(inlineCount, Math.max(0, maxImages - 1));
|
|
2983
|
+
}
|
|
3562
2984
|
for (let i = 0; i < inlineCount; i++) {
|
|
3563
|
-
const
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
2985
|
+
const img = plan.inlineImages[i];
|
|
2986
|
+
if (!img) {
|
|
2987
|
+
continue;
|
|
2988
|
+
}
|
|
3567
2989
|
slots.push({
|
|
3568
2990
|
id: `inline-${i + 1}`,
|
|
3569
2991
|
kind: "inline",
|
|
3570
2992
|
prompt: "",
|
|
3571
|
-
description:
|
|
3572
|
-
anchorAfterSection
|
|
2993
|
+
description: img.description,
|
|
2994
|
+
anchorAfterSection: Math.max(1, Math.min(sectionCount, img.anchorAfterSection))
|
|
3573
2995
|
});
|
|
3574
2996
|
}
|
|
3575
2997
|
return slots;
|
|
@@ -3594,7 +3016,7 @@ async function expandImagePrompts({
|
|
|
3594
3016
|
const dryRunStartMs = Date.now();
|
|
3595
3017
|
prompts.push({
|
|
3596
3018
|
...image,
|
|
3597
|
-
prompt: `${image.description}
|
|
3019
|
+
prompt: `${image.description}`
|
|
3598
3020
|
});
|
|
3599
3021
|
onPromptComplete?.({
|
|
3600
3022
|
imageId: image.id,
|
|
@@ -3661,29 +3083,26 @@ async function expandImagePrompts({
|
|
|
3661
3083
|
async function renderExpandedImages({
|
|
3662
3084
|
prompts,
|
|
3663
3085
|
settings,
|
|
3664
|
-
|
|
3086
|
+
limn,
|
|
3665
3087
|
markdownPath,
|
|
3666
3088
|
assetDir,
|
|
3667
3089
|
dryRun,
|
|
3668
3090
|
onProgress,
|
|
3669
3091
|
onRenderComplete,
|
|
3670
|
-
onInteraction
|
|
3671
|
-
onRetry
|
|
3092
|
+
onInteraction
|
|
3672
3093
|
}) {
|
|
3673
3094
|
const renderedImages = [];
|
|
3674
3095
|
for (let index = 0; index < prompts.length; index += 1) {
|
|
3675
3096
|
const prompt = prompts[index];
|
|
3676
3097
|
onProgress?.(`Rendering image ${index + 1}/${prompts.length} with ${settings.t2i.modelId}`);
|
|
3677
|
-
const fileName = `${prompt.kind === "cover" ? "cover" : `inline-${prompt.anchorAfterSection}`}-${index + 1}
|
|
3098
|
+
const fileName = `${prompt.kind === "cover" ? "cover" : `inline-${prompt.anchorAfterSection}`}-${index + 1}.png`;
|
|
3678
3099
|
const outputPath = path6.join(assetDir, fileName);
|
|
3679
|
-
if (dryRun || !
|
|
3100
|
+
if (dryRun || !limn) {
|
|
3680
3101
|
const dryRunStartMs = Date.now();
|
|
3681
3102
|
await writeFile4(outputPath, `Placeholder image for: ${prompt.prompt}
|
|
3682
3103
|
`, "utf8");
|
|
3683
3104
|
const outputBytes = Buffer.byteLength(`Placeholder image for: ${prompt.prompt}
|
|
3684
3105
|
`, "utf8");
|
|
3685
|
-
const dryRunInput = createReplicateInput(settings, prompt.prompt, prompt.kind);
|
|
3686
|
-
const dryRunCost = estimateImageCostUsd(settings.t2i.modelId, dryRunInput, 1);
|
|
3687
3106
|
renderedImages.push({
|
|
3688
3107
|
...prompt,
|
|
3689
3108
|
outputPath,
|
|
@@ -3698,13 +3117,13 @@ async function renderExpandedImages({
|
|
|
3698
3117
|
retries: 0,
|
|
3699
3118
|
retryBackoffMs: 0,
|
|
3700
3119
|
outputBytes,
|
|
3701
|
-
costUsd:
|
|
3702
|
-
costSource:
|
|
3120
|
+
costUsd: null,
|
|
3121
|
+
costSource: "unavailable"
|
|
3703
3122
|
});
|
|
3704
3123
|
onInteraction?.({
|
|
3705
3124
|
stageId: "images",
|
|
3706
3125
|
operationId: `images:${prompt.id}`,
|
|
3707
|
-
provider: "
|
|
3126
|
+
provider: "limn-dry-run",
|
|
3708
3127
|
modelId: settings.t2i.modelId,
|
|
3709
3128
|
kind: prompt.kind,
|
|
3710
3129
|
startedAt: new Date(dryRunStartMs).toISOString(),
|
|
@@ -3715,92 +3134,79 @@ async function renderExpandedImages({
|
|
|
3715
3134
|
retryBackoffMs: 0,
|
|
3716
3135
|
status: "succeeded",
|
|
3717
3136
|
prompt: prompt.prompt,
|
|
3718
|
-
input:
|
|
3137
|
+
input: {},
|
|
3719
3138
|
errorMessage: null
|
|
3720
3139
|
});
|
|
3721
3140
|
continue;
|
|
3722
3141
|
}
|
|
3723
|
-
const
|
|
3142
|
+
const family = settings.t2i.modelId;
|
|
3724
3143
|
const renderStartedAtMs = Date.now();
|
|
3725
|
-
let runDurationMs = 0;
|
|
3726
|
-
let runAttempts = 1;
|
|
3727
|
-
let runRetries = 0;
|
|
3728
|
-
let runRetryBackoffMs = 0;
|
|
3729
3144
|
try {
|
|
3730
|
-
const
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
runAttempts = metrics.attempts;
|
|
3734
|
-
runRetries = metrics.retries;
|
|
3735
|
-
runRetryBackoffMs = metrics.retryBackoffMs;
|
|
3736
|
-
},
|
|
3737
|
-
onRetry(event) {
|
|
3738
|
-
onRetry?.({
|
|
3739
|
-
imageId: prompt.id,
|
|
3740
|
-
kind: prompt.kind,
|
|
3741
|
-
retries: event.retries,
|
|
3742
|
-
errorMessage: event.errorMessage
|
|
3743
|
-
});
|
|
3744
|
-
}
|
|
3145
|
+
const result = await limn.generate(prompt.prompt, family, {
|
|
3146
|
+
replicateModel: settings.t2i.modelId,
|
|
3147
|
+
aspectRatio: "16:9"
|
|
3745
3148
|
});
|
|
3746
|
-
const
|
|
3747
|
-
|
|
3149
|
+
const ext = mimeTypeToExtension(result.mimeType);
|
|
3150
|
+
const liveFileName = `${prompt.kind === "cover" ? "cover" : `inline-${prompt.anchorAfterSection}`}-${index + 1}.${ext}`;
|
|
3151
|
+
const liveOutputPath = path6.join(assetDir, liveFileName);
|
|
3152
|
+
if (result.image.byteLength < MIN_IMAGE_BYTES) {
|
|
3748
3153
|
throw new Error(
|
|
3749
|
-
`Image ${index + 1} download appears corrupted: only ${
|
|
3154
|
+
`Image ${index + 1} download appears corrupted: only ${result.image.byteLength} bytes received.`
|
|
3750
3155
|
);
|
|
3751
3156
|
}
|
|
3752
|
-
await writeFile4(
|
|
3157
|
+
await writeFile4(liveOutputPath, result.image);
|
|
3753
3158
|
renderedImages.push({
|
|
3754
3159
|
...prompt,
|
|
3755
|
-
outputPath,
|
|
3756
|
-
relativePath: relativeAssetPath(markdownPath,
|
|
3160
|
+
outputPath: liveOutputPath,
|
|
3161
|
+
relativePath: relativeAssetPath(markdownPath, liveOutputPath)
|
|
3757
3162
|
});
|
|
3758
|
-
const
|
|
3163
|
+
const costSource = result.analytics.costSource === "unknown" ? "unavailable" : "estimated";
|
|
3759
3164
|
onRenderComplete?.({
|
|
3760
3165
|
imageId: prompt.id,
|
|
3761
3166
|
kind: prompt.kind,
|
|
3762
|
-
modelId:
|
|
3763
|
-
durationMs:
|
|
3764
|
-
attempts:
|
|
3765
|
-
retries:
|
|
3766
|
-
retryBackoffMs:
|
|
3767
|
-
outputBytes:
|
|
3768
|
-
costUsd:
|
|
3769
|
-
costSource
|
|
3167
|
+
modelId: result.modelSlug,
|
|
3168
|
+
durationMs: result.analytics.totalDurationMs,
|
|
3169
|
+
attempts: 1,
|
|
3170
|
+
retries: 0,
|
|
3171
|
+
retryBackoffMs: 0,
|
|
3172
|
+
outputBytes: result.image.byteLength,
|
|
3173
|
+
costUsd: result.analytics.totalEstimatedCostUsd,
|
|
3174
|
+
costSource
|
|
3770
3175
|
});
|
|
3771
3176
|
onInteraction?.({
|
|
3772
3177
|
stageId: "images",
|
|
3773
3178
|
operationId: `images:${prompt.id}`,
|
|
3774
|
-
provider: "
|
|
3775
|
-
modelId:
|
|
3179
|
+
provider: "limn",
|
|
3180
|
+
modelId: result.modelSlug,
|
|
3776
3181
|
kind: prompt.kind,
|
|
3777
3182
|
startedAt: new Date(renderStartedAtMs).toISOString(),
|
|
3778
3183
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3779
|
-
durationMs:
|
|
3780
|
-
attempts:
|
|
3781
|
-
retries:
|
|
3782
|
-
retryBackoffMs:
|
|
3184
|
+
durationMs: result.analytics.totalDurationMs,
|
|
3185
|
+
attempts: 1,
|
|
3186
|
+
retries: 0,
|
|
3187
|
+
retryBackoffMs: 0,
|
|
3783
3188
|
status: "succeeded",
|
|
3784
3189
|
prompt: prompt.prompt,
|
|
3785
|
-
input,
|
|
3190
|
+
input: {},
|
|
3786
3191
|
errorMessage: null
|
|
3787
3192
|
});
|
|
3788
3193
|
} catch (error) {
|
|
3194
|
+
const durationMs = Date.now() - renderStartedAtMs;
|
|
3789
3195
|
onInteraction?.({
|
|
3790
3196
|
stageId: "images",
|
|
3791
3197
|
operationId: `images:${prompt.id}`,
|
|
3792
|
-
provider: "
|
|
3198
|
+
provider: "limn",
|
|
3793
3199
|
modelId: settings.t2i.modelId,
|
|
3794
3200
|
kind: prompt.kind,
|
|
3795
3201
|
startedAt: new Date(renderStartedAtMs).toISOString(),
|
|
3796
3202
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3797
|
-
durationMs
|
|
3798
|
-
attempts:
|
|
3799
|
-
retries:
|
|
3800
|
-
retryBackoffMs:
|
|
3203
|
+
durationMs,
|
|
3204
|
+
attempts: 1,
|
|
3205
|
+
retries: 0,
|
|
3206
|
+
retryBackoffMs: 0,
|
|
3801
3207
|
status: "failed",
|
|
3802
3208
|
prompt: prompt.prompt,
|
|
3803
|
-
input,
|
|
3209
|
+
input: {},
|
|
3804
3210
|
errorMessage: error instanceof Error ? error.message : "Unknown image render error."
|
|
3805
3211
|
});
|
|
3806
3212
|
throw error;
|
|
@@ -3832,128 +3238,10 @@ function mergeLlmMetrics(left, right) {
|
|
|
3832
3238
|
}
|
|
3833
3239
|
};
|
|
3834
3240
|
}
|
|
3835
|
-
function
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
if (model.inputOptions.pipelineManaged.includes("aspect_ratio")) {
|
|
3840
|
-
input.aspect_ratio = kind === "cover" ? "16:9" : "16:9";
|
|
3841
|
-
}
|
|
3842
|
-
if (model.inputOptions.pipelineManaged.includes("width")) {
|
|
3843
|
-
input.width = 1536;
|
|
3844
|
-
}
|
|
3845
|
-
if (model.inputOptions.pipelineManaged.includes("height")) {
|
|
3846
|
-
input.height = 864;
|
|
3847
|
-
}
|
|
3848
|
-
if (!("output_format" in input)) {
|
|
3849
|
-
const fallback = getT2IFieldDefault(settings.t2i.modelId, "output_format");
|
|
3850
|
-
if (typeof fallback === "string") {
|
|
3851
|
-
input.output_format = fallback;
|
|
3852
|
-
}
|
|
3853
|
-
}
|
|
3854
|
-
if (!("num_outputs" in input) && "num_outputs" in model.inputOptions.fields) {
|
|
3855
|
-
input.num_outputs = coerceT2IFieldValue(settings.t2i.modelId, "num_outputs", getT2IFieldDefault(settings.t2i.modelId, "num_outputs")) ?? 1;
|
|
3856
|
-
}
|
|
3857
|
-
if (!("max_images" in input) && "max_images" in model.inputOptions.fields) {
|
|
3858
|
-
input.max_images = coerceT2IFieldValue(settings.t2i.modelId, "max_images", getT2IFieldDefault(settings.t2i.modelId, "max_images")) ?? 1;
|
|
3859
|
-
}
|
|
3860
|
-
return input;
|
|
3861
|
-
}
|
|
3862
|
-
function resolveOutputFormat(settings) {
|
|
3863
|
-
const outputFormat = coerceT2IFieldValue(settings.t2i.modelId, "output_format", settings.t2i.inputOverrides.output_format);
|
|
3864
|
-
if (typeof outputFormat === "string") {
|
|
3865
|
-
return outputFormat === "jpeg" ? "jpg" : outputFormat;
|
|
3866
|
-
}
|
|
3867
|
-
const fallback = getT2IFieldDefault(settings.t2i.modelId, "output_format");
|
|
3868
|
-
const normalizedFallback = typeof fallback === "string" ? fallback : "png";
|
|
3869
|
-
return normalizedFallback === "jpeg" ? "jpg" : normalizedFallback;
|
|
3870
|
-
}
|
|
3871
|
-
async function normalizeReplicateOutput(output) {
|
|
3872
|
-
const first = Array.isArray(output) ? output[0] : output;
|
|
3873
|
-
if (!first) {
|
|
3874
|
-
throw new Error("Replicate returned no image output.");
|
|
3875
|
-
}
|
|
3876
|
-
if (typeof first === "string") {
|
|
3877
|
-
return fetchBytes(first);
|
|
3878
|
-
}
|
|
3879
|
-
if (first instanceof URL) {
|
|
3880
|
-
return fetchBytes(first.toString());
|
|
3881
|
-
}
|
|
3882
|
-
if (first instanceof Uint8Array) {
|
|
3883
|
-
return first;
|
|
3884
|
-
}
|
|
3885
|
-
if (first instanceof ArrayBuffer) {
|
|
3886
|
-
return new Uint8Array(first);
|
|
3887
|
-
}
|
|
3888
|
-
if (typeof Blob !== "undefined" && first instanceof Blob) {
|
|
3889
|
-
return new Uint8Array(await first.arrayBuffer());
|
|
3890
|
-
}
|
|
3891
|
-
if (typeof ReadableStream !== "undefined" && first instanceof ReadableStream) {
|
|
3892
|
-
return new Uint8Array(await new Response(first).arrayBuffer());
|
|
3893
|
-
}
|
|
3894
|
-
const fromBlobMethod = await maybeBytesFromBlobMethod(first);
|
|
3895
|
-
if (fromBlobMethod) {
|
|
3896
|
-
return fromBlobMethod;
|
|
3897
|
-
}
|
|
3898
|
-
const fromUrlMethod = await maybeBytesFromUrlMethod(first);
|
|
3899
|
-
if (fromUrlMethod) {
|
|
3900
|
-
return fromUrlMethod;
|
|
3901
|
-
}
|
|
3902
|
-
const fromArrayBufferMethod = await maybeBytesFromArrayBufferMethod(first);
|
|
3903
|
-
if (fromArrayBufferMethod) {
|
|
3904
|
-
return fromArrayBufferMethod;
|
|
3905
|
-
}
|
|
3906
|
-
throw new Error("Unsupported Replicate output format.");
|
|
3907
|
-
}
|
|
3908
|
-
async function maybeBytesFromBlobMethod(value2) {
|
|
3909
|
-
if (!isRecord(value2) || typeof value2.blob !== "function") {
|
|
3910
|
-
return null;
|
|
3911
|
-
}
|
|
3912
|
-
const blobLike = await value2.blob();
|
|
3913
|
-
if (typeof Blob === "undefined" || !(blobLike instanceof Blob)) {
|
|
3914
|
-
return null;
|
|
3915
|
-
}
|
|
3916
|
-
return new Uint8Array(await blobLike.arrayBuffer());
|
|
3917
|
-
}
|
|
3918
|
-
async function maybeBytesFromUrlMethod(value2) {
|
|
3919
|
-
if (!isRecord(value2) || typeof value2.url !== "function") {
|
|
3920
|
-
return null;
|
|
3921
|
-
}
|
|
3922
|
-
const urlLike = value2.url();
|
|
3923
|
-
if (typeof urlLike === "string") {
|
|
3924
|
-
return fetchBytes(urlLike);
|
|
3925
|
-
}
|
|
3926
|
-
if (urlLike instanceof URL) {
|
|
3927
|
-
return fetchBytes(urlLike.toString());
|
|
3928
|
-
}
|
|
3929
|
-
return null;
|
|
3930
|
-
}
|
|
3931
|
-
async function maybeBytesFromArrayBufferMethod(value2) {
|
|
3932
|
-
if (!isRecord(value2) || typeof value2.arrayBuffer !== "function") {
|
|
3933
|
-
return null;
|
|
3934
|
-
}
|
|
3935
|
-
const data = await value2.arrayBuffer();
|
|
3936
|
-
if (data instanceof ArrayBuffer) {
|
|
3937
|
-
return new Uint8Array(data);
|
|
3938
|
-
}
|
|
3939
|
-
return null;
|
|
3940
|
-
}
|
|
3941
|
-
function isRecord(value2) {
|
|
3942
|
-
return typeof value2 === "object" && value2 !== null;
|
|
3943
|
-
}
|
|
3944
|
-
async function fetchBytes(url) {
|
|
3945
|
-
const controller = new AbortController();
|
|
3946
|
-
const timer = setTimeout(() => controller.abort(), 6e4);
|
|
3947
|
-
timer.unref();
|
|
3948
|
-
try {
|
|
3949
|
-
const response = await fetch(url, { signal: controller.signal });
|
|
3950
|
-
if (!response.ok) {
|
|
3951
|
-
throw new Error(`Failed to download generated asset from ${url}`);
|
|
3952
|
-
}
|
|
3953
|
-
return new Uint8Array(await response.arrayBuffer());
|
|
3954
|
-
} finally {
|
|
3955
|
-
clearTimeout(timer);
|
|
3956
|
-
}
|
|
3241
|
+
function mimeTypeToExtension(mimeType) {
|
|
3242
|
+
if (mimeType === "image/jpeg") return "jpg";
|
|
3243
|
+
if (mimeType === "image/webp") return "webp";
|
|
3244
|
+
return "png";
|
|
3957
3245
|
}
|
|
3958
3246
|
|
|
3959
3247
|
// src/llm/openRouterClient.ts
|
|
@@ -4002,9 +3290,9 @@ var OpenRouterClient = class {
|
|
|
4002
3290
|
return structured;
|
|
4003
3291
|
} catch (parseError) {
|
|
4004
3292
|
if (attempt < 2 && shouldRetryStructuredParseError(parseError)) {
|
|
4005
|
-
const backoff =
|
|
3293
|
+
const backoff = backoffMs(attempt);
|
|
4006
3294
|
aggregatedMetrics = recordParseRetryMetrics(aggregatedMetrics, backoff);
|
|
4007
|
-
await
|
|
3295
|
+
await wait(backoff);
|
|
4008
3296
|
continue;
|
|
4009
3297
|
}
|
|
4010
3298
|
throw parseError;
|
|
@@ -4116,7 +3404,7 @@ var OpenRouterClient = class {
|
|
|
4116
3404
|
if (!response.ok) {
|
|
4117
3405
|
const message = json?.error?.message ?? `OpenRouter request failed with status ${response.status}`;
|
|
4118
3406
|
if (shouldRetryStatus(response.status) && attempt < 2) {
|
|
4119
|
-
const backoff =
|
|
3407
|
+
const backoff = backoffMs(attempt);
|
|
4120
3408
|
retries += 1;
|
|
4121
3409
|
retryBackoffMs += backoff;
|
|
4122
3410
|
onInteraction?.({
|
|
@@ -4136,14 +3424,14 @@ var OpenRouterClient = class {
|
|
|
4136
3424
|
responseBody: responseBodyRaw,
|
|
4137
3425
|
errorMessage: message
|
|
4138
3426
|
});
|
|
4139
|
-
await
|
|
3427
|
+
await wait(backoff);
|
|
4140
3428
|
continue;
|
|
4141
3429
|
}
|
|
4142
3430
|
throw new Error(message);
|
|
4143
3431
|
}
|
|
4144
3432
|
const content = json.choices?.[0]?.message?.content;
|
|
4145
3433
|
if (!content && attempt < 2) {
|
|
4146
|
-
const backoff =
|
|
3434
|
+
const backoff = backoffMs(attempt);
|
|
4147
3435
|
retries += 1;
|
|
4148
3436
|
retryBackoffMs += backoff;
|
|
4149
3437
|
onInteraction?.({
|
|
@@ -4163,7 +3451,7 @@ var OpenRouterClient = class {
|
|
|
4163
3451
|
responseBody: responseBodyRaw,
|
|
4164
3452
|
errorMessage: "OpenRouter returned an empty response."
|
|
4165
3453
|
});
|
|
4166
|
-
await
|
|
3454
|
+
await wait(backoff);
|
|
4167
3455
|
continue;
|
|
4168
3456
|
}
|
|
4169
3457
|
onInteraction?.({
|
|
@@ -4218,11 +3506,11 @@ var OpenRouterClient = class {
|
|
|
4218
3506
|
responseBody: responseBodyRaw,
|
|
4219
3507
|
errorMessage: lastError.message
|
|
4220
3508
|
});
|
|
4221
|
-
if (attempt < 2 &&
|
|
4222
|
-
const backoff =
|
|
3509
|
+
if (attempt < 2 && shouldRetryError(lastError)) {
|
|
3510
|
+
const backoff = backoffMs(attempt);
|
|
4223
3511
|
retries += 1;
|
|
4224
3512
|
retryBackoffMs += backoff;
|
|
4225
|
-
await
|
|
3513
|
+
await wait(backoff);
|
|
4226
3514
|
continue;
|
|
4227
3515
|
}
|
|
4228
3516
|
} finally {
|
|
@@ -4318,7 +3606,7 @@ function isStructuredOutputCompatibilityError(message) {
|
|
|
4318
3606
|
function shouldRetryStatus(status) {
|
|
4319
3607
|
return status === 408 || status === 409 || status === 429 || status >= 500;
|
|
4320
3608
|
}
|
|
4321
|
-
function
|
|
3609
|
+
function shouldRetryError(error) {
|
|
4322
3610
|
return /timeout|network|fetch|temporarily|aborted/i.test(error.message);
|
|
4323
3611
|
}
|
|
4324
3612
|
function shouldRetryStructuredParseError(error) {
|
|
@@ -4342,7 +3630,7 @@ function normalizeClientError(error, timeoutMs) {
|
|
|
4342
3630
|
}
|
|
4343
3631
|
return new Error("Unknown OpenRouter client error.");
|
|
4344
3632
|
}
|
|
4345
|
-
function
|
|
3633
|
+
function backoffMs(attempt) {
|
|
4346
3634
|
return 500 * (attempt + 1);
|
|
4347
3635
|
}
|
|
4348
3636
|
function aggregateLlmMetrics(total, next) {
|
|
@@ -4376,7 +3664,7 @@ function sumNullableNumber(left, right) {
|
|
|
4376
3664
|
}
|
|
4377
3665
|
return left + right;
|
|
4378
3666
|
}
|
|
4379
|
-
function
|
|
3667
|
+
function wait(ms) {
|
|
4380
3668
|
return new Promise((resolve) => {
|
|
4381
3669
|
setTimeout(resolve, ms);
|
|
4382
3670
|
});
|
|
@@ -4470,7 +3758,8 @@ var pipelineArtifactSummarySchema = z6.object({
|
|
|
4470
3758
|
markdownPath: z6.string().min(1),
|
|
4471
3759
|
assetDir: z6.string().min(1),
|
|
4472
3760
|
analyticsPath: z6.string().min(1).default("unknown.analytics.json"),
|
|
4473
|
-
interactionsPath: z6.string().min(1).default("unknown.interactions.json")
|
|
3761
|
+
interactionsPath: z6.string().min(1).default("unknown.interactions.json"),
|
|
3762
|
+
planPath: z6.string().min(1).nullable().default(null)
|
|
4474
3763
|
});
|
|
4475
3764
|
var resolvedPathsSchema = z6.object({
|
|
4476
3765
|
markdownOutputDir: z6.string().min(1),
|
|
@@ -4663,7 +3952,6 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4663
3952
|
const stageTracking = /* @__PURE__ */ new Map();
|
|
4664
3953
|
const stageRetryState = /* @__PURE__ */ new Map();
|
|
4665
3954
|
const llmOperationRetryState = /* @__PURE__ */ new Map();
|
|
4666
|
-
const imageOperationRetryState = /* @__PURE__ */ new Map();
|
|
4667
3955
|
stageTracking.set("shared-brief", {
|
|
4668
3956
|
startedAtMs: runStartedAtMs,
|
|
4669
3957
|
endedAtMs: null,
|
|
@@ -4739,7 +4027,11 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4739
4027
|
const openRouter = dryRun ? null : new OpenRouterClient(requireSecret(input.config.secrets.openRouterApiKey, "OpenRouter API key"));
|
|
4740
4028
|
const canRenderImagesLive = Boolean(input.config.secrets.replicateApiToken);
|
|
4741
4029
|
const imageDryRun = dryRun || !canRenderImagesLive;
|
|
4742
|
-
const
|
|
4030
|
+
const limn = imageDryRun ? null : new Limn({
|
|
4031
|
+
openrouterApiKey: input.config.secrets.openRouterApiKey ?? void 0,
|
|
4032
|
+
replicateApiKey: requireSecret(input.config.secrets.replicateApiToken, "Replicate API token"),
|
|
4033
|
+
openrouterModel: input.config.settings.model
|
|
4034
|
+
});
|
|
4743
4035
|
let contentBrief = writeSession.contentBrief;
|
|
4744
4036
|
let plan = writeSession.plan;
|
|
4745
4037
|
let text = writeSession.text;
|
|
@@ -4989,7 +4281,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4989
4281
|
options.onUpdate?.(cloneStages(stages));
|
|
4990
4282
|
} else {
|
|
4991
4283
|
imagePrompts = await expandImagePrompts({
|
|
4992
|
-
slots:
|
|
4284
|
+
slots: buildImageSlots(plan, text.sections, { maxImages: options.maxImages }),
|
|
4993
4285
|
planContext: plan,
|
|
4994
4286
|
sections: text.sections,
|
|
4995
4287
|
settings: input.config.settings,
|
|
@@ -5152,6 +4444,10 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5152
4444
|
sourceJob: input.job
|
|
5153
4445
|
})
|
|
5154
4446
|
);
|
|
4447
|
+
const planPath = plan ? path8.join(generationDir, "plan.md") : null;
|
|
4448
|
+
if (plan && planPath) {
|
|
4449
|
+
await writeUtf8File(planPath, renderPlanMarkdown(plan));
|
|
4450
|
+
}
|
|
5155
4451
|
const primaryFilePrefix = toFilePrefix(primaryTarget.contentType);
|
|
5156
4452
|
const primaryMarkdownPath = path8.join(generationDir, `${primaryFilePrefix}-1.md`);
|
|
5157
4453
|
const sharedAssetDir = generationDir;
|
|
@@ -5172,7 +4468,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5172
4468
|
const renderedImages = await renderExpandedImages({
|
|
5173
4469
|
prompts: imagePrompts,
|
|
5174
4470
|
settings: input.config.settings,
|
|
5175
|
-
|
|
4471
|
+
limn,
|
|
5176
4472
|
markdownPath: primaryMarkdownPath,
|
|
5177
4473
|
assetDir: sharedAssetDir,
|
|
5178
4474
|
dryRun: imageDryRun,
|
|
@@ -5195,15 +4491,6 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5195
4491
|
recordStageCost(stageTracking, "images", metrics.costUsd, metrics.costSource);
|
|
5196
4492
|
addStageRetries(stageTracking, "images", metrics.retries);
|
|
5197
4493
|
},
|
|
5198
|
-
onRetry(event) {
|
|
5199
|
-
const operationKey = `images:${event.imageId}`;
|
|
5200
|
-
const previousRetries = imageOperationRetryState.get(operationKey) ?? 0;
|
|
5201
|
-
if (event.retries <= previousRetries) {
|
|
5202
|
-
return;
|
|
5203
|
-
}
|
|
5204
|
-
imageOperationRetryState.set(operationKey, event.retries);
|
|
5205
|
-
applyRetryUpdate("images", event.retries - previousRetries, event.errorMessage);
|
|
5206
|
-
},
|
|
5207
4494
|
onProgress(detail) {
|
|
5208
4495
|
stages[4] = {
|
|
5209
4496
|
...stages[4],
|
|
@@ -5265,7 +4552,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5265
4552
|
const renderedImages = await renderExpandedImages({
|
|
5266
4553
|
prompts: imagePrompts,
|
|
5267
4554
|
settings: input.config.settings,
|
|
5268
|
-
|
|
4555
|
+
limn,
|
|
5269
4556
|
markdownPath: primaryMarkdownPath,
|
|
5270
4557
|
assetDir: sharedAssetDir,
|
|
5271
4558
|
dryRun: imageDryRun,
|
|
@@ -5288,15 +4575,6 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5288
4575
|
recordStageCost(stageTracking, "images", metrics.costUsd, metrics.costSource);
|
|
5289
4576
|
addStageRetries(stageTracking, "images", metrics.retries);
|
|
5290
4577
|
},
|
|
5291
|
-
onRetry(event) {
|
|
5292
|
-
const operationKey = `images:${event.imageId}`;
|
|
5293
|
-
const previousRetries = imageOperationRetryState.get(operationKey) ?? 0;
|
|
5294
|
-
if (event.retries <= previousRetries) {
|
|
5295
|
-
return;
|
|
5296
|
-
}
|
|
5297
|
-
imageOperationRetryState.set(operationKey, event.retries);
|
|
5298
|
-
applyRetryUpdate("images", event.retries - previousRetries, event.errorMessage);
|
|
5299
|
-
},
|
|
5300
4578
|
onProgress(detail) {
|
|
5301
4579
|
stages[4] = {
|
|
5302
4580
|
...stages[4],
|
|
@@ -5687,7 +4965,8 @@ async function runPipelineShell(input, options = {}) {
|
|
|
5687
4965
|
markdownPath: primaryMarkdownPathForArtifact,
|
|
5688
4966
|
assetDir: sharedAssetDir,
|
|
5689
4967
|
analyticsPath,
|
|
5690
|
-
interactionsPath
|
|
4968
|
+
interactionsPath,
|
|
4969
|
+
planPath
|
|
5691
4970
|
};
|
|
5692
4971
|
writeSession = await patchWriteSession(
|
|
5693
4972
|
{
|
|
@@ -6011,13 +5290,13 @@ function buildPrimaryCoverPrompt(contentBrief, primaryContentType, primaryMarkdo
|
|
|
6011
5290
|
description: `Cover image for ${primaryContentType}`,
|
|
6012
5291
|
anchorAfterSection: null,
|
|
6013
5292
|
prompt: [
|
|
6014
|
-
`
|
|
5293
|
+
`Cover image for ${primaryContentType}.`,
|
|
6015
5294
|
`Core angle: ${contentBrief.description}`,
|
|
6016
5295
|
`Audience: ${contentBrief.targetAudience}`,
|
|
6017
5296
|
`Promise: ${contentBrief.corePromise}`,
|
|
6018
5297
|
`Voice: ${contentBrief.voiceNotes}`,
|
|
6019
5298
|
`Primary excerpt: ${markdownExcerpt}`,
|
|
6020
|
-
"
|
|
5299
|
+
"Do not include any words, letters, numbers, logos, watermarks, or signage in the image."
|
|
6021
5300
|
].join(" ")
|
|
6022
5301
|
};
|
|
6023
5302
|
}
|
|
@@ -6069,6 +5348,45 @@ function buildRunJobDefinition(input) {
|
|
|
6069
5348
|
}
|
|
6070
5349
|
};
|
|
6071
5350
|
}
|
|
5351
|
+
function renderPlanMarkdown(plan) {
|
|
5352
|
+
const yamlKeywords = plan.keywords.map((kw) => ` - "${kw}"`).join("\n");
|
|
5353
|
+
const sectionsRows = plan.sections.map((section, index) => `| ${index + 1} | ${section.title} | ${section.description} |`).join("\n");
|
|
5354
|
+
const inlineImageRows = plan.inlineImages.map((img, index) => `| Inline ${index + 1} | ${img.description} |`).join("\n");
|
|
5355
|
+
const imageRows = `| Cover | ${plan.coverImageDescription} |
|
|
5356
|
+
${inlineImageRows}`;
|
|
5357
|
+
return `---
|
|
5358
|
+
title: "${plan.title.replace(/"/g, '\\"')}"
|
|
5359
|
+
subtitle: "${plan.subtitle.replace(/"/g, '\\"')}"
|
|
5360
|
+
slug: "${plan.slug}"
|
|
5361
|
+
keywords:
|
|
5362
|
+
${yamlKeywords}
|
|
5363
|
+
---
|
|
5364
|
+
|
|
5365
|
+
## Description
|
|
5366
|
+
|
|
5367
|
+
${plan.description}
|
|
5368
|
+
|
|
5369
|
+
## Introduction Brief
|
|
5370
|
+
|
|
5371
|
+
${plan.introBrief}
|
|
5372
|
+
|
|
5373
|
+
## Sections
|
|
5374
|
+
|
|
5375
|
+
| # | Title | Description |
|
|
5376
|
+
|---|-------|-------------|
|
|
5377
|
+
${sectionsRows}
|
|
5378
|
+
|
|
5379
|
+
## Outro Brief
|
|
5380
|
+
|
|
5381
|
+
${plan.outroBrief}
|
|
5382
|
+
|
|
5383
|
+
## Image Plan
|
|
5384
|
+
|
|
5385
|
+
| Type | Description |
|
|
5386
|
+
|------|-------------|
|
|
5387
|
+
${imageRows}
|
|
5388
|
+
`;
|
|
5389
|
+
}
|
|
6072
5390
|
function asWriteStageId(stageId) {
|
|
6073
5391
|
if (stageId === "shared-brief" || stageId === "planning" || stageId === "sections" || stageId === "image-prompts" || stageId === "images" || stageId === "output" || stageId === "links") {
|
|
6074
5392
|
return stageId;
|
|
@@ -7236,62 +6554,240 @@ async function startIdeonMcpServer() {
|
|
|
7236
6554
|
} catch (error) {
|
|
7237
6555
|
return formatToolError(error);
|
|
7238
6556
|
}
|
|
7239
|
-
}
|
|
7240
|
-
);
|
|
7241
|
-
server.registerTool(
|
|
7242
|
-
"ideon_config_unset",
|
|
7243
|
-
{
|
|
7244
|
-
title: "Ideon Config Unset",
|
|
7245
|
-
description: "Reset a setting to its default or delete a stored secret.",
|
|
7246
|
-
inputSchema: configUnsetToolInputSchema
|
|
7247
|
-
},
|
|
7248
|
-
async (input) => {
|
|
7249
|
-
try {
|
|
7250
|
-
if (!isConfigKey(input.key)) {
|
|
7251
|
-
throw new ReportedError(`Unsupported config key: ${input.key}`);
|
|
6557
|
+
}
|
|
6558
|
+
);
|
|
6559
|
+
server.registerTool(
|
|
6560
|
+
"ideon_config_unset",
|
|
6561
|
+
{
|
|
6562
|
+
title: "Ideon Config Unset",
|
|
6563
|
+
description: "Reset a setting to its default or delete a stored secret.",
|
|
6564
|
+
inputSchema: configUnsetToolInputSchema
|
|
6565
|
+
},
|
|
6566
|
+
async (input) => {
|
|
6567
|
+
try {
|
|
6568
|
+
if (!isConfigKey(input.key)) {
|
|
6569
|
+
throw new ReportedError(`Unsupported config key: ${input.key}`);
|
|
6570
|
+
}
|
|
6571
|
+
await configUnset(input.key);
|
|
6572
|
+
return {
|
|
6573
|
+
content: [
|
|
6574
|
+
{
|
|
6575
|
+
type: "text",
|
|
6576
|
+
text: `Unset ${input.key}.`
|
|
6577
|
+
}
|
|
6578
|
+
],
|
|
6579
|
+
structuredContent: {
|
|
6580
|
+
key: input.key,
|
|
6581
|
+
updated: true
|
|
6582
|
+
}
|
|
6583
|
+
};
|
|
6584
|
+
} catch (error) {
|
|
6585
|
+
return formatToolError(error);
|
|
6586
|
+
}
|
|
6587
|
+
}
|
|
6588
|
+
);
|
|
6589
|
+
const transport = new StdioServerTransport();
|
|
6590
|
+
await server.connect(transport);
|
|
6591
|
+
}
|
|
6592
|
+
function formatToolError(error) {
|
|
6593
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6594
|
+
return {
|
|
6595
|
+
content: [{ type: "text", text: message }],
|
|
6596
|
+
isError: true
|
|
6597
|
+
};
|
|
6598
|
+
}
|
|
6599
|
+
|
|
6600
|
+
// src/cli/commands/mcp.ts
|
|
6601
|
+
async function runMcpServeCommand() {
|
|
6602
|
+
await startIdeonMcpServer();
|
|
6603
|
+
}
|
|
6604
|
+
|
|
6605
|
+
// src/cli/commands/settings.tsx
|
|
6606
|
+
import { render } from "ink";
|
|
6607
|
+
|
|
6608
|
+
// src/cli/flows/settingsFlow.tsx
|
|
6609
|
+
import { useEffect, useMemo, useState } from "react";
|
|
6610
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
6611
|
+
import SelectInput from "ink-select-input";
|
|
6612
|
+
import TextInput from "ink-text-input";
|
|
6613
|
+
|
|
6614
|
+
// src/images/limnModelCatalog.ts
|
|
6615
|
+
import { getSupportedModelCatalog } from "@telepat/limn";
|
|
6616
|
+
function getLimnGenerationModels() {
|
|
6617
|
+
return getSupportedModelCatalog().filter((entry) => entry.generationEnabled);
|
|
6618
|
+
}
|
|
6619
|
+
|
|
6620
|
+
// src/cli/flows/settingsFlowLogic.ts
|
|
6621
|
+
function handleMenuSelect(action, settings, secrets, setEditing, setShowModelSelect, setMenuMode, onDone, exit) {
|
|
6622
|
+
switch (action) {
|
|
6623
|
+
case "openrouter":
|
|
6624
|
+
setEditing({ key: action, label: "OpenRouter API key", value: secrets.openRouterApiKey ?? "" });
|
|
6625
|
+
return;
|
|
6626
|
+
case "replicate":
|
|
6627
|
+
setEditing({ key: action, label: "Replicate API token", value: secrets.replicateApiToken ?? "" });
|
|
6628
|
+
return;
|
|
6629
|
+
case "llm-model":
|
|
6630
|
+
setEditing({ key: action, label: "LLM model", value: settings.model });
|
|
6631
|
+
return;
|
|
6632
|
+
case "notifications-enabled":
|
|
6633
|
+
setEditing({ key: action, label: "Notifications > OS notifications enabled (true|false)", value: String(settings.notifications.enabled) });
|
|
6634
|
+
return;
|
|
6635
|
+
case "temperature":
|
|
6636
|
+
setEditing({ key: action, label: "Temperature", value: String(settings.modelSettings.temperature) });
|
|
6637
|
+
return;
|
|
6638
|
+
case "maxTokens":
|
|
6639
|
+
setEditing({ key: action, label: "Max tokens", value: String(settings.modelSettings.maxTokens) });
|
|
6640
|
+
return;
|
|
6641
|
+
case "topP":
|
|
6642
|
+
setEditing({ key: action, label: "Top p", value: String(settings.modelSettings.topP) });
|
|
6643
|
+
return;
|
|
6644
|
+
case "markdownOutputDir":
|
|
6645
|
+
setEditing({ key: action, label: "Markdown output directory", value: settings.markdownOutputDir });
|
|
6646
|
+
return;
|
|
6647
|
+
case "assetOutputDir":
|
|
6648
|
+
setEditing({ key: action, label: "Asset output directory", value: settings.assetOutputDir });
|
|
6649
|
+
return;
|
|
6650
|
+
case "t2i-settings":
|
|
6651
|
+
setMenuMode("t2i");
|
|
6652
|
+
return;
|
|
6653
|
+
case "t2i-model":
|
|
6654
|
+
setShowModelSelect(true);
|
|
6655
|
+
return;
|
|
6656
|
+
case "t2i-input-overrides":
|
|
6657
|
+
setEditing({
|
|
6658
|
+
key: action,
|
|
6659
|
+
label: "T2I input overrides (JSON)",
|
|
6660
|
+
value: JSON.stringify(settings.t2i.inputOverrides, null, 2)
|
|
6661
|
+
});
|
|
6662
|
+
return;
|
|
6663
|
+
case "t2i-back":
|
|
6664
|
+
setMenuMode("main");
|
|
6665
|
+
return;
|
|
6666
|
+
case "save":
|
|
6667
|
+
onDone({ settings, secrets });
|
|
6668
|
+
exit();
|
|
6669
|
+
return;
|
|
6670
|
+
case "cancel":
|
|
6671
|
+
onDone(null);
|
|
6672
|
+
exit();
|
|
6673
|
+
return;
|
|
6674
|
+
}
|
|
6675
|
+
}
|
|
6676
|
+
function applyEdit(action, value2, settings, secrets, setSettings, setSecrets) {
|
|
6677
|
+
if (action === "openrouter") {
|
|
6678
|
+
setSecrets({ ...secrets, openRouterApiKey: value2.trim() || null });
|
|
6679
|
+
return true;
|
|
6680
|
+
}
|
|
6681
|
+
if (action === "replicate") {
|
|
6682
|
+
setSecrets({ ...secrets, replicateApiToken: value2.trim() || null });
|
|
6683
|
+
return true;
|
|
6684
|
+
}
|
|
6685
|
+
if (action === "llm-model") {
|
|
6686
|
+
setSettings({ ...settings, model: value2.trim() || settings.model });
|
|
6687
|
+
return true;
|
|
6688
|
+
}
|
|
6689
|
+
if (action === "notifications-enabled") {
|
|
6690
|
+
const parsed = parseBooleanOrFallback(value2, settings.notifications.enabled);
|
|
6691
|
+
setSettings({
|
|
6692
|
+
...settings,
|
|
6693
|
+
notifications: {
|
|
6694
|
+
...settings.notifications,
|
|
6695
|
+
enabled: parsed
|
|
6696
|
+
}
|
|
6697
|
+
});
|
|
6698
|
+
return true;
|
|
6699
|
+
}
|
|
6700
|
+
if (action === "temperature") {
|
|
6701
|
+
const nextTemperature = clampNumber(parseNumberOrFallback(value2, settings.modelSettings.temperature), 0, 2);
|
|
6702
|
+
setSettings({
|
|
6703
|
+
...settings,
|
|
6704
|
+
modelSettings: {
|
|
6705
|
+
...settings.modelSettings,
|
|
6706
|
+
temperature: nextTemperature
|
|
6707
|
+
}
|
|
6708
|
+
});
|
|
6709
|
+
return true;
|
|
6710
|
+
}
|
|
6711
|
+
if (action === "maxTokens") {
|
|
6712
|
+
const nextMaxTokens = Math.max(1, Math.round(parseNumberOrFallback(value2, settings.modelSettings.maxTokens)));
|
|
6713
|
+
setSettings({
|
|
6714
|
+
...settings,
|
|
6715
|
+
modelSettings: {
|
|
6716
|
+
...settings.modelSettings,
|
|
6717
|
+
maxTokens: nextMaxTokens
|
|
6718
|
+
}
|
|
6719
|
+
});
|
|
6720
|
+
return true;
|
|
6721
|
+
}
|
|
6722
|
+
if (action === "topP") {
|
|
6723
|
+
const nextTopP = clampNumber(parseNumberOrFallback(value2, settings.modelSettings.topP), 0, 1);
|
|
6724
|
+
setSettings({
|
|
6725
|
+
...settings,
|
|
6726
|
+
modelSettings: {
|
|
6727
|
+
...settings.modelSettings,
|
|
6728
|
+
topP: nextTopP
|
|
6729
|
+
}
|
|
6730
|
+
});
|
|
6731
|
+
return true;
|
|
6732
|
+
}
|
|
6733
|
+
if (action === "markdownOutputDir") {
|
|
6734
|
+
setSettings({ ...settings, markdownOutputDir: value2.trim() || settings.markdownOutputDir });
|
|
6735
|
+
return true;
|
|
6736
|
+
}
|
|
6737
|
+
if (action === "assetOutputDir") {
|
|
6738
|
+
setSettings({ ...settings, assetOutputDir: value2.trim() || settings.assetOutputDir });
|
|
6739
|
+
return true;
|
|
6740
|
+
}
|
|
6741
|
+
if (action === "t2i-input-overrides") {
|
|
6742
|
+
const trimmed = value2.trim();
|
|
6743
|
+
if (trimmed.length === 0) {
|
|
6744
|
+
setSettings({
|
|
6745
|
+
...settings,
|
|
6746
|
+
t2i: {
|
|
6747
|
+
...settings.t2i,
|
|
6748
|
+
inputOverrides: {}
|
|
7252
6749
|
}
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
],
|
|
7261
|
-
structuredContent: {
|
|
7262
|
-
key: input.key,
|
|
7263
|
-
updated: true
|
|
7264
|
-
}
|
|
7265
|
-
};
|
|
7266
|
-
} catch (error) {
|
|
7267
|
-
return formatToolError(error);
|
|
6750
|
+
});
|
|
6751
|
+
return true;
|
|
6752
|
+
}
|
|
6753
|
+
try {
|
|
6754
|
+
const parsed = JSON.parse(trimmed);
|
|
6755
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
6756
|
+
return false;
|
|
7268
6757
|
}
|
|
6758
|
+
setSettings({
|
|
6759
|
+
...settings,
|
|
6760
|
+
t2i: {
|
|
6761
|
+
...settings.t2i,
|
|
6762
|
+
inputOverrides: parsed
|
|
6763
|
+
}
|
|
6764
|
+
});
|
|
6765
|
+
return true;
|
|
6766
|
+
} catch {
|
|
6767
|
+
return false;
|
|
7269
6768
|
}
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
await server.connect(transport);
|
|
6769
|
+
}
|
|
6770
|
+
return false;
|
|
7273
6771
|
}
|
|
7274
|
-
function
|
|
7275
|
-
const
|
|
7276
|
-
return
|
|
7277
|
-
content: [{ type: "text", text: message }],
|
|
7278
|
-
isError: true
|
|
7279
|
-
};
|
|
6772
|
+
function parseNumberOrFallback(value2, fallback) {
|
|
6773
|
+
const parsed = Number(value2.trim());
|
|
6774
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
7280
6775
|
}
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
6776
|
+
function clampNumber(value2, minimum, maximum) {
|
|
6777
|
+
return Math.min(maximum, Math.max(minimum, value2));
|
|
6778
|
+
}
|
|
6779
|
+
function parseBooleanOrFallback(value2, fallback) {
|
|
6780
|
+
const normalized = value2.trim().toLowerCase();
|
|
6781
|
+
if (normalized === "true") {
|
|
6782
|
+
return true;
|
|
6783
|
+
}
|
|
6784
|
+
if (normalized === "false") {
|
|
6785
|
+
return false;
|
|
6786
|
+
}
|
|
6787
|
+
return fallback;
|
|
7285
6788
|
}
|
|
7286
|
-
|
|
7287
|
-
// src/cli/commands/settings.tsx
|
|
7288
|
-
import { render } from "ink";
|
|
7289
6789
|
|
|
7290
6790
|
// src/cli/flows/settingsFlow.tsx
|
|
7291
|
-
import { useMemo, useState } from "react";
|
|
7292
|
-
import { Box, Text, useApp, useInput } from "ink";
|
|
7293
|
-
import SelectInput from "ink-select-input";
|
|
7294
|
-
import TextInput from "ink-text-input";
|
|
7295
6791
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7296
6792
|
function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
7297
6793
|
const { exit } = useApp();
|
|
@@ -7299,7 +6795,8 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
7299
6795
|
const [secrets, setSecrets] = useState(initialSecrets);
|
|
7300
6796
|
const [editing, setEditing] = useState(null);
|
|
7301
6797
|
const [showModelSelect, setShowModelSelect] = useState(false);
|
|
7302
|
-
const
|
|
6798
|
+
const [menuMode, setMenuMode] = useState("main");
|
|
6799
|
+
const currentModelEntry = getLimnGenerationModels().find((m) => m.family === settings.t2i.modelId) ?? getLimnGenerationModels()[0];
|
|
7303
6800
|
useInput((input, key) => {
|
|
7304
6801
|
if (key.escape) {
|
|
7305
6802
|
if (editing) {
|
|
@@ -7308,6 +6805,11 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
7308
6805
|
}
|
|
7309
6806
|
if (showModelSelect) {
|
|
7310
6807
|
setShowModelSelect(false);
|
|
6808
|
+
return;
|
|
6809
|
+
}
|
|
6810
|
+
if (menuMode === "t2i") {
|
|
6811
|
+
setMenuMode("main");
|
|
6812
|
+
return;
|
|
7311
6813
|
}
|
|
7312
6814
|
}
|
|
7313
6815
|
if (key.ctrl && input === "c") {
|
|
@@ -7315,11 +6817,28 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
7315
6817
|
exit();
|
|
7316
6818
|
}
|
|
7317
6819
|
});
|
|
6820
|
+
const formatT2iOverridesSummary = (overrides) => {
|
|
6821
|
+
const count = Object.keys(overrides).length;
|
|
6822
|
+
return count === 0 ? "none" : `${count} override${count === 1 ? "" : "s"}`;
|
|
6823
|
+
};
|
|
7318
6824
|
const menuItems = useMemo(() => {
|
|
7319
|
-
const
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
6825
|
+
const t2iSubmenu = [
|
|
6826
|
+
{
|
|
6827
|
+
label: `T2I model: ${currentModelEntry?.displayName ?? settings.t2i.modelId}`,
|
|
6828
|
+
value: "t2i-model"
|
|
6829
|
+
},
|
|
6830
|
+
{
|
|
6831
|
+
label: `T2I input overrides: ${formatT2iOverridesSummary(settings.t2i.inputOverrides)}`,
|
|
6832
|
+
value: "t2i-input-overrides"
|
|
6833
|
+
},
|
|
6834
|
+
{
|
|
6835
|
+
label: "Back",
|
|
6836
|
+
value: "t2i-back"
|
|
6837
|
+
}
|
|
6838
|
+
];
|
|
6839
|
+
if (menuMode === "t2i") {
|
|
6840
|
+
return t2iSubmenu;
|
|
6841
|
+
}
|
|
7323
6842
|
return [
|
|
7324
6843
|
{
|
|
7325
6844
|
label: `OpenRouter API key: ${secrets.openRouterApiKey ? "stored in keychain" : "missing"}`,
|
|
@@ -7358,10 +6877,9 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
7358
6877
|
value: "assetOutputDir"
|
|
7359
6878
|
},
|
|
7360
6879
|
{
|
|
7361
|
-
label: `T2I
|
|
7362
|
-
value: "t2i-
|
|
6880
|
+
label: `T2I settings: ${currentModelEntry?.displayName ?? settings.t2i.modelId}`,
|
|
6881
|
+
value: "t2i-settings"
|
|
7363
6882
|
},
|
|
7364
|
-
...t2iItems,
|
|
7365
6883
|
{
|
|
7366
6884
|
label: "Save and exit",
|
|
7367
6885
|
value: "save"
|
|
@@ -7371,11 +6889,11 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
7371
6889
|
value: "cancel"
|
|
7372
6890
|
}
|
|
7373
6891
|
];
|
|
7374
|
-
}, [
|
|
6892
|
+
}, [currentModelEntry, menuMode, secrets.openRouterApiKey, secrets.replicateApiToken, settings]);
|
|
7375
6893
|
if (showModelSelect) {
|
|
7376
|
-
const items =
|
|
7377
|
-
label: `${model.displayName} (${model.
|
|
7378
|
-
value: model.
|
|
6894
|
+
const items = getLimnGenerationModels().map((model) => ({
|
|
6895
|
+
label: `${model.displayName} (${model.family})`,
|
|
6896
|
+
value: model.family
|
|
7379
6897
|
}));
|
|
7380
6898
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
7381
6899
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: "Choose T2I Model" }),
|
|
@@ -7389,7 +6907,7 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
7389
6907
|
...current,
|
|
7390
6908
|
t2i: {
|
|
7391
6909
|
modelId: item.value,
|
|
7392
|
-
inputOverrides:
|
|
6910
|
+
inputOverrides: {}
|
|
7393
6911
|
}
|
|
7394
6912
|
}));
|
|
7395
6913
|
setShowModelSelect(false);
|
|
@@ -7404,8 +6922,11 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
7404
6922
|
{
|
|
7405
6923
|
editing,
|
|
7406
6924
|
onSubmit: (value2) => {
|
|
7407
|
-
applyEdit(editing.key, value2, settings, secrets, setSettings, setSecrets);
|
|
7408
|
-
|
|
6925
|
+
const accepted = applyEdit(editing.key, value2, settings, secrets, setSettings, setSecrets);
|
|
6926
|
+
if (accepted) {
|
|
6927
|
+
setEditing(null);
|
|
6928
|
+
}
|
|
6929
|
+
return accepted;
|
|
7409
6930
|
},
|
|
7410
6931
|
onCancel: () => {
|
|
7411
6932
|
setEditing(null);
|
|
@@ -7416,7 +6937,13 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
7416
6937
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
7417
6938
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: "Ideon Settings" }),
|
|
7418
6939
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter to edit. Esc backs out of nested menus. Ctrl+C cancels." }),
|
|
7419
|
-
/* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx(
|
|
6940
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx(
|
|
6941
|
+
SelectInput,
|
|
6942
|
+
{
|
|
6943
|
+
items: menuItems,
|
|
6944
|
+
onSelect: (item) => handleMenuSelect(item.value, settings, secrets, setEditing, setShowModelSelect, setMenuMode, onDone, exit)
|
|
6945
|
+
}
|
|
6946
|
+
) })
|
|
7420
6947
|
] });
|
|
7421
6948
|
}
|
|
7422
6949
|
function EditorView({
|
|
@@ -7425,6 +6952,11 @@ function EditorView({
|
|
|
7425
6952
|
onCancel
|
|
7426
6953
|
}) {
|
|
7427
6954
|
const [value2, setValue] = useState(editing.value);
|
|
6955
|
+
const [error, setError] = useState(null);
|
|
6956
|
+
useEffect(() => {
|
|
6957
|
+
setValue(editing.value);
|
|
6958
|
+
setError(null);
|
|
6959
|
+
}, [editing]);
|
|
7428
6960
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
7429
6961
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: editing.label }),
|
|
7430
6962
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter saves. Blank value clears nullable secrets and overrides. Esc cancels." }),
|
|
@@ -7436,11 +6968,15 @@ function EditorView({
|
|
|
7436
6968
|
value: value2,
|
|
7437
6969
|
onChange: setValue,
|
|
7438
6970
|
onSubmit: (nextValue) => {
|
|
7439
|
-
onSubmit(nextValue);
|
|
6971
|
+
const accepted = onSubmit(nextValue);
|
|
6972
|
+
if (!accepted) {
|
|
6973
|
+
setError("Invalid JSON. Please enter an object or leave blank to clear.");
|
|
6974
|
+
}
|
|
7440
6975
|
}
|
|
7441
6976
|
}
|
|
7442
6977
|
)
|
|
7443
6978
|
] }),
|
|
6979
|
+
error ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: error }) }) : null,
|
|
7444
6980
|
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
7445
6981
|
"Current value: ",
|
|
7446
6982
|
editing.value || "(empty)"
|
|
@@ -7448,170 +6984,6 @@ function EditorView({
|
|
|
7448
6984
|
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "Press Esc to return without changes." }) })
|
|
7449
6985
|
] });
|
|
7450
6986
|
}
|
|
7451
|
-
function handleMenuSelect(action, settings, secrets, setEditing, setShowModelSelect, onDone, exit) {
|
|
7452
|
-
switch (action) {
|
|
7453
|
-
case "openrouter":
|
|
7454
|
-
setEditing({ key: action, label: "OpenRouter API key", value: secrets.openRouterApiKey ?? "" });
|
|
7455
|
-
return;
|
|
7456
|
-
case "replicate":
|
|
7457
|
-
setEditing({ key: action, label: "Replicate API token", value: secrets.replicateApiToken ?? "" });
|
|
7458
|
-
return;
|
|
7459
|
-
case "llm-model":
|
|
7460
|
-
setEditing({ key: action, label: "LLM model", value: settings.model });
|
|
7461
|
-
return;
|
|
7462
|
-
case "notifications-enabled":
|
|
7463
|
-
setEditing({ key: action, label: "Notifications > OS notifications enabled (true|false)", value: String(settings.notifications.enabled) });
|
|
7464
|
-
return;
|
|
7465
|
-
case "temperature":
|
|
7466
|
-
setEditing({ key: action, label: "Temperature", value: String(settings.modelSettings.temperature) });
|
|
7467
|
-
return;
|
|
7468
|
-
case "maxTokens":
|
|
7469
|
-
setEditing({ key: action, label: "Max tokens", value: String(settings.modelSettings.maxTokens) });
|
|
7470
|
-
return;
|
|
7471
|
-
case "topP":
|
|
7472
|
-
setEditing({ key: action, label: "Top p", value: String(settings.modelSettings.topP) });
|
|
7473
|
-
return;
|
|
7474
|
-
case "markdownOutputDir":
|
|
7475
|
-
setEditing({ key: action, label: "Markdown output directory", value: settings.markdownOutputDir });
|
|
7476
|
-
return;
|
|
7477
|
-
case "assetOutputDir":
|
|
7478
|
-
setEditing({ key: action, label: "Asset output directory", value: settings.assetOutputDir });
|
|
7479
|
-
return;
|
|
7480
|
-
case "t2i-model":
|
|
7481
|
-
setShowModelSelect(true);
|
|
7482
|
-
return;
|
|
7483
|
-
case "save":
|
|
7484
|
-
onDone({ settings, secrets });
|
|
7485
|
-
exit();
|
|
7486
|
-
return;
|
|
7487
|
-
case "cancel":
|
|
7488
|
-
onDone(null);
|
|
7489
|
-
exit();
|
|
7490
|
-
return;
|
|
7491
|
-
default:
|
|
7492
|
-
if (action.startsWith("t2i:")) {
|
|
7493
|
-
const fieldName = action.slice(4);
|
|
7494
|
-
const currentModel = getT2IModel(settings.t2i.modelId);
|
|
7495
|
-
setEditing({
|
|
7496
|
-
key: action,
|
|
7497
|
-
label: `${currentModel.displayName} \u2022 ${fieldName}`,
|
|
7498
|
-
value: formatEditorValue(settings.t2i.inputOverrides[fieldName] ?? getT2IFieldDefault(settings.t2i.modelId, fieldName))
|
|
7499
|
-
});
|
|
7500
|
-
}
|
|
7501
|
-
}
|
|
7502
|
-
}
|
|
7503
|
-
function applyEdit(action, value2, settings, secrets, setSettings, setSecrets) {
|
|
7504
|
-
if (action === "openrouter") {
|
|
7505
|
-
setSecrets({ ...secrets, openRouterApiKey: value2.trim() || null });
|
|
7506
|
-
return;
|
|
7507
|
-
}
|
|
7508
|
-
if (action === "replicate") {
|
|
7509
|
-
setSecrets({ ...secrets, replicateApiToken: value2.trim() || null });
|
|
7510
|
-
return;
|
|
7511
|
-
}
|
|
7512
|
-
if (action === "llm-model") {
|
|
7513
|
-
setSettings({ ...settings, model: value2.trim() || settings.model });
|
|
7514
|
-
return;
|
|
7515
|
-
}
|
|
7516
|
-
if (action === "notifications-enabled") {
|
|
7517
|
-
const parsed = parseBooleanOrFallback(value2, settings.notifications.enabled);
|
|
7518
|
-
setSettings({
|
|
7519
|
-
...settings,
|
|
7520
|
-
notifications: {
|
|
7521
|
-
...settings.notifications,
|
|
7522
|
-
enabled: parsed
|
|
7523
|
-
}
|
|
7524
|
-
});
|
|
7525
|
-
return;
|
|
7526
|
-
}
|
|
7527
|
-
if (action === "temperature") {
|
|
7528
|
-
const nextTemperature = clampNumber2(parseNumberOrFallback(value2, settings.modelSettings.temperature), 0, 2);
|
|
7529
|
-
setSettings({
|
|
7530
|
-
...settings,
|
|
7531
|
-
modelSettings: {
|
|
7532
|
-
...settings.modelSettings,
|
|
7533
|
-
temperature: nextTemperature
|
|
7534
|
-
}
|
|
7535
|
-
});
|
|
7536
|
-
return;
|
|
7537
|
-
}
|
|
7538
|
-
if (action === "maxTokens") {
|
|
7539
|
-
const nextMaxTokens = Math.max(1, Math.round(parseNumberOrFallback(value2, settings.modelSettings.maxTokens)));
|
|
7540
|
-
setSettings({
|
|
7541
|
-
...settings,
|
|
7542
|
-
modelSettings: {
|
|
7543
|
-
...settings.modelSettings,
|
|
7544
|
-
maxTokens: nextMaxTokens
|
|
7545
|
-
}
|
|
7546
|
-
});
|
|
7547
|
-
return;
|
|
7548
|
-
}
|
|
7549
|
-
if (action === "topP") {
|
|
7550
|
-
const nextTopP = clampNumber2(parseNumberOrFallback(value2, settings.modelSettings.topP), 0, 1);
|
|
7551
|
-
setSettings({
|
|
7552
|
-
...settings,
|
|
7553
|
-
modelSettings: {
|
|
7554
|
-
...settings.modelSettings,
|
|
7555
|
-
topP: nextTopP
|
|
7556
|
-
}
|
|
7557
|
-
});
|
|
7558
|
-
return;
|
|
7559
|
-
}
|
|
7560
|
-
if (action === "markdownOutputDir") {
|
|
7561
|
-
setSettings({ ...settings, markdownOutputDir: value2.trim() || settings.markdownOutputDir });
|
|
7562
|
-
return;
|
|
7563
|
-
}
|
|
7564
|
-
if (action === "assetOutputDir") {
|
|
7565
|
-
setSettings({ ...settings, assetOutputDir: value2.trim() || settings.assetOutputDir });
|
|
7566
|
-
return;
|
|
7567
|
-
}
|
|
7568
|
-
if (action.startsWith("t2i:")) {
|
|
7569
|
-
const fieldName = action.slice(4);
|
|
7570
|
-
const parsedValue = coerceT2IFieldValue(settings.t2i.modelId, fieldName, value2);
|
|
7571
|
-
const nextOverrides = { ...settings.t2i.inputOverrides };
|
|
7572
|
-
if (parsedValue === void 0) {
|
|
7573
|
-
delete nextOverrides[fieldName];
|
|
7574
|
-
} else {
|
|
7575
|
-
nextOverrides[fieldName] = parsedValue;
|
|
7576
|
-
}
|
|
7577
|
-
setSettings({
|
|
7578
|
-
...settings,
|
|
7579
|
-
t2i: {
|
|
7580
|
-
...settings.t2i,
|
|
7581
|
-
inputOverrides: nextOverrides
|
|
7582
|
-
}
|
|
7583
|
-
});
|
|
7584
|
-
}
|
|
7585
|
-
}
|
|
7586
|
-
function formatEditorValue(value2) {
|
|
7587
|
-
if (value2 === null || value2 === void 0) {
|
|
7588
|
-
return "";
|
|
7589
|
-
}
|
|
7590
|
-
return String(value2);
|
|
7591
|
-
}
|
|
7592
|
-
function formatValue(value2) {
|
|
7593
|
-
if (value2 === null || value2 === void 0 || value2 === "") {
|
|
7594
|
-
return "(default)";
|
|
7595
|
-
}
|
|
7596
|
-
return String(value2);
|
|
7597
|
-
}
|
|
7598
|
-
function parseNumberOrFallback(value2, fallback) {
|
|
7599
|
-
const parsed = Number(value2.trim());
|
|
7600
|
-
return Number.isFinite(parsed) ? parsed : fallback;
|
|
7601
|
-
}
|
|
7602
|
-
function clampNumber2(value2, minimum, maximum) {
|
|
7603
|
-
return Math.min(maximum, Math.max(minimum, value2));
|
|
7604
|
-
}
|
|
7605
|
-
function parseBooleanOrFallback(value2, fallback) {
|
|
7606
|
-
const normalized = value2.trim().toLowerCase();
|
|
7607
|
-
if (normalized === "true") {
|
|
7608
|
-
return true;
|
|
7609
|
-
}
|
|
7610
|
-
if (normalized === "false") {
|
|
7611
|
-
return false;
|
|
7612
|
-
}
|
|
7613
|
-
return fallback;
|
|
7614
|
-
}
|
|
7615
6987
|
|
|
7616
6988
|
// src/cli/commands/settings.tsx
|
|
7617
6989
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
@@ -9565,7 +8937,7 @@ async function runServeCommand(options) {
|
|
|
9565
8937
|
}
|
|
9566
8938
|
|
|
9567
8939
|
// src/cli/commands/write.tsx
|
|
9568
|
-
import React4, { useEffect as
|
|
8940
|
+
import React4, { useEffect as useEffect3, useState as useState4 } from "react";
|
|
9569
8941
|
import { render as render2, useApp as useApp3 } from "ink";
|
|
9570
8942
|
import { createInterface } from "readline/promises";
|
|
9571
8943
|
|
|
@@ -9692,7 +9064,7 @@ function FinalSummary({
|
|
|
9692
9064
|
}
|
|
9693
9065
|
|
|
9694
9066
|
// src/cli/ui/stageRow.tsx
|
|
9695
|
-
import { useEffect, useState as useState2 } from "react";
|
|
9067
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
9696
9068
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
9697
9069
|
|
|
9698
9070
|
// src/cli/ui/progressVisibility.ts
|
|
@@ -9766,7 +9138,7 @@ function StageRow({
|
|
|
9766
9138
|
maxVisibleItems = 12
|
|
9767
9139
|
}) {
|
|
9768
9140
|
const [frameIndex, setFrameIndex] = useState2(0);
|
|
9769
|
-
|
|
9141
|
+
useEffect2(() => {
|
|
9770
9142
|
if (!isActive || stage.status !== "running") {
|
|
9771
9143
|
setFrameIndex(0);
|
|
9772
9144
|
return;
|
|
@@ -9827,7 +9199,7 @@ function ItemRows({
|
|
|
9827
9199
|
}
|
|
9828
9200
|
function ItemRow({ item, isActive }) {
|
|
9829
9201
|
const [frameIndex, setFrameIndex] = useState2(0);
|
|
9830
|
-
|
|
9202
|
+
useEffect2(() => {
|
|
9831
9203
|
if (!isActive || item.status !== "running") {
|
|
9832
9204
|
setFrameIndex(0);
|
|
9833
9205
|
return;
|
|
@@ -10430,7 +9802,7 @@ function WriteApp({
|
|
|
10430
9802
|
);
|
|
10431
9803
|
const [result, setResult] = useState4(null);
|
|
10432
9804
|
const [errorMessage, setErrorMessage] = useState4(null);
|
|
10433
|
-
|
|
9805
|
+
useEffect3(() => {
|
|
10434
9806
|
let mounted = true;
|
|
10435
9807
|
void (async () => {
|
|
10436
9808
|
try {
|
|
@@ -10480,7 +9852,7 @@ function WriteApp({
|
|
|
10480
9852
|
mounted = false;
|
|
10481
9853
|
};
|
|
10482
9854
|
}, [dryRun, enrichLinks2, input, links, unlinks, maxLinks, maxImages, onError, runMode]);
|
|
10483
|
-
|
|
9855
|
+
useEffect3(() => {
|
|
10484
9856
|
if (!result && !errorMessage) {
|
|
10485
9857
|
return;
|
|
10486
9858
|
}
|