@mosetta/editor 0.0.32 → 0.0.33
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
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @mosetta/editor
|
|
2
2
|
|
|
3
|
+
## 0.0.33
|
|
4
|
+
|
|
5
|
+
- The assistant's chat lets the person choose the model it works with, among the ones the platform
|
|
6
|
+
offers (a larger one writes blocks that look better, and costs more); the choice is kept in this
|
|
7
|
+
browser.
|
|
8
|
+
|
|
3
9
|
## 0.0.32
|
|
4
10
|
|
|
5
11
|
- Built with the framework 0.0.12, whose `@mosetta/react` ships the guide to writing tiles — a new
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mosetta/editor",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.33",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "mosetta visual editor tile palette: block picker, page tree, inspector, isolated draft preview and the content API behind them.",
|
|
6
6
|
"repository": {
|
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"react": "*",
|
|
16
16
|
"react-dom": "*",
|
|
17
|
+
"@mosetta/content": "0.0.12",
|
|
17
18
|
"@mosetta/core": "0.0.12",
|
|
18
19
|
"@mosetta/di": "0.0.12",
|
|
20
|
+
"@mosetta/runtime": "0.0.12",
|
|
19
21
|
"@mosetta/stores": "0.0.12",
|
|
20
|
-
"@mosetta/content": "0.0.12",
|
|
21
|
-
"@mosetta/react": "0.0.12",
|
|
22
|
-
"@mosetta/settings": "0.0.12",
|
|
23
22
|
"@mosetta/server-action": "0.0.12",
|
|
24
|
-
"@mosetta/
|
|
25
|
-
"@mosetta/ui": "0.0.12"
|
|
23
|
+
"@mosetta/settings": "0.0.12",
|
|
24
|
+
"@mosetta/ui": "0.0.12",
|
|
25
|
+
"@mosetta/react": "0.0.12"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/react": "*",
|
|
@@ -68,6 +68,21 @@
|
|
|
68
68
|
font-variant-numeric: tabular-nums;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
.assistant__model {
|
|
72
|
+
max-width: 11em;
|
|
73
|
+
padding: 2px 4px;
|
|
74
|
+
border: 1px solid var(--wb-border, #e4e4e7);
|
|
75
|
+
border-radius: 6px;
|
|
76
|
+
background: #ffffff;
|
|
77
|
+
color: var(--wb-ink, #18181b);
|
|
78
|
+
font: inherit;
|
|
79
|
+
font-size: 0.85em;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.assistant__model:disabled {
|
|
83
|
+
opacity: 0.5;
|
|
84
|
+
}
|
|
85
|
+
|
|
71
86
|
.assistant__head-button {
|
|
72
87
|
padding: 3px 8px;
|
|
73
88
|
border: 1px solid transparent;
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
EMPTY_CHAT,
|
|
29
29
|
IChatState,
|
|
30
30
|
IDraftTouched,
|
|
31
|
+
modelLabel,
|
|
31
32
|
nodeOfCall,
|
|
32
33
|
TChatItem,
|
|
33
34
|
touchedNodes,
|
|
@@ -48,6 +49,23 @@ function readChat(): IChatState {
|
|
|
48
49
|
return EMPTY_CHAT;
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
/** the model the person chose, kept for this browser — one the platform no longer offers is forgotten */
|
|
53
|
+
const MODEL_KEY = 'mosetta-assistant-model';
|
|
54
|
+
|
|
55
|
+
function readModel(): string | null {
|
|
56
|
+
try {
|
|
57
|
+
return window.localStorage.getItem(MODEL_KEY);
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function writeModel(model: string): void {
|
|
64
|
+
try {
|
|
65
|
+
window.localStorage.setItem(MODEL_KEY, model);
|
|
66
|
+
} catch { /* storage denied: the choice lives for the page only */ }
|
|
67
|
+
}
|
|
68
|
+
|
|
51
69
|
function writeChat(state: IChatState): void {
|
|
52
70
|
try {
|
|
53
71
|
window.sessionStorage.setItem(storageKey(), JSON.stringify(state));
|
|
@@ -232,6 +250,10 @@ export function AssistantPanel({ placement, transcription, onClose, onWorking }:
|
|
|
232
250
|
const draftRef = useRef(draft);
|
|
233
251
|
draftRef.current = draft;
|
|
234
252
|
const [working, setWorking] = useState(false);
|
|
253
|
+
// the models the platform offers, its own first; the one chosen goes with every message
|
|
254
|
+
const [models, setModels] = useState<readonly string[]>([]);
|
|
255
|
+
const [chosen, setChosen] = useState<string | null>(readModel);
|
|
256
|
+
const model = chosen && models.includes(chosen) ? chosen : models[0] ?? null;
|
|
235
257
|
// over Home and the lists the island folds to its input when the person wants the page back
|
|
236
258
|
const [folded, setFolded] = useState(false);
|
|
237
259
|
const abort = useRef<AbortController | null>(null);
|
|
@@ -239,6 +261,19 @@ export function AssistantPanel({ placement, transcription, onClose, onWorking }:
|
|
|
239
261
|
const input = useRef<HTMLTextAreaElement>(null);
|
|
240
262
|
const dictation = useDictation(setDraft, () => draftRef.current, transcription);
|
|
241
263
|
useEffect(() => writeChat(chat), [chat]);
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
let live = true;
|
|
266
|
+
fetch(ASSISTANT_PATH, { headers: { accept: 'application/json' } })
|
|
267
|
+
.then((res) => (res.ok ? res.json() as Promise<{ models?: unknown }> : null))
|
|
268
|
+
.then((answer) => {
|
|
269
|
+
const listed = Array.isArray(answer?.models) ? answer.models.filter((m): m is string => typeof m === 'string') : [];
|
|
270
|
+
if (live) setModels(listed);
|
|
271
|
+
})
|
|
272
|
+
.catch(() => undefined);
|
|
273
|
+
return () => {
|
|
274
|
+
live = false;
|
|
275
|
+
};
|
|
276
|
+
}, []);
|
|
242
277
|
useEffect(() => {
|
|
243
278
|
list.current?.scrollTo({ top: list.current.scrollHeight });
|
|
244
279
|
}, [chat.items, folded]);
|
|
@@ -353,7 +388,7 @@ export function AssistantPanel({ placement, transcription, onClose, onWorking }:
|
|
|
353
388
|
const res = await fetch(ASSISTANT_PATH, {
|
|
354
389
|
method: 'POST',
|
|
355
390
|
headers: { 'content-type': 'application/json', accept: 'text/event-stream' },
|
|
356
|
-
body: JSON.stringify({ history: chat.history, input: { text: message, context: contextNow() } }),
|
|
391
|
+
body: JSON.stringify({ history: chat.history, input: { text: message, context: contextNow(), ...(model && model !== models[0] ? { model } : {}) } }),
|
|
357
392
|
signal: controller.signal,
|
|
358
393
|
});
|
|
359
394
|
if (!res.ok || !res.body) {
|
|
@@ -389,7 +424,7 @@ export function AssistantPanel({ placement, transcription, onClose, onWorking }:
|
|
|
389
424
|
di.getByToken(SERVER_DATA_MANAGER).invalidate(['pages']);
|
|
390
425
|
}
|
|
391
426
|
}
|
|
392
|
-
}, [chat, di, dictation, openDraft, reload, sessionsOn, working]);
|
|
427
|
+
}, [chat, di, dictation, model, models, openDraft, reload, sessionsOn, working]);
|
|
393
428
|
|
|
394
429
|
const startOver = (): void => {
|
|
395
430
|
if (working) return;
|
|
@@ -404,6 +439,23 @@ export function AssistantPanel({ placement, transcription, onClose, onWorking }:
|
|
|
404
439
|
data-working={working || undefined}>
|
|
405
440
|
<div className="assistant__head">
|
|
406
441
|
<span className="assistant__title">Assistant</span>
|
|
442
|
+
{models.length > 1 && model ? (
|
|
443
|
+
<select className="assistant__model"
|
|
444
|
+
data-test-id="assistant-model"
|
|
445
|
+
aria-label="The model the assistant works with"
|
|
446
|
+
data-tooltip="The model the assistant works with — a larger one does better and costs more"
|
|
447
|
+
value={model}
|
|
448
|
+
disabled={working}
|
|
449
|
+
onChange={(e) => {
|
|
450
|
+
setChosen(e.target.value);
|
|
451
|
+
writeModel(e.target.value);
|
|
452
|
+
}}>
|
|
453
|
+
{models.map((m) => (
|
|
454
|
+
<option key={m}
|
|
455
|
+
value={m}>{modelLabel(m)}</option>
|
|
456
|
+
))}
|
|
457
|
+
</select>
|
|
458
|
+
) : null}
|
|
407
459
|
{chat.cost > 0 ? <span className="assistant__cost"
|
|
408
460
|
data-tooltip="What this conversation has cost">${chat.cost.toFixed(3)}</span> : null}
|
|
409
461
|
<button type="button"
|
|
@@ -163,3 +163,9 @@ export function nodeOfCall(name: string, args: unknown): string | null {
|
|
|
163
163
|
}
|
|
164
164
|
return null;
|
|
165
165
|
}
|
|
166
|
+
|
|
167
|
+
/** a model as the person reads it: `anthropic/claude-opus-5.5` → `Claude Opus 5.5` */
|
|
168
|
+
export function modelLabel(id: string): string {
|
|
169
|
+
return id.slice(id.indexOf('/') + 1).split('-').filter(Boolean)
|
|
170
|
+
.map((word) => (word === 'gpt' ? 'GPT' : `${word[0].toUpperCase()}${word.slice(1)}`)).join(' ');
|
|
171
|
+
}
|