@spark-apps/quickpeek 1.2.5 → 1.2.6
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/README.md +7 -14
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +147 -122
- package/dist/mcp-tools.mjs +207 -171
- package/dist/qp.js +208 -172
- package/dist/{voices-ufga0oOn.d.mts → voices-5AFt1GDp.d.mts} +45 -3
- package/dist/web.d.mts +2 -2
- package/dist/web.mjs +146 -19
- package/package.json +3 -6
package/README.md
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
[license-link]: ./LICENSE
|
|
2
|
-
[privacy-link]: ./PRIVACY.md
|
|
3
|
-
[discord-link]: https://discord.gg/mAjHWE3mSp
|
|
4
1
|
[coffee-link]: https://buymeacoffee.com/spark88
|
|
5
2
|
|
|
6
3
|
<div align="center">
|
|
7
4
|
|
|
8
|
-
<img src=
|
|
5
|
+
<img src="https://quickpeek.co/logo.png" alt="QuickPeek" width="220">
|
|
9
6
|
|
|
10
7
|
**Demo videos for your web app with AI**
|
|
11
8
|
|
|
12
9
|
*Because Tutorial Videos are Overrated!*
|
|
13
10
|
|
|
14
11
|
[](https://www.npmjs.com/package/@spark-apps/quickpeek)
|
|
15
|
-
|
|
16
|
-
[][discord-link]
|
|
12
|
+

|
|
17
13
|
|
|
18
14
|
[](https://sellular.online/badge/quickpeek/go)
|
|
19
15
|
|
|
@@ -46,8 +42,7 @@ AI planner runs on our servers and is metered per account; over
|
|
|
46
42
|
writing. A free video of 20 seconds or under comes out clean; a longer free video
|
|
47
43
|
carries a QuickPeek watermark. The hosted web recorder at quickpeek.co is
|
|
48
44
|
deliberately different: there, free videos are capped at 20 seconds and are
|
|
49
|
-
always watermarked.
|
|
50
|
-
the two side by side.
|
|
45
|
+
always watermarked. https://quickpeek.co/pricing sets the two side by side.
|
|
51
46
|
|
|
52
47
|
## Features
|
|
53
48
|
|
|
@@ -139,7 +134,7 @@ Config is auto-generated on first run. Edit `quickpeek.config.json` to customize
|
|
|
139
134
|
| `pt` | Portuguese | `hi` | Hindi |
|
|
140
135
|
| `it` | Italian | `ru` | Russian |
|
|
141
136
|
|
|
142
|
-
|
|
137
|
+
The full list comes back from the `list_voices` tool on the MCP server.
|
|
143
138
|
|
|
144
139
|
</details>
|
|
145
140
|
|
|
@@ -148,8 +143,6 @@ See `src/voices.ts` for all 60+ supported languages.
|
|
|
148
143
|
|
|
149
144
|
Edit `captions.css` (auto-generated) to customize:
|
|
150
145
|
|
|
151
|
-

|
|
152
|
-
|
|
153
146
|
```css
|
|
154
147
|
.caption {
|
|
155
148
|
font-family: Arial, sans-serif;
|
|
@@ -200,7 +193,7 @@ QuickPeek ships an MCP server (`quickpeek-mcp`) so an AI agent can record a demo
|
|
|
200
193
|
directly, with no shelling out to the CLI.
|
|
201
194
|
|
|
202
195
|
```bash
|
|
203
|
-
claude mcp add quickpeek -- npx -y --package=@spark-apps/quickpeek quickpeek-mcp
|
|
196
|
+
claude mcp add quickpeek -- npx -y --ignore-scripts --package=@spark-apps/quickpeek quickpeek-mcp
|
|
204
197
|
```
|
|
205
198
|
|
|
206
199
|
Or add it by hand, for any MCP client:
|
|
@@ -210,7 +203,7 @@ Or add it by hand, for any MCP client:
|
|
|
210
203
|
"mcpServers": {
|
|
211
204
|
"quickpeek": {
|
|
212
205
|
"command": "npx",
|
|
213
|
-
"args": ["-y", "--package=@spark-apps/quickpeek", "quickpeek-mcp"]
|
|
206
|
+
"args": ["-y", "--ignore-scripts", "--package=@spark-apps/quickpeek", "quickpeek-mcp"]
|
|
214
207
|
}
|
|
215
208
|
}
|
|
216
209
|
}
|
|
@@ -244,7 +237,7 @@ needs no account at all for the first 5 videos, where the CLI asks for an email.
|
|
|
244
237
|
☕ **Devs run on coffee** - [Buy me one?][coffee-link]
|
|
245
238
|
💰 **Crypto tips welcome** - [Tip in crypto](https://tip.md/muammar-yacoob)
|
|
246
239
|
🎬 **See it work** - [Watch a demo](https://www.youtube.com/shorts/HaibrNvJknY)
|
|
247
|
-
🐛 **Found a bug or want a feature?**
|
|
240
|
+
🐛 **Found a bug or want a feature?** Email [support@quickpeek.co](mailto:support@quickpeek.co) - the repo is private, so email is the way in.
|
|
248
241
|
|
|
249
242
|
<div align="center">
|
|
250
243
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { U as UserTier, C as CaptionStyle, a as CaptionPosition, b as Config, V as VoiceRate } from './voices-
|
|
2
|
-
export { A as AIError, c as AIResponse, d as AIResult, e as CLI_BACKOFF_MS, f as CONFIG_FILE, g as CaptionPreset, h as CaptionWordStyle, i as ChatOptions, D as DEFAULT_CAPTIONS, j as DEFAULT_CONFIG, E as EXCLUDED_LINK_PATTERNS, k as ElementInfo, l as ElementType, H as HighlightMode, I as INTERACTIVE_SELECTORS, m as INTERNATIONAL_VOICE, N as NO_CLIP_OUTRO, P as PLAN_MAX_TOKENS, n as Plan, R as RATE_FACTOR, o as RELAY_BASE, p as RateLimitInfo, q as RetryNotice, S as SERVER_BACKOFF_MS, r as SIZE_PRESETS, s as SparkStatus, t as SparkSubscription, u as SparkTrial, v as Step, w as SuggestedAction, T as Tier, x as VERSION, y as VIDEO_PROFILES, z as VideoProfileName, B as VideoSize, F as VoiceGender, G as applyProfile, J as ariaLabelSelector, K as buildSystemPrompt, L as buildUserPrompt, M as callAI, O as
|
|
1
|
+
import { U as UserTier, C as CaptionStyle, a as CaptionPosition, b as Config, V as VoiceRate } from './voices-5AFt1GDp.mjs';
|
|
2
|
+
export { A as AIError, c as AIResponse, d as AIResult, e as CLI_BACKOFF_MS, f as CONFIG_FILE, g as CaptionPreset, h as CaptionWordStyle, i as ChatOptions, D as DEFAULT_CAPTIONS, j as DEFAULT_CONFIG, E as EXCLUDED_LINK_PATTERNS, k as ElementInfo, l as ElementType, H as HighlightMode, I as INTERACTIVE_SELECTORS, m as INTERNATIONAL_VOICE, N as NO_CLIP_OUTRO, P as PLAN_MAX_TOKENS, n as Plan, R as RATE_FACTOR, o as RELAY_BASE, p as RateLimitInfo, q as RetryNotice, S as SERVER_BACKOFF_MS, r as SIZE_PRESETS, s as SparkStatus, t as SparkSubscription, u as SparkTrial, v as Step, w as SuggestedAction, T as Tier, x as VERSION, y as VIDEO_PROFILES, z as VideoProfileName, B as VideoSize, F as VoiceGender, G as applyProfile, J as ariaLabelSelector, K as buildSystemPrompt, L as buildUserPrompt, M as callAI, O as callAIForPlan, Q as callAIViaRelay, W as crawlPage, X as deStock, Y as defaultCaptionsFor, Z as getErrorMessage, _ as getLanguageName, $ as getOrRefreshRelayToken, a0 as getStatus, a1 as getStatusCached, a2 as getTierByEmail, a3 as gotoSettled, a4 as hasTrialRemaining, a5 as hasVoice, a6 as idSelector, a7 as internationalVoiceFor, a8 as isCanceling, a9 as isExcludedLink, aa as isPaid, ab as isVerified, ac as normalizeUrl, ad as parseAIPlanResponse, ae as parsePartial, af as pricingUrl, ag as rgbaToHex, ah as runPooled, ai as shouldSkipLink, aj as showPaywall, ak as stripLongDashes, al as toTitleCase, am as voiceFor, an as waitForPlaceholdersGone } from './voices-5AFt1GDp.mjs';
|
|
3
3
|
export { MALE_VOICES, MULTILINGUAL_VOICES, WIDE_VOICES, hexToAss as hexToASS, windowsDrivePathToWsl as windowsPathToWSL } from '@spark-apps/video-kit';
|
|
4
4
|
import 'playwright';
|
|
5
5
|
|
package/dist/index.mjs
CHANGED
|
@@ -1863,6 +1863,108 @@ var init_timing = __esm({
|
|
|
1863
1863
|
}
|
|
1864
1864
|
});
|
|
1865
1865
|
|
|
1866
|
+
// src/lib/json-repair.ts
|
|
1867
|
+
function repairUnicodeEscapes(content) {
|
|
1868
|
+
return content.replace(/\\u\{([0-9a-fA-F]{1,6})\}/g, (match, hex) => {
|
|
1869
|
+
const code = Number.parseInt(hex, 16);
|
|
1870
|
+
return code <= 1114111 ? String.fromCodePoint(code) : match;
|
|
1871
|
+
}).replace(/\\u(?![0-9a-fA-F]{4})/g, "\\\\u");
|
|
1872
|
+
}
|
|
1873
|
+
var MAX_SALVAGE_ATTEMPTS = 500;
|
|
1874
|
+
var CLOSERS = { "{": "}", "[": "]" };
|
|
1875
|
+
function salvageTruncatedJson(content) {
|
|
1876
|
+
const stack = [];
|
|
1877
|
+
const cuts = [];
|
|
1878
|
+
let inString = false;
|
|
1879
|
+
let escaped = false;
|
|
1880
|
+
const record = (index, closesContainer) => cuts.push({ index, open: stack.join(""), closesContainer });
|
|
1881
|
+
for (let i = 0; i < content.length; i++) {
|
|
1882
|
+
const ch = content[i];
|
|
1883
|
+
if (inString) {
|
|
1884
|
+
if (escaped) escaped = false;
|
|
1885
|
+
else if (ch === "\\") escaped = true;
|
|
1886
|
+
else if (ch === '"') {
|
|
1887
|
+
inString = false;
|
|
1888
|
+
record(i + 1, false);
|
|
1889
|
+
}
|
|
1890
|
+
continue;
|
|
1891
|
+
}
|
|
1892
|
+
if (ch === '"') {
|
|
1893
|
+
inString = true;
|
|
1894
|
+
} else if (ch === "{" || ch === "[") {
|
|
1895
|
+
stack.push(ch);
|
|
1896
|
+
} else if (ch === "}" || ch === "]") {
|
|
1897
|
+
stack.pop();
|
|
1898
|
+
record(i + 1, true);
|
|
1899
|
+
} else if (ch === ",") {
|
|
1900
|
+
record(i, content[i - 1] === "}" || content[i - 1] === "]");
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
const tryCut = (cut) => {
|
|
1904
|
+
const head = content.slice(0, cut.index).replace(/[\s,]+$/, "");
|
|
1905
|
+
if (head.endsWith(":")) return null;
|
|
1906
|
+
let closed = head;
|
|
1907
|
+
for (let d = cut.open.length - 1; d >= 0; d--) {
|
|
1908
|
+
closed += CLOSERS[cut.open[d]];
|
|
1909
|
+
}
|
|
1910
|
+
try {
|
|
1911
|
+
JSON.parse(closed);
|
|
1912
|
+
return closed;
|
|
1913
|
+
} catch {
|
|
1914
|
+
return null;
|
|
1915
|
+
}
|
|
1916
|
+
};
|
|
1917
|
+
const attempts = Math.min(cuts.length, MAX_SALVAGE_ATTEMPTS);
|
|
1918
|
+
for (const containerOnly of [true, false]) {
|
|
1919
|
+
for (let n = 0; n < attempts; n++) {
|
|
1920
|
+
const cut = cuts[cuts.length - 1 - n];
|
|
1921
|
+
if (containerOnly && !cut.closesContainer) continue;
|
|
1922
|
+
const salvaged = tryCut(cut);
|
|
1923
|
+
if (salvaged !== null) return salvaged;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
return null;
|
|
1927
|
+
}
|
|
1928
|
+
function extractJsonCandidates(content) {
|
|
1929
|
+
let text = content.trim();
|
|
1930
|
+
const fence = text.match(/```(?:json)?\s*([\s\S]*?)(?:```|$)/);
|
|
1931
|
+
if (fence?.[1]?.trim()) text = fence[1].trim();
|
|
1932
|
+
const start = text.search(/[{[]/);
|
|
1933
|
+
if (start === -1) return [];
|
|
1934
|
+
const closer = text[start] === "{" ? "}" : "]";
|
|
1935
|
+
const end = text.lastIndexOf(closer);
|
|
1936
|
+
const candidates = [text.slice(start)];
|
|
1937
|
+
if (end > start && end < text.length - 1) candidates.unshift(text.slice(start, end + 1));
|
|
1938
|
+
return candidates;
|
|
1939
|
+
}
|
|
1940
|
+
function parseAIJson(content) {
|
|
1941
|
+
const candidates = extractJsonCandidates(content);
|
|
1942
|
+
if (candidates.length === 0) return null;
|
|
1943
|
+
for (const text of candidates) {
|
|
1944
|
+
try {
|
|
1945
|
+
return { value: JSON.parse(text), repaired: false };
|
|
1946
|
+
} catch {
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
for (const text of candidates) {
|
|
1950
|
+
try {
|
|
1951
|
+
return { value: JSON.parse(repairUnicodeEscapes(text)), repaired: true };
|
|
1952
|
+
} catch {
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
for (const text of candidates) {
|
|
1956
|
+
for (const variant of [repairUnicodeEscapes(text), text]) {
|
|
1957
|
+
const salvaged = salvageTruncatedJson(variant);
|
|
1958
|
+
if (salvaged === null) continue;
|
|
1959
|
+
try {
|
|
1960
|
+
return { value: JSON.parse(salvaged), repaired: true };
|
|
1961
|
+
} catch {
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
return null;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1866
1968
|
// src/lib/api-client.ts
|
|
1867
1969
|
init_utils();
|
|
1868
1970
|
var PLAN_MAX_TOKENS = 1500;
|
|
@@ -2073,24 +2175,26 @@ async function callAIViaRelay(systemPrompt, userPrompt, email, tier = "free", op
|
|
|
2073
2175
|
}
|
|
2074
2176
|
}
|
|
2075
2177
|
function parseAIPlanResponse(content) {
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
}
|
|
2082
|
-
const jsonMatch = processedContent.match(/\{[\s\S]*\}/);
|
|
2083
|
-
if (!jsonMatch) {
|
|
2084
|
-
return { success: false, error: "No valid JSON found in AI response" };
|
|
2085
|
-
}
|
|
2086
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
2087
|
-
return { success: true, plan: parsed };
|
|
2088
|
-
} catch (err) {
|
|
2089
|
-
return {
|
|
2090
|
-
success: false,
|
|
2091
|
-
error: `Failed to parse AI response: ${err instanceof Error ? err.message : String(err)}`
|
|
2092
|
-
};
|
|
2178
|
+
const parsed = parseAIJson(content);
|
|
2179
|
+
if (!parsed) {
|
|
2180
|
+
return { success: false, error: "No valid JSON found in AI response" };
|
|
2181
|
+
}
|
|
2182
|
+
if (!Array.isArray(parsed.value.steps) || parsed.value.steps.length === 0) {
|
|
2183
|
+
return { success: false, error: "AI response has no steps" };
|
|
2093
2184
|
}
|
|
2185
|
+
return { success: true, plan: { ...parsed.value, steps: parsed.value.steps } };
|
|
2186
|
+
}
|
|
2187
|
+
async function callAIForPlan(systemPrompt, userPrompt, tier = "free", opts = {}) {
|
|
2188
|
+
let lastParseError = "No valid JSON found in AI response";
|
|
2189
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
2190
|
+
const aiResult = await callAI(systemPrompt, userPrompt, tier, opts);
|
|
2191
|
+
if (!aiResult.success) return aiResult;
|
|
2192
|
+
const parsed = parseAIPlanResponse(aiResult.content);
|
|
2193
|
+
if (parsed.success) return { success: true, plan: parsed.plan, limits: aiResult.limits };
|
|
2194
|
+
lastParseError = parsed.error;
|
|
2195
|
+
console.error(`Plan parse failed (attempt ${attempt}/2):`, parsed.error);
|
|
2196
|
+
}
|
|
2197
|
+
return { success: false, error: lastParseError, isParseError: true };
|
|
2094
2198
|
}
|
|
2095
2199
|
function normalizeUrl(input) {
|
|
2096
2200
|
let url = input.trim();
|
|
@@ -2174,7 +2278,7 @@ function getUpgradeUrl(userEmail) {
|
|
|
2174
2278
|
}
|
|
2175
2279
|
|
|
2176
2280
|
// src/lib/config.ts
|
|
2177
|
-
var VERSION = "1.2.
|
|
2281
|
+
var VERSION = "1.2.6";
|
|
2178
2282
|
var CONFIG_FILE = "quickpeek.config.json";
|
|
2179
2283
|
var SIZE_PRESETS = {
|
|
2180
2284
|
wide: { width: 1920, height: 1080, zoom: 1 },
|
|
@@ -2311,8 +2415,24 @@ function defaultCaptionsFor(width, height) {
|
|
|
2311
2415
|
};
|
|
2312
2416
|
}
|
|
2313
2417
|
|
|
2314
|
-
// src/
|
|
2418
|
+
// src/browser/headless.ts
|
|
2315
2419
|
import { chromium } from "playwright";
|
|
2420
|
+
var NO_BROWSER_MESSAGE = "QuickPeek needs a browser to record and could not find one.\n Run this once, then run QuickPeek again:\n npx playwright install chromium";
|
|
2421
|
+
async function launchHeadlessChromium(options = {}) {
|
|
2422
|
+
const opts = { ...options, headless: true };
|
|
2423
|
+
try {
|
|
2424
|
+
return await chromium.launch(opts);
|
|
2425
|
+
} catch (bundledError) {
|
|
2426
|
+
try {
|
|
2427
|
+
return await chromium.launch({ ...opts, channel: "chrome" });
|
|
2428
|
+
} catch {
|
|
2429
|
+
const detail = bundledError instanceof Error ? bundledError.message : String(bundledError);
|
|
2430
|
+
throw new Error(`${NO_BROWSER_MESSAGE}
|
|
2431
|
+
|
|
2432
|
+
Playwright reported: ${detail}`);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2316
2436
|
|
|
2317
2437
|
// src/lib/scraping.ts
|
|
2318
2438
|
var EXCLUDED_LINK_PATTERNS = [
|
|
@@ -2550,7 +2670,13 @@ async function crawlPage(url, task = noopTask, storageStatePath, viewport) {
|
|
|
2550
2670
|
return { title: cached.title, elements: [...cached.elements], scrollable: cached.scrollable };
|
|
2551
2671
|
}
|
|
2552
2672
|
const browserTask = task("Launching browser");
|
|
2553
|
-
|
|
2673
|
+
let browser;
|
|
2674
|
+
try {
|
|
2675
|
+
browser = await launchHeadlessChromium();
|
|
2676
|
+
} catch (err) {
|
|
2677
|
+
browserTask.succeed("Could not launch a browser");
|
|
2678
|
+
throw err;
|
|
2679
|
+
}
|
|
2554
2680
|
try {
|
|
2555
2681
|
const context = await browser.newContext({
|
|
2556
2682
|
...storageStatePath ? { storageState: storageStatePath } : {},
|
|
@@ -2733,108 +2859,6 @@ var CURSOR_PNG = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAAEsBAMAAAB0
|
|
|
2733
2859
|
// src/lib/index.ts
|
|
2734
2860
|
init_ffmpeg2();
|
|
2735
2861
|
|
|
2736
|
-
// src/lib/json-repair.ts
|
|
2737
|
-
function repairUnicodeEscapes(content) {
|
|
2738
|
-
return content.replace(/\\u\{([0-9a-fA-F]{1,6})\}/g, (match, hex) => {
|
|
2739
|
-
const code = Number.parseInt(hex, 16);
|
|
2740
|
-
return code <= 1114111 ? String.fromCodePoint(code) : match;
|
|
2741
|
-
}).replace(/\\u(?![0-9a-fA-F]{4})/g, "\\\\u");
|
|
2742
|
-
}
|
|
2743
|
-
var MAX_SALVAGE_ATTEMPTS = 500;
|
|
2744
|
-
var CLOSERS = { "{": "}", "[": "]" };
|
|
2745
|
-
function salvageTruncatedJson(content) {
|
|
2746
|
-
const stack = [];
|
|
2747
|
-
const cuts = [];
|
|
2748
|
-
let inString = false;
|
|
2749
|
-
let escaped = false;
|
|
2750
|
-
const record = (index, closesContainer) => cuts.push({ index, open: stack.join(""), closesContainer });
|
|
2751
|
-
for (let i = 0; i < content.length; i++) {
|
|
2752
|
-
const ch = content[i];
|
|
2753
|
-
if (inString) {
|
|
2754
|
-
if (escaped) escaped = false;
|
|
2755
|
-
else if (ch === "\\") escaped = true;
|
|
2756
|
-
else if (ch === '"') {
|
|
2757
|
-
inString = false;
|
|
2758
|
-
record(i + 1, false);
|
|
2759
|
-
}
|
|
2760
|
-
continue;
|
|
2761
|
-
}
|
|
2762
|
-
if (ch === '"') {
|
|
2763
|
-
inString = true;
|
|
2764
|
-
} else if (ch === "{" || ch === "[") {
|
|
2765
|
-
stack.push(ch);
|
|
2766
|
-
} else if (ch === "}" || ch === "]") {
|
|
2767
|
-
stack.pop();
|
|
2768
|
-
record(i + 1, true);
|
|
2769
|
-
} else if (ch === ",") {
|
|
2770
|
-
record(i, content[i - 1] === "}" || content[i - 1] === "]");
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
|
-
const tryCut = (cut) => {
|
|
2774
|
-
const head = content.slice(0, cut.index).replace(/[\s,]+$/, "");
|
|
2775
|
-
if (head.endsWith(":")) return null;
|
|
2776
|
-
let closed = head;
|
|
2777
|
-
for (let d = cut.open.length - 1; d >= 0; d--) {
|
|
2778
|
-
closed += CLOSERS[cut.open[d]];
|
|
2779
|
-
}
|
|
2780
|
-
try {
|
|
2781
|
-
JSON.parse(closed);
|
|
2782
|
-
return closed;
|
|
2783
|
-
} catch {
|
|
2784
|
-
return null;
|
|
2785
|
-
}
|
|
2786
|
-
};
|
|
2787
|
-
const attempts = Math.min(cuts.length, MAX_SALVAGE_ATTEMPTS);
|
|
2788
|
-
for (const containerOnly of [true, false]) {
|
|
2789
|
-
for (let n = 0; n < attempts; n++) {
|
|
2790
|
-
const cut = cuts[cuts.length - 1 - n];
|
|
2791
|
-
if (containerOnly && !cut.closesContainer) continue;
|
|
2792
|
-
const salvaged = tryCut(cut);
|
|
2793
|
-
if (salvaged !== null) return salvaged;
|
|
2794
|
-
}
|
|
2795
|
-
}
|
|
2796
|
-
return null;
|
|
2797
|
-
}
|
|
2798
|
-
function extractJsonCandidates(content) {
|
|
2799
|
-
let text = content.trim();
|
|
2800
|
-
const fence = text.match(/```(?:json)?\s*([\s\S]*?)(?:```|$)/);
|
|
2801
|
-
if (fence?.[1]?.trim()) text = fence[1].trim();
|
|
2802
|
-
const start = text.search(/[{[]/);
|
|
2803
|
-
if (start === -1) return [];
|
|
2804
|
-
const closer = text[start] === "{" ? "}" : "]";
|
|
2805
|
-
const end = text.lastIndexOf(closer);
|
|
2806
|
-
const candidates = [text.slice(start)];
|
|
2807
|
-
if (end > start && end < text.length - 1) candidates.unshift(text.slice(start, end + 1));
|
|
2808
|
-
return candidates;
|
|
2809
|
-
}
|
|
2810
|
-
function parseAIJson(content) {
|
|
2811
|
-
const candidates = extractJsonCandidates(content);
|
|
2812
|
-
if (candidates.length === 0) return null;
|
|
2813
|
-
for (const text of candidates) {
|
|
2814
|
-
try {
|
|
2815
|
-
return { value: JSON.parse(text), repaired: false };
|
|
2816
|
-
} catch {
|
|
2817
|
-
}
|
|
2818
|
-
}
|
|
2819
|
-
for (const text of candidates) {
|
|
2820
|
-
try {
|
|
2821
|
-
return { value: JSON.parse(repairUnicodeEscapes(text)), repaired: true };
|
|
2822
|
-
} catch {
|
|
2823
|
-
}
|
|
2824
|
-
}
|
|
2825
|
-
for (const text of candidates) {
|
|
2826
|
-
for (const variant of [repairUnicodeEscapes(text), text]) {
|
|
2827
|
-
const salvaged = salvageTruncatedJson(variant);
|
|
2828
|
-
if (salvaged === null) continue;
|
|
2829
|
-
try {
|
|
2830
|
-
return { value: JSON.parse(salvaged), repaired: true };
|
|
2831
|
-
} catch {
|
|
2832
|
-
}
|
|
2833
|
-
}
|
|
2834
|
-
}
|
|
2835
|
-
return null;
|
|
2836
|
-
}
|
|
2837
|
-
|
|
2838
2862
|
// src/lib/prompts.ts
|
|
2839
2863
|
init_utils();
|
|
2840
2864
|
function buildSystemPrompt(maxSteps, profile) {
|
|
@@ -3925,6 +3949,7 @@ export {
|
|
|
3925
3949
|
buildUserPrompt,
|
|
3926
3950
|
buildWatermarkFilters,
|
|
3927
3951
|
callAI,
|
|
3952
|
+
callAIForPlan,
|
|
3928
3953
|
callAIViaRelay,
|
|
3929
3954
|
canReuseVoiceover,
|
|
3930
3955
|
captionCanvas,
|