@blockrun/franklin 3.21.1 → 3.21.3
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/panel/html.js +27 -9
- package/dist/phone/call-log.js +8 -2
- package/dist/tools/videogen.js +44 -1
- package/dist/tools/voice.js +1 -1
- package/package.json +1 -1
package/dist/panel/html.js
CHANGED
|
@@ -10,7 +10,7 @@ export function getHTML() {
|
|
|
10
10
|
<head>
|
|
11
11
|
<meta charset="utf-8">
|
|
12
12
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
13
|
-
<title>Franklin Panel</title>
|
|
13
|
+
<title>Franklin Agent Panel</title>
|
|
14
14
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='30' y='20' width='55' height='60' rx='14' stroke='white' stroke-width='8' fill='none'/%3E%3Cpath d='M15 35 L25 35' stroke='white' stroke-width='6' stroke-linecap='round'/%3E%3Cpath d='M10 50 L25 50' stroke='white' stroke-width='6' stroke-linecap='round'/%3E%3Cpath d='M15 65 L25 65' stroke='white' stroke-width='6' stroke-linecap='round'/%3E%3C/svg%3E">
|
|
15
15
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
16
16
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
@@ -489,7 +489,7 @@ a:hover { text-decoration:underline; }
|
|
|
489
489
|
<div class="sidebar-header">
|
|
490
490
|
<div class="sidebar-brand">
|
|
491
491
|
<div class="icon"><img src="/assets/franklin-portrait.jpg" alt="F"></div>
|
|
492
|
-
<h1>Franklin</h1>
|
|
492
|
+
<h1>Franklin Agent</h1>
|
|
493
493
|
</div>
|
|
494
494
|
<div class="sidebar-sub">by <span style="color:var(--success)">BlockRun.ai</span></div>
|
|
495
495
|
<div class="sidebar-status">
|
|
@@ -1990,6 +1990,22 @@ function formatDuration(sec) {
|
|
|
1990
1990
|
return m > 0 ? m + 'm ' + s + 's' : s + 's';
|
|
1991
1991
|
}
|
|
1992
1992
|
|
|
1993
|
+
function escapeHtml(value) {
|
|
1994
|
+
return String(value == null ? '' : value).replace(/[&<>"']/g, ch => ({
|
|
1995
|
+
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
|
1996
|
+
}[ch]));
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
function safeHttpUrl(value) {
|
|
2000
|
+
if (typeof value !== 'string') return '';
|
|
2001
|
+
try {
|
|
2002
|
+
const u = new URL(value);
|
|
2003
|
+
return (u.protocol === 'http:' || u.protocol === 'https:') ? u.href : '';
|
|
2004
|
+
} catch (e) {
|
|
2005
|
+
return '';
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
|
|
1993
2009
|
function renderCallsList(calls) {
|
|
1994
2010
|
const list = document.getElementById('calls-list');
|
|
1995
2011
|
if (!list) return;
|
|
@@ -2007,13 +2023,14 @@ function renderCallsList(calls) {
|
|
|
2007
2023
|
const fromHuman = formatPhoneNumber(c.from);
|
|
2008
2024
|
const when = new Date(c.timestamp).toLocaleString();
|
|
2009
2025
|
const cost = c.paid_usd ? '$' + c.paid_usd.toFixed(2) : '—';
|
|
2010
|
-
const safeTask = (c.task || '')
|
|
2026
|
+
const safeTask = escapeHtml(c.task || '');
|
|
2027
|
+
const recordingUrl = safeHttpUrl(c.recording_url);
|
|
2011
2028
|
const transcriptHtml = c.transcript
|
|
2012
2029
|
? '<details style="margin-top:8px"><summary style="cursor:pointer;color:var(--text-dim);font-size:12px">Transcript</summary><pre style="white-space:pre-wrap;background:oklch(0 0 0 / 25%);padding:10px;border-radius:8px;font-size:12px;margin-top:6px;max-height:400px;overflow:auto">' +
|
|
2013
|
-
c.transcript
|
|
2030
|
+
escapeHtml(c.transcript) + '</pre></details>'
|
|
2014
2031
|
: '';
|
|
2015
|
-
const recordingHtml =
|
|
2016
|
-
? '<a href="' +
|
|
2032
|
+
const recordingHtml = recordingUrl
|
|
2033
|
+
? '<a href="' + escapeHtml(recordingUrl) + '" target="_blank" rel="noopener" style="font-size:11px;color:var(--brand);text-decoration:none;margin-left:8px">▶ recording</a>'
|
|
2017
2034
|
: '';
|
|
2018
2035
|
return ''
|
|
2019
2036
|
+ '<div class="phone-row" style="grid-template-columns:auto 1fr auto;align-items:start">'
|
|
@@ -2023,12 +2040,12 @@ function renderCallsList(calls) {
|
|
|
2023
2040
|
+ ' </svg>'
|
|
2024
2041
|
+ ' </div>'
|
|
2025
2042
|
+ ' <div class="phone-main">'
|
|
2026
|
-
+ ' <div class="phone-num">' + human + ' <span style="font-size:11px;color:var(--text-dim);font-weight:400">from ' + fromHuman + '</span></div>'
|
|
2043
|
+
+ ' <div class="phone-num">' + escapeHtml(human) + ' <span style="font-size:11px;color:var(--text-dim);font-weight:400">from ' + escapeHtml(fromHuman) + '</span></div>'
|
|
2027
2044
|
+ ' <div class="phone-meta">'
|
|
2028
|
-
+ ' <span class="chip ' + st.cls + '">' + st.label + '</span>'
|
|
2045
|
+
+ ' <span class="chip ' + st.cls + '">' + escapeHtml(st.label) + '</span>'
|
|
2029
2046
|
+ ' <span class="chip">' + formatDuration(c.duration_sec) + '</span>'
|
|
2030
2047
|
+ ' <span class="chip">' + cost + '</span>'
|
|
2031
|
-
+ ' <span style="font-size:11px;color:var(--text-dim)">' + when + '</span>'
|
|
2048
|
+
+ ' <span style="font-size:11px;color:var(--text-dim)">' + escapeHtml(when) + '</span>'
|
|
2032
2049
|
+ recordingHtml
|
|
2033
2050
|
+ ' </div>'
|
|
2034
2051
|
+ ' <div style="font-size:12px;color:var(--text-muted);margin-top:4px;line-height:1.5">' + (safeTask.slice(0, 200) + (safeTask.length > 200 ? '…' : '')) + '</div>'
|
|
@@ -2074,6 +2091,7 @@ document.querySelector('[data-tab="markets"]')?.addEventListener('click', loadMa
|
|
|
2074
2091
|
const initialHash = (location.hash || '').replace(/^#/, '');
|
|
2075
2092
|
if (initialHash && initialHash !== 'overview' && document.getElementById('tab-' + initialHash)) {
|
|
2076
2093
|
activateTab(initialHash);
|
|
2094
|
+
if (initialHash === 'calls') loadCalls();
|
|
2077
2095
|
}
|
|
2078
2096
|
}
|
|
2079
2097
|
|
package/dist/phone/call-log.js
CHANGED
|
@@ -100,24 +100,30 @@ export class CallLog {
|
|
|
100
100
|
summary(limit = 50) {
|
|
101
101
|
const all = this.all();
|
|
102
102
|
const latest = new Map();
|
|
103
|
+
const paidByCall = new Map();
|
|
103
104
|
for (const e of all) {
|
|
105
|
+
paidByCall.set(e.call_id, Math.max(paidByCall.get(e.call_id) ?? 0, e.paid_usd));
|
|
104
106
|
const cur = latest.get(e.call_id);
|
|
105
107
|
// Keep the row with the FRESHEST timestamp per call_id (status updates).
|
|
106
108
|
if (!cur || e.timestamp >= cur.timestamp)
|
|
107
109
|
latest.set(e.call_id, e);
|
|
108
110
|
}
|
|
109
111
|
// Sort newest-first by the latest-row timestamp.
|
|
110
|
-
const list = Array.from(latest.values())
|
|
112
|
+
const list = Array.from(latest.values())
|
|
113
|
+
.map(e => ({ ...e, paid_usd: paidByCall.get(e.call_id) ?? e.paid_usd }))
|
|
114
|
+
.sort((a, b) => b.timestamp - a.timestamp);
|
|
111
115
|
return list.slice(0, limit);
|
|
112
116
|
}
|
|
113
117
|
byCallId(callId) {
|
|
114
118
|
let best = null;
|
|
119
|
+
let paidUsd = 0;
|
|
115
120
|
for (const e of this.all()) {
|
|
116
121
|
if (e.call_id !== callId)
|
|
117
122
|
continue;
|
|
123
|
+
paidUsd = Math.max(paidUsd, e.paid_usd);
|
|
118
124
|
if (!best || e.timestamp >= best.timestamp)
|
|
119
125
|
best = e;
|
|
120
126
|
}
|
|
121
|
-
return best;
|
|
127
|
+
return best ? { ...best, paid_usd: paidUsd } : null;
|
|
122
128
|
}
|
|
123
129
|
}
|
package/dist/tools/videogen.js
CHANGED
|
@@ -28,6 +28,13 @@ import { ModelClient } from '../agent/llm.js';
|
|
|
28
28
|
import { analyzeMediaRequest, renderProposalForAskUser } from '../agent/media-router.js';
|
|
29
29
|
import { recordUsage } from '../stats/tracker.js';
|
|
30
30
|
import { findModel, estimateCostUsd } from '../gateway-models.js';
|
|
31
|
+
// BytePlus RealFace asset IDs from token360 Asset UI (after H5 verification).
|
|
32
|
+
// Format: `ta_` + alphanumeric.
|
|
33
|
+
const REAL_FACE_ASSET_ID_REGEX = /^ta_[A-Za-z0-9]+$/;
|
|
34
|
+
const REAL_FACE_MODELS = new Set([
|
|
35
|
+
'bytedance/seedance-2.0',
|
|
36
|
+
'bytedance/seedance-2.0-fast',
|
|
37
|
+
]);
|
|
31
38
|
const DEFAULT_MODEL = 'xai/grok-imagine-video';
|
|
32
39
|
const DEFAULT_DURATION = 8;
|
|
33
40
|
const PRICE_PER_SECOND_USD = 0.05;
|
|
@@ -44,9 +51,33 @@ function estimateVideoCostUsd(durationSeconds = DEFAULT_DURATION) {
|
|
|
44
51
|
function buildExecute(deps) {
|
|
45
52
|
return async function execute(input, ctx) {
|
|
46
53
|
const rawInput = input;
|
|
47
|
-
const { output_path, model, image_url, duration_seconds, contentId, aspect_ratio } = rawInput;
|
|
54
|
+
const { output_path, model, image_url, duration_seconds, contentId, aspect_ratio, real_face_asset_id } = rawInput;
|
|
48
55
|
if (!rawInput.prompt)
|
|
49
56
|
return { output: 'Error: prompt is required', isError: true };
|
|
57
|
+
// RealFace asset client-side validations (the gateway 400s on the same
|
|
58
|
+
// conditions but a local check is friendlier — and the rejected request
|
|
59
|
+
// doesn't burn an x402 round-trip).
|
|
60
|
+
if (real_face_asset_id !== undefined) {
|
|
61
|
+
if (typeof real_face_asset_id !== 'string' || !REAL_FACE_ASSET_ID_REGEX.test(real_face_asset_id)) {
|
|
62
|
+
return {
|
|
63
|
+
output: `Error: real_face_asset_id must match "ta_<alphanumeric>" (e.g. ta_abc123). Got: ${JSON.stringify(real_face_asset_id)}`,
|
|
64
|
+
isError: true,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const chosenModel = model || DEFAULT_MODEL;
|
|
68
|
+
if (!REAL_FACE_MODELS.has(chosenModel)) {
|
|
69
|
+
return {
|
|
70
|
+
output: `Error: real_face_asset_id is only supported on Seedance 2.0 variants (${[...REAL_FACE_MODELS].join(', ')}). Current model: ${chosenModel}.`,
|
|
71
|
+
isError: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (image_url) {
|
|
75
|
+
return {
|
|
76
|
+
output: 'Error: real_face_asset_id and image_url both seed the first frame — pick one. Drop image_url to use RealFace, or drop real_face_asset_id to use the image.',
|
|
77
|
+
isError: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
50
81
|
// Resolve image_url before sending. The gateway requires a URL (http(s)
|
|
51
82
|
// or data: URI), but agents naturally pass a local file path —
|
|
52
83
|
// verified 2026-05-04 in a live session: agent passed
|
|
@@ -162,6 +193,10 @@ function buildExecute(deps) {
|
|
|
162
193
|
// value, the 400 body surfaces via 3.15.45 diagnostic so the agent
|
|
163
194
|
// can drop the param and retry.
|
|
164
195
|
...(aspect_ratio ? { aspect_ratio } : {}),
|
|
196
|
+
// RealFace (BytePlus, Seedance 2.0 only) — seeds the first frame from
|
|
197
|
+
// a real-person asset for cross-frame character consistency. Client
|
|
198
|
+
// already validated the ID + model gate above; just pass through.
|
|
199
|
+
...(real_face_asset_id ? { real_face_asset_id } : {}),
|
|
165
200
|
});
|
|
166
201
|
const headers = {
|
|
167
202
|
'Content-Type': 'application/json',
|
|
@@ -502,6 +537,14 @@ export function createVideoGenCapability(deps = {}) {
|
|
|
502
537
|
'error body surfaces — drop the param and retry.',
|
|
503
538
|
},
|
|
504
539
|
contentId: { type: 'string', description: 'Optional Content id to attach and budget against.' },
|
|
540
|
+
real_face_asset_id: {
|
|
541
|
+
type: 'string',
|
|
542
|
+
description: 'Optional BytePlus RealFace asset id (format `ta_<alphanumeric>`) for cross-frame ' +
|
|
543
|
+
'character consistency. Users get asset IDs from token360\'s Asset UI after H5 ' +
|
|
544
|
+
'verification. Seedance 2.0 variants only (bytedance/seedance-2.0, ' +
|
|
545
|
+
'bytedance/seedance-2.0-fast). Mutually exclusive with image_url — both seed the ' +
|
|
546
|
+
'first frame; pick one.',
|
|
547
|
+
},
|
|
505
548
|
},
|
|
506
549
|
required: ['prompt'],
|
|
507
550
|
},
|
package/dist/tools/voice.js
CHANGED
|
@@ -346,7 +346,7 @@ export const voiceStatusCapability = {
|
|
|
346
346
|
callLog().append({
|
|
347
347
|
...prior,
|
|
348
348
|
timestamp: Date.now(),
|
|
349
|
-
paid_usd:
|
|
349
|
+
paid_usd: prior.paid_usd, // status polls are free; preserve the per-call total
|
|
350
350
|
status: normalizeStatus(res.status ?? res.queue_status ?? res.disposition),
|
|
351
351
|
duration_sec: duration ?? prior.duration_sec,
|
|
352
352
|
transcript: transcript ?? prior.transcript,
|
package/package.json
CHANGED