@mevdragon/vidfarm-devcli 0.17.0 → 0.18.0
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/.agents/skills/vidfarm-media/SKILL.md +3 -3
- package/SKILL.director.md +20 -20
- package/SKILL.platform.md +4 -4
- package/dist/src/app.js +301 -45
- package/dist/src/cli.js +11 -10
- package/dist/src/devcli/clips.js +64 -64
- package/dist/src/reskin/agency-page.js +214 -170
- package/dist/src/reskin/calendar-page.js +502 -187
- package/dist/src/reskin/chat-page.js +470 -277
- package/dist/src/reskin/discover-page.js +988 -272
- package/dist/src/reskin/document.js +602 -13
- package/dist/src/reskin/help-page.js +138 -100
- package/dist/src/reskin/index-page.js +1 -1
- package/dist/src/reskin/inpaint-page.js +541 -0
- package/dist/src/reskin/job-runs-page.js +355 -127
- package/dist/src/reskin/library-page.js +435 -262
- package/dist/src/reskin/login-page.js +118 -81
- package/dist/src/reskin/pricing-page.js +195 -166
- package/dist/src/reskin/settings-page.js +520 -186
- package/dist/src/reskin/theme.js +106 -9
- package/package.json +1 -1
|
@@ -1,173 +1,148 @@
|
|
|
1
|
-
// /reskin/chat — the AI brainstorm chat,
|
|
2
|
-
//
|
|
1
|
+
// /reskin/chat — the AI brainstorm chat, MIGRATED from a static mockup into a
|
|
2
|
+
// REAL working chat wired to the live editor-chat backend.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// a few starter chips, and a quiet sticky composer.
|
|
4
|
+
// Like settings-page.ts, this renderer accepts the SAME input object the live
|
|
5
|
+
// chat route already builds (`Parameters<typeof renderChatPage>[0]`). The
|
|
6
|
+
// `input.editorChat` boot carries the REAL streaming endpoint, the vidfarm API
|
|
7
|
+
// key, the template context, and the cached context the agent needs — we embed
|
|
8
|
+
// that boot as JSON in the page and the composer POSTs straight to it.
|
|
10
9
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
10
|
+
// What's REAL: the composer sends the user's message to the real editor-chat
|
|
11
|
+
// endpoint (`boot.apiUrl`, with the `vidfarm-api-key` header + same-origin
|
|
12
|
+
// creds + the correct body shape) and renders the streamed assistant reply
|
|
13
|
+
// (SSE `text-delta` events). What's SIMPLIFIED: tool calls are rendered as
|
|
14
|
+
// plain status notes (no rich artifact/HTTP cards), attachments are not wired,
|
|
15
|
+
// and there is no thread-history sidebar (the boot carries no history). The
|
|
16
|
+
// design stays farmville — shared rk-* primitives + a page-scoped rk-chat-
|
|
17
|
+
// block + one self-contained script. No app.ts imports.
|
|
16
18
|
import { reskinDocument, rkEscape } from "./document.js";
|
|
19
|
+
// Default keeps the current zero-arg route (`renderReskinChat()`) compiling.
|
|
20
|
+
// With no editorChat boot the composer degrades gracefully: it renders the
|
|
21
|
+
// user bubble + a friendly "endpoint not available" reply instead of throwing.
|
|
22
|
+
const DEFAULT_INPUT = {
|
|
23
|
+
userId: "",
|
|
24
|
+
currentAccountId: "",
|
|
25
|
+
name: null,
|
|
26
|
+
email: "",
|
|
27
|
+
editorChat: null
|
|
28
|
+
};
|
|
17
29
|
// ── icons ───────────────────────────────────────────────────────────────────
|
|
18
30
|
const SEND = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 12l16-8-6 16-2.5-6.5L4 12Z"/></svg>`;
|
|
19
|
-
|
|
20
|
-
function p(html) {
|
|
21
|
-
return `<p class="rk-chat-p">${html}</p>`;
|
|
22
|
-
}
|
|
23
|
-
function hookList(items) {
|
|
24
|
-
return `<div class="rk-chat-hooks">${items
|
|
25
|
-
.map((h) => `<div class="rk-chat-hook">
|
|
26
|
-
<span class="rk-chat-hook-dot" aria-hidden="true"></span>
|
|
27
|
-
<span class="rk-chat-hook-text"><strong>${rkEscape(h.angle)}</strong> — “${rkEscape(h.text)}”</span>
|
|
28
|
-
</div>`)
|
|
29
|
-
.join("")}</div>`;
|
|
30
|
-
}
|
|
31
|
-
function note(label, text) {
|
|
32
|
-
return `<p class="rk-chat-note"><strong>${rkEscape(label)}</strong> ${rkEscape(text)}</p>`;
|
|
33
|
-
}
|
|
34
|
-
function artifact(a) {
|
|
35
|
-
return `<div class="rk-chat-art">
|
|
36
|
-
<div class="rk-chat-poster">
|
|
37
|
-
<span class="rk-chat-poster-dur">${rkEscape(a.dur)}</span>
|
|
38
|
-
<span class="rk-chat-poster-play" aria-hidden="true">▶</span>
|
|
39
|
-
</div>
|
|
40
|
-
<div class="rk-chat-art-main">
|
|
41
|
-
<span class="rk-chat-art-eyebrow">${rkEscape(a.eyebrow)}</span>
|
|
42
|
-
<h4 class="rk-chat-art-title">${rkEscape(a.title)}</h4>
|
|
43
|
-
<p class="rk-chat-art-meta">${rkEscape(a.meta)}</p>
|
|
44
|
-
<p class="rk-chat-art-desc">${rkEscape(a.desc)}</p>
|
|
45
|
-
<div class="rk-chat-art-actions">
|
|
46
|
-
<button type="button" class="rk-btn rk-btn-ink rk-btn-sm">Open in editor <span class="rk-arrow">→</span></button>
|
|
47
|
-
<button type="button" class="rk-btn rk-btn-ghost rk-btn-sm">Fork</button>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
</div>`;
|
|
51
|
-
}
|
|
52
|
-
const SAMPLE = [
|
|
53
|
-
{
|
|
54
|
-
role: "user",
|
|
55
|
-
time: "9:41 AM",
|
|
56
|
-
text: "I'm launching a $39 magnesium sleep gummy for stressed founders. Give me short-form ideas that'll convert cold TikTok traffic — I keep drawing a blank."
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
role: "assistant",
|
|
60
|
-
time: "9:41 AM",
|
|
61
|
-
body: p("Great starting point — a founder audience plus a sleep product is a hook goldmine. Here are the three angles I'd test first, each pulling a different psychological lever:") +
|
|
62
|
-
hookList([
|
|
63
|
-
{ angle: "Problem-aware", text: "POV: it's 2am and your brain won't stop pitching investors." },
|
|
64
|
-
{ angle: "Contrarian", text: "Melatonin is why you wake up groggy. Here's what founders take instead." },
|
|
65
|
-
{ angle: "Demo", text: "I swapped my nightcap for one gummy for 14 nights. Night 12 broke me." }
|
|
66
|
-
]) +
|
|
67
|
-
note("Format —", "talking-head UGC, 22–28s, hard cut every ~2s. Shoot vertical in natural light and read it like a voice memo, not an ad.")
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
role: "user",
|
|
71
|
-
time: "9:43 AM",
|
|
72
|
-
text: "The '2am' one is 🔥. Turn it into a full UGC ad script and spin up a template I can hand to a creator."
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
role: "assistant",
|
|
76
|
-
time: "9:43 AM",
|
|
77
|
-
body: p("Here's that hook as a ready-to-shoot 24-second cut — the emotional turn lands right before the CTA:") +
|
|
78
|
-
artifact({
|
|
79
|
-
dur: "0:24",
|
|
80
|
-
eyebrow: "Proposed template",
|
|
81
|
-
title: "The 2am Founder — UGC cut",
|
|
82
|
-
meta: "9:16 · 24s · Talking-head · word-by-word captions",
|
|
83
|
-
desc: "Wired to your hook and a five-beat script, with captions timed to the read. Fork it to swap the shot list or recut for a specific creator."
|
|
84
|
-
}) +
|
|
85
|
-
p("Want me to draft the on-screen captions and lock a trending sound? Say the word and I'll build the shot list.")
|
|
86
|
-
}
|
|
87
|
-
];
|
|
31
|
+
// Full-sentence starter prompts — clicking one sends it straight to the agent.
|
|
88
32
|
const STARTERS = [
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
33
|
+
"Pitch me 5 scroll-stopping hooks I can test on cold TikTok traffic.",
|
|
34
|
+
"Turn my best idea into a ready-to-shoot UGC ad script.",
|
|
35
|
+
"Remix a trending short-form format for my product.",
|
|
36
|
+
"Write word-by-word captions for a 24-second talking-head cut."
|
|
93
37
|
];
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
];
|
|
102
|
-
// ── render ───────────────────────────────────────────────────────────────────
|
|
103
|
-
function renderMessage(m) {
|
|
104
|
-
if (m.role === "user") {
|
|
105
|
-
return `<div class="rk-chat-msg is-user">
|
|
106
|
-
<div class="rk-chat-meta">You <span class="rk-chat-time">${rkEscape(m.time)}</span></div>
|
|
107
|
-
<div class="rk-chat-bubble rk-chat-bubble-user">${rkEscape(m.text)}</div>
|
|
108
|
-
</div>`;
|
|
109
|
-
}
|
|
110
|
-
return `<div class="rk-chat-msg is-assistant">
|
|
111
|
-
<div class="rk-chat-meta"><span class="rk-chat-avatar" aria-hidden="true">V</span>Copilot <span class="rk-chat-time">${rkEscape(m.time)}</span></div>
|
|
112
|
-
<div class="rk-chat-bubble rk-chat-bubble-assistant">${m.body}</div>
|
|
113
|
-
</div>`;
|
|
114
|
-
}
|
|
115
|
-
export function renderReskinChat() {
|
|
116
|
-
const thread = SAMPLE.map(renderMessage).join("");
|
|
117
|
-
const starterChips = STARTERS.map((s) => `<button type="button" class="rk-chat-chip" data-rk-prompt="${rkEscape(s.label)}">${rkEscape(s.label)}</button>`).join("");
|
|
118
|
-
const historyItems = RECENTS.map((r) => `<button type="button" class="rk-chat-hist-item${r.active ? " is-active" : ""}" data-rk-title="${rkEscape(r.title)}">
|
|
119
|
-
<span class="rk-chat-hist-title">${rkEscape(r.title)}</span>
|
|
120
|
-
<span class="rk-chat-hist-when">${rkEscape(r.when)}</span>
|
|
121
|
-
</button>`).join("");
|
|
38
|
+
export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
39
|
+
const boot = input.editorChat ?? null;
|
|
40
|
+
// Embed the real boot as JSON. Escaping only `<` is the correct (and
|
|
41
|
+
// sufficient) way to keep a `<script type="application/json">` block from
|
|
42
|
+
// breaking out; HTML-entity escaping would corrupt JSON.parse.
|
|
43
|
+
const bootJson = JSON.stringify(boot).replace(/</g, "\\u003c");
|
|
44
|
+
const starterChips = STARTERS.map((s) => `<button type="button" class="rk-chat-chip" data-rk-prompt="${rkEscape(s)}">${rkEscape(s)}</button>`).join("");
|
|
122
45
|
const body = `
|
|
123
46
|
<main class="rk-chat-page">
|
|
124
|
-
<
|
|
125
|
-
<
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
47
|
+
<aside class="rk-chat-history" id="rk-chat-history" aria-label="Chat history">
|
|
48
|
+
<div class="rk-chat-history-head">
|
|
49
|
+
<span class="rk-chat-history-title">Chats</span>
|
|
50
|
+
<button type="button" class="rk-chat-newbtn" id="rk-chat-new"><span class="rk-chat-plus" aria-hidden="true">+</span> New</button>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="rk-chat-history-list" id="rk-chat-history-list"></div>
|
|
53
|
+
</aside>
|
|
54
|
+
<div class="rk-chat-histbackdrop" id="rk-chat-histbackdrop" hidden></div>
|
|
55
|
+
<div class="rk-chat-main">
|
|
56
|
+
<div class="rk-chat-col">
|
|
57
|
+
<header class="rk-chat-head">
|
|
58
|
+
<button type="button" class="rk-chat-histtoggle" id="rk-chat-histtoggle" aria-label="Show chat history">☰</button>
|
|
59
|
+
<div class="rk-chat-head-titles">
|
|
60
|
+
<span class="rk-chat-eyebrow">Brainstorm</span>
|
|
61
|
+
<h1 class="rk-chat-title" id="rk-chat-title">Brainstorm studio</h1>
|
|
62
|
+
<p class="rk-chat-subhead">Short-form angles, hooks & scripts — brainstormed live on your own AI keys, never billed by vidfarm.</p>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="rk-chat-head-actions">
|
|
65
|
+
<label class="rk-chat-mode" aria-label="Studio mode">
|
|
66
|
+
<select class="rk-chat-mode-select" id="rk-chat-mode">
|
|
67
|
+
<option value="/chat" selected>Brainstorm</option>
|
|
68
|
+
<option value="/inpaint">Create Image</option>
|
|
69
|
+
</select>
|
|
70
|
+
<span class="rk-chat-mode-chev" aria-hidden="true">▾</span>
|
|
71
|
+
</label>
|
|
72
|
+
</div>
|
|
73
|
+
</header>
|
|
74
|
+
|
|
75
|
+
<div class="rk-chat-thread" id="rk-chat-thread">
|
|
76
|
+
<div class="rk-chat-msg is-assistant">
|
|
77
|
+
<div class="rk-chat-meta"><span class="rk-chat-avatar" aria-hidden="true">V</span>Copilot</div>
|
|
78
|
+
<div class="rk-chat-bubble rk-chat-bubble-assistant">
|
|
79
|
+
<div class="rk-chat-stream">Tell me the offer, who it’s for, and where the traffic is cold — I’ll come back with angles, hooks, and scripts you can render. Pick a starter below or just start typing.</div>
|
|
137
80
|
</div>
|
|
138
81
|
</div>
|
|
139
|
-
<button type="button" class="rk-btn rk-btn-ghost rk-btn-sm rk-chat-new" id="rk-chat-new"><span class="rk-chat-plus" aria-hidden="true">+</span> New</button>
|
|
140
82
|
</div>
|
|
141
|
-
</header>
|
|
142
|
-
|
|
143
|
-
<div class="rk-chat-thread" id="rk-chat-thread">
|
|
144
|
-
${thread}
|
|
145
|
-
</div>
|
|
146
83
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
84
|
+
<div class="rk-chat-dock">
|
|
85
|
+
<div class="rk-chat-starters" aria-label="Starter prompts">
|
|
86
|
+
${starterChips}
|
|
87
|
+
</div>
|
|
88
|
+
<div class="rk-chat-composer" id="rk-chat-composer">
|
|
89
|
+
<textarea id="rk-chat-input" class="rk-chat-input" rows="1"
|
|
90
|
+
placeholder="Describe your offer, audience, or the problem to solve…"></textarea>
|
|
91
|
+
<button type="button" class="rk-btn rk-btn-gold rk-chat-send" id="rk-chat-send">Send ${SEND}</button>
|
|
92
|
+
</div>
|
|
93
|
+
<p class="rk-chat-hint">Brainstormed on your own AI keys — never billed by vidfarm. <kbd>Enter</kbd> to send · <kbd>Shift</kbd>+<kbd>Enter</kbd> for a new line.</p>
|
|
156
94
|
</div>
|
|
157
|
-
<p class="rk-chat-hint">Brainstormed on your own AI keys — never billed by vidfarm. <kbd>Enter</kbd> to send · <kbd>Shift</kbd>+<kbd>Enter</kbd> for a new line.</p>
|
|
158
95
|
</div>
|
|
159
96
|
</div>
|
|
97
|
+
<script type="application/json" id="rk-chat-boot">${bootJson}</script>
|
|
160
98
|
</main>`;
|
|
161
99
|
const pageCss = `
|
|
162
|
-
/* ── centered
|
|
163
|
-
.rk-chat-page{width:100%;
|
|
100
|
+
/* ── two-pane: history sidebar + centered chat column ─────────────────────── */
|
|
101
|
+
.rk-chat-page{display:flex;width:100%;background:var(--rk-bg);min-height:100vh}
|
|
102
|
+
.rk-chat-main{flex:1;min-width:0;display:flex;justify-content:center;padding:0 var(--rk-gutter)}
|
|
164
103
|
.rk-chat-col{width:min(800px,100%);margin:0 auto;display:flex;flex-direction:column;
|
|
165
104
|
min-height:100vh;padding-top:34px}
|
|
166
105
|
|
|
106
|
+
/* history sidebar (real editor-chat threads) */
|
|
107
|
+
.rk-chat-history{flex:none;width:266px;background:var(--rk-surface);border-right:1px solid var(--rk-border);
|
|
108
|
+
display:flex;flex-direction:column;position:sticky;top:0;height:100vh}
|
|
109
|
+
.rk-chat-history-head{display:flex;align-items:center;justify-content:space-between;gap:10px;
|
|
110
|
+
padding:20px 16px 12px;flex:none}
|
|
111
|
+
.rk-chat-history-title{font-family:var(--rk-font-display);font-weight:800;font-size:16px;color:var(--rk-ink);letter-spacing:-.01em}
|
|
112
|
+
.rk-chat-newbtn{display:inline-flex;align-items:center;gap:5px;padding:7px 13px;border-radius:var(--rk-r-full);
|
|
113
|
+
border:1px solid var(--rk-gold-600);background:var(--rk-gold-tint);color:var(--rk-ink);cursor:pointer;
|
|
114
|
+
font-family:var(--rk-font-body);font-size:12.5px;font-weight:700;transition:background var(--rk-dur) var(--rk-ease)}
|
|
115
|
+
.rk-chat-newbtn:hover{background:var(--rk-gold-tint2)}
|
|
116
|
+
.rk-chat-history-list{flex:1;min-height:0;overflow-y:auto;padding:4px 10px 16px;display:grid;gap:2px;align-content:start}
|
|
117
|
+
.rk-chat-hrow{display:flex;align-items:center;gap:8px;width:100%;padding:9px 10px;border-radius:var(--rk-r-lg);
|
|
118
|
+
border:0;background:transparent;cursor:pointer;text-align:left;font-family:inherit;min-width:0;
|
|
119
|
+
transition:background var(--rk-dur) var(--rk-ease),box-shadow var(--rk-dur) var(--rk-ease)}
|
|
120
|
+
.rk-chat-hrow:hover{background:var(--rk-n-50);box-shadow:var(--rk-shadow-xs)}
|
|
121
|
+
.rk-chat-hrow.is-active{background:var(--rk-gold-tint)}
|
|
122
|
+
.rk-chat-hrow-main{flex:1;min-width:0;display:grid;gap:1px}
|
|
123
|
+
.rk-chat-hrow-title{font-size:13px;font-weight:600;color:var(--rk-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
124
|
+
.rk-chat-hrow-sub{font-size:11px;color:var(--rk-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
125
|
+
.rk-chat-hrow-del{flex:none;border:0;background:transparent;color:var(--rk-n-400);cursor:pointer;font-size:15px;
|
|
126
|
+
line-height:1;padding:2px 5px;border-radius:var(--rk-r-sm);opacity:0;
|
|
127
|
+
transition:opacity var(--rk-dur) var(--rk-ease),color var(--rk-dur) var(--rk-ease),background var(--rk-dur) var(--rk-ease)}
|
|
128
|
+
.rk-chat-hrow:hover .rk-chat-hrow-del{opacity:1}
|
|
129
|
+
.rk-chat-hrow-del:hover{color:var(--rk-red);background:var(--rk-red-tint)}
|
|
130
|
+
.rk-chat-history-note{padding:16px 14px;font-size:12.5px;color:var(--rk-text-muted);line-height:1.55;display:grid;gap:9px;justify-items:start}
|
|
131
|
+
.rk-chat-history-note a{color:var(--rk-gold-700);font-weight:600}
|
|
132
|
+
.rk-chat-history-note button{font-family:inherit;font-size:12px;font-weight:600;color:var(--rk-ink);cursor:pointer;
|
|
133
|
+
background:#fff;border:1px solid var(--rk-border);border-radius:var(--rk-r-full);padding:5px 12px}
|
|
134
|
+
.rk-chat-history-note button:hover{background:var(--rk-n-100)}
|
|
135
|
+
|
|
136
|
+
/* narrow-screen history toggle + slide-over (hidden on wide) */
|
|
137
|
+
.rk-chat-histtoggle{display:none;flex:none;width:34px;height:34px;border-radius:var(--rk-r-lg);
|
|
138
|
+
border:1px solid var(--rk-border);background:#fff;color:var(--rk-n-600);cursor:pointer;font-size:15px;
|
|
139
|
+
align-items:center;justify-content:center;margin-right:2px;margin-top:2px}
|
|
140
|
+
.rk-chat-histbackdrop{display:none}
|
|
141
|
+
|
|
167
142
|
/* compact header */
|
|
168
|
-
.rk-chat-head{display:flex;align-items:flex-start;
|
|
143
|
+
.rk-chat-head{display:flex;align-items:flex-start;gap:12px;
|
|
169
144
|
padding-bottom:22px;margin-bottom:6px;border-bottom:1px solid var(--rk-border)}
|
|
170
|
-
.rk-chat-head-titles{display:grid;gap:6px;min-width:0}
|
|
145
|
+
.rk-chat-head-titles{display:grid;gap:6px;min-width:0;flex:1}
|
|
171
146
|
.rk-chat-eyebrow{display:inline-flex;align-items:center;font-size:12px;font-weight:700;
|
|
172
147
|
letter-spacing:.06em;text-transform:uppercase;color:var(--rk-gold-700)}
|
|
173
148
|
.rk-chat-title{font-family:var(--rk-font-display);font-weight:800;font-size:1.7rem;letter-spacing:-.025em;
|
|
@@ -175,24 +150,16 @@ export function renderReskinChat() {
|
|
|
175
150
|
.rk-chat-subhead{font-size:14.5px;color:var(--rk-text-muted);line-height:1.5;max-width:56ch}
|
|
176
151
|
.rk-chat-head-actions{display:flex;align-items:center;gap:9px;flex:none}
|
|
177
152
|
.rk-chat-plus{font-size:14px;font-weight:700;line-height:1;margin-right:1px}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
.rk-chat-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
box-shadow
|
|
185
|
-
.rk-chat-
|
|
186
|
-
.rk-chat-
|
|
187
|
-
|
|
188
|
-
.rk-chat-hist-item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;
|
|
189
|
-
text-align:left;cursor:pointer;font-family:var(--rk-font-body);background:transparent;border:1px solid transparent;
|
|
190
|
-
border-radius:var(--rk-r-lg);padding:9px 11px;transition:background var(--rk-dur) var(--rk-ease)}
|
|
191
|
-
.rk-chat-hist-item:hover{background:var(--rk-n-100)}
|
|
192
|
-
.rk-chat-hist-item.is-active{background:var(--rk-gold-tint)}
|
|
193
|
-
.rk-chat-hist-title{font-size:13px;font-weight:600;color:var(--rk-ink);min-width:0;white-space:nowrap;
|
|
194
|
-
overflow:hidden;text-overflow:ellipsis}
|
|
195
|
-
.rk-chat-hist-when{flex:none;font-size:11px;font-weight:600;color:var(--rk-text-faint)}
|
|
153
|
+
|
|
154
|
+
/* studio mode select (Brainstorm ↔ Create Image → /reskin/inpaint) */
|
|
155
|
+
.rk-chat-mode{position:relative;display:inline-flex;align-items:center}
|
|
156
|
+
.rk-chat-mode-select{appearance:none;-webkit-appearance:none;font-family:var(--rk-font-body);font-size:13px;font-weight:700;
|
|
157
|
+
color:var(--rk-ink);background:var(--rk-surface);border:1px solid var(--rk-border-strong);border-radius:var(--rk-r-full);
|
|
158
|
+
padding:9px 32px 9px 15px;cursor:pointer;box-shadow:var(--rk-shadow-xs);
|
|
159
|
+
transition:border-color var(--rk-dur) var(--rk-ease),box-shadow var(--rk-dur) var(--rk-ease)}
|
|
160
|
+
.rk-chat-mode-select:hover{border-color:var(--rk-gold-600)}
|
|
161
|
+
.rk-chat-mode-select:focus{outline:none;border-color:var(--rk-gold-600);box-shadow:var(--rk-ring-gold)}
|
|
162
|
+
.rk-chat-mode-chev{position:absolute;right:13px;top:50%;transform:translateY(-50%);pointer-events:none;font-size:11px;color:var(--rk-n-500)}
|
|
196
163
|
|
|
197
164
|
/* thread — flex:1 pushes the dock to the bottom when the page is short */
|
|
198
165
|
.rk-chat-thread{flex:1;display:grid;gap:28px;align-content:start;padding:30px 0 26px}
|
|
@@ -211,38 +178,36 @@ export function renderReskinChat() {
|
|
|
211
178
|
border:1px solid var(--rk-n-800);box-shadow:var(--rk-shadow-sm)}
|
|
212
179
|
.rk-chat-bubble-assistant{width:100%;background:var(--rk-surface);color:var(--rk-n-700);
|
|
213
180
|
border:1px solid var(--rk-border);box-shadow:var(--rk-shadow-card)}
|
|
214
|
-
|
|
215
|
-
/* rich (but calm) assistant content */
|
|
216
|
-
.rk-chat-p{margin:0}
|
|
217
|
-
.rk-chat-p + *{margin-top:14px}
|
|
218
181
|
.rk-chat-bubble-assistant strong{color:var(--rk-ink);font-weight:700}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
.rk-chat-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
.rk-chat-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
.rk-chat-
|
|
231
|
-
.rk-chat-
|
|
232
|
-
.rk-chat-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
.rk-chat-
|
|
236
|
-
|
|
237
|
-
/*
|
|
238
|
-
.rk-chat-
|
|
239
|
-
border
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
.rk-chat-
|
|
244
|
-
|
|
245
|
-
|
|
182
|
+
|
|
183
|
+
/* streamed assistant text — pre-wrap keeps the model's own line breaks */
|
|
184
|
+
.rk-chat-stream{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--rk-n-700);line-height:1.65}
|
|
185
|
+
.rk-chat-stream[hidden]{display:none}
|
|
186
|
+
|
|
187
|
+
/* thinking / status row */
|
|
188
|
+
.rk-chat-status{display:flex;align-items:center;gap:10px;color:var(--rk-text-faint);font-size:13.5px}
|
|
189
|
+
.rk-chat-status[hidden]{display:none}
|
|
190
|
+
.rk-chat-status-label{font-size:13px;color:var(--rk-text-muted)}
|
|
191
|
+
|
|
192
|
+
/* simplified tool-call notes (rich artifact/HTTP cards are intentionally omitted) */
|
|
193
|
+
.rk-chat-notes{display:grid;gap:6px;margin-bottom:10px}
|
|
194
|
+
.rk-chat-notes:empty{display:none}
|
|
195
|
+
.rk-chat-toolnote{display:inline-flex;align-items:center;gap:7px;font-size:12.5px;color:var(--rk-text-muted);
|
|
196
|
+
font-family:var(--rk-font-mono);background:var(--rk-n-50);border:1px solid var(--rk-border);
|
|
197
|
+
border-radius:var(--rk-r-lg);padding:5px 10px;width:fit-content}
|
|
198
|
+
.rk-chat-toolnote::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--rk-gold-500);flex:none}
|
|
199
|
+
|
|
200
|
+
/* error surface */
|
|
201
|
+
.rk-chat-error{margin-top:2px;font-size:13.5px;color:#b42318;background:var(--rk-n-50);
|
|
202
|
+
border:1px solid var(--rk-border);border-radius:var(--rk-r-lg);padding:10px 12px;white-space:pre-wrap}
|
|
203
|
+
|
|
204
|
+
/* typing indicator */
|
|
205
|
+
.rk-chat-typing{display:inline-flex;gap:4px;align-items:center;padding:2px 0}
|
|
206
|
+
.rk-chat-typing span{width:7px;height:7px;border-radius:50%;background:var(--rk-n-400);
|
|
207
|
+
animation:rk-chat-blink 1.2s var(--rk-ease) infinite}
|
|
208
|
+
.rk-chat-typing span:nth-child(2){animation-delay:.18s}
|
|
209
|
+
.rk-chat-typing span:nth-child(3){animation-delay:.36s}
|
|
210
|
+
@keyframes rk-chat-blink{0%,80%,100%{opacity:.28;transform:translateY(0)}40%{opacity:1;transform:translateY(-2px)}}
|
|
246
211
|
|
|
247
212
|
/* ── quiet sticky composer dock ── */
|
|
248
213
|
.rk-chat-dock{position:sticky;bottom:0;z-index:10;background:var(--rk-bg);
|
|
@@ -252,70 +217,77 @@ export function renderReskinChat() {
|
|
|
252
217
|
.rk-chat-starters{display:flex;flex-wrap:wrap;gap:9px}
|
|
253
218
|
.rk-chat-chip{display:inline-flex;align-items:center;gap:7px;padding:8px 14px;border-radius:var(--rk-r-full);
|
|
254
219
|
font-family:var(--rk-font-body);font-size:13px;font-weight:600;color:var(--rk-n-700);cursor:pointer;
|
|
255
|
-
background:var(--rk-surface);border:1px solid var(--rk-border);box-shadow:var(--rk-shadow-xs);
|
|
220
|
+
background:var(--rk-surface);border:1px solid var(--rk-border);box-shadow:var(--rk-shadow-xs);text-align:left;
|
|
256
221
|
transition:transform var(--rk-dur) var(--rk-ease),box-shadow var(--rk-dur) var(--rk-ease),border-color var(--rk-dur) var(--rk-ease),background var(--rk-dur) var(--rk-ease)}
|
|
257
222
|
.rk-chat-chip:hover{border-color:var(--rk-gold-600);background:var(--rk-gold-tint);color:var(--rk-ink);
|
|
258
223
|
transform:translateY(-1px);box-shadow:var(--rk-shadow-sm)}
|
|
224
|
+
.rk-chat-chip:disabled{opacity:.5;cursor:default;transform:none;box-shadow:var(--rk-shadow-xs)}
|
|
259
225
|
.rk-chat-composer{display:flex;align-items:flex-end;gap:9px;background:var(--rk-surface);
|
|
260
|
-
border:1px solid var(--rk-border-strong);border-radius:var(--rk-r-3xl);padding:9px 9px 9px
|
|
226
|
+
border:1px solid var(--rk-border-strong);border-radius:var(--rk-r-3xl);padding:9px 9px 9px 16px;
|
|
261
227
|
box-shadow:var(--rk-shadow-card);transition:border-color var(--rk-dur) var(--rk-ease),box-shadow var(--rk-dur) var(--rk-ease)}
|
|
262
228
|
.rk-chat-composer:focus-within{border-color:var(--rk-gold-600);box-shadow:var(--rk-ring-gold)}
|
|
263
|
-
.rk-chat-attach{flex:none;width:40px;height:40px;border-radius:var(--rk-r-full);border:0;cursor:pointer;
|
|
264
|
-
display:grid;place-items:center;background:var(--rk-n-100);color:var(--rk-n-600);
|
|
265
|
-
transition:background var(--rk-dur) var(--rk-ease),color var(--rk-dur) var(--rk-ease)}
|
|
266
|
-
.rk-chat-attach:hover{background:var(--rk-n-200);color:var(--rk-ink)}
|
|
267
|
-
.rk-chat-attach svg{width:19px;height:19px}
|
|
268
229
|
.rk-chat-input{flex:1;min-width:0;border:0;background:transparent;resize:none;font-family:var(--rk-font-body);
|
|
269
230
|
font-size:15px;line-height:1.5;color:var(--rk-ink);padding:10px 2px;max-height:148px}
|
|
270
231
|
.rk-chat-input:focus{outline:none;box-shadow:none}
|
|
271
232
|
.rk-chat-input::placeholder{color:var(--rk-n-400)}
|
|
272
233
|
.rk-chat-send{flex:none;padding:11px 18px}
|
|
273
234
|
.rk-chat-send svg{width:16px;height:16px}
|
|
235
|
+
.rk-chat-send:disabled{opacity:.55;cursor:default}
|
|
274
236
|
.rk-chat-hint{text-align:center;font-size:11.5px;color:var(--rk-text-faint);line-height:1.5}
|
|
275
237
|
.rk-chat-hint kbd{font-family:var(--rk-font-mono);font-size:10.5px;background:var(--rk-n-100);
|
|
276
238
|
border:1px solid var(--rk-border);border-radius:5px;padding:1px 5px;color:var(--rk-n-600)}
|
|
277
239
|
|
|
278
|
-
/*
|
|
279
|
-
|
|
280
|
-
.rk-chat-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
.rk-chat-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
240
|
+
/* ── narrow: history becomes a slide-over, bubbles go full-width ── */
|
|
241
|
+
@media(max-width:900px){
|
|
242
|
+
.rk-chat-histtoggle{display:inline-flex}
|
|
243
|
+
.rk-chat-history{position:fixed;left:0;top:0;bottom:0;height:auto;z-index:70;width:min(300px,86vw);
|
|
244
|
+
transform:translateX(-100%);box-shadow:var(--rk-shadow-xl);transition:transform var(--rk-dur) var(--rk-ease)}
|
|
245
|
+
.rk-chat-history.is-open{transform:none}
|
|
246
|
+
.rk-chat-histbackdrop{display:block;position:fixed;inset:0;z-index:65;background:rgba(0,0,0,.28)}
|
|
247
|
+
.rk-chat-histbackdrop[hidden]{display:none}
|
|
248
|
+
}
|
|
287
249
|
@media(max-width:720px){
|
|
288
250
|
.rk-chat-col{padding-top:24px}
|
|
289
|
-
.rk-chat-head{flex-direction:column;gap:14px}
|
|
290
|
-
.rk-chat-head-actions{align-self:flex-start}
|
|
291
251
|
.rk-chat-title{font-size:1.5rem}
|
|
292
252
|
.rk-chat-bubble-user{max-width:100%}
|
|
293
|
-
}
|
|
294
|
-
@media(max-width:560px){
|
|
295
|
-
.rk-chat-art{flex-direction:column}
|
|
296
|
-
.rk-chat-poster{width:132px}
|
|
297
253
|
.rk-chat-thread{gap:22px}
|
|
298
254
|
}
|
|
299
255
|
`;
|
|
300
256
|
const script = `
|
|
301
257
|
(function(){
|
|
302
258
|
var root=document;
|
|
259
|
+
|
|
260
|
+
// ── read the real editor-chat boot (endpoint + auth + context) ──────────
|
|
261
|
+
var boot=null;
|
|
262
|
+
try{ var bootEl=root.getElementById('rk-chat-boot'); if(bootEl){ boot=JSON.parse(bootEl.textContent||'null'); } }catch(e){ boot=null; }
|
|
263
|
+
var ENDPOINT=(boot && boot.apiUrl) || '/api/v1/editor-chat';
|
|
264
|
+
var API_KEY=(boot && boot.vidfarmApiKey) || null;
|
|
265
|
+
var TEMPLATE=(boot && boot.template) || null;
|
|
266
|
+
var CACHED=(boot && boot.cachedContext) || null;
|
|
267
|
+
|
|
303
268
|
var input=root.getElementById('rk-chat-input');
|
|
304
269
|
var send=root.getElementById('rk-chat-send');
|
|
305
270
|
var thread=root.getElementById('rk-chat-thread');
|
|
306
271
|
var title=root.getElementById('rk-chat-title');
|
|
307
272
|
var AVATAR='V';
|
|
308
|
-
var REPLIES=[
|
|
309
|
-
"On it. Give me a beat — I'll shape that into hooks, a tight script, and a template you can render.",
|
|
310
|
-
"Good direction. I'll pull a trending format that fits and draft the on-screen captions to match.",
|
|
311
|
-
"Love it. Let me turn that into three testable angles and flag which one is cheapest to shoot first."
|
|
312
|
-
];
|
|
313
|
-
var replyTurn=0;
|
|
314
273
|
|
|
274
|
+
var history=[]; // [{role, text}] — the running conversation
|
|
275
|
+
var threadId=null; // generated on first send, reused for persistence
|
|
276
|
+
var busy=false;
|
|
277
|
+
|
|
278
|
+
function genId(p){ return p+'-'+Date.now().toString(36)+'-'+Math.random().toString(36).slice(2,8); }
|
|
315
279
|
function autosize(){ if(!input)return; input.style.height='auto'; input.style.height=Math.min(input.scrollHeight,148)+'px'; }
|
|
316
280
|
function toBottom(){ try{ window.scrollTo({top:document.body.scrollHeight,behavior:'smooth'}); }catch(e){ window.scrollTo(0,document.body.scrollHeight); } }
|
|
317
281
|
function nowLabel(){ try{ return new Date().toLocaleTimeString([], {hour:'numeric', minute:'2-digit'}); }catch(e){ return ''; } }
|
|
318
282
|
function el(cls){ var d=root.createElement('div'); d.className=cls; return d; }
|
|
283
|
+
function setBusy(v){ busy=v; if(send){ send.disabled=v; } root.querySelectorAll('[data-rk-prompt]').forEach(function(c){ c.disabled=v; }); }
|
|
284
|
+
|
|
285
|
+
function clientContext(){
|
|
286
|
+
var now=new Date(); var local=null, tz=null;
|
|
287
|
+
try{ local=new Intl.DateTimeFormat(undefined,{dateStyle:'full',timeStyle:'long'}).format(now); }catch(e){ local=now.toString(); }
|
|
288
|
+
try{ tz=(Intl.DateTimeFormat().resolvedOptions().timeZone)||null; }catch(e){ tz=null; }
|
|
289
|
+
return { currentDateTime: now.toISOString(), localDateTime: local, timeZone: tz };
|
|
290
|
+
}
|
|
319
291
|
|
|
320
292
|
function appendUser(text){
|
|
321
293
|
var msg=el('rk-chat-msg is-user');
|
|
@@ -324,87 +296,308 @@ export function renderReskinChat() {
|
|
|
324
296
|
msg.appendChild(meta); msg.appendChild(bub);
|
|
325
297
|
if(thread) thread.appendChild(msg);
|
|
326
298
|
}
|
|
299
|
+
|
|
300
|
+
// Returns a small controller for the streaming assistant bubble.
|
|
327
301
|
function appendAssistant(){
|
|
328
302
|
var msg=el('rk-chat-msg is-assistant');
|
|
329
303
|
var meta=el('rk-chat-meta');
|
|
330
304
|
meta.innerHTML='<span class="rk-chat-avatar" aria-hidden="true">'+AVATAR+'</span>Copilot <span class="rk-chat-time">'+nowLabel()+'</span>';
|
|
331
305
|
var bub=el('rk-chat-bubble rk-chat-bubble-assistant');
|
|
332
|
-
|
|
306
|
+
var status=el('rk-chat-status');
|
|
307
|
+
status.innerHTML='<span class="rk-chat-typing"><span></span><span></span><span></span></span><span class="rk-chat-status-label">Thinking…</span>';
|
|
308
|
+
var notes=el('rk-chat-notes');
|
|
309
|
+
var textEl=el('rk-chat-stream'); textEl.hidden=true;
|
|
310
|
+
bub.appendChild(status); bub.appendChild(notes); bub.appendChild(textEl);
|
|
333
311
|
msg.appendChild(meta); msg.appendChild(bub);
|
|
334
312
|
if(thread) thread.appendChild(msg);
|
|
335
|
-
return
|
|
313
|
+
return {
|
|
314
|
+
setStatus:function(s){ var l=status.querySelector('.rk-chat-status-label'); if(l && s){ l.textContent=s; } },
|
|
315
|
+
hideStatus:function(){ status.hidden=true; },
|
|
316
|
+
showText:function(){ status.hidden=true; textEl.hidden=false; },
|
|
317
|
+
setText:function(t){ textEl.textContent=t; },
|
|
318
|
+
addNote:function(t){ var n=el('rk-chat-toolnote'); n.textContent=t; notes.appendChild(n); },
|
|
319
|
+
fail:function(m){ status.hidden=true; var er=el('rk-chat-error'); er.textContent=m||'Something went wrong.'; bub.appendChild(er); }
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Minimal SSE reader — mirrors the live client's text/event-stream parsing.
|
|
324
|
+
function readStream(response, h){
|
|
325
|
+
if(!response.body || !response.body.getReader){
|
|
326
|
+
// No streaming reader available — fall back to reading the whole body.
|
|
327
|
+
return response.text().then(function(t){ parseEvents(t, h); });
|
|
328
|
+
}
|
|
329
|
+
var reader=response.body.getReader();
|
|
330
|
+
var decoder=new TextDecoder();
|
|
331
|
+
var buffer='';
|
|
332
|
+
function pump(){
|
|
333
|
+
return reader.read().then(function(res){
|
|
334
|
+
if(res.done){ if(buffer){ parseEvents(buffer, h); buffer=''; } return; }
|
|
335
|
+
buffer+=decoder.decode(res.value,{stream:true});
|
|
336
|
+
var events=buffer.split('\\n\\n');
|
|
337
|
+
buffer=events.pop()||'';
|
|
338
|
+
for(var i=0;i<events.length;i++){ handleEvent(events[i], h); }
|
|
339
|
+
return pump();
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return pump();
|
|
343
|
+
}
|
|
344
|
+
function parseEvents(blob, h){ blob.split('\\n\\n').forEach(function(ev){ handleEvent(ev, h); }); }
|
|
345
|
+
function handleEvent(raw, h){
|
|
346
|
+
var lines=raw.split('\\n');
|
|
347
|
+
var dataLine=null;
|
|
348
|
+
for(var j=0;j<lines.length;j++){ var t=lines[j].trim(); if(t.indexOf('data:')===0){ dataLine=t; break; } }
|
|
349
|
+
if(!dataLine) return;
|
|
350
|
+
var payload=dataLine.slice(5).trim();
|
|
351
|
+
if(payload==='[DONE]' || !payload) return;
|
|
352
|
+
var chunk; try{ chunk=JSON.parse(payload); }catch(e){ return; }
|
|
353
|
+
if(!chunk || typeof chunk!=='object') return;
|
|
354
|
+
if(chunk.type==='text-delta' && typeof chunk.delta==='string'){ h.onDelta(chunk.delta); }
|
|
355
|
+
else if(chunk.type==='status'){ var s=(typeof chunk.status==='string')?chunk.status:((typeof chunk.message==='string')?chunk.message:null); if(s){ h.onStatus(s); } }
|
|
356
|
+
else if(chunk.type==='tool-call' && typeof chunk.toolName==='string'){ h.onToolCall(chunk.toolName); }
|
|
357
|
+
else if(chunk.type==='error'){ h.onError((typeof chunk.errorText==='string')?chunk.errorText:((chunk.error && chunk.error.message)||'Unable to read assistant response.')); }
|
|
336
358
|
}
|
|
337
359
|
|
|
338
|
-
function sendMessage(){
|
|
339
|
-
if(
|
|
340
|
-
|
|
360
|
+
function sendMessage(text){
|
|
361
|
+
if(busy) return;
|
|
362
|
+
text=(text||'').trim();
|
|
341
363
|
if(!text) return;
|
|
364
|
+
|
|
365
|
+
history.push({role:'user', text:text});
|
|
342
366
|
appendUser(text);
|
|
343
|
-
input.value=''; autosize();
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
setTimeout(function(){
|
|
347
|
-
if(bub){ bub.innerHTML='<p class="rk-chat-p"></p>'; bub.firstChild.textContent=line; toBottom(); }
|
|
348
|
-
}, 850);
|
|
349
|
-
}
|
|
367
|
+
if(input){ input.value=''; autosize(); }
|
|
368
|
+
if(title && history.length===1){ title.textContent=text.slice(0,60); }
|
|
369
|
+
toBottom();
|
|
350
370
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
371
|
+
var view=appendAssistant();
|
|
372
|
+
toBottom();
|
|
373
|
+
setBusy(true);
|
|
374
|
+
|
|
375
|
+
if(!threadId){ threadId=genId('thread'); }
|
|
376
|
+
var userMsgId=genId('user');
|
|
377
|
+
var asstMsgId=genId('assistant');
|
|
378
|
+
|
|
379
|
+
var body={
|
|
380
|
+
messages: history.map(function(m){ return { role:m.role, content:[{ type:'text', text:m.text }] }; }),
|
|
381
|
+
thread_id: threadId,
|
|
382
|
+
thread_template_id: (TEMPLATE && TEMPLATE.templateId) || 'chat-brainstorm',
|
|
383
|
+
thread_title: text.slice(0,42),
|
|
384
|
+
thread_tracers: (TEMPLATE && TEMPLATE.tracers) || [],
|
|
385
|
+
client_context: clientContext(),
|
|
386
|
+
user_message: { id:userMsgId, role:'user', text:text, attachments:[] },
|
|
387
|
+
assistant_message: { id:asstMsgId }
|
|
388
|
+
};
|
|
389
|
+
if(TEMPLATE){ body.template=TEMPLATE; }
|
|
390
|
+
if(CACHED){ body.cached_context=CACHED; }
|
|
391
|
+
|
|
392
|
+
var headers={ 'content-type':'application/json' };
|
|
393
|
+
if(API_KEY){ headers['vidfarm-api-key']=API_KEY; }
|
|
394
|
+
|
|
395
|
+
var acc='';
|
|
396
|
+
var sawText=false;
|
|
397
|
+
var errored=false;
|
|
398
|
+
|
|
399
|
+
var handlers={
|
|
400
|
+
onDelta:function(d){ if(!sawText){ sawText=true; view.showText(); } acc+=d; view.setText(acc); toBottom(); },
|
|
401
|
+
onStatus:function(s){ if(!sawText){ view.setStatus(s); } },
|
|
402
|
+
onToolCall:function(name){ view.addNote('Running '+name+'…'); toBottom(); },
|
|
403
|
+
onError:function(m){ errored=true; view.fail(m); toBottom(); }
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
var req;
|
|
407
|
+
try{
|
|
408
|
+
req=fetch(ENDPOINT,{ method:'POST', headers:headers, credentials:'same-origin', body:JSON.stringify(body) });
|
|
409
|
+
}catch(err){
|
|
410
|
+
// Synchronous fetch failure (e.g. invalid URL) — never let it throw uncaught.
|
|
411
|
+
req=Promise.reject(err);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
req.then(function(resp){
|
|
415
|
+
if(!resp.ok){
|
|
416
|
+
return resp.json().catch(function(){ return {}; }).then(function(j){
|
|
417
|
+
throw new Error((j && j.error) || ('Chat request failed ('+resp.status+').'));
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
return readStream(resp, handlers);
|
|
421
|
+
}).then(function(){
|
|
422
|
+
view.hideStatus();
|
|
423
|
+
if(sawText){
|
|
424
|
+
history.push({role:'assistant', text:acc});
|
|
425
|
+
} else if(!errored){
|
|
426
|
+
view.showText();
|
|
427
|
+
view.setText('The assistant finished without returning any text.');
|
|
428
|
+
}
|
|
429
|
+
setBusy(false);
|
|
430
|
+
if(input){ input.focus(); }
|
|
431
|
+
toBottom();
|
|
432
|
+
loadHistory();
|
|
433
|
+
}).catch(function(err){
|
|
434
|
+
var msg=(err && err.message) ? err.message : String(err);
|
|
435
|
+
if(!API_KEY){ msg=msg+'\\n\\nAdd an AI provider key in Settings to brainstorm on your own keys.'; }
|
|
436
|
+
view.fail(msg);
|
|
437
|
+
setBusy(false);
|
|
438
|
+
if(input){ input.focus(); }
|
|
439
|
+
toBottom();
|
|
356
440
|
});
|
|
357
441
|
}
|
|
358
442
|
|
|
359
|
-
//
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
});
|
|
443
|
+
// ── real chat history (editor-chat threads) ────────────────────────────
|
|
444
|
+
var THREADS_URL=(boot && boot.threadsUrl) || '/api/v1/editor-chat/threads';
|
|
445
|
+
var TEMPLATE_ID=(TEMPLATE && TEMPLATE.templateId) || 'chat-brainstorm';
|
|
446
|
+
var histList=root.getElementById('rk-chat-history-list');
|
|
447
|
+
var histAside=root.getElementById('rk-chat-history');
|
|
448
|
+
var histBackdrop=root.getElementById('rk-chat-histbackdrop');
|
|
449
|
+
var histToggle=root.getElementById('rk-chat-histtoggle');
|
|
450
|
+
var loadingThread=false;
|
|
368
451
|
|
|
369
|
-
|
|
370
|
-
|
|
452
|
+
function authHeaders(extra){ var h=extra||{}; if(API_KEY){ h['vidfarm-api-key']=API_KEY; } return h; }
|
|
453
|
+
function relTime(iso){
|
|
454
|
+
if(!iso) return '';
|
|
455
|
+
var t=Date.parse(iso); if(isNaN(t)) return '';
|
|
456
|
+
var s=Math.max(0,(Date.now()-t)/1000);
|
|
457
|
+
if(s<60) return 'just now';
|
|
458
|
+
if(s<3600) return Math.floor(s/60)+'m ago';
|
|
459
|
+
if(s<86400) return Math.floor(s/3600)+'h ago';
|
|
460
|
+
if(s<604800) return Math.floor(s/86400)+'d ago';
|
|
461
|
+
try{ return new Date(t).toLocaleDateString([], {month:'short', day:'numeric'}); }catch(e){ return ''; }
|
|
462
|
+
}
|
|
463
|
+
function histNote(text, opts){
|
|
464
|
+
var wrap=el('rk-chat-history-note');
|
|
465
|
+
var p=root.createElement('div'); p.textContent=text; wrap.appendChild(p);
|
|
466
|
+
if(opts && opts.href){ var a=root.createElement('a'); a.href=opts.href; a.textContent=opts.linkText||'Open'; wrap.appendChild(a); }
|
|
467
|
+
if(opts && opts.retry){ var b=root.createElement('button'); b.type='button'; b.textContent='Try again'; b.addEventListener('click',function(){ loadHistory(); }); wrap.appendChild(b); }
|
|
468
|
+
return wrap;
|
|
469
|
+
}
|
|
470
|
+
function setActiveThread(id){
|
|
471
|
+
threadId=id;
|
|
472
|
+
if(!histList) return;
|
|
473
|
+
var rows=histList.querySelectorAll('.rk-chat-hrow');
|
|
474
|
+
for(var i=0;i<rows.length;i++){ rows[i].classList.toggle('is-active', rows[i].getAttribute('data-id')===id); }
|
|
475
|
+
}
|
|
476
|
+
function renderHistory(threads){
|
|
477
|
+
if(!histList) return;
|
|
478
|
+
histList.innerHTML='';
|
|
479
|
+
if(!threads.length){ histList.appendChild(histNote('No conversations yet. Start one below \\u2014 it saves automatically.')); return; }
|
|
480
|
+
threads.forEach(function(t){
|
|
481
|
+
var row=root.createElement('button');
|
|
482
|
+
row.type='button'; row.className='rk-chat-hrow'; row.setAttribute('data-id', t.id);
|
|
483
|
+
if(t.id===threadId){ row.classList.add('is-active'); }
|
|
484
|
+
var main=el('rk-chat-hrow-main');
|
|
485
|
+
var ti=el('rk-chat-hrow-title'); ti.textContent=t.title || 'Untitled chat';
|
|
486
|
+
var sub=el('rk-chat-hrow-sub');
|
|
487
|
+
var count=(typeof t.messageCount==='number')?t.messageCount:0;
|
|
488
|
+
var when=relTime(t.lastMessageAt || t.updatedAt);
|
|
489
|
+
sub.textContent=(count?count+' message'+(count===1?'':'s'):'')+((count&&when)?' \\u00b7 ':'')+(when||'');
|
|
490
|
+
main.appendChild(ti); main.appendChild(sub);
|
|
491
|
+
var del=root.createElement('button');
|
|
492
|
+
del.type='button'; del.className='rk-chat-hrow-del'; del.setAttribute('aria-label','Delete chat'); del.textContent='\\u00d7';
|
|
493
|
+
del.addEventListener('click', function(ev){ ev.stopPropagation(); deleteThread(t.id, row); });
|
|
494
|
+
row.appendChild(main); row.appendChild(del);
|
|
495
|
+
row.addEventListener('click', function(){ openThread(t.id); });
|
|
496
|
+
histList.appendChild(row);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
function loadHistory(){
|
|
500
|
+
if(!histList) return;
|
|
501
|
+
if(!API_KEY){ histList.innerHTML=''; histList.appendChild(histNote('Add an AI provider key in Settings to sync your saved chats.', {href:'/settings', linkText:'Open Settings'})); return; }
|
|
502
|
+
var url=THREADS_URL+'?template_id='+encodeURIComponent(TEMPLATE_ID)+'&limit=30&include_messages=false';
|
|
503
|
+
fetch(url, { headers:authHeaders(), credentials:'same-origin' })
|
|
504
|
+
.then(function(r){
|
|
505
|
+
if(r.status===401||r.status===403){ return {threads:[], _auth:true}; }
|
|
506
|
+
if(!r.ok) throw new Error('http '+r.status);
|
|
507
|
+
return r.json();
|
|
508
|
+
})
|
|
509
|
+
.then(function(j){
|
|
510
|
+
if(j && j._auth){ histList.innerHTML=''; histList.appendChild(histNote('Sign in to sync your saved chats.', {href:'/login', linkText:'Sign in'})); return; }
|
|
511
|
+
renderHistory((j && j.threads) || []);
|
|
512
|
+
})
|
|
513
|
+
.catch(function(){ if(histList){ histList.innerHTML=''; histList.appendChild(histNote('Couldn\\u2019t load your chats.', {retry:true})); } });
|
|
514
|
+
}
|
|
515
|
+
function appendAssistantStatic(text){ var v=appendAssistant(); v.showText(); v.setText(text||''); v.hideStatus(); return v; }
|
|
516
|
+
function openThread(id){
|
|
517
|
+
if(busy || loadingThread) return;
|
|
518
|
+
loadingThread=true;
|
|
519
|
+
closeHistoryOverlay();
|
|
520
|
+
fetch(THREADS_URL+'/'+encodeURIComponent(id), { headers:authHeaders(), credentials:'same-origin' })
|
|
521
|
+
.then(function(r){ if(!r.ok) throw new Error('http '+r.status); return r.json(); })
|
|
522
|
+
.then(function(j){
|
|
523
|
+
var t=j && j.thread; if(!t){ throw new Error('missing thread'); }
|
|
524
|
+
history=[];
|
|
525
|
+
if(thread){ thread.innerHTML=''; }
|
|
526
|
+
var msgs=t.messages||[];
|
|
527
|
+
for(var i=0;i<msgs.length;i++){
|
|
528
|
+
var m=msgs[i]; var role=(m.role==='assistant')?'assistant':'user'; var txt=m.text||'';
|
|
529
|
+
history.push({role:role, text:txt});
|
|
530
|
+
if(role==='user'){ appendUser(txt); } else { appendAssistantStatic(txt); }
|
|
531
|
+
}
|
|
532
|
+
if(title){ title.textContent=t.title || 'Brainstorm studio'; }
|
|
533
|
+
setActiveThread(id);
|
|
534
|
+
loadingThread=false;
|
|
535
|
+
toBottom();
|
|
536
|
+
})
|
|
537
|
+
.catch(function(){ loadingThread=false; });
|
|
538
|
+
}
|
|
539
|
+
function deleteThread(id, row){
|
|
540
|
+
if(!API_KEY) return;
|
|
541
|
+
fetch(THREADS_URL+'/'+encodeURIComponent(id), { method:'DELETE', headers:authHeaders(), credentials:'same-origin' })
|
|
542
|
+
.then(function(r){
|
|
543
|
+
if(!r.ok && r.status!==404) throw new Error('http '+r.status);
|
|
544
|
+
if(row && row.parentNode){ row.parentNode.removeChild(row); }
|
|
545
|
+
if(id===threadId){ resetChat(); }
|
|
546
|
+
if(histList && !histList.querySelector('.rk-chat-hrow')){ loadHistory(); }
|
|
547
|
+
})
|
|
548
|
+
.catch(function(){});
|
|
549
|
+
}
|
|
550
|
+
function resetChat(){
|
|
551
|
+
history=[]; threadId=null;
|
|
371
552
|
if(thread){
|
|
372
553
|
thread.innerHTML='<div class="rk-chat-msg is-assistant">'+
|
|
373
|
-
'<div class="rk-chat-meta"><span class="rk-chat-avatar" aria-hidden="true">'+AVATAR+'</span>Copilot
|
|
374
|
-
'<div class="rk-chat-bubble rk-chat-bubble-assistant"><
|
|
554
|
+
'<div class="rk-chat-meta"><span class="rk-chat-avatar" aria-hidden="true">'+AVATAR+'</span>Copilot</div>'+
|
|
555
|
+
'<div class="rk-chat-bubble rk-chat-bubble-assistant"><div class="rk-chat-stream">Fresh page. Tell me the offer, who it\\u2019s for, and where the traffic is cold \\u2014 I\\u2019ll come back with angles, hooks, and scripts you can render.</div></div>'+
|
|
375
556
|
'</div>';
|
|
376
557
|
}
|
|
377
|
-
if(title) title.textContent='
|
|
378
|
-
|
|
558
|
+
if(title){ title.textContent='Brainstorm studio'; }
|
|
559
|
+
setActiveThread(null);
|
|
560
|
+
if(input){ input.value=''; autosize(); input.focus(); }
|
|
379
561
|
window.scrollTo(0,0);
|
|
380
|
-
}
|
|
562
|
+
}
|
|
563
|
+
function openHistoryOverlay(){ if(histAside){ histAside.classList.add('is-open'); } if(histBackdrop){ histBackdrop.hidden=false; } }
|
|
564
|
+
function closeHistoryOverlay(){ if(histAside){ histAside.classList.remove('is-open'); } if(histBackdrop){ histBackdrop.hidden=true; } }
|
|
565
|
+
if(histToggle){ histToggle.addEventListener('click', openHistoryOverlay); }
|
|
566
|
+
if(histBackdrop){ histBackdrop.addEventListener('click', closeHistoryOverlay); }
|
|
567
|
+
loadHistory();
|
|
381
568
|
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
ev.stopPropagation();
|
|
389
|
-
var open=histMenu.hidden;
|
|
390
|
-
histMenu.hidden=!open; histBtn.setAttribute('aria-expanded', open?'true':'false');
|
|
391
|
-
});
|
|
392
|
-
histMenu.querySelectorAll('[data-rk-title]').forEach(function(it){
|
|
393
|
-
it.addEventListener('click',function(){
|
|
394
|
-
histMenu.querySelectorAll('[data-rk-title]').forEach(function(x){ x.classList.toggle('is-active',x===it); });
|
|
395
|
-
if(title){ var t=it.getAttribute('data-rk-title'); if(t) title.textContent=t; }
|
|
396
|
-
closeHist();
|
|
397
|
-
});
|
|
569
|
+
// composer wiring
|
|
570
|
+
if(input){ input.addEventListener('input',autosize); autosize(); }
|
|
571
|
+
if(send){ send.addEventListener('click',function(){ if(input){ sendMessage(input.value); } }); }
|
|
572
|
+
if(input){
|
|
573
|
+
input.addEventListener('keydown',function(ev){
|
|
574
|
+
if(ev.key==='Enter' && !ev.shiftKey){ ev.preventDefault(); sendMessage(input.value); }
|
|
398
575
|
});
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// starter chips send their full prompt directly
|
|
579
|
+
root.querySelectorAll('[data-rk-prompt]').forEach(function(chip){
|
|
580
|
+
chip.addEventListener('click',function(){ if(busy) return; sendMessage(chip.getAttribute('data-rk-prompt')||''); });
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// "New" resets the conversation to the warm empty state (shared resetChat)
|
|
584
|
+
var newBtn=root.getElementById('rk-chat-new');
|
|
585
|
+
if(newBtn){
|
|
586
|
+
newBtn.addEventListener('click',function(){ if(busy) return; resetChat(); });
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// studio mode: choosing "Create Image" navigates to the inpaint workbench
|
|
590
|
+
var modeSel=root.getElementById('rk-chat-mode');
|
|
591
|
+
if(modeSel){
|
|
592
|
+
modeSel.addEventListener('change',function(){
|
|
593
|
+
var dest=modeSel.value;
|
|
594
|
+
if(dest && dest!=='/chat'){ window.location.href=dest; }
|
|
402
595
|
});
|
|
403
596
|
}
|
|
404
597
|
})();`;
|
|
405
598
|
return reskinDocument({
|
|
406
599
|
title: "vidfarm reskin — Brainstorm",
|
|
407
|
-
description: "Reskinned vidfarm AI brainstorm chat — a clean
|
|
600
|
+
description: "Reskinned vidfarm AI brainstorm chat, wired to the live editor-chat backend — a clean single-column conversation with starter chips and a working composer.",
|
|
408
601
|
activeSlug: "chat",
|
|
409
602
|
pageCss,
|
|
410
603
|
body,
|