@mevdragon/vidfarm-devcli 0.18.0 → 0.18.1
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 +41 -4
- package/SKILL.director.md +17 -3
- package/SKILL.platform.md +3 -3
- package/demo/dist/app.js +69 -69
- package/demo/dist/favicon.ico +0 -0
- package/dist/src/app.js +1550 -187
- package/dist/src/cli.js +227 -7
- package/dist/src/devcli/clip-store.js +29 -2
- package/dist/src/devcli/clips.js +52 -9
- package/dist/src/devcli/composition-edit.js +262 -0
- package/dist/src/devcli/timeline-edit.js +283 -0
- package/dist/src/editor-chat.js +29 -6
- package/dist/src/frontend/discover-client.js +130 -0
- package/dist/src/frontend/discover-store.js +23 -0
- package/dist/src/frontend/file-directory.js +744 -0
- package/dist/src/frontend/template-editor-chat.js +22 -19
- package/dist/src/landing-page.js +24 -7
- package/dist/src/page-shell.js +25 -1
- package/dist/src/reskin/agency-page.js +1 -1
- package/dist/src/reskin/calendar-page.js +2 -1
- package/dist/src/reskin/chat-page.js +319 -72
- package/dist/src/reskin/discover-page.js +486 -31
- package/dist/src/reskin/document.js +1171 -384
- package/dist/src/reskin/help-page.js +1 -0
- package/dist/src/reskin/inpaint-page.js +10 -4
- package/dist/src/reskin/library-page.js +918 -220
- package/dist/src/reskin/login-page.js +6 -6
- package/dist/src/reskin/pricing-page.js +2 -0
- package/dist/src/reskin/settings-page.js +55 -10
- package/dist/src/reskin/theme.js +337 -17
- package/dist/src/services/clip-curation/gemini.js +5 -0
- package/dist/src/services/clip-curation/hunt.js +79 -1
- package/dist/src/services/clip-curation/index.js +2 -1
- package/dist/src/services/clip-curation/local-agent.js +4 -3
- package/dist/src/services/clip-curation/media-select.js +85 -0
- package/dist/src/services/clip-curation/query.js +5 -1
- package/dist/src/services/clip-curation/refine.js +50 -20
- package/dist/src/services/clip-curation/scan.js +10 -3
- package/dist/src/services/clip-curation/taxonomy.js +3 -1
- package/dist/src/services/clip-curation/taxonomy.v1.json +13 -1
- package/dist/src/services/clip-records.js +14 -1
- package/dist/src/services/clip-search.js +43 -13
- package/dist/src/services/file-directory.js +114 -0
- package/dist/src/services/storage.js +24 -1
- package/dist/src/services/upstream.js +5 -5
- package/dist/src/template-editor-shell.js +16 -2
- package/package.json +1 -1
- package/public/assets/discover-client-app.js +1 -0
- package/public/assets/file-directory-app.js +2 -0
- package/public/assets/homepage-client-app.js +12 -12
- package/public/assets/page-runtime-client-app.js +24 -24
- package/src/assets/favicon.ico +0 -0
- package/src/assets/logo-vidfarm.png +0 -0
|
@@ -28,22 +28,17 @@ const DEFAULT_INPUT = {
|
|
|
28
28
|
};
|
|
29
29
|
// ── icons ───────────────────────────────────────────────────────────────────
|
|
30
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>`;
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
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."
|
|
37
|
-
];
|
|
38
|
-
export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
31
|
+
const STOP = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2.5"/></svg>`;
|
|
32
|
+
const CLIP = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>`;
|
|
33
|
+
export function renderReskinChat(input = DEFAULT_INPUT, opts = {}) {
|
|
39
34
|
const boot = input.editorChat ?? null;
|
|
35
|
+
const initialThreadId = opts.initialThreadId ?? null;
|
|
40
36
|
// Embed the real boot as JSON. Escaping only `<` is the correct (and
|
|
41
37
|
// sufficient) way to keep a `<script type="application/json">` block from
|
|
42
38
|
// breaking out; HTML-entity escaping would corrupt JSON.parse.
|
|
43
39
|
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("");
|
|
45
40
|
const body = `
|
|
46
|
-
<main class="rk-chat-page">
|
|
41
|
+
<main class="rk-chat-page"${initialThreadId ? ` data-initial-thread="${rkEscape(initialThreadId)}"` : ""}>
|
|
47
42
|
<aside class="rk-chat-history" id="rk-chat-history" aria-label="Chat history">
|
|
48
43
|
<div class="rk-chat-history-head">
|
|
49
44
|
<span class="rk-chat-history-title">Chats</span>
|
|
@@ -57,14 +52,12 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
57
52
|
<header class="rk-chat-head">
|
|
58
53
|
<button type="button" class="rk-chat-histtoggle" id="rk-chat-histtoggle" aria-label="Show chat history">☰</button>
|
|
59
54
|
<div class="rk-chat-head-titles">
|
|
60
|
-
<
|
|
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>
|
|
55
|
+
<h1 class="rk-chat-title" id="rk-chat-title">Chat with Agent</h1>
|
|
63
56
|
</div>
|
|
64
57
|
<div class="rk-chat-head-actions">
|
|
65
58
|
<label class="rk-chat-mode" aria-label="Studio mode">
|
|
66
59
|
<select class="rk-chat-mode-select" id="rk-chat-mode">
|
|
67
|
-
<option value="/chat" selected>
|
|
60
|
+
<option value="/chat" selected>Chat</option>
|
|
68
61
|
<option value="/inpaint">Create Image</option>
|
|
69
62
|
</select>
|
|
70
63
|
<span class="rk-chat-mode-chev" aria-hidden="true">▾</span>
|
|
@@ -72,25 +65,19 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
72
65
|
</div>
|
|
73
66
|
</header>
|
|
74
67
|
|
|
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>
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
68
|
+
<div class="rk-chat-thread" id="rk-chat-thread"></div>
|
|
83
69
|
|
|
84
70
|
<div class="rk-chat-dock">
|
|
85
|
-
<div class="rk-chat-
|
|
86
|
-
${starterChips}
|
|
87
|
-
</div>
|
|
71
|
+
<div class="rk-chat-chips" id="rk-chat-chips" aria-label="Attached files"></div>
|
|
88
72
|
<div class="rk-chat-composer" id="rk-chat-composer">
|
|
89
|
-
<textarea id="rk-chat-input" class="rk-chat-input" rows="
|
|
73
|
+
<textarea id="rk-chat-input" class="rk-chat-input" rows="3"
|
|
90
74
|
placeholder="Describe your offer, audience, or the problem to solve…"></textarea>
|
|
91
|
-
<
|
|
75
|
+
<div class="rk-chat-composer-actions">
|
|
76
|
+
<button type="button" class="rk-chat-attach" id="rk-chat-attach" aria-label="Attach a file" title="Attach a file">${CLIP}</button>
|
|
77
|
+
<button type="button" class="rk-btn rk-btn-gold rk-chat-send" id="rk-chat-send">Send ${SEND}</button>
|
|
78
|
+
</div>
|
|
92
79
|
</div>
|
|
93
|
-
<p class="rk-chat-hint"
|
|
80
|
+
<p class="rk-chat-hint"><kbd>Enter</kbd> to send · <kbd>Shift</kbd>+<kbd>Enter</kbd> for a new line</p>
|
|
94
81
|
</div>
|
|
95
82
|
</div>
|
|
96
83
|
</div>
|
|
@@ -126,6 +113,8 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
126
113
|
line-height:1;padding:2px 5px;border-radius:var(--rk-r-sm);opacity:0;
|
|
127
114
|
transition:opacity var(--rk-dur) var(--rk-ease),color var(--rk-dur) var(--rk-ease),background var(--rk-dur) var(--rk-ease)}
|
|
128
115
|
.rk-chat-hrow:hover .rk-chat-hrow-del{opacity:1}
|
|
116
|
+
/* touch: the history panel is the primary mobile surface — keep delete visible */
|
|
117
|
+
@media(hover:none){.rk-chat-hrow-del{opacity:1}}
|
|
129
118
|
.rk-chat-hrow-del:hover{color:var(--rk-red);background:var(--rk-red-tint)}
|
|
130
119
|
.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
120
|
.rk-chat-history-note a{color:var(--rk-gold-700);font-weight:600}
|
|
@@ -139,13 +128,12 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
139
128
|
align-items:center;justify-content:center;margin-right:2px;margin-top:2px}
|
|
140
129
|
.rk-chat-histbackdrop{display:none}
|
|
141
130
|
|
|
142
|
-
/* compact header */
|
|
131
|
+
/* compact header — sticky so the title stays pinned while the thread scrolls */
|
|
143
132
|
.rk-chat-head{display:flex;align-items:flex-start;gap:12px;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
.rk-chat-
|
|
147
|
-
|
|
148
|
-
.rk-chat-title{font-family:var(--rk-font-display);font-weight:800;font-size:1.7rem;letter-spacing:-.025em;
|
|
133
|
+
position:sticky;top:0;z-index:20;background:var(--rk-bg);
|
|
134
|
+
padding-top:18px;margin-top:-18px;padding-bottom:18px;margin-bottom:6px;border-bottom:1px solid var(--rk-border)}
|
|
135
|
+
.rk-chat-head-titles{display:grid;gap:5px;min-width:0;flex:1}
|
|
136
|
+
.rk-chat-title{font-family:var(--rk-font-display);font-weight:800;font-size:1.6rem;letter-spacing:-.025em;
|
|
149
137
|
line-height:1.15;color:var(--rk-ink);min-width:0}
|
|
150
138
|
.rk-chat-subhead{font-size:14.5px;color:var(--rk-text-muted);line-height:1.5;max-width:56ch}
|
|
151
139
|
.rk-chat-head-actions{display:flex;align-items:center;gap:9px;flex:none}
|
|
@@ -172,16 +160,23 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
172
160
|
font-family:var(--rk-font-display);font-weight:800;font-size:11px;line-height:1;
|
|
173
161
|
background:linear-gradient(142deg,var(--rk-gold-500),var(--rk-violet))}
|
|
174
162
|
|
|
175
|
-
/* bubbles — uniform 1px hairline on all four sides, no colored side stripes
|
|
176
|
-
|
|
163
|
+
/* bubbles — uniform 1px hairline on all four sides, no colored side stripes.
|
|
164
|
+
overflow-wrap keeps a long pasted URL/token in a USER bubble from forcing
|
|
165
|
+
horizontal page scroll on phones (assistant text already wraps via -stream). */
|
|
166
|
+
.rk-chat-bubble{border-radius:var(--rk-r-2xl);padding:16px 20px;font-size:15px;line-height:1.65;overflow-wrap:anywhere}
|
|
177
167
|
.rk-chat-bubble-user{max-width:min(78%,560px);background:var(--rk-ink);color:#f4f4f5;
|
|
178
168
|
border:1px solid var(--rk-n-800);box-shadow:var(--rk-shadow-sm)}
|
|
179
169
|
.rk-chat-bubble-assistant{width:100%;background:var(--rk-surface);color:var(--rk-n-700);
|
|
180
170
|
border:1px solid var(--rk-border);box-shadow:var(--rk-shadow-card)}
|
|
181
171
|
.rk-chat-bubble-assistant strong{color:var(--rk-ink);font-weight:700}
|
|
172
|
+
.rk-chat-bubble-assistant em{font-style:italic}
|
|
173
|
+
.rk-chat-bubble-assistant a{color:var(--rk-gold-700);font-weight:600;text-decoration:underline;text-underline-offset:2px;overflow-wrap:anywhere}
|
|
182
174
|
|
|
183
175
|
/* streamed assistant text — pre-wrap keeps the model's own line breaks */
|
|
184
176
|
.rk-chat-stream{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--rk-n-700);line-height:1.65}
|
|
177
|
+
.rk-chat-stream a{color:var(--rk-gold-700);font-weight:600;text-decoration:underline;text-underline-offset:2px;overflow-wrap:anywhere}
|
|
178
|
+
.rk-chat-stream strong{color:var(--rk-ink);font-weight:700}
|
|
179
|
+
.rk-chat-stream em{font-style:italic}
|
|
185
180
|
.rk-chat-stream[hidden]{display:none}
|
|
186
181
|
|
|
187
182
|
/* thinking / status row */
|
|
@@ -222,12 +217,46 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
222
217
|
.rk-chat-chip:hover{border-color:var(--rk-gold-600);background:var(--rk-gold-tint);color:var(--rk-ink);
|
|
223
218
|
transform:translateY(-1px);box-shadow:var(--rk-shadow-sm)}
|
|
224
219
|
.rk-chat-chip:disabled{opacity:.5;cursor:default;transform:none;box-shadow:var(--rk-shadow-xs)}
|
|
225
|
-
|
|
226
|
-
|
|
220
|
+
|
|
221
|
+
/* empty new-chat state — no greeting, just a few subtle suggestions */
|
|
222
|
+
.rk-chat-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;
|
|
223
|
+
min-height:min(46vh,360px);text-align:center;padding:24px 0}
|
|
224
|
+
.rk-chat-empty-hint{font-size:13px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--rk-text-faint)}
|
|
225
|
+
.rk-chat-suggests{display:flex;flex-direction:column;gap:8px;width:min(420px,100%)}
|
|
226
|
+
.rk-chat-suggest{display:block;width:100%;text-align:left;padding:12px 15px;border-radius:var(--rk-r-xl);
|
|
227
|
+
border:1px solid var(--rk-border);background:var(--rk-surface);color:var(--rk-n-600);cursor:pointer;
|
|
228
|
+
font-family:var(--rk-font-body);font-size:13.5px;font-weight:500;line-height:1.4;box-shadow:var(--rk-shadow-xs);
|
|
229
|
+
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),color var(--rk-dur) var(--rk-ease)}
|
|
230
|
+
.rk-chat-suggest:hover{border-color:var(--rk-gold-600);background:var(--rk-gold-tint);color:var(--rk-ink);
|
|
231
|
+
transform:translateY(-1px);box-shadow:var(--rk-shadow-sm)}
|
|
232
|
+
/* composer = one bordered "text area" box: textarea fills the top, the attach +
|
|
233
|
+
send controls sit inside it along the bottom row */
|
|
234
|
+
.rk-chat-composer{display:flex;flex-direction:column;gap:9px;background:var(--rk-surface);
|
|
235
|
+
border:1px solid var(--rk-border-strong);border-radius:var(--rk-r-2xl);padding:11px 12px 10px;
|
|
227
236
|
box-shadow:var(--rk-shadow-card);transition:border-color var(--rk-dur) var(--rk-ease),box-shadow var(--rk-dur) var(--rk-ease)}
|
|
228
237
|
.rk-chat-composer:focus-within{border-color:var(--rk-gold-600);box-shadow:var(--rk-ring-gold)}
|
|
229
|
-
.rk-chat-
|
|
230
|
-
|
|
238
|
+
.rk-chat-composer-actions{display:flex;align-items:center;gap:9px}
|
|
239
|
+
.rk-chat-composer-actions .rk-chat-send{margin-left:auto}
|
|
240
|
+
|
|
241
|
+
/* attach button — opens the file-explorer drawer (chrome script wires it) */
|
|
242
|
+
.rk-chat-attach{flex:none;width:38px;height:38px;border-radius:var(--rk-r-full);border:1px solid var(--rk-border);
|
|
243
|
+
background:#fff;color:var(--rk-n-500);cursor:pointer;display:grid;place-items:center;
|
|
244
|
+
transition:background var(--rk-dur) var(--rk-ease),border-color var(--rk-dur) var(--rk-ease),color var(--rk-dur) var(--rk-ease)}
|
|
245
|
+
.rk-chat-attach:hover{border-color:var(--rk-gold-600);background:var(--rk-gold-tint);color:var(--rk-ink)}
|
|
246
|
+
.rk-chat-attach svg{width:18px;height:18px}
|
|
247
|
+
|
|
248
|
+
/* attached-file chips (populated when a file is picked from the drawer) */
|
|
249
|
+
.rk-chat-chips{display:flex;flex-wrap:wrap;gap:7px}
|
|
250
|
+
.rk-chat-chips:empty{display:none}
|
|
251
|
+
.rk-chat-attach-chip{display:inline-flex;align-items:center;gap:5px;padding:5px 8px 5px 12px;border-radius:var(--rk-r-full);
|
|
252
|
+
background:var(--rk-gold-tint2);color:var(--rk-gold-700);font-size:12.5px;font-weight:600;max-width:230px}
|
|
253
|
+
.rk-chat-attach-chip span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
254
|
+
.rk-chat-attach-chip button{border:0;background:transparent;color:inherit;cursor:pointer;font-size:15px;line-height:1;padding:0 2px}
|
|
255
|
+
.rk-chat-attach-chip .rk-chat-attach-preview{font-family:inherit;font-size:12.5px;font-weight:600;padding:0;max-width:200px;
|
|
256
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-decoration:underline;text-underline-offset:2px;text-decoration-color:transparent}
|
|
257
|
+
.rk-chat-attach-chip .rk-chat-attach-preview:hover{text-decoration-color:currentColor}
|
|
258
|
+
.rk-chat-input{width:100%;min-width:0;min-height:76px;border:0;background:transparent;resize:none;font-family:var(--rk-font-body);
|
|
259
|
+
font-size:15px;line-height:1.5;color:var(--rk-ink);padding:4px 4px 0;max-height:210px}
|
|
231
260
|
.rk-chat-input:focus{outline:none;box-shadow:none}
|
|
232
261
|
.rk-chat-input::placeholder{color:var(--rk-n-400)}
|
|
233
262
|
.rk-chat-send{flex:none;padding:11px 18px}
|
|
@@ -267,6 +296,8 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
267
296
|
|
|
268
297
|
var input=root.getElementById('rk-chat-input');
|
|
269
298
|
var send=root.getElementById('rk-chat-send');
|
|
299
|
+
var SEND_HTML=${JSON.stringify('Send ' + SEND)};
|
|
300
|
+
var STOP_HTML=${JSON.stringify('Stop ' + STOP)};
|
|
270
301
|
var thread=root.getElementById('rk-chat-thread');
|
|
271
302
|
var title=root.getElementById('rk-chat-title');
|
|
272
303
|
var AVATAR='V';
|
|
@@ -274,13 +305,174 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
274
305
|
var history=[]; // [{role, text}] — the running conversation
|
|
275
306
|
var threadId=null; // generated on first send, reused for persistence
|
|
276
307
|
var busy=false;
|
|
308
|
+
var pendingAbort=null; // AbortController for the in-flight reply (Stop button)
|
|
309
|
+
var attachments=[]; // files picked from the file-explorer drawer
|
|
310
|
+
var chipsEl=root.getElementById('rk-chat-chips');
|
|
311
|
+
|
|
312
|
+
// Render the pending-attachment chips above the composer.
|
|
313
|
+
function renderAttachChips(){
|
|
314
|
+
if(!chipsEl) return;
|
|
315
|
+
chipsEl.innerHTML='';
|
|
316
|
+
attachments.forEach(function(f, i){
|
|
317
|
+
var chip=el('rk-chat-attach-chip');
|
|
318
|
+
var label=root.createElement('button'); label.type='button'; label.className='rk-chat-attach-preview';
|
|
319
|
+
label.title='Preview '+f.name; label.textContent=f.name;
|
|
320
|
+
if(f.viewUrl){ label.addEventListener('click',function(){ openFilePreview(attachments, i); }); }
|
|
321
|
+
chip.appendChild(label);
|
|
322
|
+
var x=root.createElement('button'); x.type='button'; x.setAttribute('aria-label','Remove '+f.name); x.textContent='\\u00d7';
|
|
323
|
+
x.addEventListener('click',function(){ attachments=attachments.filter(function(a){ return a.id!==f.id; }); renderAttachChips(); });
|
|
324
|
+
chip.appendChild(x); chipsEl.appendChild(chip);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
// The file-explorer drawer (chrome script) hands picked files over via a DOM
|
|
328
|
+
// event — it lives in a separate script, so a CustomEvent is the bridge.
|
|
329
|
+
document.addEventListener('rk-chat-attach-file',function(ev){
|
|
330
|
+
var d=(ev && ev.detail)||{};
|
|
331
|
+
if(!d.id || !d.viewUrl) return;
|
|
332
|
+
if(attachments.some(function(a){ return a.id===String(d.id); })) return;
|
|
333
|
+
attachments.push({ id:String(d.id), name:d.name||'file', contentType:d.contentType||'application/octet-stream', viewUrl:d.viewUrl });
|
|
334
|
+
renderAttachChips();
|
|
335
|
+
if(input) input.focus();
|
|
336
|
+
});
|
|
277
337
|
|
|
338
|
+
// Each conversation has its own URL (/chat/<id>); a fresh page is /chat.
|
|
339
|
+
var pageEl=root.querySelector('.rk-chat-page');
|
|
340
|
+
var INITIAL_THREAD=(pageEl && pageEl.getAttribute('data-initial-thread')) || null;
|
|
341
|
+
function chatScopePrefix(){ var m=(location.pathname||'').match(/^(\\/u\\/[^/]+)\\/chat/); return m ? m[1] : ''; } // preserve account scope
|
|
342
|
+
function chatUrlFor(id){ return chatScopePrefix()+(id ? '/chat/'+encodeURIComponent(id) : '/chat'); }
|
|
343
|
+
function threadFromLocation(){ var m=(location.pathname||'').match(/\\/chat\\/(.+)$/); return m ? decodeURIComponent(m[1]) : null; }
|
|
344
|
+
function setChatUrl(id, replace){
|
|
345
|
+
// NB: the local "history" var is the message list — use window.history here.
|
|
346
|
+
var h=window.history; if(!h || (!h.pushState && !h.replaceState)) return;
|
|
347
|
+
var url=chatUrlFor(id);
|
|
348
|
+
try{ if(replace){ h.replaceState(null,'',url); } else { h.pushState(null,'',url); } }catch(e){}
|
|
349
|
+
}
|
|
278
350
|
function genId(p){ return p+'-'+Date.now().toString(36)+'-'+Math.random().toString(36).slice(2,8); }
|
|
279
|
-
function autosize(){ if(!input)return; input.style.height='auto'; input.style.height=Math.min(input.scrollHeight,
|
|
351
|
+
function autosize(){ if(!input)return; input.style.height='auto'; input.style.height=Math.min(input.scrollHeight,210)+'px'; }
|
|
280
352
|
function toBottom(){ try{ window.scrollTo({top:document.body.scrollHeight,behavior:'smooth'}); }catch(e){ window.scrollTo(0,document.body.scrollHeight); } }
|
|
281
353
|
function nowLabel(){ try{ return new Date().toLocaleTimeString([], {hour:'numeric', minute:'2-digit'}); }catch(e){ return ''; } }
|
|
282
354
|
function el(cls){ var d=root.createElement('div'); d.className=cls; return d; }
|
|
283
|
-
|
|
355
|
+
// While a reply streams, the Send button becomes an interruptive Stop (stays
|
|
356
|
+
// clickable so the user can abort); it flips back to Send when the reply ends.
|
|
357
|
+
function setBusy(v){ busy=v; if(send){ send.innerHTML=v?STOP_HTML:SEND_HTML; send.classList.toggle('is-stop',v); } root.querySelectorAll('[data-rk-prompt]').forEach(function(c){ c.disabled=v; }); }
|
|
358
|
+
|
|
359
|
+
// Inline markdown → HTML (escape first, then bold/italic/links). pre-wrap on
|
|
360
|
+
// the container keeps the model's own line breaks, so no block handling needed.
|
|
361
|
+
function escapeHtml(s){ return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
|
362
|
+
function mdToHtml(text){
|
|
363
|
+
var s=escapeHtml(text);
|
|
364
|
+
s=s.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>');
|
|
365
|
+
s=s.replace(/(^|[^\\w*])\\*([^*\\n]+)\\*/g,'$1<em>$2</em>');
|
|
366
|
+
s=s.replace(/(^|[^\\w_])_([^_\\n]+)_/g,'$1<em>$2</em>');
|
|
367
|
+
s=s.replace(/\\[([^\\]]+)\\]\\((https?:\\/\\/[^\\s)]+)\\)/g,'<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
368
|
+
s=s.replace(/(^|[\\s(])(https?:\\/\\/[^\\s<]+[^<.,:;"')\\]\\s])/g,'$1<a href="$2" target="_blank" rel="noopener noreferrer">$2</a>');
|
|
369
|
+
return s;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ── file preview modal (shares the rk-aichat-preview styling from theme.ts):
|
|
373
|
+
// images carousel, video, audio, pdf, and text (json pretty-print, md render,
|
|
374
|
+
// txt/csv/srt/vtt as <pre>) ──
|
|
375
|
+
function fileKind(name, ct){
|
|
376
|
+
ct=(ct||'').toLowerCase();
|
|
377
|
+
var ext=((name||'').split('.').pop()||'').toLowerCase();
|
|
378
|
+
if(ct.indexOf('image/')===0 || /^(png|jpe?g|gif|webp|svg|avif|bmp|heic|ico)$/.test(ext)) return 'image';
|
|
379
|
+
if(ct.indexOf('video/')===0 || /^(mp4|mov|webm|m4v|mkv|ogv)$/.test(ext)) return 'video';
|
|
380
|
+
if(ct.indexOf('audio/')===0 || /^(mp3|wav|m4a|aac|ogg|oga|flac|opus)$/.test(ext)) return 'audio';
|
|
381
|
+
if(ct.indexOf('application/pdf')===0 || ext==='pdf') return 'pdf';
|
|
382
|
+
if(ct.indexOf('json')>=0 || ext==='json') return 'json';
|
|
383
|
+
if(ext==='md' || ext==='markdown' || ct.indexOf('markdown')>=0) return 'markdown';
|
|
384
|
+
if(ct.indexOf('text/')===0 || /^(txt|csv|tsv|srt|vtt|log|xml|yaml|yml|ini|conf|html?)$/.test(ext)) return 'text';
|
|
385
|
+
return 'other';
|
|
386
|
+
}
|
|
387
|
+
// Block-level markdown for .md previews (headings, lists, quotes, rules, code,
|
|
388
|
+
// plus the inline bold/italic/link handling from mdToHtml).
|
|
389
|
+
function renderMarkdownBlock(src){
|
|
390
|
+
var BT=String.fromCharCode(96), FENCE=BT+BT+BT;
|
|
391
|
+
function inline(s){ return mdToHtml(s).replace(new RegExp(BT+'([^'+BT+']+)'+BT,'g'),'<code>$1</code>'); }
|
|
392
|
+
var lines=String(src).replace(/\\r\\n?/g,'\\n').split('\\n');
|
|
393
|
+
var out=[], inCode=false, code=[], listType=null, listItems=[];
|
|
394
|
+
function flushList(){ if(!listType)return; out.push('<'+listType+'>'+listItems.join('')+'</'+listType+'>'); listType=null; listItems=[]; }
|
|
395
|
+
function flushCode(){ out.push('<pre class="rk-aichat-preview-code"><code>'+escapeHtml(code.join('\\n'))+'</code></pre>'); code=[]; }
|
|
396
|
+
for(var i=0;i<lines.length;i++){
|
|
397
|
+
var ln=lines[i];
|
|
398
|
+
if(ln.trim().indexOf(FENCE)===0){ if(inCode){ flushCode(); inCode=false; } else { flushList(); inCode=true; } continue; }
|
|
399
|
+
if(inCode){ code.push(ln); continue; }
|
|
400
|
+
var h=ln.match(/^(#{1,6})\\s+(.*)$/);
|
|
401
|
+
if(h){ flushList(); var lvl=h[1].length; out.push('<h'+lvl+'>'+inline(h[2])+'</h'+lvl+'>'); continue; }
|
|
402
|
+
if(/^\\s*([-*+])\\s+/.test(ln)){ if(listType!=='ul'){ flushList(); listType='ul'; } listItems.push('<li>'+inline(ln.replace(/^\\s*[-*+]\\s+/,''))+'</li>'); continue; }
|
|
403
|
+
var om=ln.match(/^\\s*\\d+\\.\\s+(.*)$/);
|
|
404
|
+
if(om){ if(listType!=='ol'){ flushList(); listType='ol'; } listItems.push('<li>'+inline(om[1])+'</li>'); continue; }
|
|
405
|
+
if(/^\\s*>\\s?/.test(ln)){ flushList(); out.push('<blockquote>'+inline(ln.replace(/^\\s*>\\s?/,''))+'</blockquote>'); continue; }
|
|
406
|
+
if(/^\\s*(-{3,}|\\*{3,}|_{3,})\\s*$/.test(ln)){ flushList(); out.push('<hr>'); continue; }
|
|
407
|
+
if(!ln.trim()){ flushList(); continue; }
|
|
408
|
+
flushList(); out.push('<p>'+inline(ln)+'</p>');
|
|
409
|
+
}
|
|
410
|
+
if(inCode) flushCode();
|
|
411
|
+
flushList();
|
|
412
|
+
return out.join('');
|
|
413
|
+
}
|
|
414
|
+
function openFilePreview(items, index){
|
|
415
|
+
items=(items||[]).filter(function(f){ return f && f.viewUrl; });
|
|
416
|
+
if(!items.length) return;
|
|
417
|
+
var idx=Math.max(0,Math.min(index||0,items.length-1));
|
|
418
|
+
var back=el('rk-aichat-modal-back');
|
|
419
|
+
var modal=el('rk-aichat-modal rk-aichat-preview');
|
|
420
|
+
var head=el('rk-aichat-modal-head');
|
|
421
|
+
var nameEl=root.createElement('span'); nameEl.className='rk-aichat-http-url rk-aichat-preview-name';
|
|
422
|
+
var openLink=root.createElement('a'); openLink.className='rk-aichat-preview-open'; openLink.target='_blank'; openLink.rel='noopener noreferrer'; openLink.textContent='Open \\u2197';
|
|
423
|
+
var xBtn=root.createElement('button'); xBtn.type='button'; xBtn.className='rk-aichat-modal-x'; xBtn.setAttribute('aria-label','Close'); xBtn.innerHTML='×';
|
|
424
|
+
head.appendChild(nameEl); head.appendChild(openLink); head.appendChild(xBtn);
|
|
425
|
+
var body=el('rk-aichat-modal-body rk-aichat-preview-body');
|
|
426
|
+
var nav=el('rk-aichat-preview-nav');
|
|
427
|
+
var prev=root.createElement('button'); prev.type='button'; prev.className='rk-aichat-preview-btn'; prev.innerHTML='\\u2039 Prev';
|
|
428
|
+
var counter=root.createElement('span'); counter.className='rk-aichat-preview-count';
|
|
429
|
+
var next=root.createElement('button'); next.type='button'; next.className='rk-aichat-preview-btn'; next.innerHTML='Next \\u203a';
|
|
430
|
+
nav.appendChild(prev); nav.appendChild(counter); nav.appendChild(next);
|
|
431
|
+
function renderUnknown(container, it, msg){
|
|
432
|
+
var wrap=el('rk-aichat-preview-empty');
|
|
433
|
+
var p=root.createElement('div'); p.textContent=msg||'No inline preview for this file type.'; wrap.appendChild(p);
|
|
434
|
+
var a=root.createElement('a'); a.href=it.viewUrl; a.target='_blank'; a.rel='noopener noreferrer'; a.className='rk-aichat-preview-dl'; a.textContent='Open / download \\u2197'; wrap.appendChild(a);
|
|
435
|
+
container.appendChild(wrap);
|
|
436
|
+
}
|
|
437
|
+
function renderText(container, it, kind){
|
|
438
|
+
var loading=el('rk-aichat-preview-loading'); loading.textContent='Loading\\u2026'; container.appendChild(loading);
|
|
439
|
+
fetch(it.viewUrl,{ credentials:'same-origin' })
|
|
440
|
+
.then(function(r){ if(!r.ok) throw new Error('http '+r.status); return r.text(); })
|
|
441
|
+
.then(function(txt){
|
|
442
|
+
container.innerHTML='';
|
|
443
|
+
if(kind==='json'){ var pretty=txt; try{ pretty=JSON.stringify(JSON.parse(txt),null,2); }catch(e){} var pj=root.createElement('pre'); pj.className='rk-aichat-http-pre rk-aichat-preview-text'; pj.textContent=pretty; container.appendChild(pj); return; }
|
|
444
|
+
if(kind==='markdown'){ var md=el('rk-aichat-preview-md'); md.innerHTML=renderMarkdownBlock(txt); container.appendChild(md); return; }
|
|
445
|
+
var pre=root.createElement('pre'); pre.className='rk-aichat-http-pre rk-aichat-preview-text'; pre.textContent=txt; container.appendChild(pre);
|
|
446
|
+
})
|
|
447
|
+
.catch(function(){ container.innerHTML=''; renderUnknown(container, it, 'Couldn\\u2019t load this file.'); });
|
|
448
|
+
}
|
|
449
|
+
function render(){
|
|
450
|
+
var it=items[idx];
|
|
451
|
+
nameEl.textContent=it.name||'file'; nameEl.title=it.name||'';
|
|
452
|
+
openLink.href=it.viewUrl;
|
|
453
|
+
body.innerHTML='';
|
|
454
|
+
var kind=fileKind(it.name, it.contentType);
|
|
455
|
+
if(kind==='image'){ var im=root.createElement('img'); im.className='rk-aichat-preview-img'; im.src=it.viewUrl; im.alt=it.name||''; body.appendChild(im); }
|
|
456
|
+
else if(kind==='video'){ var v=root.createElement('video'); v.className='rk-aichat-preview-media'; v.src=it.viewUrl; v.controls=true; v.playsInline=true; body.appendChild(v); }
|
|
457
|
+
else if(kind==='audio'){ var wrap=el('rk-aichat-preview-audiowrap'); var a=root.createElement('audio'); a.className='rk-aichat-preview-audio'; a.src=it.viewUrl; a.controls=true; wrap.appendChild(a); body.appendChild(wrap); }
|
|
458
|
+
else if(kind==='pdf'){ var f=root.createElement('iframe'); f.className='rk-aichat-preview-frame'; f.src=it.viewUrl; body.appendChild(f); }
|
|
459
|
+
else if(kind==='json'||kind==='markdown'||kind==='text'){ renderText(body, it, kind); }
|
|
460
|
+
else { renderUnknown(body, it); }
|
|
461
|
+
counter.textContent=(idx+1)+' / '+items.length;
|
|
462
|
+
prev.disabled=idx<=0; next.disabled=idx>=items.length-1;
|
|
463
|
+
nav.hidden=items.length<2;
|
|
464
|
+
}
|
|
465
|
+
prev.addEventListener('click',function(){ if(idx>0){ idx--; render(); } });
|
|
466
|
+
next.addEventListener('click',function(){ if(idx<items.length-1){ idx++; render(); } });
|
|
467
|
+
modal.appendChild(head); modal.appendChild(body); modal.appendChild(nav); back.appendChild(modal);
|
|
468
|
+
function close(){ if(back.parentNode) back.parentNode.removeChild(back); document.removeEventListener('keydown',onKey); }
|
|
469
|
+
function onKey(e){ if(e.key==='Escape'){ e.stopPropagation(); close(); } else if(e.key==='ArrowLeft'){ if(idx>0){ idx--; render(); } } else if(e.key==='ArrowRight'){ if(idx<items.length-1){ idx++; render(); } } }
|
|
470
|
+
back.addEventListener('click',function(e){ if(e.target===back) close(); });
|
|
471
|
+
xBtn.addEventListener('click',close);
|
|
472
|
+
document.addEventListener('keydown',onKey);
|
|
473
|
+
document.body.appendChild(back);
|
|
474
|
+
render();
|
|
475
|
+
}
|
|
284
476
|
|
|
285
477
|
function clientContext(){
|
|
286
478
|
var now=new Date(); var local=null, tz=null;
|
|
@@ -301,7 +493,7 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
301
493
|
function appendAssistant(){
|
|
302
494
|
var msg=el('rk-chat-msg is-assistant');
|
|
303
495
|
var meta=el('rk-chat-meta');
|
|
304
|
-
meta.innerHTML='
|
|
496
|
+
meta.innerHTML='Agent <span class="rk-chat-time">'+nowLabel()+'</span>';
|
|
305
497
|
var bub=el('rk-chat-bubble rk-chat-bubble-assistant');
|
|
306
498
|
var status=el('rk-chat-status');
|
|
307
499
|
status.innerHTML='<span class="rk-chat-typing"><span></span><span></span><span></span></span><span class="rk-chat-status-label">Thinking…</span>';
|
|
@@ -310,11 +502,14 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
310
502
|
bub.appendChild(status); bub.appendChild(notes); bub.appendChild(textEl);
|
|
311
503
|
msg.appendChild(meta); msg.appendChild(bub);
|
|
312
504
|
if(thread) thread.appendChild(msg);
|
|
505
|
+
var lastText='';
|
|
313
506
|
return {
|
|
314
507
|
setStatus:function(s){ var l=status.querySelector('.rk-chat-status-label'); if(l && s){ l.textContent=s; } },
|
|
315
508
|
hideStatus:function(){ status.hidden=true; },
|
|
316
509
|
showText:function(){ status.hidden=true; textEl.hidden=false; },
|
|
317
|
-
setText:function(t){ textEl.textContent=t; },
|
|
510
|
+
setText:function(t){ lastText=t; textEl.textContent=t; },
|
|
511
|
+
// After streaming completes, re-render as markdown (bold/italic/links).
|
|
512
|
+
renderMd:function(){ if(lastText){ textEl.innerHTML=mdToHtml(lastText); } },
|
|
318
513
|
addNote:function(t){ var n=el('rk-chat-toolnote'); n.textContent=t; notes.appendChild(n); },
|
|
319
514
|
fail:function(m){ status.hidden=true; var er=el('rk-chat-error'); er.textContent=m||'Something went wrong.'; bub.appendChild(er); }
|
|
320
515
|
};
|
|
@@ -360,19 +555,26 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
360
555
|
function sendMessage(text){
|
|
361
556
|
if(busy) return;
|
|
362
557
|
text=(text||'').trim();
|
|
363
|
-
|
|
558
|
+
// Attachments picked from the drawer travel in user_message.attachments;
|
|
559
|
+
// sending is allowed with only files (no text) too.
|
|
560
|
+
var atts=attachments.map(function(f){ return { id:String(f.id), fileName:f.name, contentType:f.contentType, viewUrl:f.viewUrl }; });
|
|
561
|
+
if(!text && !atts.length) return;
|
|
364
562
|
|
|
365
|
-
|
|
366
|
-
|
|
563
|
+
var line=text;
|
|
564
|
+
if(attachments.length){ line=(text?text+' ':'')+'(attached: '+attachments.map(function(f){ return f.name; }).join(', ')+')'; }
|
|
565
|
+
|
|
566
|
+
clearEmptyState();
|
|
567
|
+
history.push({role:'user', text:text||line});
|
|
568
|
+
appendUser(line);
|
|
367
569
|
if(input){ input.value=''; autosize(); }
|
|
368
|
-
|
|
570
|
+
attachments=[]; renderAttachChips();
|
|
369
571
|
toBottom();
|
|
370
572
|
|
|
371
573
|
var view=appendAssistant();
|
|
372
574
|
toBottom();
|
|
373
575
|
setBusy(true);
|
|
374
576
|
|
|
375
|
-
if(!threadId){ threadId=genId('thread'); }
|
|
577
|
+
if(!threadId){ threadId=genId('thread'); setChatUrl(threadId, true); }
|
|
376
578
|
var userMsgId=genId('user');
|
|
377
579
|
var asstMsgId=genId('assistant');
|
|
378
580
|
|
|
@@ -380,10 +582,10 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
380
582
|
messages: history.map(function(m){ return { role:m.role, content:[{ type:'text', text:m.text }] }; }),
|
|
381
583
|
thread_id: threadId,
|
|
382
584
|
thread_template_id: (TEMPLATE && TEMPLATE.templateId) || 'chat-brainstorm',
|
|
383
|
-
thread_title: text.slice(0,42),
|
|
585
|
+
thread_title: (text||line).slice(0,42),
|
|
384
586
|
thread_tracers: (TEMPLATE && TEMPLATE.tracers) || [],
|
|
385
587
|
client_context: clientContext(),
|
|
386
|
-
user_message: { id:userMsgId, role:'user', text:text, attachments:
|
|
588
|
+
user_message: { id:userMsgId, role:'user', text:text||line, attachments:atts },
|
|
387
589
|
assistant_message: { id:asstMsgId }
|
|
388
590
|
};
|
|
389
591
|
if(TEMPLATE){ body.template=TEMPLATE; }
|
|
@@ -403,9 +605,11 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
403
605
|
onError:function(m){ errored=true; view.fail(m); toBottom(); }
|
|
404
606
|
};
|
|
405
607
|
|
|
608
|
+
var controller=(typeof AbortController!=='undefined') ? new AbortController() : null;
|
|
609
|
+
pendingAbort=controller;
|
|
406
610
|
var req;
|
|
407
611
|
try{
|
|
408
|
-
req=fetch(ENDPOINT,{ method:'POST', headers:headers, credentials:'same-origin', body:JSON.stringify(body) });
|
|
612
|
+
req=fetch(ENDPOINT,{ method:'POST', headers:headers, credentials:'same-origin', body:JSON.stringify(body), signal: controller?controller.signal:undefined });
|
|
409
613
|
}catch(err){
|
|
410
614
|
// Synchronous fetch failure (e.g. invalid URL) — never let it throw uncaught.
|
|
411
615
|
req=Promise.reject(err);
|
|
@@ -419,9 +623,11 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
419
623
|
}
|
|
420
624
|
return readStream(resp, handlers);
|
|
421
625
|
}).then(function(){
|
|
626
|
+
pendingAbort=null;
|
|
422
627
|
view.hideStatus();
|
|
423
628
|
if(sawText){
|
|
424
629
|
history.push({role:'assistant', text:acc});
|
|
630
|
+
view.renderMd();
|
|
425
631
|
} else if(!errored){
|
|
426
632
|
view.showText();
|
|
427
633
|
view.setText('The assistant finished without returning any text.');
|
|
@@ -431,6 +637,17 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
431
637
|
toBottom();
|
|
432
638
|
loadHistory();
|
|
433
639
|
}).catch(function(err){
|
|
640
|
+
pendingAbort=null;
|
|
641
|
+
// User hit Stop mid-reply — keep whatever streamed so far, no error.
|
|
642
|
+
if(err && (err.name==='AbortError' || (controller && controller.signal && controller.signal.aborted))){
|
|
643
|
+
view.hideStatus();
|
|
644
|
+
if(sawText){ history.push({role:'assistant', text:acc}); view.renderMd(); }
|
|
645
|
+
else{ view.showText(); view.setText('Stopped.'); }
|
|
646
|
+
setBusy(false);
|
|
647
|
+
if(input){ input.focus(); }
|
|
648
|
+
toBottom();
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
434
651
|
var msg=(err && err.message) ? err.message : String(err);
|
|
435
652
|
if(!API_KEY){ msg=msg+'\\n\\nAdd an AI provider key in Settings to brainstorm on your own keys.'; }
|
|
436
653
|
view.fail(msg);
|
|
@@ -512,9 +729,10 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
512
729
|
})
|
|
513
730
|
.catch(function(){ if(histList){ histList.innerHTML=''; histList.appendChild(histNote('Couldn\\u2019t load your chats.', {retry:true})); } });
|
|
514
731
|
}
|
|
515
|
-
function appendAssistantStatic(text){ var v=appendAssistant(); v.showText(); v.setText(text||''); v.hideStatus(); return v; }
|
|
516
|
-
function openThread(id){
|
|
732
|
+
function appendAssistantStatic(text){ var v=appendAssistant(); v.showText(); v.setText(text||''); v.hideStatus(); v.renderMd(); return v; }
|
|
733
|
+
function openThread(id, opts){
|
|
517
734
|
if(busy || loadingThread) return;
|
|
735
|
+
if(!(opts && opts.skipUrl)) setChatUrl(id, false);
|
|
518
736
|
loadingThread=true;
|
|
519
737
|
closeHistoryOverlay();
|
|
520
738
|
fetch(THREADS_URL+'/'+encodeURIComponent(id), { headers:authHeaders(), credentials:'same-origin' })
|
|
@@ -529,7 +747,6 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
529
747
|
history.push({role:role, text:txt});
|
|
530
748
|
if(role==='user'){ appendUser(txt); } else { appendAssistantStatic(txt); }
|
|
531
749
|
}
|
|
532
|
-
if(title){ title.textContent=t.title || 'Brainstorm studio'; }
|
|
533
750
|
setActiveThread(id);
|
|
534
751
|
loadingThread=false;
|
|
535
752
|
toBottom();
|
|
@@ -547,15 +764,37 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
547
764
|
})
|
|
548
765
|
.catch(function(){});
|
|
549
766
|
}
|
|
550
|
-
|
|
767
|
+
// A fresh chat opens empty — no greeting message — with a few subtle
|
|
768
|
+
// suggestion pills the user can tap to start (or just type their own).
|
|
769
|
+
function renderEmptyState(){
|
|
770
|
+
if(!thread) return;
|
|
771
|
+
thread.innerHTML='';
|
|
772
|
+
var suggestions=[
|
|
773
|
+
'Give me 10 ad hooks for my offer',
|
|
774
|
+
'What angle works best for cold traffic?',
|
|
775
|
+
'Help me plan a cold-start strategy'
|
|
776
|
+
];
|
|
777
|
+
var wrap=el('rk-chat-empty');
|
|
778
|
+
var hint=el('rk-chat-empty-hint'); hint.textContent='Ask anything, or try one of these';
|
|
779
|
+
var list=el('rk-chat-suggests');
|
|
780
|
+
suggestions.forEach(function(s){
|
|
781
|
+
var b=root.createElement('button');
|
|
782
|
+
b.type='button'; b.className='rk-chat-suggest'; b.textContent=s;
|
|
783
|
+
b.addEventListener('click',function(){ if(busy) return; sendMessage(s); });
|
|
784
|
+
list.appendChild(b);
|
|
785
|
+
});
|
|
786
|
+
wrap.appendChild(hint); wrap.appendChild(list);
|
|
787
|
+
thread.appendChild(wrap);
|
|
788
|
+
}
|
|
789
|
+
function clearEmptyState(){
|
|
790
|
+
if(!thread) return;
|
|
791
|
+
var e=thread.querySelector('.rk-chat-empty');
|
|
792
|
+
if(e && e.parentNode){ e.parentNode.removeChild(e); }
|
|
793
|
+
}
|
|
794
|
+
function resetChat(opts){
|
|
551
795
|
history=[]; threadId=null;
|
|
552
|
-
if(
|
|
553
|
-
|
|
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>'+
|
|
556
|
-
'</div>';
|
|
557
|
-
}
|
|
558
|
-
if(title){ title.textContent='Brainstorm studio'; }
|
|
796
|
+
if(!(opts && opts.skipUrl)) setChatUrl(null, false);
|
|
797
|
+
renderEmptyState();
|
|
559
798
|
setActiveThread(null);
|
|
560
799
|
if(input){ input.value=''; autosize(); input.focus(); }
|
|
561
800
|
window.scrollTo(0,0);
|
|
@@ -566,20 +805,27 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
566
805
|
if(histBackdrop){ histBackdrop.addEventListener('click', closeHistoryOverlay); }
|
|
567
806
|
loadHistory();
|
|
568
807
|
|
|
808
|
+
// Deep-link: /chat/<id> opens that saved thread on load; otherwise the page
|
|
809
|
+
// starts on the empty new-chat state with subtle suggestions.
|
|
810
|
+
if(INITIAL_THREAD){ openThread(INITIAL_THREAD, {skipUrl:true}); }
|
|
811
|
+
else{ renderEmptyState(); }
|
|
812
|
+
// Back/forward between conversations — sync the visible thread to the URL.
|
|
813
|
+
window.addEventListener('popstate',function(){
|
|
814
|
+
if(busy || loadingThread) return;
|
|
815
|
+
var id=threadFromLocation();
|
|
816
|
+
if(id){ if(id!==threadId){ openThread(id, {skipUrl:true}); } }
|
|
817
|
+
else if(threadId){ resetChat({skipUrl:true}); }
|
|
818
|
+
});
|
|
819
|
+
|
|
569
820
|
// composer wiring
|
|
570
821
|
if(input){ input.addEventListener('input',autosize); autosize(); }
|
|
571
|
-
if(send){ send.addEventListener('click',function(){ if(input){ sendMessage(input.value); } }); }
|
|
822
|
+
if(send){ send.addEventListener('click',function(){ if(busy){ if(pendingAbort){ pendingAbort.abort(); } return; } if(input){ sendMessage(input.value); } }); }
|
|
572
823
|
if(input){
|
|
573
824
|
input.addEventListener('keydown',function(ev){
|
|
574
|
-
if(ev.key==='Enter' && !ev.shiftKey){ ev.preventDefault(); sendMessage(input.value); }
|
|
825
|
+
if(ev.key==='Enter' && !ev.shiftKey){ ev.preventDefault(); if(busy){ if(pendingAbort){ pendingAbort.abort(); } return; } sendMessage(input.value); }
|
|
575
826
|
});
|
|
576
827
|
}
|
|
577
828
|
|
|
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
829
|
// "New" resets the conversation to the warm empty state (shared resetChat)
|
|
584
830
|
var newBtn=root.getElementById('rk-chat-new');
|
|
585
831
|
if(newBtn){
|
|
@@ -596,9 +842,10 @@ export function renderReskinChat(input = DEFAULT_INPUT) {
|
|
|
596
842
|
}
|
|
597
843
|
})();`;
|
|
598
844
|
return reskinDocument({
|
|
599
|
-
title: "vidfarm
|
|
600
|
-
description: "
|
|
845
|
+
title: "vidfarm — Chat with Agent",
|
|
846
|
+
description: "Vidfarm AI agent chat, wired to the live editor-chat backend — a clean single-column conversation that opens empty with subtle suggestions and a working composer.",
|
|
601
847
|
activeSlug: "chat",
|
|
848
|
+
account: { name: input.name, email: input.email },
|
|
602
849
|
pageCss,
|
|
603
850
|
body,
|
|
604
851
|
script
|