@aglyn/plugins-ai 1.0.0-beta.293 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/components/ai-job-links.d.ts +17 -3
- package/src/lib/components/ai-job-links.js +32 -5
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +9 -0
- package/src/lib/components/ai-job-requests.js +34 -0
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +28 -2
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/assist-edit-canvas.d.ts +12 -6
- package/src/lib/components/assist-edit-canvas.js +86 -36
- package/src/lib/components/assist-edit-canvas.js.map +1 -1
- package/src/lib/components/assist-edit-card.component.js +9 -8
- package/src/lib/components/assist-edit-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.d.ts +7 -1
- package/src/lib/components/assist-jobs-drawer.component.js +20 -6
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-page-change-card.component.d.ts +44 -0
- package/src/lib/components/assist-page-change-card.component.js +166 -0
- package/src/lib/components/assist-page-change-card.component.js.map +1 -0
- package/src/lib/components/assist-panel.component.d.ts +105 -1
- package/src/lib/components/assist-panel.component.js +242 -36
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/jobs/ai-job-edit-step.d.ts +82 -16
- package/src/lib/jobs/ai-job-edit-step.js +209 -62
- package/src/lib/jobs/ai-job-edit-step.js.map +1 -1
- package/src/lib/jobs/ai-job-navigation-step.js +4 -2
- package/src/lib/jobs/ai-job-navigation-step.js.map +1 -1
- package/src/lib/model/ai-edit-job.d.ts +33 -16
- package/src/lib/model/ai-edit-job.js +50 -34
- package/src/lib/model/ai-edit-job.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +6 -11
- package/src/lib/model/ai-job-activity.js +8 -15
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-notice.js +3 -1
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +6 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-inventory.d.ts +6 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/assist-edit.d.ts +19 -1
- package/src/lib/model/assist-edit.js +7 -0
- package/src/lib/model/assist-edit.js.map +1 -1
- package/src/lib/model/assist-page-change.d.ts +100 -0
- package/src/lib/model/assist-page-change.js +107 -0
- package/src/lib/model/assist-page-change.js.map +1 -0
- package/src/lib/runtime/ai-eval.js +3 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.d.ts +5 -0
- package/src/lib/runtime/ai-node-tree.js +18 -3
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/site-inventory.d.ts +8 -1
- package/src/lib/runtime/site-inventory.js +18 -0
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/assist-build.js +2 -0
- package/src/lib/server/assist-build.js.map +1 -1
- package/src/lib/server/assist-chat.js +56 -3
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit.d.ts +61 -4
- package/src/lib/server/assist-edit.js +248 -38
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/server/assist-page-change.d.ts +62 -0
- package/src/lib/server/assist-page-change.js +140 -0
- package/src/lib/server/assist-page-change.js.map +1 -0
|
@@ -25,7 +25,7 @@ import { AppLink, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
|
25
25
|
import { BetaChip } from "@aglyn/shared-ui-jsx/components/beta-chip.component";
|
|
26
26
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
27
27
|
import { Alert, Badge, Box, Button, Chip, CircularProgress, Collapse, Drawer, Fab, IconButton, Link, Paper, Stack, TextField, Tooltip, Typography } from "@mui/material";
|
|
28
|
-
import { usePathname } from "next/navigation";
|
|
28
|
+
import { usePathname, useRouter } from "next/navigation";
|
|
29
29
|
import { Fragment, memo, useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
30
30
|
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
31
31
|
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
@@ -35,11 +35,14 @@ import { AssistEditCard } from "./assist-edit-card.component.js";
|
|
|
35
35
|
import { AssistBuildCard } from "./assist-build-card.component.js";
|
|
36
36
|
import { isAssistBuildProposal } from "../model/assist-build.js";
|
|
37
37
|
import { assistBuildDraftLinks, isAssistFollowUpDue, isLiveAssistFollowUp } from "../model/assist-follow-up.js";
|
|
38
|
-
import { startAssistBuildRequest } from "./ai-job-requests.js";
|
|
38
|
+
import { cancelAiJobRequest, startAssistBuildRequest, startAssistPageChangeRequest } from "./ai-job-requests.js";
|
|
39
|
+
import { assistPageChangeDraftVersion, assistPageChangeHistoryNote, isAssistPageChangeProposal } from "../model/assist-page-change.js";
|
|
40
|
+
import { AssistPageChangeCard } from "./assist-page-change-card.component.js";
|
|
39
41
|
import { aiInsightSurfaceForPath } from "../model/ai-insight.js";
|
|
40
42
|
import { AssistJobsDrawer } from "./assist-jobs-drawer.component.js";
|
|
41
43
|
import { AI_JOB_LINK_PARAM, openAiJobs, useAiJobsInFlight, useAiJobsOpenRequest } from "./ai-jobs-store.js";
|
|
42
|
-
import { aiJobsActivity, aiJobsLauncherLabel } from "../model/ai-job-activity.js";
|
|
44
|
+
import { aiJobPhase, aiJobsActivity, aiJobsLauncherLabel } from "../model/ai-job-activity.js";
|
|
45
|
+
import { AI_SITE_BUILD_HREF } from "../model/ai-job-notice.js";
|
|
43
46
|
import { AiModelSelector } from "./ai-model-selector.component.js";
|
|
44
47
|
import { AiUsageStrip } from "./ai-usage-strip.component.js";
|
|
45
48
|
import { useAiModelChoice } from "./use-ai-model-choice.js";
|
|
@@ -47,6 +50,15 @@ import { followAiJobsForUsageMeter, refreshAiUsageMeterFree } from "./ai-usage-m
|
|
|
47
50
|
import { readAiUsageMeter, usePublishAiUsageMeter } from "./use-ai-usage-meter.js";
|
|
48
51
|
import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
|
|
49
52
|
import { HelpTip } from "@aglyn/shared-ui-jsx";
|
|
53
|
+
/**
|
|
54
|
+
* A turn as the next question's history carries it (AGL-4116): its words, and
|
|
55
|
+
* what became of the change it made, which the words alone never say.
|
|
56
|
+
*/ function assistHistoryText(message) {
|
|
57
|
+
var _assistPageChangeHistoryNote;
|
|
58
|
+
var _message_editOutcome;
|
|
59
|
+
const note = (_assistPageChangeHistoryNote = assistPageChangeHistoryNote(message)) != null ? _assistPageChangeHistoryNote : message.edit && ((_message_editOutcome = message.editOutcome) == null ? void 0 : _message_editOutcome.kind) === 'applied' ? `(Applied to the open canvas as an unsaved change: ${message.edit.summary})` : null;
|
|
60
|
+
return note ? `${message.text}\n\n${note}` : message.text;
|
|
61
|
+
}
|
|
50
62
|
/** The key a follow-up waiting for its canvas is kept under, per org (AGL-3616). */ const followUpKey = (orgId)=>`aglyn-assist-follow-up:${orgId}`;
|
|
51
63
|
function loadFollowUp(orgId) {
|
|
52
64
|
if (!orgId || typeof sessionStorage === 'undefined') return null;
|
|
@@ -80,28 +92,69 @@ function saveFollowUp(orgId, followUp) {
|
|
|
80
92
|
] : []).reverse();
|
|
81
93
|
return assistBuildDraftLinks(jobs, orgSlug);
|
|
82
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* The builds in a thread whose plan is still waiting on the person
|
|
97
|
+
* (AGL-4116): queued, being planned, or planned and not yet confirmed. A new
|
|
98
|
+
* plan in the same thread replaces them, so a conversation that keeps
|
|
99
|
+
* changing a plan holds one plan, not one per message.
|
|
100
|
+
*/ export function assistUnconfirmedBuilds(messages) {
|
|
101
|
+
return messages.flatMap((message, index)=>{
|
|
102
|
+
const job = message.build && !message.buildReplaced ? message.buildJob : null;
|
|
103
|
+
if (!job) return [];
|
|
104
|
+
const phase = aiJobPhase(job);
|
|
105
|
+
return phase === 'queued' || phase === 'planning' || phase === 'plan-ready' ? [
|
|
106
|
+
{
|
|
107
|
+
index,
|
|
108
|
+
jobId: job.id
|
|
109
|
+
}
|
|
110
|
+
] : [];
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** What a plan a later one replaced says in its card's place (AGL-4116). */ export const ASSIST_BUILD_REPLACED_COPY = 'Replaced by the updated plan below. Nothing from this one was built.';
|
|
83
114
|
/** What a stored build that never got its job says, rather than planning forever. */ const BUILD_NOT_STARTED = 'This build was not started. Ask again to plan it.';
|
|
115
|
+
/** What a stored page change that never got its job says (AGL-4116). */ const PAGE_CHANGE_NOT_STARTED = 'This change was not started. Ask again to make it.';
|
|
84
116
|
const HISTORY_TURNS_SENT = 12;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
117
|
+
/**
|
|
118
|
+
* The thread a question belongs to: one per site, and one for the
|
|
119
|
+
* workspace's own pages (AGL-4116). A workspace-wide thread carried one
|
|
120
|
+
* site's request into a build on another site, because the history that
|
|
121
|
+
* rides each question is the thread's last turns.
|
|
122
|
+
*/ export function assistThreadKey(orgId, hostId) {
|
|
123
|
+
return hostId ? `aglyn-assist:${orgId}:${hostId}` : `aglyn-assist:${orgId}`;
|
|
124
|
+
}
|
|
125
|
+
const NO_MESSAGES = [];
|
|
126
|
+
/**
|
|
127
|
+
* The AI jobs page of the site a console page is on (AGL-4116), or `null`
|
|
128
|
+
* off a site: `/{orgSlug}/hosts/{site}/ai-jobs`, the site named as the
|
|
129
|
+
* address names it.
|
|
130
|
+
*/ export function assistSiteAiJobsHref(pathname, orgSlug, hostId) {
|
|
131
|
+
if (!pathname || !orgSlug || !hostId) return null;
|
|
132
|
+
const parts = pathname.split('/').filter(Boolean);
|
|
133
|
+
const site = parts[0] === orgSlug && parts[1] === 'hosts' ? parts[2] : undefined;
|
|
134
|
+
return site ? `/${orgSlug}/hosts/${site}${AI_SITE_BUILD_HREF}` : null;
|
|
135
|
+
}
|
|
136
|
+
function loadThread(key) {
|
|
137
|
+
if (typeof sessionStorage === 'undefined') return [];
|
|
88
138
|
try {
|
|
89
|
-
const raw = sessionStorage.getItem(
|
|
139
|
+
const raw = sessionStorage.getItem(key);
|
|
90
140
|
const parsed = raw ? JSON.parse(raw) : [];
|
|
91
141
|
if (!Array.isArray(parsed)) return [];
|
|
92
142
|
// A build whose job was still being created when the page went away is
|
|
93
143
|
// not started again from a stored thread (AGL-3616): it says so instead.
|
|
144
|
+
// A page change likewise (AGL-4116).
|
|
94
145
|
return parsed.map((message)=>message.build && !message.buildJob && !message.buildNotice ? _extends({}, message, {
|
|
95
146
|
buildNotice: BUILD_NOT_STARTED
|
|
147
|
+
}) : message.pageChange && !message.pageChangeJob && !message.pageChangeNotice ? _extends({}, message, {
|
|
148
|
+
pageChangeNotice: PAGE_CHANGE_NOT_STARTED
|
|
96
149
|
}) : message);
|
|
97
150
|
} catch (unused) {
|
|
98
151
|
return [];
|
|
99
152
|
}
|
|
100
153
|
}
|
|
101
|
-
function saveThread(
|
|
102
|
-
if (
|
|
154
|
+
function saveThread(key, messages) {
|
|
155
|
+
if (typeof sessionStorage === 'undefined') return;
|
|
103
156
|
try {
|
|
104
|
-
sessionStorage.setItem(
|
|
157
|
+
sessionStorage.setItem(key, JSON.stringify(messages));
|
|
105
158
|
} catch (unused) {
|
|
106
159
|
// Quota/full storage never breaks chat.
|
|
107
160
|
}
|
|
@@ -523,7 +576,25 @@ export function AssistPanelComponent(props) {
|
|
|
523
576
|
const { enqueueSnackbar } = useSnackbar();
|
|
524
577
|
const [open, setOpen] = useState(false);
|
|
525
578
|
const [busy, setBusy] = useState(false);
|
|
526
|
-
|
|
579
|
+
// Every thread this panel has opened, by its key (AGL-4116): the one on
|
|
580
|
+
// screen is this site's, and an answer still streaming lands in the thread
|
|
581
|
+
// it was asked in, even after the page moved to another site.
|
|
582
|
+
const threadKey = scopedOrgId ? assistThreadKey(scopedOrgId, hostId) : null;
|
|
583
|
+
const [threads, setThreads] = useState({});
|
|
584
|
+
const messages = threadKey && threads[threadKey] || NO_MESSAGES;
|
|
585
|
+
const updateThread = useCallback((key, update)=>{
|
|
586
|
+
if (!key) return;
|
|
587
|
+
setThreads((prior)=>{
|
|
588
|
+
var _prior_key;
|
|
589
|
+
return _extends({}, prior, {
|
|
590
|
+
[key]: update((_prior_key = prior[key]) != null ? _prior_key : loadThread(key))
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
}, []);
|
|
594
|
+
const setMessages = useCallback((update)=>updateThread(threadKey, update), [
|
|
595
|
+
threadKey,
|
|
596
|
+
updateThread
|
|
597
|
+
]);
|
|
527
598
|
const [quota, setQuota] = useState(null);
|
|
528
599
|
const scrollRef = useRef(null);
|
|
529
600
|
// The model switch and the usage strip (AGL-2942): the pick rides the
|
|
@@ -540,22 +611,40 @@ export function AssistPanelComponent(props) {
|
|
|
540
611
|
// the one open.
|
|
541
612
|
const editorRevision = useSyncExternalStore(subscribeEditorSessions, editorSessionsRevision, editorSessionsRevision);
|
|
542
613
|
const editorSession = openEditorSession();
|
|
543
|
-
//
|
|
544
|
-
// Keyed on the SCOPED org (AGL-1934): gating only
|
|
545
|
-
// load — and, on the next keystroke, save — a
|
|
546
|
-
// org's key on every mount of a page that named
|
|
614
|
+
// A thread per site, session-persisted; moving to another site or org
|
|
615
|
+
// swaps threads (AGL-4116). Keyed on the SCOPED org (AGL-1934): gating only
|
|
616
|
+
// the render would still load — and, on the next keystroke, save — a
|
|
617
|
+
// thread under the fallback org's key on every mount of a page that named
|
|
618
|
+
// no workspace.
|
|
619
|
+
useEffect(()=>{
|
|
620
|
+
if (!threadKey) return;
|
|
621
|
+
setThreads((prior)=>threadKey in prior ? prior : _extends({}, prior, {
|
|
622
|
+
[threadKey]: loadThread(threadKey)
|
|
623
|
+
}));
|
|
624
|
+
}, [
|
|
625
|
+
threadKey
|
|
626
|
+
]);
|
|
547
627
|
useEffect(()=>{
|
|
548
|
-
setMessages(loadThread(scopedOrgId));
|
|
549
628
|
setQuota(null);
|
|
550
629
|
}, [
|
|
551
630
|
scopedOrgId
|
|
552
631
|
]);
|
|
632
|
+
// Each thread is written back when it changes, the one on screen or one an
|
|
633
|
+
// answer is still streaming into.
|
|
634
|
+
const savedThreads = useRef({});
|
|
635
|
+
useEffect(()=>{
|
|
636
|
+
for (const [key, list] of Object.entries(threads)){
|
|
637
|
+
if (savedThreads.current[key] === list) continue;
|
|
638
|
+
savedThreads.current[key] = list;
|
|
639
|
+
saveThread(key, list);
|
|
640
|
+
}
|
|
641
|
+
}, [
|
|
642
|
+
threads
|
|
643
|
+
]);
|
|
553
644
|
useEffect(()=>{
|
|
554
|
-
saveThread(scopedOrgId, messages);
|
|
555
645
|
const node = scrollRef.current;
|
|
556
646
|
if (node) node.scrollTop = node.scrollHeight;
|
|
557
647
|
}, [
|
|
558
|
-
scopedOrgId,
|
|
559
648
|
messages
|
|
560
649
|
]);
|
|
561
650
|
const entitled = orgReady && checkEntitlement(org, 'aiAssist');
|
|
@@ -574,10 +663,16 @@ export function AssistPanelComponent(props) {
|
|
|
574
663
|
// jobs that spend between chat messages move it (AGL-3722).
|
|
575
664
|
useEffect(()=>followAiJobsForUsageMeter(), []);
|
|
576
665
|
const jobsActivity = aiJobsActivity(inFlight);
|
|
666
|
+
// On a site, its jobs are read on its AI jobs page (AGL-4116): the list in
|
|
667
|
+
// the chat was too cramped to read, and opening it scrolled the chat to
|
|
668
|
+
// its top.
|
|
669
|
+
const siteJobsHref = assistSiteAiJobsHref(pathname, orgSlug, hostId);
|
|
670
|
+
const router = useRouter();
|
|
577
671
|
// Any surface asking to open AI jobs — an "Open AI jobs" button, the
|
|
578
|
-
// indicator, a notification's link —
|
|
579
|
-
//
|
|
580
|
-
//
|
|
672
|
+
// indicator, a notification's link — goes to the site's AI jobs page, on
|
|
673
|
+
// the job it names. Off a site, it opens the panel on its drawer, and the
|
|
674
|
+
// request is handed to the drawer until the panel closes, so opening the
|
|
675
|
+
// panel again later does not re-highlight a job asked for before.
|
|
581
676
|
const openRequest = useAiJobsOpenRequest();
|
|
582
677
|
const [jobsFocus, setJobsFocus] = useState(null);
|
|
583
678
|
// The request outlives the panel: it is module state, and a page in
|
|
@@ -587,6 +682,11 @@ export function AssistPanelComponent(props) {
|
|
|
587
682
|
useEffect(()=>{
|
|
588
683
|
if (openRequest.seq <= answeredSeq.current) return;
|
|
589
684
|
answeredSeq.current = openRequest.seq;
|
|
685
|
+
if (siteJobsHref) {
|
|
686
|
+
setOpen(false);
|
|
687
|
+
router.push(openRequest.jobId ? `${siteJobsHref}/${encodeURIComponent(openRequest.jobId)}` : siteJobsHref);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
590
690
|
setJobsFocus(openRequest);
|
|
591
691
|
setOpen(true);
|
|
592
692
|
// A new `seq` is a new request; the object is read as it stands.
|
|
@@ -620,11 +720,14 @@ export function AssistPanelComponent(props) {
|
|
|
620
720
|
// The drafts this thread's builds made (AGL-3616): the chat sees refs and
|
|
621
721
|
// labels; the addresses stay here, for the card a follow-up raises.
|
|
622
722
|
const draftLinks = hostId ? assistThreadDraftLinks(messages, orgSlug) : [];
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
723
|
+
// Only this site's turns ride the question (AGL-4116), and the answer
|
|
724
|
+
// lands in the thread it was asked in.
|
|
725
|
+
const history = messages.slice(-HISTORY_TURNS_SENT).map((message)=>({
|
|
726
|
+
role: message.role,
|
|
727
|
+
text: assistHistoryText(message)
|
|
626
728
|
}));
|
|
627
|
-
|
|
729
|
+
const askedIn = threadKey;
|
|
730
|
+
updateThread(askedIn, (prior)=>[
|
|
628
731
|
...prior,
|
|
629
732
|
{
|
|
630
733
|
role: 'user',
|
|
@@ -636,7 +739,7 @@ export function AssistPanelComponent(props) {
|
|
|
636
739
|
}
|
|
637
740
|
]);
|
|
638
741
|
const patchAnswer = (patch)=>{
|
|
639
|
-
|
|
742
|
+
updateThread(askedIn, (prior)=>{
|
|
640
743
|
const next = [
|
|
641
744
|
...prior
|
|
642
745
|
];
|
|
@@ -655,15 +758,18 @@ export function AssistPanelComponent(props) {
|
|
|
655
758
|
}));
|
|
656
759
|
};
|
|
657
760
|
/** The build this turn proposed, started once its stream ends (AGL-3616). */ let proposedBuild = null;
|
|
761
|
+
/** The page change this turn asked for, started once its stream ends (AGL-4116). */ let proposedChange = null;
|
|
658
762
|
try {
|
|
659
763
|
var _ref;
|
|
660
764
|
// The canvas outline rides along from the besigner document the open
|
|
661
|
-
// editor holds
|
|
662
|
-
//
|
|
663
|
-
//
|
|
765
|
+
// editor holds, every element with its settings and styles (AGL-4116),
|
|
766
|
+
// and only while that editor is on the version the route names, so the
|
|
767
|
+
// outline and the selection are the ones on screen.
|
|
664
768
|
const editDocument = editRungHint ? assistEditDocumentOf(pathname != null ? pathname : '') : null;
|
|
665
769
|
const session = editDocument ? openEditorSession() : undefined;
|
|
666
|
-
const canvasOutline = editDocument && session && session.documentKind === editDocument.kind && session.documentId === editDocument.documentId && session.versionId === editDocument.versionId ? describeAssistEditCanvas(canvas, (_ref = session.selectedNodeId == null ? void 0 : session.selectedNodeId.call(session)) != null ? _ref : null
|
|
770
|
+
const canvasOutline = editDocument && session && session.documentKind === editDocument.kind && session.documentId === editDocument.documentId && session.versionId === editDocument.versionId ? describeAssistEditCanvas(canvas, (_ref = session.selectedNodeId == null ? void 0 : session.selectedNodeId.call(session)) != null ? _ref : null, {
|
|
771
|
+
whole: true
|
|
772
|
+
}) : null;
|
|
667
773
|
const response = await authorizedFetch(user, '/api/assist/chat', {
|
|
668
774
|
method: 'POST',
|
|
669
775
|
headers: {
|
|
@@ -782,6 +888,9 @@ export function AssistPanelComponent(props) {
|
|
|
782
888
|
// The build proposal (AGL-3616), shape-checked before it can start a job.
|
|
783
889
|
const build = isAssistBuildProposal(event.build) ? event.build : null;
|
|
784
890
|
proposedBuild = build;
|
|
891
|
+
// The page change (AGL-4116), shape-checked before it can start a job.
|
|
892
|
+
const pageChange = isAssistPageChangeProposal(event.pageChange) ? event.pageChange : null;
|
|
893
|
+
proposedChange = pageChange;
|
|
785
894
|
patchAnswer((message)=>{
|
|
786
895
|
var _event_exchangeId;
|
|
787
896
|
return _extends({}, message, {
|
|
@@ -793,6 +902,10 @@ export function AssistPanelComponent(props) {
|
|
|
793
902
|
build,
|
|
794
903
|
buildJob: null,
|
|
795
904
|
buildNotice: null
|
|
905
|
+
} : {}, pageChange ? {
|
|
906
|
+
pageChange,
|
|
907
|
+
pageChangeJob: null,
|
|
908
|
+
pageChangeNotice: null
|
|
796
909
|
} : {});
|
|
797
910
|
});
|
|
798
911
|
if (event.quota) setQuota(event.quota);
|
|
@@ -816,6 +929,11 @@ export function AssistPanelComponent(props) {
|
|
|
816
929
|
action: build.id
|
|
817
930
|
});
|
|
818
931
|
}
|
|
932
|
+
if (pageChange) {
|
|
933
|
+
trackEvent('assistant_proposal_shown', {
|
|
934
|
+
action: pageChange.id
|
|
935
|
+
});
|
|
936
|
+
}
|
|
819
937
|
} else if (event.type === 'error') {
|
|
820
938
|
var _event_error;
|
|
821
939
|
failAnswer(String((_event_error = event.error) != null ? _event_error : 'The assistant stream failed.'));
|
|
@@ -825,6 +943,21 @@ export function AssistPanelComponent(props) {
|
|
|
825
943
|
// One request, one build (AGL-3616): the job is created here, once,
|
|
826
944
|
// through the job door, and plans inline; its card shows the plan.
|
|
827
945
|
if (proposedBuild) {
|
|
946
|
+
// One plan per conversation (AGL-4116): a plan this thread proposed
|
|
947
|
+
// and the person has not confirmed is replaced by the new one, which
|
|
948
|
+
// the chat wrote from the whole request as it now stands. Its job is
|
|
949
|
+
// canceled, so it never builds and never warns another door.
|
|
950
|
+
for (const replaced of assistUnconfirmedBuilds(messages)){
|
|
951
|
+
updateThread(askedIn, (prior)=>prior.map((entry, i)=>i === replaced.index ? _extends({}, entry, {
|
|
952
|
+
buildReplaced: true
|
|
953
|
+
}) : entry));
|
|
954
|
+
void cancelAiJobRequest(user, scopedOrgId, replaced.jobId).then(({ job })=>{
|
|
955
|
+
if (!job) return;
|
|
956
|
+
updateThread(askedIn, (prior)=>prior.map((entry, i)=>i === replaced.index ? _extends({}, entry, {
|
|
957
|
+
buildJob: job
|
|
958
|
+
}) : entry));
|
|
959
|
+
});
|
|
960
|
+
}
|
|
828
961
|
const decision = await startAssistBuildRequest(user, scopedOrgId, proposedBuild);
|
|
829
962
|
// The plan the door ran inline has just spent (AGL-3722), so the strip
|
|
830
963
|
// moves with it, to the figure the plan card quotes. Left to the jobs
|
|
@@ -843,6 +976,17 @@ export function AssistPanelComponent(props) {
|
|
|
843
976
|
buildNotice: decision.error
|
|
844
977
|
}));
|
|
845
978
|
}
|
|
979
|
+
// A change to a page (AGL-4116): its edit job starts here, once, with
|
|
980
|
+
// no plan to confirm. A follow-up to a page this conversation already
|
|
981
|
+
// changed starts from the draft that change made.
|
|
982
|
+
if (proposedChange) {
|
|
983
|
+
const decision = await startAssistPageChangeRequest(user, scopedOrgId, proposedChange, assistPageChangeDraftVersion(messages, proposedChange.pageId));
|
|
984
|
+
publishMeter(decision.meter);
|
|
985
|
+
patchAnswer((message)=>_extends({}, message, {
|
|
986
|
+
pageChangeJob: decision.job,
|
|
987
|
+
pageChangeNotice: decision.error
|
|
988
|
+
}));
|
|
989
|
+
}
|
|
846
990
|
} catch (error) {
|
|
847
991
|
console.error(error);
|
|
848
992
|
failAnswer('The assistant request failed — check your connection and try again.');
|
|
@@ -861,6 +1005,8 @@ export function AssistPanelComponent(props) {
|
|
|
861
1005
|
scopedOrgId,
|
|
862
1006
|
orgSlug,
|
|
863
1007
|
pathname,
|
|
1008
|
+
threadKey,
|
|
1009
|
+
updateThread,
|
|
864
1010
|
user
|
|
865
1011
|
]);
|
|
866
1012
|
// A follow-up waiting for its draft's canvas (AGL-3616): kept per org, so a
|
|
@@ -924,7 +1070,9 @@ export function AssistPanelComponent(props) {
|
|
|
924
1070
|
setMessages((prior)=>prior.map((entry, i)=>i === index ? _extends({}, entry, {
|
|
925
1071
|
proposalResolved: true
|
|
926
1072
|
}) : entry));
|
|
927
|
-
}, [
|
|
1073
|
+
}, [
|
|
1074
|
+
setMessages
|
|
1075
|
+
]);
|
|
928
1076
|
/**
|
|
929
1077
|
* Land a confirmed edit on the open editor (AGL-2906): through the canvas's
|
|
930
1078
|
* own mutators, as one undo step, unsaved. The only request that follows is
|
|
@@ -979,18 +1127,52 @@ export function AssistPanelComponent(props) {
|
|
|
979
1127
|
enqueueSnackbar,
|
|
980
1128
|
messages,
|
|
981
1129
|
scopedOrgId,
|
|
1130
|
+
setMessages,
|
|
982
1131
|
user
|
|
983
1132
|
]);
|
|
984
1133
|
/** Keep what a build card learned about its job on its own message (AGL-3616). */ const patchBuild = useCallback((index, patch)=>{
|
|
985
1134
|
setMessages((prior)=>prior.map((entry, i)=>i === index ? _extends({}, entry, patch) : entry));
|
|
986
|
-
}, [
|
|
1135
|
+
}, [
|
|
1136
|
+
setMessages
|
|
1137
|
+
]);
|
|
1138
|
+
/** Keep what a page change card learned about its job on its own message (AGL-4116). */ const patchPageChange = useCallback((index, patch)=>{
|
|
1139
|
+
setMessages((prior)=>prior.map((entry, i)=>i === index ? _extends({}, entry, patch) : entry));
|
|
1140
|
+
}, [
|
|
1141
|
+
setMessages
|
|
1142
|
+
]);
|
|
1143
|
+
/** Start a page change again after it failed: a new edit job, from the same draft. */ const [retryingChange, setRetryingChange] = useState(null);
|
|
1144
|
+
const retryPageChange = useCallback(async (index)=>{
|
|
1145
|
+
var _messages_index;
|
|
1146
|
+
const proposal = (_messages_index = messages[index]) == null ? void 0 : _messages_index.pageChange;
|
|
1147
|
+
if (!proposal || !scopedOrgId) return;
|
|
1148
|
+
setRetryingChange(index);
|
|
1149
|
+
patchPageChange(index, {
|
|
1150
|
+
pageChangeJob: null,
|
|
1151
|
+
pageChangeNotice: null
|
|
1152
|
+
});
|
|
1153
|
+
const decision = await startAssistPageChangeRequest(user, scopedOrgId, proposal, assistPageChangeDraftVersion(messages.slice(0, index), proposal.pageId));
|
|
1154
|
+
publishMeter(decision.meter);
|
|
1155
|
+
patchPageChange(index, {
|
|
1156
|
+
pageChangeJob: decision.job,
|
|
1157
|
+
pageChangeNotice: decision.error
|
|
1158
|
+
});
|
|
1159
|
+
setRetryingChange(null);
|
|
1160
|
+
}, [
|
|
1161
|
+
messages,
|
|
1162
|
+
patchPageChange,
|
|
1163
|
+
publishMeter,
|
|
1164
|
+
scopedOrgId,
|
|
1165
|
+
user
|
|
1166
|
+
]);
|
|
987
1167
|
/** Wave an edit card away. Nothing to tell the server: nothing happened. */ const dismissEdit = useCallback((index)=>{
|
|
988
1168
|
setMessages((prior)=>prior.map((entry, i)=>i === index ? _extends({}, entry, {
|
|
989
1169
|
editOutcome: {
|
|
990
1170
|
kind: 'dismissed'
|
|
991
1171
|
}
|
|
992
1172
|
}) : entry));
|
|
993
|
-
}, [
|
|
1173
|
+
}, [
|
|
1174
|
+
setMessages
|
|
1175
|
+
]);
|
|
994
1176
|
const sendFeedback = useCallback(async (index, feedback)=>{
|
|
995
1177
|
const message = messages[index];
|
|
996
1178
|
if (!(message == null ? void 0 : message.exchangeId) || !scopedOrgId) return;
|
|
@@ -1022,6 +1204,7 @@ export function AssistPanelComponent(props) {
|
|
|
1022
1204
|
enqueueSnackbar,
|
|
1023
1205
|
messages,
|
|
1024
1206
|
scopedOrgId,
|
|
1207
|
+
setMessages,
|
|
1025
1208
|
user
|
|
1026
1209
|
]);
|
|
1027
1210
|
// A released-off feature does not exist (staff preview excepted) — and
|
|
@@ -1163,7 +1346,8 @@ export function AssistPanelComponent(props) {
|
|
|
1163
1346
|
// the jobs door where the member may not generate.
|
|
1164
1347
|
insight: ai.generate ? aiInsightSurfaceForPath(pathname) : null,
|
|
1165
1348
|
inFlight: jobsVisible ? inFlight : undefined,
|
|
1166
|
-
focus: jobsFocus
|
|
1349
|
+
focus: jobsFocus,
|
|
1350
|
+
pageHref: siteJobsHref
|
|
1167
1351
|
}),
|
|
1168
1352
|
!messages.length && /*#__PURE__*/ _jsxs(Alert, {
|
|
1169
1353
|
severity: "info",
|
|
@@ -1189,7 +1373,7 @@ export function AssistPanelComponent(props) {
|
|
|
1189
1373
|
/*#__PURE__*/ _jsx(Stack, {
|
|
1190
1374
|
spacing: 1.5,
|
|
1191
1375
|
children: messages.map((message, index)=>{
|
|
1192
|
-
var _message_buildJob, _message_buildNotice;
|
|
1376
|
+
var _message_pageChangeJob, _message_pageChangeNotice, _message_buildJob, _message_buildNotice;
|
|
1193
1377
|
var _message_editOutcome, _message_editOutcome1, _message_editOutcome2, _message_editOutcome3;
|
|
1194
1378
|
return /*#__PURE__*/ _jsxs(Box, {
|
|
1195
1379
|
sx: {
|
|
@@ -1249,7 +1433,29 @@ export function AssistPanelComponent(props) {
|
|
|
1249
1433
|
},
|
|
1250
1434
|
onDismiss: ()=>dismissEdit(index)
|
|
1251
1435
|
}),
|
|
1252
|
-
message.role === 'assistant' && message.
|
|
1436
|
+
message.role === 'assistant' && message.pageChange && scopedOrgId && /*#__PURE__*/ _jsx(AssistPageChangeCard, {
|
|
1437
|
+
proposal: message.pageChange,
|
|
1438
|
+
job: (_message_pageChangeJob = message.pageChangeJob) != null ? _message_pageChangeJob : null,
|
|
1439
|
+
notice: (_message_pageChangeNotice = message.pageChangeNotice) != null ? _message_pageChangeNotice : null,
|
|
1440
|
+
orgId: scopedOrgId,
|
|
1441
|
+
orgSlug: orgSlug,
|
|
1442
|
+
user: user,
|
|
1443
|
+
busy: retryingChange === index,
|
|
1444
|
+
onJob: (job)=>patchPageChange(index, {
|
|
1445
|
+
pageChangeJob: job
|
|
1446
|
+
}),
|
|
1447
|
+
onRetry: ()=>void retryPageChange(index)
|
|
1448
|
+
}),
|
|
1449
|
+
message.role === 'assistant' && message.build && message.buildReplaced && /*#__PURE__*/ _jsx(Typography, {
|
|
1450
|
+
variant: "caption",
|
|
1451
|
+
color: "text.secondary",
|
|
1452
|
+
component: "div",
|
|
1453
|
+
sx: {
|
|
1454
|
+
mt: 0.5
|
|
1455
|
+
},
|
|
1456
|
+
children: ASSIST_BUILD_REPLACED_COPY
|
|
1457
|
+
}),
|
|
1458
|
+
message.role === 'assistant' && message.build && !message.buildReplaced && scopedOrgId && /*#__PURE__*/ _jsx(AssistBuildCard, {
|
|
1253
1459
|
proposal: message.build,
|
|
1254
1460
|
job: (_message_buildJob = message.buildJob) != null ? _message_buildJob : null,
|
|
1255
1461
|
notice: (_message_buildNotice = message.buildNotice) != null ? _message_buildNotice : null,
|