@humaan/payload-plugin-ai-assistant 0.0.0-stage → 0.1.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/README.md +277 -2
- package/dist/admin/AiAssistantControl.d.ts +11 -0
- package/dist/admin/AiAssistantControl.js +31 -0
- package/dist/admin/AiAssistantControl.js.map +1 -0
- package/dist/admin/AiAssistantPanel.d.ts +10 -0
- package/dist/admin/AiAssistantPanel.js +208 -0
- package/dist/admin/AiAssistantPanel.js.map +1 -0
- package/dist/admin/AiAssistantPanel.module.scss +689 -0
- package/dist/admin/AiSourceSwitch.d.ts +13 -0
- package/dist/admin/AiSourceSwitch.js +14 -0
- package/dist/admin/AiSourceSwitch.js.map +1 -0
- package/dist/admin/BudgetField.d.ts +8 -0
- package/dist/admin/BudgetField.js +145 -0
- package/dist/admin/BudgetField.js.map +1 -0
- package/dist/admin/BudgetField.module.scss +47 -0
- package/dist/admin/Conversation.d.ts +12 -0
- package/dist/admin/Conversation.js +469 -0
- package/dist/admin/Conversation.js.map +1 -0
- package/dist/admin/Markdown.d.ts +9 -0
- package/dist/admin/Markdown.js +63 -0
- package/dist/admin/Markdown.js.map +1 -0
- package/dist/admin/PageContextBridge.d.ts +5 -0
- package/dist/admin/PageContextBridge.js +72 -0
- package/dist/admin/PageContextBridge.js.map +1 -0
- package/dist/admin/Pill.d.ts +11 -0
- package/dist/admin/Pill.js +27 -0
- package/dist/admin/Pill.js.map +1 -0
- package/dist/admin/PlanCard.d.ts +14 -0
- package/dist/admin/PlanCard.js +465 -0
- package/dist/admin/PlanCard.js.map +1 -0
- package/dist/admin/SettingsAlerts.d.ts +7 -0
- package/dist/admin/SettingsAlerts.js +37 -0
- package/dist/admin/SettingsAlerts.js.map +1 -0
- package/dist/admin/UsageSummary.d.ts +11 -0
- package/dist/admin/UsageSummary.js +129 -0
- package/dist/admin/UsageSummary.js.map +1 -0
- package/dist/admin/attachmentUpload.d.ts +25 -0
- package/dist/admin/attachmentUpload.js +76 -0
- package/dist/admin/attachmentUpload.js.map +1 -0
- package/dist/admin/openDocument.d.ts +13 -0
- package/dist/admin/openDocument.js +34 -0
- package/dist/admin/openDocument.js.map +1 -0
- package/dist/admin/percent.d.ts +10 -0
- package/dist/admin/percent.js +21 -0
- package/dist/admin/percent.js.map +1 -0
- package/dist/admin/threads.d.ts +21 -0
- package/dist/admin/threads.js +92 -0
- package/dist/admin/threads.js.map +1 -0
- package/dist/admin/toolSteps.d.ts +12 -0
- package/dist/admin/toolSteps.js +57 -0
- package/dist/admin/toolSteps.js.map +1 -0
- package/dist/admin/transport.d.ts +24 -0
- package/dist/admin/transport.js +75 -0
- package/dist/admin/transport.js.map +1 -0
- package/dist/admin/useAttachments.d.ts +32 -0
- package/dist/admin/useAttachments.js +91 -0
- package/dist/admin/useAttachments.js.map +1 -0
- package/dist/admin/useEntityLabel.d.ts +3 -0
- package/dist/admin/useEntityLabel.js +23 -0
- package/dist/admin/useEntityLabel.js.map +1 -0
- package/dist/attachments/collection.d.ts +15 -0
- package/dist/attachments/collection.js +90 -0
- package/dist/attachments/collection.js.map +1 -0
- package/dist/attachments/conversation.d.ts +25 -0
- package/dist/attachments/conversation.js +105 -0
- package/dist/attachments/conversation.js.map +1 -0
- package/dist/attachments/rules.d.ts +9 -0
- package/dist/attachments/rules.js +17 -0
- package/dist/attachments/rules.js.map +1 -0
- package/dist/attachments/store.d.ts +27 -0
- package/dist/attachments/store.js +187 -0
- package/dist/attachments/store.js.map +1 -0
- package/dist/billing.d.ts +60 -0
- package/dist/billing.js +116 -0
- package/dist/billing.js.map +1 -0
- package/dist/chat/history.d.ts +37 -0
- package/dist/chat/history.js +165 -0
- package/dist/chat/history.js.map +1 -0
- package/dist/chat/messages.d.ts +5 -0
- package/dist/chat/messages.js +3 -0
- package/dist/chat/messages.js.map +1 -0
- package/dist/chat/threads.d.ts +33 -0
- package/dist/chat/threads.js +77 -0
- package/dist/chat/threads.js.map +1 -0
- package/dist/chat/tools.d.ts +76 -0
- package/dist/chat/tools.js +596 -0
- package/dist/chat/tools.js.map +1 -0
- package/dist/chat/usage.d.ts +66 -0
- package/dist/chat/usage.js +219 -0
- package/dist/chat/usage.js.map +1 -0
- package/dist/collectionAccess.d.ts +6 -0
- package/dist/collectionAccess.js +32 -0
- package/dist/collectionAccess.js.map +1 -0
- package/dist/constants.d.ts +64 -0
- package/dist/constants.js +51 -0
- package/dist/constants.js.map +1 -0
- package/dist/endpoints/access.d.ts +4 -0
- package/dist/endpoints/access.js +28 -0
- package/dist/endpoints/access.js.map +1 -0
- package/dist/endpoints/allowance.d.ts +22 -0
- package/dist/endpoints/allowance.js +37 -0
- package/dist/endpoints/allowance.js.map +1 -0
- package/dist/endpoints/applyEndpoint.d.ts +12 -0
- package/dist/endpoints/applyEndpoint.js +170 -0
- package/dist/endpoints/applyEndpoint.js.map +1 -0
- package/dist/endpoints/budget.d.ts +30 -0
- package/dist/endpoints/budget.js +153 -0
- package/dist/endpoints/budget.js.map +1 -0
- package/dist/endpoints/chatEndpoint.d.ts +12 -0
- package/dist/endpoints/chatEndpoint.js +295 -0
- package/dist/endpoints/chatEndpoint.js.map +1 -0
- package/dist/endpoints/form.d.ts +23 -0
- package/dist/endpoints/form.js +95 -0
- package/dist/endpoints/form.js.map +1 -0
- package/dist/endpoints/usageEndpoint.d.ts +13 -0
- package/dist/endpoints/usageEndpoint.js +70 -0
- package/dist/endpoints/usageEndpoint.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/lexical/editorConfig.d.ts +4 -0
- package/dist/lexical/editorConfig.js +19 -0
- package/dist/lexical/editorConfig.js.map +1 -0
- package/dist/lexical/lexicalToMarkdown.d.ts +10 -0
- package/dist/lexical/lexicalToMarkdown.js +10 -0
- package/dist/lexical/lexicalToMarkdown.js.map +1 -0
- package/dist/lexical/markdownToLexical.d.ts +7 -0
- package/dist/lexical/markdownToLexical.js +10 -0
- package/dist/lexical/markdownToLexical.js.map +1 -0
- package/dist/plan/allowed.d.ts +7 -0
- package/dist/plan/allowed.js +19 -0
- package/dist/plan/allowed.js.map +1 -0
- package/dist/plan/applyPlan.d.ts +16 -0
- package/dist/plan/applyPlan.js +353 -0
- package/dist/plan/applyPlan.js.map +1 -0
- package/dist/plan/catalogue.d.ts +37 -0
- package/dist/plan/catalogue.js +157 -0
- package/dist/plan/catalogue.js.map +1 -0
- package/dist/plan/diff.d.ts +13 -0
- package/dist/plan/diff.js +87 -0
- package/dist/plan/diff.js.map +1 -0
- package/dist/plan/parsePlan.d.ts +27 -0
- package/dist/plan/parsePlan.js +322 -0
- package/dist/plan/parsePlan.js.map +1 -0
- package/dist/plan/prompt.d.ts +1 -0
- package/dist/plan/prompt.js +45 -0
- package/dist/plan/prompt.js.map +1 -0
- package/dist/plan/stored.d.ts +53 -0
- package/dist/plan/stored.js +3 -0
- package/dist/plan/stored.js.map +1 -0
- package/dist/plan/tokens.d.ts +8 -0
- package/dist/plan/tokens.js +79 -0
- package/dist/plan/tokens.js.map +1 -0
- package/dist/plan/view.d.ts +4 -0
- package/dist/plan/view.js +43 -0
- package/dist/plan/view.js.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +464 -0
- package/dist/plugin.js.map +1 -0
- package/dist/read/outline.d.ts +7 -0
- package/dist/read/outline.js +70 -0
- package/dist/read/outline.js.map +1 -0
- package/dist/read/readTarget.d.ts +4 -0
- package/dist/read/readTarget.js +13 -0
- package/dist/read/readTarget.js.map +1 -0
- package/dist/read/readable.d.ts +8 -0
- package/dist/read/readable.js +131 -0
- package/dist/read/readable.js.map +1 -0
- package/dist/runtime.d.ts +15 -0
- package/dist/runtime.js +14 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema/fields.d.ts +113 -0
- package/dist/schema/fields.js +1036 -0
- package/dist/schema/fields.js.map +1 -0
- package/dist/schema/listingFields.d.ts +9 -0
- package/dist/schema/listingFields.js +95 -0
- package/dist/schema/listingFields.js.map +1 -0
- package/dist/schema/slugify.d.ts +2 -0
- package/dist/schema/slugify.js +5 -0
- package/dist/schema/slugify.js.map +1 -0
- package/dist/settings.d.ts +10 -0
- package/dist/settings.js +54 -0
- package/dist/settings.js.map +1 -0
- package/dist/types.d.ts +198 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/uploads/routeAsset.d.ts +27 -0
- package/dist/uploads/routeAsset.js +127 -0
- package/dist/uploads/routeAsset.js.map +1 -0
- package/package.json +122 -3
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useChat } from '@ai-sdk/react';
|
|
4
|
+
import { Button } from '@payloadcms/ui';
|
|
5
|
+
import { useEffect, useId, useRef, useState } from 'react';
|
|
6
|
+
import { markPlan } from '../chat/history.js';
|
|
7
|
+
import { maxFiles } from '../constants.js';
|
|
8
|
+
import styles from './AiAssistantPanel.module.scss';
|
|
9
|
+
import { Markdown } from './Markdown.js';
|
|
10
|
+
import { currentOpenDocument, useOpenDocument } from './openDocument.js';
|
|
11
|
+
import { Pill } from './Pill.js';
|
|
12
|
+
import { PlanCard } from './PlanCard.js';
|
|
13
|
+
import { toolStep } from './toolSteps.js';
|
|
14
|
+
import { AssistantTransport } from './transport.js';
|
|
15
|
+
import { useAttachments } from './useAttachments.js';
|
|
16
|
+
import { useEntityLabel } from './useEntityLabel.js';
|
|
17
|
+
/** One conversation. Remounted with a new key to switch conversations. */ export function Conversation({ apiRoute, session, attachmentsEnabled, onReply }) {
|
|
18
|
+
const openDocument = useOpenDocument();
|
|
19
|
+
const [draft, setDraft] = useState('');
|
|
20
|
+
const [fileNotice, setFileNotice] = useState('');
|
|
21
|
+
const attachments = useAttachments({
|
|
22
|
+
apiRoute,
|
|
23
|
+
initial: session.attachments
|
|
24
|
+
});
|
|
25
|
+
const fileInputRef = useRef(null);
|
|
26
|
+
const endRef = useRef(null);
|
|
27
|
+
const messageId = useId();
|
|
28
|
+
const [transport] = useState(()=>new AssistantTransport({
|
|
29
|
+
api: `${apiRoute}/ai-assistant/chat`,
|
|
30
|
+
openDocument: currentOpenDocument
|
|
31
|
+
}));
|
|
32
|
+
const { messages, sendMessage, setMessages, status, stop, error, clearError } = useChat({
|
|
33
|
+
id: session.chatId,
|
|
34
|
+
messages: session.messages,
|
|
35
|
+
transport,
|
|
36
|
+
onFinish: onReply
|
|
37
|
+
});
|
|
38
|
+
const busy = status === 'submitted' || status === 'streaming';
|
|
39
|
+
const { unsent, uploading } = attachments;
|
|
40
|
+
const canAttach = attachmentsEnabled && !attachments.full;
|
|
41
|
+
// Keep the newest message in view as it streams.
|
|
42
|
+
useEffect(()=>{
|
|
43
|
+
endRef.current?.scrollIntoView({
|
|
44
|
+
block: 'end'
|
|
45
|
+
});
|
|
46
|
+
}, [
|
|
47
|
+
messages,
|
|
48
|
+
status
|
|
49
|
+
]);
|
|
50
|
+
const onPickFiles = (event)=>{
|
|
51
|
+
const picked = [
|
|
52
|
+
...event.target.files ?? []
|
|
53
|
+
];
|
|
54
|
+
event.target.value = '';
|
|
55
|
+
const omitted = attachments.add(picked);
|
|
56
|
+
setFileNotice(omitted > 0 ? `A conversation can have up to ${maxFiles} files. Extra files were left out.` : '');
|
|
57
|
+
};
|
|
58
|
+
const submit = ()=>{
|
|
59
|
+
const text = draft.trim();
|
|
60
|
+
if (busy || uploading || !text && unsent.length === 0) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const sentBefore = attachments.sentIds.size;
|
|
64
|
+
const added = unsent.map((file, offset)=>({
|
|
65
|
+
index: sentBefore + offset,
|
|
66
|
+
name: file.name,
|
|
67
|
+
type: file.type
|
|
68
|
+
}));
|
|
69
|
+
const context = currentOpenDocument()?.context;
|
|
70
|
+
clearError();
|
|
71
|
+
transport.setAttachmentIds(attachments.readyIds);
|
|
72
|
+
void sendMessage({
|
|
73
|
+
text,
|
|
74
|
+
metadata: {
|
|
75
|
+
...added.length > 0 ? {
|
|
76
|
+
attachments: added
|
|
77
|
+
} : {},
|
|
78
|
+
...context ? {
|
|
79
|
+
context
|
|
80
|
+
} : {}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
attachments.markSent(unsent.map((file)=>file.id));
|
|
84
|
+
setDraft('');
|
|
85
|
+
setFileNotice('');
|
|
86
|
+
};
|
|
87
|
+
const onKeyDown = (event)=>{
|
|
88
|
+
if (event.key !== 'Enter' || event.shiftKey || event.nativeEvent.isComposing) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
submit();
|
|
93
|
+
};
|
|
94
|
+
const recordOutcome = (planId, update)=>{
|
|
95
|
+
setMessages((current)=>markPlan(current, planId, update).messages);
|
|
96
|
+
};
|
|
97
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
98
|
+
className: styles.assistant__chat,
|
|
99
|
+
children: [
|
|
100
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
101
|
+
className: styles.assistant__log,
|
|
102
|
+
children: [
|
|
103
|
+
messages.length === 0 ? /*#__PURE__*/ _jsx(Welcome, {
|
|
104
|
+
context: openDocument?.context
|
|
105
|
+
}) : null,
|
|
106
|
+
messages.map((message, index)=>message.role === 'user' ? /*#__PURE__*/ _jsx(UserMessage, {
|
|
107
|
+
message: message
|
|
108
|
+
}, message.id) : /*#__PURE__*/ _jsx(AssistantMessage, {
|
|
109
|
+
apiRoute: apiRoute,
|
|
110
|
+
message: message,
|
|
111
|
+
onOutcome: recordOutcome,
|
|
112
|
+
running: busy && index === messages.length - 1
|
|
113
|
+
}, message.id)),
|
|
114
|
+
status === 'submitted' ? /*#__PURE__*/ _jsx("p", {
|
|
115
|
+
className: styles.assistant__thinking,
|
|
116
|
+
children: "Thinking…"
|
|
117
|
+
}) : null,
|
|
118
|
+
/*#__PURE__*/ _jsx("div", {
|
|
119
|
+
"aria-hidden": "true",
|
|
120
|
+
ref: endRef
|
|
121
|
+
})
|
|
122
|
+
]
|
|
123
|
+
}),
|
|
124
|
+
busy ? /*#__PURE__*/ _jsx("div", {
|
|
125
|
+
"aria-label": "The assistant is working",
|
|
126
|
+
className: styles.assistant__progress,
|
|
127
|
+
role: "progressbar"
|
|
128
|
+
}) : null,
|
|
129
|
+
/*#__PURE__*/ _jsx("p", {
|
|
130
|
+
className: styles.assistant__sr,
|
|
131
|
+
role: "status",
|
|
132
|
+
children: uploading ? 'Uploading attachments.' : announcement(messages, busy)
|
|
133
|
+
}),
|
|
134
|
+
error ? /*#__PURE__*/ _jsx("p", {
|
|
135
|
+
className: styles.assistant__error,
|
|
136
|
+
role: "alert",
|
|
137
|
+
children: error.message || 'The assistant could not reply. Try again.'
|
|
138
|
+
}) : null,
|
|
139
|
+
/*#__PURE__*/ _jsxs("form", {
|
|
140
|
+
className: styles.assistant__composer,
|
|
141
|
+
onSubmit: (event)=>{
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
submit();
|
|
144
|
+
},
|
|
145
|
+
children: [
|
|
146
|
+
openDocument ? /*#__PURE__*/ _jsx(ContextPill, {
|
|
147
|
+
context: openDocument.context
|
|
148
|
+
}) : null,
|
|
149
|
+
attachments.items.length > 0 ? /*#__PURE__*/ _jsx("ul", {
|
|
150
|
+
"aria-label": "Attached files",
|
|
151
|
+
className: styles.assistant__chips,
|
|
152
|
+
children: attachments.items.map((item)=>/*#__PURE__*/ _jsx(AttachmentChip, {
|
|
153
|
+
item: item,
|
|
154
|
+
onRemove: ()=>attachments.remove(item.key),
|
|
155
|
+
removable: !busy && !(item.id && attachments.sentIds.has(item.id))
|
|
156
|
+
}, item.key))
|
|
157
|
+
}) : null,
|
|
158
|
+
/*#__PURE__*/ _jsx("label", {
|
|
159
|
+
className: styles.assistant__sr,
|
|
160
|
+
htmlFor: messageId,
|
|
161
|
+
children: "Message"
|
|
162
|
+
}),
|
|
163
|
+
/*#__PURE__*/ _jsx("textarea", {
|
|
164
|
+
className: styles.assistant__textarea,
|
|
165
|
+
id: messageId,
|
|
166
|
+
onChange: (event)=>setDraft(event.target.value),
|
|
167
|
+
onKeyDown: onKeyDown,
|
|
168
|
+
placeholder: "Ask about the site, or describe a change",
|
|
169
|
+
rows: 3,
|
|
170
|
+
value: draft
|
|
171
|
+
}),
|
|
172
|
+
/*#__PURE__*/ _jsx("input", {
|
|
173
|
+
"aria-hidden": true,
|
|
174
|
+
className: styles.assistant__sr,
|
|
175
|
+
disabled: !canAttach,
|
|
176
|
+
multiple: true,
|
|
177
|
+
onChange: onPickFiles,
|
|
178
|
+
ref: fileInputRef,
|
|
179
|
+
tabIndex: -1,
|
|
180
|
+
type: "file"
|
|
181
|
+
}),
|
|
182
|
+
/*#__PURE__*/ _jsx("div", {
|
|
183
|
+
className: styles.assistant__composerRow,
|
|
184
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
185
|
+
className: styles.assistant__composerActions,
|
|
186
|
+
children: [
|
|
187
|
+
attachmentsEnabled ? /*#__PURE__*/ _jsx(Button, {
|
|
188
|
+
buttonStyle: "secondary",
|
|
189
|
+
disabled: !canAttach || busy,
|
|
190
|
+
margin: false,
|
|
191
|
+
onClick: ()=>fileInputRef.current?.click(),
|
|
192
|
+
size: "small",
|
|
193
|
+
type: "button",
|
|
194
|
+
children: "Attach files"
|
|
195
|
+
}) : null,
|
|
196
|
+
busy ? /*#__PURE__*/ _jsx(Button, {
|
|
197
|
+
buttonStyle: "secondary",
|
|
198
|
+
margin: false,
|
|
199
|
+
onClick: ()=>void stop(),
|
|
200
|
+
size: "small",
|
|
201
|
+
type: "button",
|
|
202
|
+
children: "Stop"
|
|
203
|
+
}) : /*#__PURE__*/ _jsx(Button, {
|
|
204
|
+
buttonStyle: "primary",
|
|
205
|
+
disabled: uploading || !draft.trim() && unsent.length === 0,
|
|
206
|
+
margin: false,
|
|
207
|
+
size: "small",
|
|
208
|
+
type: "submit",
|
|
209
|
+
children: "Send"
|
|
210
|
+
})
|
|
211
|
+
]
|
|
212
|
+
})
|
|
213
|
+
}),
|
|
214
|
+
fileNotice ? /*#__PURE__*/ _jsx("p", {
|
|
215
|
+
className: styles.assistant__hint,
|
|
216
|
+
role: "status",
|
|
217
|
+
children: fileNotice
|
|
218
|
+
}) : null
|
|
219
|
+
]
|
|
220
|
+
})
|
|
221
|
+
]
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/** A file in the composer. Its status is part of its name for screen readers, and announced as it changes. */ function AttachmentChip({ item, removable, onRemove }) {
|
|
225
|
+
return /*#__PURE__*/ _jsxs("li", {
|
|
226
|
+
className: styles.assistant__chip,
|
|
227
|
+
"data-status": item.status,
|
|
228
|
+
children: [
|
|
229
|
+
/*#__PURE__*/ _jsx("span", {
|
|
230
|
+
className: styles.assistant__chipName,
|
|
231
|
+
children: item.name
|
|
232
|
+
}),
|
|
233
|
+
item.status === 'uploading' ? /*#__PURE__*/ _jsx("span", {
|
|
234
|
+
className: styles.assistant__chipStatus,
|
|
235
|
+
children: "Uploading…"
|
|
236
|
+
}) : null,
|
|
237
|
+
item.status === 'failed' ? /*#__PURE__*/ _jsx("span", {
|
|
238
|
+
className: styles.assistant__chipStatus,
|
|
239
|
+
children: item.error ?? 'Upload failed'
|
|
240
|
+
}) : null,
|
|
241
|
+
removable ? /*#__PURE__*/ _jsxs("button", {
|
|
242
|
+
className: styles.assistant__chipRemove,
|
|
243
|
+
onClick: onRemove,
|
|
244
|
+
type: "button",
|
|
245
|
+
children: [
|
|
246
|
+
/*#__PURE__*/ _jsx("span", {
|
|
247
|
+
"aria-hidden": "true",
|
|
248
|
+
children: "×"
|
|
249
|
+
}),
|
|
250
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
251
|
+
className: styles.assistant__sr,
|
|
252
|
+
children: [
|
|
253
|
+
"Remove ",
|
|
254
|
+
item.name
|
|
255
|
+
]
|
|
256
|
+
})
|
|
257
|
+
]
|
|
258
|
+
}) : null
|
|
259
|
+
]
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
function Welcome({ context }) {
|
|
263
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
264
|
+
className: styles.assistant__welcome,
|
|
265
|
+
children: [
|
|
266
|
+
/*#__PURE__*/ _jsx("p", {
|
|
267
|
+
children: "Ask a question about the site's content, or describe a page or change you want. The assistant reads the content it needs and proposes a plan. Nothing is saved until you choose Proceed."
|
|
268
|
+
}),
|
|
269
|
+
context ? /*#__PURE__*/ _jsxs("p", {
|
|
270
|
+
children: [
|
|
271
|
+
"It can see ",
|
|
272
|
+
context.label,
|
|
273
|
+
", including changes you have not saved yet."
|
|
274
|
+
]
|
|
275
|
+
}) : null
|
|
276
|
+
]
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
function ContextPill({ context }) {
|
|
280
|
+
const entityLabel = useEntityLabel();
|
|
281
|
+
return /*#__PURE__*/ _jsxs("p", {
|
|
282
|
+
className: styles.assistant__context,
|
|
283
|
+
children: [
|
|
284
|
+
/*#__PURE__*/ _jsx(Pill, {
|
|
285
|
+
entity: context.slug,
|
|
286
|
+
children: entityLabel(context.slug, context.kind)
|
|
287
|
+
}),
|
|
288
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
289
|
+
children: [
|
|
290
|
+
"Using ",
|
|
291
|
+
/*#__PURE__*/ _jsx("strong", {
|
|
292
|
+
children: context.label
|
|
293
|
+
})
|
|
294
|
+
]
|
|
295
|
+
})
|
|
296
|
+
]
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function UserMessage({ message }) {
|
|
300
|
+
const text = message.parts.flatMap((part)=>part.type === 'text' ? [
|
|
301
|
+
part.text
|
|
302
|
+
] : []).join('\n');
|
|
303
|
+
const attachments = message.metadata?.attachments ?? [];
|
|
304
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
305
|
+
className: styles.assistant__user,
|
|
306
|
+
children: [
|
|
307
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
308
|
+
className: styles.assistant__sr,
|
|
309
|
+
children: "You said"
|
|
310
|
+
}),
|
|
311
|
+
text ? /*#__PURE__*/ _jsx("p", {
|
|
312
|
+
className: styles.assistant__text,
|
|
313
|
+
children: text
|
|
314
|
+
}) : null,
|
|
315
|
+
attachments.length > 0 ? /*#__PURE__*/ _jsx("ul", {
|
|
316
|
+
"aria-label": "Attached",
|
|
317
|
+
className: styles.assistant__chips,
|
|
318
|
+
children: attachments.map((file)=>/*#__PURE__*/ _jsx("li", {
|
|
319
|
+
className: styles.assistant__chip,
|
|
320
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
321
|
+
className: styles.assistant__chipName,
|
|
322
|
+
children: file.name
|
|
323
|
+
})
|
|
324
|
+
}, file.index))
|
|
325
|
+
}) : null,
|
|
326
|
+
message.metadata?.context ? /*#__PURE__*/ _jsxs("p", {
|
|
327
|
+
className: styles.assistant__meta,
|
|
328
|
+
children: [
|
|
329
|
+
"On ",
|
|
330
|
+
message.metadata.context.label
|
|
331
|
+
]
|
|
332
|
+
}) : null
|
|
333
|
+
]
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
function AssistantMessage({ message, running, apiRoute, onOutcome }) {
|
|
337
|
+
const usage = message.metadata?.usage;
|
|
338
|
+
const entityLabel = useEntityLabel();
|
|
339
|
+
const segments = segmentsOf(message, entityLabel);
|
|
340
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
341
|
+
className: styles.assistant__reply,
|
|
342
|
+
children: [
|
|
343
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
344
|
+
className: styles.assistant__sr,
|
|
345
|
+
children: "Humaan AI said"
|
|
346
|
+
}),
|
|
347
|
+
segments.map((segment)=>{
|
|
348
|
+
if (segment.kind === 'text') {
|
|
349
|
+
return /*#__PURE__*/ _jsx(Markdown, {
|
|
350
|
+
text: segment.text
|
|
351
|
+
}, segment.key);
|
|
352
|
+
}
|
|
353
|
+
if (segment.kind === 'steps') {
|
|
354
|
+
return /*#__PURE__*/ _jsx(Steps, {
|
|
355
|
+
live: running && segment === segments.at(-1),
|
|
356
|
+
steps: segment.steps
|
|
357
|
+
}, segment.key);
|
|
358
|
+
}
|
|
359
|
+
return /*#__PURE__*/ _jsx(PlanCard, {
|
|
360
|
+
apiRoute: apiRoute,
|
|
361
|
+
onOutcome: onOutcome,
|
|
362
|
+
output: segment.output
|
|
363
|
+
}, segment.key);
|
|
364
|
+
}),
|
|
365
|
+
usage?.totalTokens ? /*#__PURE__*/ _jsxs("p", {
|
|
366
|
+
className: styles.assistant__meta,
|
|
367
|
+
children: [
|
|
368
|
+
usage.totalTokens.toLocaleString('en-AU'),
|
|
369
|
+
" tokens",
|
|
370
|
+
usage.cachedInputTokens ? `, ${usage.cachedInputTokens.toLocaleString('en-AU')} from cache` : ''
|
|
371
|
+
]
|
|
372
|
+
}) : null
|
|
373
|
+
]
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
/** While the reply streams, steps show as they run. Afterwards they fold into one line the editor can open. */ function Steps({ steps, live }) {
|
|
377
|
+
const list = /*#__PURE__*/ _jsx("ol", {
|
|
378
|
+
"aria-label": "Steps",
|
|
379
|
+
className: styles.assistant__steps,
|
|
380
|
+
children: steps.map((step)=>/*#__PURE__*/ _jsxs("li", {
|
|
381
|
+
className: styles.assistant__step,
|
|
382
|
+
"data-state": step.state,
|
|
383
|
+
children: [
|
|
384
|
+
/*#__PURE__*/ _jsx("span", {
|
|
385
|
+
"aria-hidden": "true",
|
|
386
|
+
children: step.state === 'done' ? '✓' : step.state === 'failed' ? '!' : '…'
|
|
387
|
+
}),
|
|
388
|
+
step.label,
|
|
389
|
+
step.state === 'failed' ? /*#__PURE__*/ _jsx("span", {
|
|
390
|
+
className: styles.assistant__sr,
|
|
391
|
+
children: " (failed)"
|
|
392
|
+
}) : null
|
|
393
|
+
]
|
|
394
|
+
}, step.id))
|
|
395
|
+
});
|
|
396
|
+
if (live) {
|
|
397
|
+
return list;
|
|
398
|
+
}
|
|
399
|
+
const failed = steps.filter((step)=>step.state === 'failed').length;
|
|
400
|
+
return /*#__PURE__*/ _jsxs("details", {
|
|
401
|
+
className: styles.assistant__stepsFold,
|
|
402
|
+
children: [
|
|
403
|
+
/*#__PURE__*/ _jsxs("summary", {
|
|
404
|
+
children: [
|
|
405
|
+
"Worked through ",
|
|
406
|
+
steps.length,
|
|
407
|
+
" ",
|
|
408
|
+
steps.length === 1 ? 'step' : 'steps',
|
|
409
|
+
failed > 0 ? `, ${failed} failed` : ''
|
|
410
|
+
]
|
|
411
|
+
}),
|
|
412
|
+
list
|
|
413
|
+
]
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
/** Groups tool calls that sit next to each other into one list of steps. */ function segmentsOf(message, entityLabel) {
|
|
417
|
+
const segments = [];
|
|
418
|
+
message.parts.forEach((part, index)=>{
|
|
419
|
+
const key = `${message.id}-${index}`;
|
|
420
|
+
if (part.type === 'text') {
|
|
421
|
+
if (part.text.trim()) {
|
|
422
|
+
segments.push({
|
|
423
|
+
kind: 'text',
|
|
424
|
+
key,
|
|
425
|
+
text: part.text.trim()
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (part.type === 'tool-submitPlan' && part.state === 'output-available' && part.output.ok) {
|
|
431
|
+
segments.push({
|
|
432
|
+
kind: 'plan',
|
|
433
|
+
key,
|
|
434
|
+
output: part.output
|
|
435
|
+
});
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const step = toolStep(part, entityLabel);
|
|
439
|
+
if (!step) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const last = segments.at(-1);
|
|
443
|
+
if (last?.kind === 'steps') {
|
|
444
|
+
last.steps.push(step);
|
|
445
|
+
} else {
|
|
446
|
+
segments.push({
|
|
447
|
+
kind: 'steps',
|
|
448
|
+
key,
|
|
449
|
+
steps: [
|
|
450
|
+
step
|
|
451
|
+
]
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
return segments;
|
|
456
|
+
}
|
|
457
|
+
/** Read by screen readers instead of the streaming text, which would be announced word by word. */ function announcement(messages, busy) {
|
|
458
|
+
if (busy) {
|
|
459
|
+
return 'The assistant is replying.';
|
|
460
|
+
}
|
|
461
|
+
const last = messages.at(-1);
|
|
462
|
+
if (last?.role !== 'assistant') {
|
|
463
|
+
return '';
|
|
464
|
+
}
|
|
465
|
+
const plan = last.parts.some((part)=>part.type === 'tool-submitPlan' && part.state === 'output-available' && part.output.ok && part.output.status === 'proposed');
|
|
466
|
+
return plan ? 'The assistant replied with a plan to review.' : 'The assistant replied.';
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
//# sourceMappingURL=Conversation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/admin/Conversation.tsx"],"sourcesContent":["'use client'\n\nimport type { ChangeEvent, KeyboardEvent } from 'react'\n\nimport { useChat } from '@ai-sdk/react'\nimport { Button } from '@payloadcms/ui'\nimport { useEffect, useId, useRef, useState } from 'react'\n\nimport type { AssistantUIMessage } from '../chat/messages.js'\nimport type { AssistantChange, AttachmentView, PageContext, PlanStatus } from '../types.js'\nimport type { LoadedThread } from './threads.js'\nimport type { EntityLabel, ToolStep } from './toolSteps.js'\nimport type { ComposerAttachment } from './useAttachments.js'\n\nimport { markPlan } from '../chat/history.js'\nimport { maxFiles } from '../constants.js'\nimport styles from './AiAssistantPanel.module.scss'\nimport { Markdown } from './Markdown.js'\nimport { currentOpenDocument, useOpenDocument } from './openDocument.js'\nimport { Pill } from './Pill.js'\nimport { PlanCard } from './PlanCard.js'\nimport { toolStep } from './toolSteps.js'\nimport { AssistantTransport } from './transport.js'\nimport { useAttachments } from './useAttachments.js'\nimport { useEntityLabel } from './useEntityLabel.js'\n\ninterface ConversationProps {\n apiRoute: string\n /** Whether the project registered the attachments collection. */\n attachmentsEnabled: boolean\n /** Called when a reply finishes and the thread has been saved. */\n onReply: () => void\n session: LoadedThread\n}\n\n/** One conversation. Remounted with a new key to switch conversations. */\nexport function Conversation({\n apiRoute,\n session,\n attachmentsEnabled,\n onReply,\n}: ConversationProps) {\n const openDocument = useOpenDocument()\n const [draft, setDraft] = useState('')\n const [fileNotice, setFileNotice] = useState('')\n const attachments = useAttachments({ apiRoute, initial: session.attachments })\n const fileInputRef = useRef<HTMLInputElement>(null)\n const endRef = useRef<HTMLDivElement>(null)\n const messageId = useId()\n const [transport] = useState(\n () =>\n new AssistantTransport({\n api: `${apiRoute}/ai-assistant/chat`,\n openDocument: currentOpenDocument,\n }),\n )\n const { messages, sendMessage, setMessages, status, stop, error, clearError } =\n useChat<AssistantUIMessage>({\n id: session.chatId,\n messages: session.messages,\n transport,\n onFinish: onReply,\n })\n\n const busy = status === 'submitted' || status === 'streaming'\n const { unsent, uploading } = attachments\n const canAttach = attachmentsEnabled && !attachments.full\n\n // Keep the newest message in view as it streams.\n useEffect(() => {\n endRef.current?.scrollIntoView({ block: 'end' })\n }, [messages, status])\n\n const onPickFiles = (event: ChangeEvent<HTMLInputElement>) => {\n const picked = [...(event.target.files ?? [])]\n event.target.value = ''\n const omitted = attachments.add(picked)\n setFileNotice(\n omitted > 0\n ? `A conversation can have up to ${maxFiles} files. Extra files were left out.`\n : '',\n )\n }\n\n const submit = () => {\n const text = draft.trim()\n if (busy || uploading || (!text && unsent.length === 0)) {\n return\n }\n const sentBefore = attachments.sentIds.size\n const added: AttachmentView[] = unsent.map((file, offset) => ({\n index: sentBefore + offset,\n name: file.name,\n type: file.type,\n }))\n const context = currentOpenDocument()?.context\n clearError()\n transport.setAttachmentIds(attachments.readyIds)\n void sendMessage({\n text,\n metadata: {\n ...(added.length > 0 ? { attachments: added } : {}),\n ...(context ? { context } : {}),\n },\n })\n attachments.markSent(unsent.map((file) => file.id))\n setDraft('')\n setFileNotice('')\n }\n\n const onKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key !== 'Enter' || event.shiftKey || event.nativeEvent.isComposing) {\n return\n }\n event.preventDefault()\n submit()\n }\n\n const recordOutcome = (\n planId: string,\n update: { changes?: AssistantChange[]; status: PlanStatus },\n ) => {\n setMessages((current) => markPlan(current, planId, update).messages)\n }\n\n return (\n <div className={styles.assistant__chat}>\n <div className={styles.assistant__log}>\n {messages.length === 0 ? <Welcome context={openDocument?.context} /> : null}\n {messages.map((message, index) =>\n message.role === 'user' ? (\n <UserMessage key={message.id} message={message} />\n ) : (\n <AssistantMessage\n apiRoute={apiRoute}\n key={message.id}\n message={message}\n onOutcome={recordOutcome}\n running={busy && index === messages.length - 1}\n />\n ),\n )}\n {status === 'submitted' ? <p className={styles.assistant__thinking}>Thinking…</p> : null}\n <div aria-hidden=\"true\" ref={endRef} />\n </div>\n\n {busy ? (\n <div\n aria-label=\"The assistant is working\"\n className={styles.assistant__progress}\n role=\"progressbar\"\n />\n ) : null}\n <p className={styles.assistant__sr} role=\"status\">\n {uploading ? 'Uploading attachments.' : announcement(messages, busy)}\n </p>\n {error ? (\n <p className={styles.assistant__error} role=\"alert\">\n {error.message || 'The assistant could not reply. Try again.'}\n </p>\n ) : null}\n\n <form\n className={styles.assistant__composer}\n onSubmit={(event) => {\n event.preventDefault()\n submit()\n }}\n >\n {openDocument ? <ContextPill context={openDocument.context} /> : null}\n {attachments.items.length > 0 ? (\n <ul aria-label=\"Attached files\" className={styles.assistant__chips}>\n {attachments.items.map((item) => (\n <AttachmentChip\n item={item}\n key={item.key}\n onRemove={() => attachments.remove(item.key)}\n removable={!busy && !(item.id && attachments.sentIds.has(item.id))}\n />\n ))}\n </ul>\n ) : null}\n <label className={styles.assistant__sr} htmlFor={messageId}>\n Message\n </label>\n {/* eslint-disable-next-line jsx-a11y/control-has-associated-label -- labelled by the <label htmlFor> above */}\n <textarea\n className={styles.assistant__textarea}\n id={messageId}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={onKeyDown}\n placeholder=\"Ask about the site, or describe a change\"\n rows={3}\n value={draft}\n />\n <input\n aria-hidden\n className={styles.assistant__sr}\n disabled={!canAttach}\n multiple\n onChange={onPickFiles}\n ref={fileInputRef}\n tabIndex={-1}\n type=\"file\"\n />\n <div className={styles.assistant__composerRow}>\n <div className={styles.assistant__composerActions}>\n {attachmentsEnabled ? (\n <Button\n buttonStyle=\"secondary\"\n disabled={!canAttach || busy}\n margin={false}\n onClick={() => fileInputRef.current?.click()}\n size=\"small\"\n type=\"button\"\n >\n Attach files\n </Button>\n ) : null}\n {busy ? (\n <Button\n buttonStyle=\"secondary\"\n margin={false}\n onClick={() => void stop()}\n size=\"small\"\n type=\"button\"\n >\n Stop\n </Button>\n ) : (\n <Button\n buttonStyle=\"primary\"\n disabled={uploading || (!draft.trim() && unsent.length === 0)}\n margin={false}\n size=\"small\"\n type=\"submit\"\n >\n Send\n </Button>\n )}\n </div>\n </div>\n {fileNotice ? (\n <p className={styles.assistant__hint} role=\"status\">\n {fileNotice}\n </p>\n ) : null}\n </form>\n </div>\n )\n}\n\n/** A file in the composer. Its status is part of its name for screen readers, and announced as it changes. */\nfunction AttachmentChip({\n item,\n removable,\n onRemove,\n}: {\n item: ComposerAttachment\n onRemove: () => void\n removable: boolean\n}) {\n return (\n <li className={styles.assistant__chip} data-status={item.status}>\n <span className={styles.assistant__chipName}>{item.name}</span>\n {item.status === 'uploading' ? (\n <span className={styles.assistant__chipStatus}>Uploading…</span>\n ) : null}\n {item.status === 'failed' ? (\n <span className={styles.assistant__chipStatus}>{item.error ?? 'Upload failed'}</span>\n ) : null}\n {removable ? (\n <button className={styles.assistant__chipRemove} onClick={onRemove} type=\"button\">\n <span aria-hidden=\"true\">×</span>\n <span className={styles.assistant__sr}>Remove {item.name}</span>\n </button>\n ) : null}\n </li>\n )\n}\n\nfunction Welcome({ context }: { context?: PageContext }) {\n return (\n <div className={styles.assistant__welcome}>\n <p>\n Ask a question about the site's content, or describe a page or change you want. The\n assistant reads the content it needs and proposes a plan. Nothing is saved until you choose\n Proceed.\n </p>\n {context ? (\n <p>It can see {context.label}, including changes you have not saved yet.</p>\n ) : null}\n </div>\n )\n}\n\nfunction ContextPill({ context }: { context: PageContext }) {\n const entityLabel = useEntityLabel()\n return (\n <p className={styles.assistant__context}>\n <Pill entity={context.slug}>{entityLabel(context.slug, context.kind)}</Pill>\n <span>\n Using <strong>{context.label}</strong>\n </span>\n </p>\n )\n}\n\nfunction UserMessage({ message }: { message: AssistantUIMessage }) {\n const text = message.parts.flatMap((part) => (part.type === 'text' ? [part.text] : [])).join('\\n')\n const attachments = message.metadata?.attachments ?? []\n return (\n <div className={styles.assistant__user}>\n <h3 className={styles.assistant__sr}>You said</h3>\n {text ? <p className={styles.assistant__text}>{text}</p> : null}\n {attachments.length > 0 ? (\n <ul aria-label=\"Attached\" className={styles.assistant__chips}>\n {attachments.map((file) => (\n <li className={styles.assistant__chip} key={file.index}>\n <span className={styles.assistant__chipName}>{file.name}</span>\n </li>\n ))}\n </ul>\n ) : null}\n {message.metadata?.context ? (\n <p className={styles.assistant__meta}>On {message.metadata.context.label}</p>\n ) : null}\n </div>\n )\n}\n\ntype Segment =\n | { key: string; kind: 'plan'; output: Extract<PlanOutput, { ok: true }> }\n | { key: string; kind: 'steps'; steps: ToolStep[] }\n | { key: string; kind: 'text'; text: string }\n\ntype PlanOutput = Extract<\n AssistantUIMessage['parts'][number],\n { state: 'output-available'; type: 'tool-submitPlan' }\n>['output']\n\nfunction AssistantMessage({\n message,\n running,\n apiRoute,\n onOutcome,\n}: {\n apiRoute: string\n message: AssistantUIMessage\n onOutcome: (planId: string, update: { changes?: AssistantChange[]; status: PlanStatus }) => void\n /** This reply is still streaming, so its newest steps stay in view. */\n running: boolean\n}) {\n const usage = message.metadata?.usage\n const entityLabel = useEntityLabel()\n const segments = segmentsOf(message, entityLabel)\n return (\n <div className={styles.assistant__reply}>\n <h3 className={styles.assistant__sr}>Humaan AI said</h3>\n {segments.map((segment) => {\n if (segment.kind === 'text') {\n return <Markdown key={segment.key} text={segment.text} />\n }\n if (segment.kind === 'steps') {\n return (\n <Steps\n key={segment.key}\n live={running && segment === segments.at(-1)}\n steps={segment.steps}\n />\n )\n }\n return (\n <PlanCard\n apiRoute={apiRoute}\n key={segment.key}\n onOutcome={onOutcome}\n output={segment.output}\n />\n )\n })}\n {usage?.totalTokens ? (\n <p className={styles.assistant__meta}>\n {usage.totalTokens.toLocaleString('en-AU')} tokens\n {usage.cachedInputTokens\n ? `, ${usage.cachedInputTokens.toLocaleString('en-AU')} from cache`\n : ''}\n </p>\n ) : null}\n </div>\n )\n}\n\n/** While the reply streams, steps show as they run. Afterwards they fold into one line the editor can open. */\nfunction Steps({ steps, live }: { live: boolean; steps: ToolStep[] }) {\n const list = (\n <ol aria-label=\"Steps\" className={styles.assistant__steps}>\n {steps.map((step) => (\n <li className={styles.assistant__step} data-state={step.state} key={step.id}>\n <span aria-hidden=\"true\">\n {step.state === 'done' ? '✓' : step.state === 'failed' ? '!' : '…'}\n </span>\n {step.label}\n {step.state === 'failed' ? <span className={styles.assistant__sr}> (failed)</span> : null}\n </li>\n ))}\n </ol>\n )\n if (live) {\n return list\n }\n const failed = steps.filter((step) => step.state === 'failed').length\n return (\n <details className={styles.assistant__stepsFold}>\n <summary>\n Worked through {steps.length} {steps.length === 1 ? 'step' : 'steps'}\n {failed > 0 ? `, ${failed} failed` : ''}\n </summary>\n {list}\n </details>\n )\n}\n\n/** Groups tool calls that sit next to each other into one list of steps. */\nfunction segmentsOf(message: AssistantUIMessage, entityLabel: EntityLabel): Segment[] {\n const segments: Segment[] = []\n message.parts.forEach((part, index) => {\n const key = `${message.id}-${index}`\n if (part.type === 'text') {\n if (part.text.trim()) {\n segments.push({ kind: 'text', key, text: part.text.trim() })\n }\n return\n }\n if (part.type === 'tool-submitPlan' && part.state === 'output-available' && part.output.ok) {\n segments.push({ kind: 'plan', key, output: part.output })\n return\n }\n const step = toolStep(part, entityLabel)\n if (!step) {\n return\n }\n const last = segments.at(-1)\n if (last?.kind === 'steps') {\n last.steps.push(step)\n } else {\n segments.push({ kind: 'steps', key, steps: [step] })\n }\n })\n return segments\n}\n\n/** Read by screen readers instead of the streaming text, which would be announced word by word. */\nfunction announcement(messages: AssistantUIMessage[], busy: boolean): string {\n if (busy) {\n return 'The assistant is replying.'\n }\n const last = messages.at(-1)\n if (last?.role !== 'assistant') {\n return ''\n }\n const plan = last.parts.some(\n (part) =>\n part.type === 'tool-submitPlan' &&\n part.state === 'output-available' &&\n part.output.ok &&\n part.output.status === 'proposed',\n )\n return plan ? 'The assistant replied with a plan to review.' : 'The assistant replied.'\n}\n"],"names":["useChat","Button","useEffect","useId","useRef","useState","markPlan","maxFiles","styles","Markdown","currentOpenDocument","useOpenDocument","Pill","PlanCard","toolStep","AssistantTransport","useAttachments","useEntityLabel","Conversation","apiRoute","session","attachmentsEnabled","onReply","openDocument","draft","setDraft","fileNotice","setFileNotice","attachments","initial","fileInputRef","endRef","messageId","transport","api","messages","sendMessage","setMessages","status","stop","error","clearError","id","chatId","onFinish","busy","unsent","uploading","canAttach","full","current","scrollIntoView","block","onPickFiles","event","picked","target","files","value","omitted","add","submit","text","trim","length","sentBefore","sentIds","size","added","map","file","offset","index","name","type","context","setAttachmentIds","readyIds","metadata","markSent","onKeyDown","key","shiftKey","nativeEvent","isComposing","preventDefault","recordOutcome","planId","update","div","className","assistant__chat","assistant__log","Welcome","message","role","UserMessage","AssistantMessage","onOutcome","running","p","assistant__thinking","aria-hidden","ref","aria-label","assistant__progress","assistant__sr","announcement","assistant__error","form","assistant__composer","onSubmit","ContextPill","items","ul","assistant__chips","item","AttachmentChip","onRemove","remove","removable","has","label","htmlFor","textarea","assistant__textarea","onChange","placeholder","rows","input","disabled","multiple","tabIndex","assistant__composerRow","assistant__composerActions","buttonStyle","margin","onClick","click","assistant__hint","li","assistant__chip","data-status","span","assistant__chipName","assistant__chipStatus","button","assistant__chipRemove","assistant__welcome","entityLabel","assistant__context","entity","slug","kind","strong","parts","flatMap","part","join","assistant__user","h3","assistant__text","assistant__meta","usage","segments","segmentsOf","assistant__reply","segment","Steps","live","at","steps","output","totalTokens","toLocaleString","cachedInputTokens","list","ol","assistant__steps","step","assistant__step","data-state","state","failed","filter","details","assistant__stepsFold","summary","forEach","push","ok","last","plan","some"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface MarkdownProps {
|
|
2
|
+
text: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Renders an assistant reply. Raw HTML is dropped, so content the model read cannot inject markup,
|
|
6
|
+
* and react-markdown removes unsafe link protocols.
|
|
7
|
+
*/
|
|
8
|
+
export declare function Markdown({ text }: MarkdownProps): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import ReactMarkdown from 'react-markdown';
|
|
4
|
+
import remarkGfm from 'remark-gfm';
|
|
5
|
+
import styles from './AiAssistantPanel.module.scss';
|
|
6
|
+
/**
|
|
7
|
+
* Renders an assistant reply. Raw HTML is dropped, so content the model read cannot inject markup,
|
|
8
|
+
* and react-markdown removes unsafe link protocols.
|
|
9
|
+
*/ export function Markdown({ text }) {
|
|
10
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
11
|
+
className: styles.assistant__markdown,
|
|
12
|
+
children: /*#__PURE__*/ _jsx(ReactMarkdown, {
|
|
13
|
+
components: components,
|
|
14
|
+
remarkPlugins: [
|
|
15
|
+
remarkGfm
|
|
16
|
+
],
|
|
17
|
+
skipHtml: true,
|
|
18
|
+
children: text
|
|
19
|
+
})
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** Headings in a reply would break the drawer's outline, so they render as bold paragraphs. */ function Heading({ children }) {
|
|
23
|
+
return /*#__PURE__*/ _jsx("p", {
|
|
24
|
+
className: styles.assistant__markdownHeading,
|
|
25
|
+
children: /*#__PURE__*/ _jsx("strong", {
|
|
26
|
+
children: children
|
|
27
|
+
})
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const components = {
|
|
31
|
+
h1: Heading,
|
|
32
|
+
h2: Heading,
|
|
33
|
+
h3: Heading,
|
|
34
|
+
h4: Heading,
|
|
35
|
+
h5: Heading,
|
|
36
|
+
h6: Heading,
|
|
37
|
+
a: ({ href, children })=>{
|
|
38
|
+
// Admin links are relative, so an absolute URL leaves the CMS.
|
|
39
|
+
const external = typeof href === 'string' && /^https?:\/\//.test(href);
|
|
40
|
+
return /*#__PURE__*/ _jsxs("a", {
|
|
41
|
+
href: href,
|
|
42
|
+
...external ? {
|
|
43
|
+
target: '_blank',
|
|
44
|
+
rel: 'noopener noreferrer'
|
|
45
|
+
} : {},
|
|
46
|
+
children: [
|
|
47
|
+
children,
|
|
48
|
+
external ? /*#__PURE__*/ _jsx("span", {
|
|
49
|
+
className: styles.assistant__sr,
|
|
50
|
+
children: " (opens in a new tab)"
|
|
51
|
+
}) : null
|
|
52
|
+
]
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
table: ({ children })=>/*#__PURE__*/ _jsx("div", {
|
|
56
|
+
className: styles.assistant__markdownTable,
|
|
57
|
+
children: /*#__PURE__*/ _jsx("table", {
|
|
58
|
+
children: children
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=Markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/admin/Markdown.tsx"],"sourcesContent":["'use client'\n\nimport type { ReactNode } from 'react'\n\nimport ReactMarkdown, { type Components } from 'react-markdown'\nimport remarkGfm from 'remark-gfm'\n\nimport styles from './AiAssistantPanel.module.scss'\n\ninterface MarkdownProps {\n text: string\n}\n\n/**\n * Renders an assistant reply. Raw HTML is dropped, so content the model read cannot inject markup,\n * and react-markdown removes unsafe link protocols.\n */\nexport function Markdown({ text }: MarkdownProps) {\n return (\n <div className={styles.assistant__markdown}>\n <ReactMarkdown components={components} remarkPlugins={[remarkGfm]} skipHtml>\n {text}\n </ReactMarkdown>\n </div>\n )\n}\n\n/** Headings in a reply would break the drawer's outline, so they render as bold paragraphs. */\nfunction Heading({ children }: { children?: ReactNode }) {\n return (\n <p className={styles.assistant__markdownHeading}>\n <strong>{children}</strong>\n </p>\n )\n}\n\nconst components: Components = {\n h1: Heading,\n h2: Heading,\n h3: Heading,\n h4: Heading,\n h5: Heading,\n h6: Heading,\n a: ({ href, children }) => {\n // Admin links are relative, so an absolute URL leaves the CMS.\n const external = typeof href === 'string' && /^https?:\\/\\//.test(href)\n return (\n <a href={href} {...(external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}>\n {children}\n {external ? <span className={styles.assistant__sr}> (opens in a new tab)</span> : null}\n </a>\n )\n },\n table: ({ children }) => (\n <div className={styles.assistant__markdownTable}>\n <table>{children}</table>\n </div>\n ),\n}\n"],"names":["ReactMarkdown","remarkGfm","styles","Markdown","text","div","className","assistant__markdown","components","remarkPlugins","skipHtml","Heading","children","p","assistant__markdownHeading","strong","h1","h2","h3","h4","h5","h6","a","href","external","test","target","rel","span","assistant__sr","table","assistant__markdownTable"],"mappings":"AAAA;;AAIA,OAAOA,mBAAwC,iBAAgB;AAC/D,OAAOC,eAAe,aAAY;AAElC,OAAOC,YAAY,iCAAgC;AAMnD;;;CAGC,GACD,OAAO,SAASC,SAAS,EAAEC,IAAI,EAAiB;IAC9C,qBACE,KAACC;QAAIC,WAAWJ,OAAOK,mBAAmB;kBACxC,cAAA,KAACP;YAAcQ,YAAYA;YAAYC,eAAe;gBAACR;aAAU;YAAES,QAAQ;sBACxEN;;;AAIT;AAEA,6FAA6F,GAC7F,SAASO,QAAQ,EAAEC,QAAQ,EAA4B;IACrD,qBACE,KAACC;QAAEP,WAAWJ,OAAOY,0BAA0B;kBAC7C,cAAA,KAACC;sBAAQH;;;AAGf;AAEA,MAAMJ,aAAyB;IAC7BQ,IAAIL;IACJM,IAAIN;IACJO,IAAIP;IACJQ,IAAIR;IACJS,IAAIT;IACJU,IAAIV;IACJW,GAAG,CAAC,EAAEC,IAAI,EAAEX,QAAQ,EAAE;QACpB,+DAA+D;QAC/D,MAAMY,WAAW,OAAOD,SAAS,YAAY,eAAeE,IAAI,CAACF;QACjE,qBACE,MAACD;YAAEC,MAAMA;YAAO,GAAIC,WAAW;gBAAEE,QAAQ;gBAAUC,KAAK;YAAsB,IAAI,CAAC,CAAC;;gBACjFf;gBACAY,yBAAW,KAACI;oBAAKtB,WAAWJ,OAAO2B,aAAa;8BAAE;qBAA+B;;;IAGxF;IACAC,OAAO,CAAC,EAAElB,QAAQ,EAAE,iBAClB,KAACP;YAAIC,WAAWJ,OAAO6B,wBAAwB;sBAC7C,cAAA,KAACD;0BAAOlB;;;AAGd"}
|