@graview/primitives 0.1.65 β 0.1.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-bar.d.ts +114 -15
- package/dist/app-bar.d.ts.map +1 -1
- package/dist/app-bar.js +192 -130
- package/dist/app-bar.js.map +1 -1
- package/dist/app-menu.d.ts +9 -0
- package/dist/app-menu.d.ts.map +1 -0
- package/dist/app-menu.js +60 -0
- package/dist/app-menu.js.map +1 -0
- package/dist/bar-activity.d.ts +5 -9
- package/dist/bar-activity.d.ts.map +1 -1
- package/dist/bar-activity.js +24 -7
- package/dist/bar-activity.js.map +1 -1
- package/dist/bar-panes.d.ts +5 -5
- package/dist/bar-panes.d.ts.map +1 -1
- package/dist/bar-panes.js +9 -5
- package/dist/bar-panes.js.map +1 -1
- package/dist/bar-suggested.d.ts +9 -1
- package/dist/bar-suggested.d.ts.map +1 -1
- package/dist/bar-suggested.js +11 -17
- package/dist/bar-suggested.js.map +1 -1
- package/dist/better-bar.d.ts +77 -0
- package/dist/better-bar.d.ts.map +1 -0
- package/dist/better-bar.js +312 -0
- package/dist/better-bar.js.map +1 -0
- package/dist/better-panel.d.ts +55 -0
- package/dist/better-panel.d.ts.map +1 -0
- package/dist/better-panel.js +151 -0
- package/dist/better-panel.js.map +1 -0
- package/dist/editable.d.ts +8 -1
- package/dist/editable.d.ts.map +1 -1
- package/dist/editable.js +140 -16
- package/dist/editable.js.map +1 -1
- package/dist/first-look-door.d.ts +2 -2
- package/dist/first-look-door.js +2 -2
- package/dist/first-look.d.ts.map +1 -1
- package/dist/first-look.js +9 -7
- package/dist/first-look.js.map +1 -1
- package/dist/frame.d.ts +2 -2
- package/dist/frame.d.ts.map +1 -1
- package/dist/frame.js +1 -1
- package/dist/frame.js.map +1 -1
- package/dist/inbox-pane.d.ts +31 -0
- package/dist/inbox-pane.d.ts.map +1 -0
- package/dist/inbox-pane.js +89 -0
- package/dist/inbox-pane.js.map +1 -0
- package/dist/inbox.d.ts +17 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +84 -0
- package/dist/inbox.js.map +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/lens/calendar-drawing.js +7 -3
- package/dist/lens/calendar-drawing.js.map +1 -1
- package/dist/menu-suggested.d.ts +54 -0
- package/dist/menu-suggested.d.ts.map +1 -0
- package/dist/menu-suggested.js +140 -0
- package/dist/menu-suggested.js.map +1 -0
- package/dist/menu-told-door.js +1 -1
- package/dist/menu-told-door.js.map +1 -1
- package/dist/menu-told.d.ts +7 -0
- package/dist/menu-told.d.ts.map +1 -1
- package/dist/menu-told.js +24 -0
- package/dist/menu-told.js.map +1 -1
- package/dist/notify-sheet.js +2 -2
- package/dist/notify-sheet.js.map +1 -1
- package/dist/page-better.d.ts +27 -0
- package/dist/page-better.d.ts.map +1 -0
- package/dist/page-better.js +747 -0
- package/dist/page-better.js.map +1 -0
- package/dist/page-parts.d.ts +41 -0
- package/dist/page-parts.d.ts.map +1 -0
- package/dist/page-parts.js +101 -0
- package/dist/page-parts.js.map +1 -0
- package/dist/pages.d.ts +2 -3
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +2 -3
- package/dist/pages.js.map +1 -1
- package/dist/profile.d.ts +1 -10
- package/dist/profile.d.ts.map +1 -1
- package/dist/profile.js +2 -3
- package/dist/profile.js.map +1 -1
- package/dist/scene-bar.d.ts +1 -1
- package/dist/scene-bar.d.ts.map +1 -1
- package/dist/scene-bar.js +4 -7
- package/dist/scene-bar.js.map +1 -1
- package/dist/section-better.d.ts +66 -9
- package/dist/section-better.d.ts.map +1 -1
- package/dist/section-better.js +285 -136
- package/dist/section-better.js.map +1 -1
- package/dist/section-door.d.ts +7 -0
- package/dist/section-door.d.ts.map +1 -1
- package/dist/section-door.js +39 -5
- package/dist/section-door.js.map +1 -1
- package/dist/section-menu.d.ts +23 -9
- package/dist/section-menu.d.ts.map +1 -1
- package/dist/section-menu.js +176 -23
- package/dist/section-menu.js.map +1 -1
- package/dist/section-words.d.ts +8 -0
- package/dist/section-words.d.ts.map +1 -1
- package/dist/section-words.js +25 -0
- package/dist/section-words.js.map +1 -1
- package/dist/shell.d.ts +7 -1
- package/dist/shell.d.ts.map +1 -1
- package/dist/shell.js +4 -2
- package/dist/shell.js.map +1 -1
- package/dist/suggested.d.ts +21 -10
- package/dist/suggested.d.ts.map +1 -1
- package/dist/suggested.js +80 -66
- package/dist/suggested.js.map +1 -1
- package/dist/suggestion-sentence.d.ts +50 -0
- package/dist/suggestion-sentence.d.ts.map +1 -0
- package/dist/suggestion-sentence.js +119 -0
- package/dist/suggestion-sentence.js.map +1 -0
- package/dist/told-card.js +1 -1
- package/dist/told-card.js.map +1 -1
- package/dist/told-ui.d.ts +1 -1
- package/dist/told-ui.d.ts.map +1 -1
- package/dist/told-ui.js +1 -1
- package/dist/told-ui.js.map +1 -1
- package/dist/when-words.d.ts +21 -0
- package/dist/when-words.d.ts.map +1 -0
- package/dist/when-words.js +43 -0
- package/dist/when-words.js.map +1 -0
- package/dist/workbench/activity.d.ts +44 -0
- package/dist/workbench/activity.d.ts.map +1 -1
- package/dist/workbench/activity.js +74 -59
- package/dist/workbench/activity.js.map +1 -1
- package/dist/workbench/inspector.d.ts.map +1 -1
- package/dist/workbench/inspector.js +170 -31
- package/dist/workbench/inspector.js.map +1 -1
- package/dist/workbench/standing.d.ts.map +1 -1
- package/dist/workbench/standing.js +1 -1
- package/dist/workbench/standing.js.map +1 -1
- package/package.json +6 -6
package/dist/section-better.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
2
|
-
import { layer } from "@graview/core";
|
|
3
|
-
import { compareViewRevision } from "@graview/core/revisions";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { labelOf, layer } from "@graview/core";
|
|
3
|
+
import { compareViewRevision, sectionsOfDrawing } from "@graview/core/revisions";
|
|
4
4
|
import { useGraview } from "@graview/react/provider";
|
|
5
5
|
import { sectionUnderNow, viewPlaceOf } from "@graview/react/drawing";
|
|
6
|
-
import {
|
|
6
|
+
import { CONTROL_WORDS, describeChange, drawnFindings, lookOf, lostUnlessReworded, runDesignAgent } from "@graview/tools/section";
|
|
7
7
|
import { interactionId } from "@graview/tools/inline-ai";
|
|
8
8
|
import { retryingImport } from "@graview/core/retry";
|
|
9
9
|
import { lazyModule } from "@graview/react/provider";
|
|
10
10
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
11
11
|
import { boxOf, SectionOutline } from "./section-outline.js";
|
|
12
|
+
import { headingsOf, readDrawn, recordLabelsOf, Redesigning } from "./better-bar.js";
|
|
13
|
+
import { BetterPanel } from "./better-panel.js";
|
|
12
14
|
export { SectionOutline };
|
|
13
|
-
import { nameParts, partLines, placeOfSection } from "./section-words.js";
|
|
14
|
-
import { VISUALLY_HIDDEN } from "./primitives/measure.js";
|
|
15
|
+
import { aboutOf, nameParts, partLines, placeOfSection } from "./section-words.js";
|
|
15
16
|
/*
|
|
16
17
|
* WHAT WAS TOLD (Roadmap f-menu-learns): each revision offered is kept as a
|
|
17
18
|
* telling β the words, the view's section it was said on, the slot that
|
|
@@ -23,10 +24,15 @@ const toldCard = /* @__PURE__ */ lazyModule(retryingImport(() => import("./told-
|
|
|
23
24
|
const Rate = /* @__PURE__ */ toldCard.part((module, props) => _jsx(module.RateTelling, { ...props }), { quiet: true });
|
|
24
25
|
import "./frame.js"; // A door: tied to what the page drew first (TIED DOORS, packages/primitives/src/frame.ts).
|
|
25
26
|
export function SectionDrawn({ ask, open, close }) {
|
|
26
|
-
if (ask)
|
|
27
|
+
if (ask?.part)
|
|
28
|
+
return _jsx(PageCard, { ask: ask, close: close }, ask.at);
|
|
29
|
+
if (ask?.section)
|
|
27
30
|
return _jsx(SectionCard, { ask: ask, close: close }, ask.at);
|
|
28
31
|
return open ? _jsx(SectionOutline, { elements: open }) : null;
|
|
29
32
|
}
|
|
33
|
+
/* A built-in page's card (Roadmap f-make-any-page-better), fetched the first time it is asked for. */
|
|
34
|
+
const pageCard = /* @__PURE__ */ lazyModule(retryingImport(() => import("./page-better.js")));
|
|
35
|
+
const PageCard = /* @__PURE__ */ pageCard.part((module, props) => _jsx(module.PageCard, { ...props }), { what: "Make this better" });
|
|
30
36
|
const WIDTH = 360;
|
|
31
37
|
const GAP = 12;
|
|
32
38
|
/** The card's place: beside the box where the window has room, else under it, else over it. */
|
|
@@ -97,36 +103,59 @@ function partsIn(root, rect, point) {
|
|
|
97
103
|
}
|
|
98
104
|
return outermost;
|
|
99
105
|
}
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
/**
|
|
107
|
+
* WHAT A REVISION OF A VIEW BROKE IN ITS DRAWING: a class still drawn that
|
|
108
|
+
* lost every rule styling it, and a part drawn before and not now. A part
|
|
109
|
+
* is found by where it stands, not by its words (Roadmap
|
|
110
|
+
* f-better-ui-draws-first-time): one whose heading was reworded in place
|
|
111
|
+
* is the same part, and is the person's to reword where they asked it of
|
|
112
|
+
* the part being changed (`mayReword`, its headings).
|
|
113
|
+
*/
|
|
114
|
+
export function drawingFindings(before, after, options = {}) {
|
|
115
|
+
const side = (one) => ({ source: one.source, css: one.css, ...(one.root ? { drawing: one.root } : {}), ...(one.height !== undefined ? { height: one.height } : {}) });
|
|
116
|
+
const revision = compareViewRevision(side(before), side(after));
|
|
104
117
|
const findings = [];
|
|
105
118
|
for (const one of revision.unstyled)
|
|
106
119
|
findings.push(`The class β${one.class}β is still drawn and no rule styles it now.`);
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
const names = (one) => (one.root ? sectionsOfDrawing(one.root).map((section) => section.name) : []);
|
|
121
|
+
const missing = before.root && after.root ? lostUnlessReworded(revision.sectionsMissing.map((one) => one.name), names(before), names(after), options) : revision.sectionsMissing.map((one) => one.name);
|
|
122
|
+
for (const name of missing)
|
|
123
|
+
findings.push(`β${name}β is not drawn any more.`);
|
|
124
|
+
return findings;
|
|
125
|
+
}
|
|
126
|
+
/** The words of a revision's findings that keep it from being offered. */
|
|
127
|
+
function judged(before, live, liveRefused, draft, source, options) {
|
|
128
|
+
const after = draft.drawing();
|
|
129
|
+
const findings = drawingFindings({ source: before, css: live.css, root: live.root, height: live.height }, { source, css: after.css, root: after.root, height: after.height }, options);
|
|
109
130
|
for (const refused of draft.refused())
|
|
110
131
|
if (!liveRefused.includes(refused))
|
|
111
132
|
findings.push(refused);
|
|
112
|
-
|
|
113
|
-
const height = revision.height;
|
|
114
|
-
if (height && Math.abs(height.ratio) >= 0.1)
|
|
115
|
-
notes.push(`The view is ${Math.abs(Math.round(height.change))} px ${height.change > 0 ? "taller" : "shorter"} (${Math.round(height.before)} β ${Math.round(height.after)}).`);
|
|
116
|
-
void handle;
|
|
117
|
-
return { findings, notes };
|
|
133
|
+
return findings;
|
|
118
134
|
}
|
|
119
135
|
export function SectionCard({ ask, close }) {
|
|
120
|
-
const { ai, aiDesk, principal, keptViews } = useGraview();
|
|
136
|
+
const { ai, aiDesk, aiHere, session, principal, keptViews, store } = useGraview();
|
|
121
137
|
const [section, setSection] = useState(ask.section);
|
|
122
138
|
const place = placeOfSection(ask.section);
|
|
123
139
|
const handle = useRef(viewPlaceOf(place));
|
|
124
140
|
const draft = useRef(null);
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const [phase, setPhase] = useState(ask.mode === "highlight" ? { at: "highlighting" } : ask.mode === "describe" ? { at: "asking" } : { at: "working" });
|
|
128
|
-
const [words, setWords] = useState("");
|
|
141
|
+
const [phase, setPhase] = useState(ask.mode === "highlight" ? { at: "highlighting" } : { at: "open" });
|
|
142
|
+
/* The view's code before any step, the page as it was read, the drafts that passed, the conversation and this turn's steps. */
|
|
129
143
|
const original = useRef(undefined);
|
|
144
|
+
const before = useRef(null);
|
|
145
|
+
/* The headings of the part being changed, read where it stood before any step: the ones a person may ask to reword. */
|
|
146
|
+
const partHeadings = useRef(null);
|
|
147
|
+
const steps = useRef([]);
|
|
148
|
+
const talk = useRef([]);
|
|
149
|
+
const events = useRef([]);
|
|
150
|
+
const since = useRef(Date.now());
|
|
151
|
+
const stopping = useRef(null);
|
|
152
|
+
const pointedAt = useRef(null);
|
|
153
|
+
/** What the conversation is about, where the person pointed at a part of the section. */
|
|
154
|
+
const aboutNow = useRef(null);
|
|
155
|
+
const [covering, setCovering] = useState(false);
|
|
156
|
+
const [comparing, setComparing] = useState(false);
|
|
157
|
+
const [, setTick] = useState(0);
|
|
158
|
+
const redraw = useCallback(() => setTick((one) => one + 1), []);
|
|
130
159
|
/* The telling of the revision on offer, kept with the host's door (`ai.told`), and how it ended. */
|
|
131
160
|
const [telling, setTelling] = useState(null);
|
|
132
161
|
const tellingNow = useRef(null);
|
|
@@ -135,23 +164,32 @@ export function SectionCard({ ask, close }) {
|
|
|
135
164
|
if (now && ai.told)
|
|
136
165
|
void toldCard.load().then((module) => module.changeTelling(ai.told, now.id, { outcome }), () => undefined);
|
|
137
166
|
}, [ai.told]);
|
|
138
|
-
/** Keeps what was said and decided as a telling: offered as a draft ("previewed"), or
|
|
167
|
+
/** Keeps what was said and decided as a telling: offered as a draft ("previewed"), or not. */
|
|
139
168
|
const tell = useCallback((words, on, refused, stages, ms, interaction) => {
|
|
140
169
|
const keeps = ai.told;
|
|
141
170
|
if (!keeps)
|
|
142
171
|
return;
|
|
143
|
-
const slot = stages.at(-1)?.stage === "thinking" ? "full" : stages.length > 0 ? "light" : undefined;
|
|
144
172
|
void toldCard.load().then((module) => {
|
|
145
|
-
const kept = module.keepTelling(keeps, { words, target: { on: "place", place: on.view }, decided: refused ? { type: "refusal" } : { type: "view", section: on.name },
|
|
173
|
+
const kept = module.keepTelling(keeps, { words, target: { on: "place", place: on.view }, decided: refused ? { type: "refusal" } : { type: "view", section: on.name }, slot: "full", stages: stages.map((one) => ({ stage: one.stage === "thinking" ? "full" : "light", ms: one.ms, model: one.model })), ms, outcome: refused ? "abandoned" : "previewed", interaction }, principal);
|
|
146
174
|
tellingNow.current = kept ?? null;
|
|
147
175
|
setTelling(kept ?? null);
|
|
148
176
|
}, () => undefined);
|
|
149
177
|
}, [ai.told, principal]);
|
|
150
178
|
/* Put away: a draft not kept goes with the card, and the view stands as it was. */
|
|
151
179
|
useEffect(() => () => {
|
|
180
|
+
stopping.current?.abort();
|
|
152
181
|
draft.current?.dispose();
|
|
153
182
|
draft.current = null;
|
|
154
183
|
}, []);
|
|
184
|
+
/* GRAVIEW IS HERE WHILE IT WORKS, as it is beside the quick ask. */
|
|
185
|
+
const working = phase.at === "working";
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
if (!working)
|
|
188
|
+
return;
|
|
189
|
+
aiHere({ participant: `agent:graview-for-${principal.id ?? "anyone"}:${session}`, kind: "agent", name: "Graview", ...(principal.id ? { onBehalfOf: principal.id } : {}), over: null, robot: { at: null, mode: "thinking" } });
|
|
190
|
+
return () => aiHere(null);
|
|
191
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
192
|
+
}, [working]);
|
|
155
193
|
const entryOf = useCallback((said, outcome, stages, extra = {}) => ({
|
|
156
194
|
id: interactionId(),
|
|
157
195
|
at: new Date().toISOString(),
|
|
@@ -165,95 +203,221 @@ export function SectionCard({ ask, close }) {
|
|
|
165
203
|
usage: stages.flatMap((one) => (one.usage ? [{ tier: one.stage === "light" ? "fast" : "thinking", model: one.model, inputTokens: one.usage.inputTokens, outputTokens: one.usage.outputTokens }] : [])),
|
|
166
204
|
...extra,
|
|
167
205
|
}), [principal, section.view]);
|
|
168
|
-
/**
|
|
169
|
-
const
|
|
206
|
+
/** The draft drawn in the view's place: the code given, drawn again. */
|
|
207
|
+
const drawDraft = useCallback(async (source) => {
|
|
208
|
+
draft.current?.dispose();
|
|
170
209
|
const view = handle.current;
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
210
|
+
const drawn = view.draft(source);
|
|
211
|
+
draft.current = drawn;
|
|
212
|
+
await drawn.drawn;
|
|
213
|
+
return drawn;
|
|
214
|
+
}, []);
|
|
215
|
+
/** The view as the person last saw it: the newest draft that passed, or the view as it was. */
|
|
216
|
+
const showLatest = useCallback(async () => {
|
|
217
|
+
const latest = steps.current.at(-1);
|
|
218
|
+
setComparing(false);
|
|
219
|
+
if (latest)
|
|
220
|
+
await drawDraft(latest.source);
|
|
221
|
+
else {
|
|
222
|
+
draft.current?.dispose();
|
|
223
|
+
draft.current = null;
|
|
224
|
+
}
|
|
225
|
+
}, [drawDraft]);
|
|
226
|
+
const say = useCallback((who, words) => {
|
|
227
|
+
talk.current.push({ who, said: words });
|
|
228
|
+
redraw();
|
|
229
|
+
}, [redraw]);
|
|
230
|
+
const askedWords = () => talk.current.filter((one) => one.who === "person").map((one) => one.said).join(" ");
|
|
231
|
+
/** One turn of the agent on the section: it looks, drafts the whole script with the section changed, is told what the checks found, and drafts again. */
|
|
232
|
+
const askFor = useCallback(async (words) => {
|
|
233
|
+
const view = handle.current;
|
|
234
|
+
if (!view || phase.at === "working")
|
|
235
|
+
return;
|
|
236
|
+
const pointed = pointedAt.current;
|
|
237
|
+
pointedAt.current = null;
|
|
238
|
+
// The person's words as they typed them: what they are about is said beside them, never glued on.
|
|
239
|
+
if (pointed)
|
|
240
|
+
aboutNow.current = pointed;
|
|
241
|
+
say("person", words);
|
|
242
|
+
events.current = [];
|
|
243
|
+
since.current = Date.now();
|
|
244
|
+
setPhase({ at: "working" });
|
|
245
|
+
const live = await view.source();
|
|
246
|
+
if (live === undefined) {
|
|
247
|
+
say("graview", "This view can't be changed here.");
|
|
248
|
+
return setPhase({ at: "open" });
|
|
249
|
+
}
|
|
250
|
+
original.current ??= live;
|
|
251
|
+
const liveDrawing = view.drawing();
|
|
181
252
|
const liveRefused = view.refused();
|
|
182
|
-
|
|
183
|
-
const
|
|
253
|
+
// Each record by the name its kind gives it, or its link's own words: never a card's words run together.
|
|
254
|
+
const named = (id) => {
|
|
255
|
+
const node = store.graph.getNode(id);
|
|
256
|
+
return node ? labelOf(store.schema.tryDefinition(node.kind), node) : undefined;
|
|
257
|
+
};
|
|
258
|
+
before.current ??= readDrawn(liveDrawing.root?.parentElement ?? liveDrawing.root ?? place, recordLabelsOf(liveDrawing.root, named));
|
|
259
|
+
partHeadings.current ??= headingsOf(section.elements);
|
|
260
|
+
const was = before.current;
|
|
261
|
+
const mayReword = partHeadings.current;
|
|
262
|
+
const stop = new AbortController();
|
|
263
|
+
stopping.current = stop;
|
|
264
|
+
const started = Date.now();
|
|
265
|
+
const turnBegan = steps.current.length;
|
|
266
|
+
const result = await runDesignAgent({
|
|
184
267
|
ai,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
...(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
268
|
+
part: { name: section.name, slot: "place" },
|
|
269
|
+
manifest: { name: view.name, attach: "view", cardinality: "many" },
|
|
270
|
+
talk: talk.current,
|
|
271
|
+
keep: { title: "", headings: was.headings, records: was.records.map((one) => one.label), says: Object.fromEntries(was.records.flatMap((one) => (one.facts?.length || one.under ? [[one.label, { ...(one.facts?.length ? { shows: one.facts } : {}), ...(one.under ? { under: one.under } : {}) }]] : []))) },
|
|
272
|
+
current: steps.current.at(-1)?.source ?? live,
|
|
273
|
+
section: section.name,
|
|
274
|
+
rewordable: mayReword,
|
|
275
|
+
noun: "records",
|
|
276
|
+
...(aboutNow.current ? { about: aboutNow.current } : {}),
|
|
277
|
+
host: {
|
|
278
|
+
look: () => lookOf(readDrawn(view.drawing().root?.parentElement ?? place, was.records.map((one) => one.label))),
|
|
279
|
+
read: () => ({ shows: shownBy(section.elements) }),
|
|
280
|
+
draft: async (source) => {
|
|
281
|
+
setCovering(true);
|
|
282
|
+
try {
|
|
283
|
+
const refused = await view.check(source);
|
|
284
|
+
if (refused.length > 0) {
|
|
285
|
+
await showLatest();
|
|
286
|
+
return { findings: refused, drew: undefined };
|
|
287
|
+
}
|
|
288
|
+
const drawn = await drawDraft(source);
|
|
289
|
+
const { failed } = await drawn.drawn;
|
|
290
|
+
if (failed) {
|
|
291
|
+
await showLatest();
|
|
292
|
+
return { findings: [`The view did not draw: ${failed}`], drew: undefined };
|
|
293
|
+
}
|
|
294
|
+
await new Promise((settled) => requestAnimationFrame(() => requestAnimationFrame(settled)));
|
|
295
|
+
const root = drawn.drawing().root;
|
|
296
|
+
const now = readDrawn(root?.parentElement ?? root, was.records.map((one) => one.label));
|
|
297
|
+
// And every control it draws pressed, headless and nothing applied: one that does nothing, throws or is refused goes back to the agent.
|
|
298
|
+
const pressed = view.presses ? await view.presses(source).catch(() => undefined) : undefined;
|
|
299
|
+
const asked = { asked: askedWords(), mayReword };
|
|
300
|
+
const findings = [...judged(original.current, liveDrawing, liveRefused, drawn, source, asked), ...drawnFindings(was, now, asked), ...(pressed?.problems ?? []).slice(0, 5).map((one) => `${one.control}: ${one.says}`)];
|
|
301
|
+
if (findings.length > 0)
|
|
302
|
+
await showLatest();
|
|
303
|
+
else {
|
|
304
|
+
steps.current.push({ source, said: describeChange(section.name, was, now) ?? `β${section.name}β has changed. Compare Before and After.`, entry: entryOf(`Make β${section.name}β better: ${askedWords()}`, "offered", []) });
|
|
305
|
+
setComparing(false);
|
|
306
|
+
}
|
|
307
|
+
return { findings, drew: lookOf(now) };
|
|
308
|
+
}
|
|
309
|
+
finally {
|
|
310
|
+
setCovering(false);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
onEvent: (event) => {
|
|
315
|
+
// The words as the model writes them stand in for the step it is on, and give way to the step once it is said.
|
|
316
|
+
if (events.current.at(-1)?.kind === "streaming")
|
|
317
|
+
events.current.pop();
|
|
318
|
+
events.current.push(event);
|
|
319
|
+
redraw();
|
|
205
320
|
},
|
|
321
|
+
signal: stop.signal,
|
|
206
322
|
});
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
323
|
+
if (stopping.current !== stop)
|
|
324
|
+
return;
|
|
325
|
+
stopping.current = null;
|
|
326
|
+
if (events.current.at(-1)?.kind === "streaming")
|
|
327
|
+
events.current.pop();
|
|
328
|
+
const offered = steps.current.length > turnBegan;
|
|
329
|
+
const entry = entryOf(`Make β${section.name}β better: ${askedWords()}`, offered ? "offered" : "failed", result.stages, offered ? {} : { failed: result.last?.findings.join(" ") ?? result.said });
|
|
330
|
+
aiDesk.record(entry);
|
|
331
|
+
tell(askedWords(), section, !offered, result.stages, Date.now() - started, entry.id);
|
|
332
|
+
// What a control does is the press check's to say, never the model's.
|
|
333
|
+
say("graview", result.passed && CONTROL_WORDS.test(result.said) ? "Here it is on the page." : result.said);
|
|
334
|
+
// Two drafts failed the same way: the person chooses β keep the last one anyway, where it drew, or stop.
|
|
335
|
+
if (result.ended === "stuck" && !offered)
|
|
336
|
+
return setPhase({ at: "stuck", ...(result.keepable && result.last ? { source: result.last.source } : {}) });
|
|
337
|
+
setPhase(steps.current.length > 0 ? { at: "draft" } : { at: "open" });
|
|
338
|
+
}, [ai, aiDesk, drawDraft, entryOf, phase.at, place, redraw, say, section, showLatest, store, tell]);
|
|
339
|
+
/** Stop: at once β the turn's drafts gone, the view as it was before it began. */
|
|
340
|
+
const stopTurn = useCallback(() => {
|
|
341
|
+
if (phase.at !== "working")
|
|
342
|
+
return;
|
|
343
|
+
const stop = stopping.current;
|
|
344
|
+
stopping.current = null;
|
|
345
|
+
stop?.abort();
|
|
346
|
+
setCovering(false);
|
|
347
|
+
const began = new Date(since.current).toISOString();
|
|
348
|
+
steps.current = steps.current.filter((one) => one.entry.at < began);
|
|
349
|
+
void showLatest();
|
|
350
|
+
say("graview", "Stopped. The view is as it was.");
|
|
351
|
+
setPhase(steps.current.length > 0 ? { at: "draft" } : { at: "open" });
|
|
352
|
+
}, [phase.at, say, showLatest]);
|
|
353
|
+
/* Words that came with the ask β typed in the menu, or a suggestion taken β are asked at once. */
|
|
221
354
|
const began = useRef(false);
|
|
222
355
|
useEffect(() => {
|
|
223
|
-
if (ask.mode
|
|
356
|
+
if (began.current || !(ask.mode === "just" || (ask.mode === "describe" && ask.words)))
|
|
224
357
|
return;
|
|
225
358
|
began.current = true;
|
|
226
|
-
void
|
|
227
|
-
}, [ask.mode,
|
|
228
|
-
const
|
|
229
|
-
|
|
359
|
+
void askFor(ask.mode === "just" ? "Make it easier to read at a glance" : ask.words);
|
|
360
|
+
}, [ask.mode, ask.words, askFor]);
|
|
361
|
+
const current = steps.current.at(-1);
|
|
362
|
+
const compare = useCallback(async (originalShown) => {
|
|
363
|
+
if (!current || originalShown === comparing)
|
|
364
|
+
return;
|
|
365
|
+
setComparing(originalShown);
|
|
366
|
+
if (originalShown) {
|
|
367
|
+
draft.current?.dispose();
|
|
368
|
+
draft.current = null;
|
|
369
|
+
}
|
|
370
|
+
else
|
|
371
|
+
await drawDraft(current.source);
|
|
372
|
+
}, [current, comparing, drawDraft]);
|
|
373
|
+
const keep = useCallback(async (anyway) => {
|
|
374
|
+
// Kept anyway: the last draft of a turn that failed the same way twice, as the person chose.
|
|
375
|
+
if (anyway !== undefined && phase.at === "stuck")
|
|
376
|
+
steps.current.push({ source: anyway, said: `β${section.name}β kept as it was last drafted.`, entry: entryOf(`Make β${section.name}β better: ${askedWords()}`, "offered", []) });
|
|
377
|
+
const latest = steps.current.at(-1);
|
|
378
|
+
if ((phase.at !== "draft" && !(anyway !== undefined && phase.at === "stuck")) || !latest || !keptViews?.revise || !handle.current)
|
|
230
379
|
return;
|
|
231
380
|
const view = handle.current;
|
|
232
|
-
const
|
|
233
|
-
setPhase({ at: "keeping"
|
|
381
|
+
const was = (await view.source()) ?? "";
|
|
382
|
+
setPhase({ at: "keeping" });
|
|
234
383
|
try {
|
|
235
|
-
const answer = await keptViews.revise({ name: view.name, title: view.title, source:
|
|
384
|
+
const answer = await keptViews.revise({ name: view.name, title: view.title, source: latest.source, before: was, section: section.name, said: latest.said });
|
|
236
385
|
draft.current?.dispose();
|
|
237
386
|
draft.current = null;
|
|
238
|
-
view.draw(
|
|
239
|
-
|
|
240
|
-
aiDesk.record(entry);
|
|
387
|
+
view.draw(latest.source);
|
|
388
|
+
aiDesk.record({ ...latest.entry, outcome: "accepted" });
|
|
241
389
|
ended("accepted");
|
|
242
|
-
|
|
390
|
+
steps.current = [];
|
|
391
|
+
setPhase({ at: "kept", source: was, ...(answer && answer.note ? { note: answer.note } : {}), answer, entry: latest.entry });
|
|
243
392
|
}
|
|
244
|
-
catch
|
|
245
|
-
|
|
393
|
+
catch {
|
|
394
|
+
say("graview", "It couldn't be kept.");
|
|
395
|
+
if (anyway !== undefined) {
|
|
396
|
+
steps.current.pop();
|
|
397
|
+
return setPhase(steps.current.length > 0 ? { at: "draft" } : { at: "open" });
|
|
398
|
+
}
|
|
399
|
+
setPhase({ at: "draft" });
|
|
246
400
|
}
|
|
247
|
-
|
|
401
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
402
|
+
}, [phase, keptViews, aiDesk, section.name, ended, say, entryOf]);
|
|
403
|
+
/** Stop, where two drafts failed the same way: nothing kept, the view as it was. */
|
|
404
|
+
const stopStuck = useCallback(() => {
|
|
405
|
+
say("graview", "Stopped. The view is as it was.");
|
|
406
|
+
setPhase(steps.current.length > 0 ? { at: "draft" } : { at: "open" });
|
|
407
|
+
}, [say]);
|
|
248
408
|
const discard = useCallback(() => {
|
|
249
409
|
draft.current?.dispose();
|
|
250
410
|
draft.current = null;
|
|
251
|
-
|
|
252
|
-
|
|
411
|
+
const latest = steps.current.at(-1);
|
|
412
|
+
if (latest) {
|
|
413
|
+
aiDesk.record({ ...latest.entry, outcome: "dismissed" });
|
|
253
414
|
ended("declined");
|
|
254
415
|
}
|
|
255
|
-
|
|
256
|
-
|
|
416
|
+
steps.current = [];
|
|
417
|
+
setComparing(false);
|
|
418
|
+
say("graview", "Put back as it was.");
|
|
419
|
+
setPhase({ at: "open" });
|
|
420
|
+
}, [aiDesk, ended, say]);
|
|
257
421
|
const undo = useCallback(async () => {
|
|
258
422
|
if (phase.at !== "kept" || !handle.current)
|
|
259
423
|
return;
|
|
@@ -268,61 +432,44 @@ export function SectionCard({ ask, close }) {
|
|
|
268
432
|
aiDesk.record({ ...phase.entry, outcome: "dismissed" });
|
|
269
433
|
// A quiet sign, never a π.
|
|
270
434
|
ended("undone");
|
|
435
|
+
say("graview", "Undone. The view is as it was.");
|
|
271
436
|
setPhase({ at: "undone" });
|
|
272
437
|
}
|
|
273
|
-
catch
|
|
274
|
-
|
|
438
|
+
catch {
|
|
439
|
+
say("graview", "It couldn't be put back.");
|
|
275
440
|
}
|
|
276
|
-
}, [phase, keptViews, aiDesk, section.name, ended]);
|
|
277
|
-
/*
|
|
278
|
-
useLayoutEffect(() => {
|
|
279
|
-
let frame = 0;
|
|
280
|
-
const follow = () => {
|
|
281
|
-
if (card.current) {
|
|
282
|
-
const drafted = place?.querySelector("[data-graview-section-draft]");
|
|
283
|
-
placeBeside(card.current, drafted ? boxOf([drafted]) : boxOf(section.elements) ?? (place ? boxOf([place]) : null));
|
|
284
|
-
}
|
|
285
|
-
frame = requestAnimationFrame(follow);
|
|
286
|
-
};
|
|
287
|
-
follow();
|
|
288
|
-
return () => cancelAnimationFrame(frame);
|
|
289
|
-
}, [section, place]);
|
|
290
|
-
/* The keyboard: in the field when it asks, else on the card's first button. */
|
|
291
|
-
useEffect(() => {
|
|
292
|
-
if (phase.at === "asking")
|
|
293
|
-
field.current?.focus();
|
|
294
|
-
else if (phase.at !== "highlighting")
|
|
295
|
-
card.current?.querySelector("button[data-first]")?.focus();
|
|
296
|
-
}, [phase.at]);
|
|
297
|
-
/* Escape puts it away: a draft is discarded, the keyboard goes back where it was. */
|
|
441
|
+
}, [phase, keptViews, aiDesk, section.name, ended, say]);
|
|
442
|
+
/* Escape: Stop while it works, else put it away. */
|
|
298
443
|
useEffect(() => {
|
|
299
444
|
const key = (event) => {
|
|
300
|
-
if (event.key !== "Escape")
|
|
445
|
+
if (event.key !== "Escape" || phase.at === "highlighting")
|
|
301
446
|
return;
|
|
302
447
|
event.preventDefault();
|
|
303
|
-
|
|
448
|
+
if (phase.at === "working")
|
|
449
|
+
return stopTurn();
|
|
450
|
+
if (steps.current.length > 0)
|
|
451
|
+
ended("declined");
|
|
452
|
+
close();
|
|
304
453
|
ask.returnTo?.focus?.();
|
|
305
454
|
};
|
|
306
455
|
document.addEventListener("keydown", key);
|
|
307
456
|
return () => document.removeEventListener("keydown", key);
|
|
308
|
-
}, [
|
|
309
|
-
const pointed = phase.at === "asking" || phase.at === "working" ? phase.pointed : undefined;
|
|
457
|
+
}, [phase.at, stopTurn, close, ended, ask.returnTo]);
|
|
310
458
|
if (phase.at === "highlighting") {
|
|
311
|
-
return (_jsx(Highlighter, { place: place,
|
|
459
|
+
return (_jsx(Highlighter, { box: place, root: place?.querySelector("[data-worker-view]")?.shadowRoot?.querySelector("[data-graview-view-root]") ?? undefined, whole: section.elements, cancel: () => setPhase({ at: "open" }), chose: (elements) => {
|
|
312
460
|
const first = elements[0];
|
|
313
461
|
const under = first ? sectionUnderNow(first) : undefined;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
setPhase({ at: "
|
|
462
|
+
setSection(under ?? section);
|
|
463
|
+
pointedAt.current = { name: aboutOf(elements) ?? nameParts(elements.map(readPart)), lines: partLines(elements.map(readPart)) };
|
|
464
|
+
setPhase({ at: "open" });
|
|
317
465
|
} }));
|
|
318
466
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}, style: { display: "grid", gap: 8 }, children: [_jsx("label", { style: VISUALLY_HIDDEN, htmlFor: "graview-section-words", children: phase.pointed ? "What should change here?" : "What should change?" }), _jsx("input", { id: "graview-section-words", ref: field, "data-testid": "section-words", value: words, onChange: (event) => setWords(event.target.value), placeholder: phase.pointed ? "What should change here?" : "What should change?", style: FIELD }), _jsxs("div", { style: ROW, children: [_jsx("button", { type: "submit", "data-first": "", disabled: !words.trim(), style: PRIMARY, children: "Revise" }), _jsx("button", { type: "button", onClick: close, style: QUIET, children: "Cancel" })] })] })) : null, _jsx("p", { role: "status", "aria-live": "polite", "data-testid": "section-status", style: phase.at === "working" || phase.at === "keeping" ? THINKING : MUTED, children: phase.at === "working" ? `Revising β${section.name}ββ¦` : phase.at === "keeping" ? "Keeping itβ¦" : phase.at === "draft" ? phase.said : phase.at === "kept" ? `Kept.${phase.note ? ` ${phase.note}` : ""}` : phase.at === "undone" ? "Put back as it was." : phase.at === "refused" ? phase.said : "" }), phase.at === "draft" ? (_jsxs(_Fragment, { children: [_jsx("p", { "data-testid": "section-draft-line", style: MUTED, children: "Draft \u2014 shown in place, not kept." }), phase.notes.map((note) => (_jsx("p", { "data-testid": "section-note", style: MUTED, children: note }, note))), _jsxs("div", { style: ROW, children: [_jsx("button", { type: "button", "data-first": "", "data-testid": "section-keep", onClick: () => void keep(), style: PRIMARY, children: "Keep" }), _jsx("button", { type: "button", "data-testid": "section-discard", onClick: discard, style: QUIET, children: "Discard" })] })] })) : null, phase.at === "kept" ? (_jsxs("div", { style: ROW, children: [_jsx("button", { type: "button", "data-first": "", "data-testid": "section-undo", onClick: () => void undo(), style: QUIET, children: "Undo" }), _jsx("button", { type: "button", onClick: close, style: QUIET, children: "Close" })] })) : null, phase.at === "refused" || phase.at === "undone" ? (_jsx("div", { style: ROW, children: _jsx("button", { type: "button", "data-first": "", onClick: close, style: QUIET, children: "Close" }) })) : null, telling && ai.told && phase.at !== "working" && phase.at !== "asking" ? _jsx(Rate, { told: ai.told, telling: telling }) : null] })] }));
|
|
467
|
+
return (_jsxs(_Fragment, { children: [section.elements.length > 0 && phase.at === "open" && talk.current.length === 0 ? _jsx(SectionOutline, { elements: section.elements }) : null, covering ? _jsx(Redesigning, { over: place, name: section.name, said: `Graview is drafting β${section.name}ββ¦` }) : null, _jsx(BetterPanel, { name: section.name, scope: `This changes how β${section.name}β looks for everyone in this app.`, phase: phase.at, talk: talk.current, steps: events.current, since: since.current, working: phase.at === "working" || phase.at === "keeping", ...(current && phase.at === "draft" ? { draft: { said: current.said, before: comparing } } : {}), ...(phase.at === "kept" ? { kept: { ...(phase.note ? { note: phase.note } : {}), undo: () => void undo() } } : {}), ...(phase.at === "stuck" ? { stuck: { ...(phase.source !== undefined ? { keepAnyway: () => void keep(phase.source) } : {}), stop: stopStuck } } : {}), about: (pointedAt.current ?? aboutNow.current)?.name ?? section.name, ask: (words) => void askFor(words), just: () => void askFor("Make it easier to read at a glance"), point: () => setPhase({ at: "highlighting" }), stop: stopTurn, compare: (originalShown) => void compare(originalShown), keep: () => void keep(), discard: discard, close: () => {
|
|
468
|
+
stopping.current?.abort();
|
|
469
|
+
if (steps.current.length > 0)
|
|
470
|
+
ended("declined");
|
|
471
|
+
close();
|
|
472
|
+
}, children: telling && ai.told && phase.at !== "working" ? _jsx(Rate, { told: ai.told, telling: telling }) : null })] }));
|
|
326
473
|
}
|
|
327
474
|
/**
|
|
328
475
|
* HIGHLIGHT AREA: an overlay over the view. A drag draws a rectangle and
|
|
@@ -330,10 +477,9 @@ export function SectionCard({ ask, close }) {
|
|
|
330
477
|
* or "Cancel", puts it away; "The whole section" takes the section itself
|
|
331
478
|
* for a keyboard.
|
|
332
479
|
*/
|
|
333
|
-
function Highlighter({ place, section, cancel, chose }) {
|
|
480
|
+
export function Highlighter({ box: place, root, whole, wholeSaid = "The whole section", cancel, chose }) {
|
|
334
481
|
const cover = useRef(null);
|
|
335
482
|
const [drag, setDrag] = useState(null);
|
|
336
|
-
const root = place?.querySelector("[data-worker-view]")?.shadowRoot?.querySelector("[data-graview-view-root]") ?? undefined;
|
|
337
483
|
useLayoutEffect(() => {
|
|
338
484
|
let frame = 0;
|
|
339
485
|
const follow = () => {
|
|
@@ -377,8 +523,9 @@ function Highlighter({ place, section, cancel, chose }) {
|
|
|
377
523
|
event.preventDefault();
|
|
378
524
|
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
379
525
|
setDrag({ x0: event.clientX, y0: event.clientY, x1: event.clientX, y1: event.clientY });
|
|
380
|
-
}, onPointerMove: (event) => setDrag((was) => (was ? { ...was, x1: event.clientX, y1: event.clientY } : was)), onPointerUp: end, onPointerCancel: () => setDrag(null), style: COVER, children: [_jsxs("p", { style: COVER_HINT, children: ["Drag over the part to change, or click one \u00B7", " ", _jsx("button", { type: "button", onPointerDown: (event) => event.stopPropagation(), onClick: () => chose(
|
|
526
|
+
}, onPointerMove: (event) => setDrag((was) => (was ? { ...was, x1: event.clientX, y1: event.clientY } : was)), onPointerUp: end, onPointerCancel: () => setDrag(null), style: COVER, children: [_jsxs("p", { style: COVER_HINT, children: ["Drag over the part to change, or click one \u00B7", " ", _jsx("button", { type: "button", onPointerDown: (event) => event.stopPropagation(), onClick: () => chose(whole), style: LINK, children: wholeSaid }), " ", "\u00B7", " ", _jsx("button", { type: "button", onPointerDown: (event) => event.stopPropagation(), onClick: cancel, style: LINK, children: "Cancel" })] }), box ? _jsx("div", { "data-testid": "section-drag", style: { ...DRAG, left: box.left - (cover.current?.getBoundingClientRect().left ?? 0), top: box.top - (cover.current?.getBoundingClientRect().top ?? 0), width: box.width, height: box.height } }) : null] }));
|
|
381
527
|
}
|
|
528
|
+
export { readPart, placeBeside };
|
|
382
529
|
const CARD = {
|
|
383
530
|
position: "fixed",
|
|
384
531
|
zIndex: layer("popover"),
|
|
@@ -408,4 +555,6 @@ const PAD = 12;
|
|
|
408
555
|
const COVER = { userSelect: "none", WebkitUserSelect: "none", position: "fixed", zIndex: layer("popover"), boxSizing: "border-box", cursor: "crosshair", touchAction: "none", outline: "2px dashed var(--graview-accent)", background: "color-mix(in srgb, var(--graview-accent) 6%, transparent)" };
|
|
409
556
|
const COVER_HINT = { position: "absolute", left: 8, top: 8, margin: 0, padding: "4px 10px", borderRadius: 999, fontSize: "0.8125rem", color: "var(--graview-ink)", background: "var(--graview-float, var(--graview-panel))", border: "1px solid var(--graview-edge)", boxShadow: "var(--graview-lift-low)", cursor: "default" };
|
|
410
557
|
const DRAG = { position: "absolute", border: "2px solid var(--graview-accent)", background: "color-mix(in srgb, var(--graview-accent) 14%, transparent)", borderRadius: 4, pointerEvents: "none" };
|
|
558
|
+
/** The card's look, shared with a built-in page's card. */
|
|
559
|
+
export const LOOK = { CARD, HEAD, MUTED, THINKING, ROW, FIELD, PRIMARY, QUIET };
|
|
411
560
|
//# sourceMappingURL=section-better.js.map
|