@love-moon/app-sdk 0.4.2 → 0.5.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/CHANGELOG.md +11 -0
- package/dist/index.d.ts +12 -0
- package/dist/react/index.d.ts +66 -16
- package/dist/react/index.js +1069 -92
- package/dist/react/styles.css +351 -5
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.js +26 -0
- package/examples/02_bff/app/api/conductor/[...path]/route.ts +9 -0
- package/examples/02_bff/app/page.tsx +3 -0
- package/package.json +1 -1
package/dist/react/styles.css
CHANGED
|
@@ -99,13 +99,23 @@
|
|
|
99
99
|
|
|
100
100
|
/* ----- Message list ------------------------------------------------------- */
|
|
101
101
|
|
|
102
|
+
/* Positioning context for the scroll-to-bottom button, the question-anchor
|
|
103
|
+
* rail, and the action-menu overlay — all of which are absolutely positioned
|
|
104
|
+
* relative to the scroll area (not the page). This wrapper is the `1fr` grid
|
|
105
|
+
* row; the inner list scrolls inside it. */
|
|
106
|
+
.conductor-message-list-viewport {
|
|
107
|
+
position: relative;
|
|
108
|
+
min-height: 0; /* lets it shrink inside the grid row */
|
|
109
|
+
overflow: hidden; /* contain the absolutely-positioned affordances */
|
|
110
|
+
}
|
|
111
|
+
|
|
102
112
|
.conductor-message-list {
|
|
113
|
+
height: 100%;
|
|
103
114
|
overflow-y: auto;
|
|
104
115
|
padding: var(--conductor-spacing);
|
|
105
116
|
display: flex;
|
|
106
117
|
flex-direction: column;
|
|
107
118
|
gap: 8px;
|
|
108
|
-
min-height: 0; /* lets it shrink inside the grid row */
|
|
109
119
|
}
|
|
110
120
|
|
|
111
121
|
.conductor-load-earlier {
|
|
@@ -141,8 +151,27 @@
|
|
|
141
151
|
justify-content: flex-start;
|
|
142
152
|
}
|
|
143
153
|
|
|
144
|
-
|
|
154
|
+
/* Wrapper that stacks the bubble + its timestamp + attachments. This is the
|
|
155
|
+
* flex child that gets aligned left/right; `.conductor-bubble` inside it holds
|
|
156
|
+
* only the message text. */
|
|
157
|
+
.conductor-bubble-group {
|
|
158
|
+
position: relative;
|
|
159
|
+
display: flex;
|
|
160
|
+
flex-direction: column;
|
|
161
|
+
gap: 4px;
|
|
145
162
|
max-width: 75%;
|
|
163
|
+
min-width: 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.conductor-message--user .conductor-bubble-group {
|
|
167
|
+
align-items: flex-end;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.conductor-message--assistant .conductor-bubble-group {
|
|
171
|
+
align-items: flex-start;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.conductor-bubble {
|
|
146
175
|
padding: 10px 14px;
|
|
147
176
|
border-radius: var(--conductor-radius);
|
|
148
177
|
white-space: pre-wrap;
|
|
@@ -164,6 +193,153 @@
|
|
|
164
193
|
opacity: 0.6;
|
|
165
194
|
}
|
|
166
195
|
|
|
196
|
+
/* Hover/tap-revealed timestamp, absolutely positioned so it never reserves
|
|
197
|
+
* vertical space in the list. */
|
|
198
|
+
.conductor-bubble__time {
|
|
199
|
+
position: absolute;
|
|
200
|
+
top: -13px;
|
|
201
|
+
font-size: 10px;
|
|
202
|
+
line-height: 1;
|
|
203
|
+
color: var(--conductor-text-muted);
|
|
204
|
+
white-space: nowrap;
|
|
205
|
+
opacity: 0;
|
|
206
|
+
transition: opacity 120ms ease;
|
|
207
|
+
pointer-events: none;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.conductor-message--user .conductor-bubble__time {
|
|
211
|
+
right: 4px;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.conductor-message--assistant .conductor-bubble__time {
|
|
215
|
+
left: 4px;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.conductor-bubble-group:hover .conductor-bubble__time,
|
|
219
|
+
.conductor-bubble-group--timestamp-visible .conductor-bubble__time {
|
|
220
|
+
opacity: 1;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* "via {app}" origin chip rendered inline at the start of the bubble. */
|
|
224
|
+
.conductor-bubble__origin {
|
|
225
|
+
display: inline-block;
|
|
226
|
+
margin-right: 6px;
|
|
227
|
+
padding: 1px 6px;
|
|
228
|
+
border-radius: 999px;
|
|
229
|
+
font-size: 11px;
|
|
230
|
+
background: var(--conductor-border);
|
|
231
|
+
color: var(--conductor-text-muted);
|
|
232
|
+
vertical-align: middle;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* ----- Attachments -------------------------------------------------------- */
|
|
236
|
+
|
|
237
|
+
.conductor-bubble__attachments {
|
|
238
|
+
display: flex;
|
|
239
|
+
flex-direction: column;
|
|
240
|
+
gap: 8px;
|
|
241
|
+
width: 100%;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.conductor-attachment {
|
|
245
|
+
overflow: hidden;
|
|
246
|
+
border: 1px solid var(--conductor-border);
|
|
247
|
+
border-radius: var(--conductor-radius);
|
|
248
|
+
background: var(--conductor-bubble-assistant);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.conductor-attachment__media-link {
|
|
252
|
+
display: block;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.conductor-attachment__image {
|
|
256
|
+
display: block;
|
|
257
|
+
width: 100%;
|
|
258
|
+
max-height: 28rem;
|
|
259
|
+
object-fit: cover;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.conductor-attachment__video {
|
|
263
|
+
display: block;
|
|
264
|
+
width: 100%;
|
|
265
|
+
max-height: 28rem;
|
|
266
|
+
background: #000;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.conductor-attachment__audio {
|
|
270
|
+
display: block;
|
|
271
|
+
width: 100%;
|
|
272
|
+
padding: 8px;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.conductor-attachment__file {
|
|
276
|
+
display: flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
justify-content: space-between;
|
|
279
|
+
gap: 12px;
|
|
280
|
+
padding: 10px 12px;
|
|
281
|
+
font-size: 13px;
|
|
282
|
+
color: var(--conductor-text);
|
|
283
|
+
text-decoration: none;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.conductor-attachment__meta {
|
|
287
|
+
display: flex;
|
|
288
|
+
align-items: center;
|
|
289
|
+
justify-content: space-between;
|
|
290
|
+
gap: 12px;
|
|
291
|
+
padding: 6px 12px;
|
|
292
|
+
font-size: 11px;
|
|
293
|
+
color: var(--conductor-text-muted);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.conductor-attachment__name {
|
|
297
|
+
overflow: hidden;
|
|
298
|
+
text-overflow: ellipsis;
|
|
299
|
+
white-space: nowrap;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.conductor-attachment__size {
|
|
303
|
+
flex-shrink: 0;
|
|
304
|
+
color: var(--conductor-text-muted);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* ----- Empty state -------------------------------------------------------- */
|
|
308
|
+
|
|
309
|
+
.conductor-empty {
|
|
310
|
+
display: flex;
|
|
311
|
+
flex-direction: column;
|
|
312
|
+
align-items: center;
|
|
313
|
+
justify-content: center;
|
|
314
|
+
flex: 1;
|
|
315
|
+
gap: 6px;
|
|
316
|
+
padding: 32px 16px;
|
|
317
|
+
text-align: center;
|
|
318
|
+
color: var(--conductor-text-muted);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.conductor-empty__icon {
|
|
322
|
+
width: 48px;
|
|
323
|
+
height: 48px;
|
|
324
|
+
opacity: 0.35;
|
|
325
|
+
margin-bottom: 4px;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.conductor-empty__title {
|
|
329
|
+
font-size: 16px;
|
|
330
|
+
font-weight: 600;
|
|
331
|
+
color: var(--conductor-text);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.conductor-empty__body {
|
|
335
|
+
max-width: 30rem;
|
|
336
|
+
font-size: 13px;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.conductor-empty__restart {
|
|
340
|
+
margin-top: 12px;
|
|
341
|
+
}
|
|
342
|
+
|
|
167
343
|
/* ----- Message input ------------------------------------------------------ */
|
|
168
344
|
|
|
169
345
|
.conductor-message-input {
|
|
@@ -244,21 +420,191 @@
|
|
|
244
420
|
filter: brightness(0.92);
|
|
245
421
|
}
|
|
246
422
|
|
|
423
|
+
/* ----- Scroll-to-bottom button -------------------------------------------- */
|
|
424
|
+
|
|
425
|
+
.conductor-scroll-to-bottom {
|
|
426
|
+
position: absolute;
|
|
427
|
+
right: 12px;
|
|
428
|
+
bottom: 12px;
|
|
429
|
+
z-index: 10;
|
|
430
|
+
display: flex;
|
|
431
|
+
align-items: center;
|
|
432
|
+
justify-content: center;
|
|
433
|
+
width: 36px;
|
|
434
|
+
height: 36px;
|
|
435
|
+
padding: 0;
|
|
436
|
+
border-radius: 50%;
|
|
437
|
+
border: 1px solid var(--conductor-border);
|
|
438
|
+
background: var(--conductor-bubble-assistant);
|
|
439
|
+
color: var(--conductor-text);
|
|
440
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
441
|
+
cursor: pointer;
|
|
442
|
+
transition: background-color 120ms ease;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.conductor-scroll-to-bottom:hover {
|
|
446
|
+
background: rgba(0, 0, 0, 0.04);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.conductor-scroll-to-bottom svg {
|
|
450
|
+
width: 16px;
|
|
451
|
+
height: 16px;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/* ----- Question anchor navigation ----------------------------------------- */
|
|
455
|
+
|
|
456
|
+
.conductor-question-nav {
|
|
457
|
+
position: absolute;
|
|
458
|
+
right: 8px;
|
|
459
|
+
top: 50%;
|
|
460
|
+
transform: translateY(-50%);
|
|
461
|
+
z-index: 20;
|
|
462
|
+
display: flex;
|
|
463
|
+
flex-direction: column;
|
|
464
|
+
align-items: center;
|
|
465
|
+
max-height: 80%;
|
|
466
|
+
overflow-y: auto;
|
|
467
|
+
padding: 4px 0;
|
|
468
|
+
transition: opacity 200ms ease;
|
|
469
|
+
scrollbar-width: none; /* Firefox: hide the rail's own scrollbar */
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.conductor-question-nav::-webkit-scrollbar {
|
|
473
|
+
width: 0;
|
|
474
|
+
height: 0;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.conductor-question-nav--visible {
|
|
478
|
+
opacity: 1;
|
|
479
|
+
pointer-events: auto;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.conductor-question-nav--hidden {
|
|
483
|
+
opacity: 0;
|
|
484
|
+
pointer-events: none;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.conductor-question-nav__group {
|
|
488
|
+
display: flex;
|
|
489
|
+
flex-direction: column;
|
|
490
|
+
align-items: center;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.conductor-question-nav__sep {
|
|
494
|
+
width: 1px;
|
|
495
|
+
height: 12px;
|
|
496
|
+
background: rgba(0, 0, 0, 0.2);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.conductor-question-nav__btn {
|
|
500
|
+
display: flex;
|
|
501
|
+
align-items: center;
|
|
502
|
+
justify-content: center;
|
|
503
|
+
width: 24px;
|
|
504
|
+
height: 24px;
|
|
505
|
+
padding: 0;
|
|
506
|
+
border: 0;
|
|
507
|
+
background: transparent;
|
|
508
|
+
cursor: pointer;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.conductor-question-nav__dot {
|
|
512
|
+
display: block;
|
|
513
|
+
width: 6px;
|
|
514
|
+
height: 6px;
|
|
515
|
+
border-radius: 50%;
|
|
516
|
+
background: rgba(0, 0, 0, 0.35);
|
|
517
|
+
transition: all 150ms ease;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.conductor-question-nav__btn:hover .conductor-question-nav__dot {
|
|
521
|
+
background: rgba(0, 0, 0, 0.6);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.conductor-question-nav__dot--active {
|
|
525
|
+
width: 12px;
|
|
526
|
+
height: 12px;
|
|
527
|
+
background: var(--accent);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/* ----- Bubble action menu (double-click / double-tap) --------------------- */
|
|
531
|
+
|
|
532
|
+
.conductor-bubble-menu-overlay {
|
|
533
|
+
position: absolute;
|
|
534
|
+
inset: 0;
|
|
535
|
+
z-index: 50;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.conductor-bubble-menu-backdrop {
|
|
539
|
+
position: absolute;
|
|
540
|
+
inset: 0;
|
|
541
|
+
background: rgba(0, 0, 0, 0.2);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.conductor-bubble-menu-sheet {
|
|
545
|
+
position: absolute;
|
|
546
|
+
left: 0;
|
|
547
|
+
right: 0;
|
|
548
|
+
bottom: 0;
|
|
549
|
+
padding: 12px;
|
|
550
|
+
border-top: 1px solid var(--conductor-border);
|
|
551
|
+
border-top-left-radius: 16px;
|
|
552
|
+
border-top-right-radius: 16px;
|
|
553
|
+
background: var(--conductor-bubble-assistant);
|
|
554
|
+
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.18);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.conductor-bubble-menu-handle {
|
|
558
|
+
width: 48px;
|
|
559
|
+
height: 5px;
|
|
560
|
+
margin: 0 auto 12px;
|
|
561
|
+
border-radius: 999px;
|
|
562
|
+
background: var(--conductor-border);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.conductor-bubble-menu-actions {
|
|
566
|
+
display: flex;
|
|
567
|
+
align-items: center;
|
|
568
|
+
justify-content: center;
|
|
569
|
+
gap: 8px;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.conductor-bubble-menu-item {
|
|
573
|
+
flex: 0 0 auto;
|
|
574
|
+
padding: 8px 16px;
|
|
575
|
+
border-radius: var(--conductor-radius);
|
|
576
|
+
border: 1px solid var(--conductor-border);
|
|
577
|
+
background: var(--conductor-paper);
|
|
578
|
+
color: var(--conductor-text);
|
|
579
|
+
font: inherit;
|
|
580
|
+
cursor: pointer;
|
|
581
|
+
transition: background-color 120ms ease;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.conductor-bubble-menu-item:hover {
|
|
585
|
+
background: rgba(0, 0, 0, 0.04);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.conductor-bubble-menu-item:disabled {
|
|
589
|
+
opacity: 0.4;
|
|
590
|
+
cursor: not-allowed;
|
|
591
|
+
}
|
|
592
|
+
|
|
247
593
|
/* ----- Mobile layout ------------------------------------------------------ */
|
|
248
594
|
|
|
249
595
|
@media (max-width: 600px) {
|
|
250
596
|
.conductor-chat-view {
|
|
251
597
|
font-size: 15px;
|
|
252
598
|
}
|
|
253
|
-
.conductor-bubble {
|
|
599
|
+
.conductor-bubble-group {
|
|
254
600
|
max-width: 85%;
|
|
255
601
|
}
|
|
256
602
|
}
|
|
257
603
|
|
|
258
|
-
.conductor-chat-view[data-layout='mobile'] .conductor-bubble {
|
|
604
|
+
.conductor-chat-view[data-layout='mobile'] .conductor-bubble-group {
|
|
259
605
|
max-width: 85%;
|
|
260
606
|
}
|
|
261
607
|
|
|
262
|
-
.conductor-chat-view[data-layout='desktop'] .conductor-bubble {
|
|
608
|
+
.conductor-chat-view[data-layout='desktop'] .conductor-bubble-group {
|
|
263
609
|
max-width: 60%;
|
|
264
610
|
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -322,6 +322,11 @@ declare class TasksApi {
|
|
|
322
322
|
targetReplyTo: string;
|
|
323
323
|
signal?: AbortSignal;
|
|
324
324
|
}): Promise<void>;
|
|
325
|
+
/** Restart the task's AI session (e.g. `restartMode: 'refresh_session'`). */
|
|
326
|
+
restart(taskId: string, opts?: {
|
|
327
|
+
restartMode?: string;
|
|
328
|
+
signal?: AbortSignal;
|
|
329
|
+
}): Promise<void>;
|
|
325
330
|
/**
|
|
326
331
|
* Subscribe to a task's event stream. Yields ChatEvents until the caller
|
|
327
332
|
* breaks out of the `for await` loop, calls `signal.abort()`, or the
|
package/dist/server/index.js
CHANGED
|
@@ -564,6 +564,27 @@ var TasksRestApi = class {
|
|
|
564
564
|
signal: opts.signal
|
|
565
565
|
});
|
|
566
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* Restart the task's AI session. `restartMode` mirrors Conductor's REST
|
|
569
|
+
* contract (e.g. `'refresh_session'`); when omitted the backend chooses the
|
|
570
|
+
* default. The effect is observed via the event stream (runtime_status / new
|
|
571
|
+
* messages), so this resolves to void.
|
|
572
|
+
*/
|
|
573
|
+
async restart(taskId, opts) {
|
|
574
|
+
if (!taskId) {
|
|
575
|
+
throw new ConductorAppError({
|
|
576
|
+
code: "invalid_input",
|
|
577
|
+
message: "tasks.restart requires a taskId"
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
const restartMode = opts?.restartMode?.trim();
|
|
581
|
+
await this.fetcher.request({
|
|
582
|
+
method: "POST",
|
|
583
|
+
path: `/api/tasks/${encodeURIComponent(taskId)}/restart`,
|
|
584
|
+
body: restartMode ? { restart_mode: restartMode } : {},
|
|
585
|
+
signal: opts?.signal
|
|
586
|
+
});
|
|
587
|
+
}
|
|
567
588
|
};
|
|
568
589
|
function buildOutboundMetadata(callerMetadata) {
|
|
569
590
|
const base = callerMetadata && typeof callerMetadata === "object" ? { ...callerMetadata } : {};
|
|
@@ -1247,6 +1268,11 @@ var TasksApi = class {
|
|
|
1247
1268
|
this.assertOpen();
|
|
1248
1269
|
return this.rest.interrupt(taskId, opts);
|
|
1249
1270
|
}
|
|
1271
|
+
/** Restart the task's AI session (e.g. `restartMode: 'refresh_session'`). */
|
|
1272
|
+
restart(taskId, opts) {
|
|
1273
|
+
this.assertOpen();
|
|
1274
|
+
return this.rest.restart(taskId, opts);
|
|
1275
|
+
}
|
|
1250
1276
|
/**
|
|
1251
1277
|
* Subscribe to a task's event stream. Yields ChatEvents until the caller
|
|
1252
1278
|
* breaks out of the `for await` loop, calls `signal.abort()`, or the
|
|
@@ -114,6 +114,15 @@ export async function POST(req: NextRequest, ctx: RouteContext) {
|
|
|
114
114
|
return NextResponse.json({ ok: true });
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
if (op === 'restart') {
|
|
118
|
+
// Only reached when the widget's adapter is created with
|
|
119
|
+
// `enableRestart: true` (see app/page.tsx). Forward to Conductor.
|
|
120
|
+
const restartMode =
|
|
121
|
+
typeof body?.restart_mode === 'string' ? body.restart_mode : 'refresh_session';
|
|
122
|
+
await client.tasks.restart(taskId, { restartMode });
|
|
123
|
+
return NextResponse.json({ ok: true });
|
|
124
|
+
}
|
|
125
|
+
|
|
117
126
|
return notFound();
|
|
118
127
|
} catch (err) {
|
|
119
128
|
return errorResponse(err);
|
|
@@ -26,6 +26,9 @@ const adapter = createRestAdapter({
|
|
|
26
26
|
baseUrl: '/api/conductor',
|
|
27
27
|
// No authToken — we trust the browser session and let the BFF authenticate
|
|
28
28
|
// upstream via its server-held Conductor token.
|
|
29
|
+
// Opt into restart: the catch-all route forwards POST /tasks/:id/restart to
|
|
30
|
+
// client.tasks.restart(). Omit this to hide all restart UI.
|
|
31
|
+
enableRestart: true,
|
|
29
32
|
});
|
|
30
33
|
|
|
31
34
|
export default function Page() {
|