@alpaca-editor/core 1.0.3896 → 1.0.3898
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/components/ActionButton.js +2 -2
- package/dist/components/ActionButton.js.map +1 -1
- package/dist/components/ui/button.js +3 -3
- package/dist/components/ui/button.js.map +1 -1
- package/dist/config/config.js +44 -22
- package/dist/config/config.js.map +1 -1
- package/dist/editor/FieldListField.js +1 -1
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/Titlebar.js +2 -1
- package/dist/editor/Titlebar.js.map +1 -1
- package/dist/editor/client/EditorClient.d.ts +27 -2
- package/dist/editor/client/EditorClient.js +140 -1
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +6 -1
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/itemsRepository.js +1 -1
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/operations.js +1 -1
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/control-center/About.d.ts +1 -0
- package/dist/editor/control-center/About.js +8 -0
- package/dist/editor/control-center/About.js.map +1 -0
- package/dist/editor/control-center/ControlCenterMenu.js +3 -0
- package/dist/editor/control-center/ControlCenterMenu.js.map +1 -1
- package/dist/editor/control-center/Info.d.ts +1 -0
- package/dist/editor/control-center/Info.js +10 -0
- package/dist/editor/control-center/Info.js.map +1 -0
- package/dist/editor/control-center/QuotaInfo.d.ts +1 -0
- package/dist/editor/control-center/QuotaInfo.js +102 -0
- package/dist/editor/control-center/QuotaInfo.js.map +1 -0
- package/dist/editor/control-center/Status.js +69 -2
- package/dist/editor/control-center/Status.js.map +1 -1
- package/dist/editor/control-center/WebSocketMessages.d.ts +1 -0
- package/dist/editor/control-center/WebSocketMessages.js +66 -0
- package/dist/editor/control-center/WebSocketMessages.js.map +1 -0
- package/dist/editor/page-editor-chrome/FieldActionIndicator.js +7 -6
- package/dist/editor/page-editor-chrome/FieldActionIndicator.js.map +1 -1
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +7 -1
- package/dist/editor/services/aiService.js +8 -1
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.js +1 -1
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/ViewSelector.js +9 -4
- package/dist/editor/sidebar/ViewSelector.js.map +1 -1
- package/dist/editor/ui/Icons.d.ts +19 -1
- package/dist/editor/ui/Icons.js +23 -5
- package/dist/editor/ui/Icons.js.map +1 -1
- package/dist/editor/ui/SimpleMenu.js +1 -1
- package/dist/editor/ui/SimpleMenu.js.map +1 -1
- package/dist/fonts/index.d.ts +4 -0
- package/dist/fonts/index.js +9 -0
- package/dist/fonts/index.js.map +1 -0
- package/dist/images/wizard-bg.png +0 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/page-wizard/WizardBox.d.ts +8 -0
- package/dist/page-wizard/WizardBox.js +6 -0
- package/dist/page-wizard/WizardBox.js.map +1 -0
- package/dist/page-wizard/WizardBoxConnector.d.ts +3 -0
- package/dist/page-wizard/WizardBoxConnector.js +6 -0
- package/dist/page-wizard/WizardBoxConnector.js.map +1 -0
- package/dist/page-wizard/WizardSteps.d.ts +4 -2
- package/dist/page-wizard/WizardSteps.js +44 -18
- package/dist/page-wizard/WizardSteps.js.map +1 -1
- package/dist/page-wizard/steps/CollectStep.js +16 -21
- package/dist/page-wizard/steps/CollectStep.js.map +1 -1
- package/dist/page-wizard/steps/ComponentTypesSelector.js +50 -45
- package/dist/page-wizard/steps/ComponentTypesSelector.js.map +1 -1
- package/dist/page-wizard/steps/CreatePage.js +6 -3
- package/dist/page-wizard/steps/CreatePage.js.map +1 -1
- package/dist/page-wizard/steps/CreatePageAndLayoutStep.js +21 -28
- package/dist/page-wizard/steps/CreatePageAndLayoutStep.js.map +1 -1
- package/dist/page-wizard/steps/Generate.js +27 -5
- package/dist/page-wizard/steps/Generate.js.map +1 -1
- package/dist/page-wizard/steps/ImagesStep.js +46 -44
- package/dist/page-wizard/steps/ImagesStep.js.map +1 -1
- package/dist/page-wizard/steps/SelectStep.js +11 -19
- package/dist/page-wizard/steps/SelectStep.js.map +1 -1
- package/dist/page-wizard/steps/usePageCreator.js +41 -12
- package/dist/page-wizard/steps/usePageCreator.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/styles.css +236 -120
- package/images/wizard-bg.png +0 -0
- package/package.json +1 -1
- package/src/components/ActionButton.tsx +6 -8
- package/src/components/ui/button.tsx +3 -3
- package/src/config/config.tsx +54 -22
- package/src/editor/FieldListField.tsx +2 -2
- package/src/editor/Titlebar.tsx +2 -1
- package/src/editor/client/EditorClient.tsx +192 -9
- package/src/editor/client/editContext.ts +12 -2
- package/src/editor/client/itemsRepository.ts +1 -1
- package/src/editor/client/operations.ts +1 -1
- package/src/editor/control-center/About.tsx +342 -0
- package/src/editor/control-center/ControlCenterMenu.tsx +5 -0
- package/src/editor/control-center/Info.tsx +104 -0
- package/src/editor/control-center/QuotaInfo.tsx +301 -0
- package/src/editor/control-center/Status.tsx +108 -2
- package/src/editor/control-center/WebSocketMessages.tsx +155 -0
- package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +20 -5
- package/src/editor/page-viewer/PageViewer.tsx +1 -1
- package/src/editor/services/aiService.ts +17 -2
- package/src/editor/sidebar/ComponentTree.tsx +1 -1
- package/src/editor/sidebar/ViewSelector.tsx +10 -11
- package/src/editor/ui/Icons.tsx +146 -26
- package/src/editor/ui/SimpleMenu.tsx +1 -1
- package/src/fonts/index.ts +10 -0
- package/src/index.ts +7 -1
- package/src/page-wizard/WizardBox.tsx +40 -0
- package/src/page-wizard/WizardBoxConnector.tsx +21 -0
- package/src/page-wizard/WizardSteps.tsx +236 -116
- package/src/page-wizard/steps/CollectStep.tsx +129 -67
- package/src/page-wizard/steps/ComponentTypesSelector.tsx +32 -11
- package/src/page-wizard/steps/CreatePage.tsx +130 -84
- package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +47 -30
- package/src/page-wizard/steps/Generate.tsx +45 -17
- package/src/page-wizard/steps/ImagesStep.tsx +161 -141
- package/src/page-wizard/steps/SelectStep.tsx +92 -76
- package/src/page-wizard/steps/usePageCreator.ts +40 -14
- package/src/revision.ts +2 -2
- package/styles.css +49 -8
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useEditContext } from "../client/editContext";
|
|
3
|
+
|
|
4
|
+
export function QuotaInfo() {
|
|
5
|
+
const editContext = useEditContext();
|
|
6
|
+
const quotaInfo = editContext?.quotaInfo;
|
|
7
|
+
|
|
8
|
+
const formatQuotaPercentage = (used: number, limit: number) => {
|
|
9
|
+
if (!limit || limit === 0 || limit === -1) return "No limit";
|
|
10
|
+
if (!used && used !== 0) return "0%";
|
|
11
|
+
if (isNaN(used) || isNaN(limit)) return "0%";
|
|
12
|
+
const percentage = Math.round((used / limit) * 100);
|
|
13
|
+
return `${percentage}%`;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const getQuotaStatus = (used: number, limit: number) => {
|
|
17
|
+
if (!limit || limit === 0 || limit === -1) return "unlimited";
|
|
18
|
+
if (!used && used !== 0) return "ok";
|
|
19
|
+
if (isNaN(used) || isNaN(limit)) return "ok";
|
|
20
|
+
const percentage = (used / limit) * 100;
|
|
21
|
+
if (percentage >= 100) return "exceeded";
|
|
22
|
+
if (percentage >= 90) return "warning";
|
|
23
|
+
if (percentage >= 75) return "caution";
|
|
24
|
+
return "ok";
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getProgressWidth = (used: number, limit: number) => {
|
|
28
|
+
if (
|
|
29
|
+
!limit ||
|
|
30
|
+
limit === 0 ||
|
|
31
|
+
limit === -1 ||
|
|
32
|
+
!used ||
|
|
33
|
+
isNaN(used) ||
|
|
34
|
+
isNaN(limit)
|
|
35
|
+
)
|
|
36
|
+
return 0;
|
|
37
|
+
return Math.min(100, (used / limit) * 100);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (!quotaInfo) {
|
|
41
|
+
return (
|
|
42
|
+
<div className="space-y-6 p-4">
|
|
43
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
44
|
+
<h2 className="mb-3 text-xl font-semibold text-gray-800">
|
|
45
|
+
AI Usage Quota
|
|
46
|
+
</h2>
|
|
47
|
+
<p className="text-gray-600">No quota information available</p>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className="space-y-6 p-4">
|
|
55
|
+
{/* Token Usage Box */}
|
|
56
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
57
|
+
<h3 className="mb-3 text-lg font-semibold text-gray-800">
|
|
58
|
+
Token Usage
|
|
59
|
+
</h3>
|
|
60
|
+
<div className="space-y-4">
|
|
61
|
+
{/* Total Token Usage */}
|
|
62
|
+
<div>
|
|
63
|
+
<div className="mb-2 flex items-center justify-between">
|
|
64
|
+
<label className="text-sm font-medium text-gray-500">
|
|
65
|
+
Total Usage
|
|
66
|
+
</label>
|
|
67
|
+
<span className="text-sm text-gray-600">
|
|
68
|
+
{quotaInfo.usage.totalTokens.toLocaleString()} /{" "}
|
|
69
|
+
{quotaInfo.limits.totalTokens &&
|
|
70
|
+
quotaInfo.limits.totalTokens > 0
|
|
71
|
+
? quotaInfo.limits.totalTokens.toLocaleString()
|
|
72
|
+
: "∞"}
|
|
73
|
+
</span>
|
|
74
|
+
</div>
|
|
75
|
+
{quotaInfo.limits.totalTokens &&
|
|
76
|
+
quotaInfo.limits.totalTokens > 0 && (
|
|
77
|
+
<div className="h-2 w-full rounded-full bg-gray-200">
|
|
78
|
+
<div
|
|
79
|
+
className={`h-2 rounded-full ${
|
|
80
|
+
getQuotaStatus(
|
|
81
|
+
quotaInfo.usage.totalTokens,
|
|
82
|
+
quotaInfo.limits.totalTokens,
|
|
83
|
+
) === "exceeded"
|
|
84
|
+
? "bg-red-500"
|
|
85
|
+
: getQuotaStatus(
|
|
86
|
+
quotaInfo.usage.totalTokens,
|
|
87
|
+
quotaInfo.limits.totalTokens,
|
|
88
|
+
) === "warning"
|
|
89
|
+
? "bg-orange-500"
|
|
90
|
+
: getQuotaStatus(
|
|
91
|
+
quotaInfo.usage.totalTokens,
|
|
92
|
+
quotaInfo.limits.totalTokens,
|
|
93
|
+
) === "caution"
|
|
94
|
+
? "bg-yellow-500"
|
|
95
|
+
: "bg-green-500"
|
|
96
|
+
}`}
|
|
97
|
+
style={{
|
|
98
|
+
width: `${getProgressWidth(quotaInfo.usage.totalTokens, quotaInfo.limits.totalTokens)}%`,
|
|
99
|
+
}}
|
|
100
|
+
></div>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
104
|
+
{formatQuotaPercentage(
|
|
105
|
+
quotaInfo.usage.totalTokens,
|
|
106
|
+
quotaInfo.limits.totalTokens,
|
|
107
|
+
)}{" "}
|
|
108
|
+
used
|
|
109
|
+
</p>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
{/* Daily Token Usage */}
|
|
113
|
+
<div>
|
|
114
|
+
<div className="mb-2 flex items-center justify-between">
|
|
115
|
+
<label className="text-sm font-medium text-gray-500">
|
|
116
|
+
Daily Usage
|
|
117
|
+
</label>
|
|
118
|
+
<span className="text-sm text-gray-600">
|
|
119
|
+
{quotaInfo.usage.dailyTokens.toLocaleString()} /{" "}
|
|
120
|
+
{quotaInfo.limits.dailyTokens &&
|
|
121
|
+
quotaInfo.limits.dailyTokens > 0
|
|
122
|
+
? quotaInfo.limits.dailyTokens.toLocaleString()
|
|
123
|
+
: "∞"}
|
|
124
|
+
</span>
|
|
125
|
+
</div>
|
|
126
|
+
{quotaInfo.limits.dailyTokens &&
|
|
127
|
+
quotaInfo.limits.dailyTokens > 0 && (
|
|
128
|
+
<div className="h-2 w-full rounded-full bg-gray-200">
|
|
129
|
+
<div
|
|
130
|
+
className={`h-2 rounded-full ${
|
|
131
|
+
getQuotaStatus(
|
|
132
|
+
quotaInfo.usage.dailyTokens,
|
|
133
|
+
quotaInfo.limits.dailyTokens,
|
|
134
|
+
) === "exceeded"
|
|
135
|
+
? "bg-red-500"
|
|
136
|
+
: getQuotaStatus(
|
|
137
|
+
quotaInfo.usage.dailyTokens,
|
|
138
|
+
quotaInfo.limits.dailyTokens,
|
|
139
|
+
) === "warning"
|
|
140
|
+
? "bg-orange-500"
|
|
141
|
+
: getQuotaStatus(
|
|
142
|
+
quotaInfo.usage.dailyTokens,
|
|
143
|
+
quotaInfo.limits.dailyTokens,
|
|
144
|
+
) === "caution"
|
|
145
|
+
? "bg-yellow-500"
|
|
146
|
+
: "bg-green-500"
|
|
147
|
+
}`}
|
|
148
|
+
style={{
|
|
149
|
+
width: `${getProgressWidth(quotaInfo.usage.dailyTokens, quotaInfo.limits.dailyTokens)}%`,
|
|
150
|
+
}}
|
|
151
|
+
></div>
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
155
|
+
{quotaInfo.limits.dailyTokens === -1
|
|
156
|
+
? "No daily limit"
|
|
157
|
+
: `${formatQuotaPercentage(
|
|
158
|
+
quotaInfo.usage.dailyTokens,
|
|
159
|
+
quotaInfo.limits.dailyTokens,
|
|
160
|
+
)} used today`}
|
|
161
|
+
</p>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
{/* Image Usage Box */}
|
|
167
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
168
|
+
<h3 className="mb-3 text-lg font-semibold text-gray-800">
|
|
169
|
+
Image Usage
|
|
170
|
+
</h3>
|
|
171
|
+
<div className="space-y-4">
|
|
172
|
+
{/* Total Image Usage */}
|
|
173
|
+
<div>
|
|
174
|
+
<div className="mb-2 flex items-center justify-between">
|
|
175
|
+
<label className="text-sm font-medium text-gray-500">
|
|
176
|
+
Total Usage
|
|
177
|
+
</label>
|
|
178
|
+
<span className="text-sm text-gray-600">
|
|
179
|
+
{quotaInfo.usage.totalImages.toLocaleString()} /{" "}
|
|
180
|
+
{quotaInfo.limits.totalImages &&
|
|
181
|
+
quotaInfo.limits.totalImages > 0
|
|
182
|
+
? quotaInfo.limits.totalImages.toLocaleString()
|
|
183
|
+
: "∞"}
|
|
184
|
+
</span>
|
|
185
|
+
</div>
|
|
186
|
+
{quotaInfo.limits.totalImages &&
|
|
187
|
+
quotaInfo.limits.totalImages > 0 && (
|
|
188
|
+
<div className="h-2 w-full rounded-full bg-gray-200">
|
|
189
|
+
<div
|
|
190
|
+
className={`h-2 rounded-full ${
|
|
191
|
+
getQuotaStatus(
|
|
192
|
+
quotaInfo.usage.totalImages,
|
|
193
|
+
quotaInfo.limits.totalImages,
|
|
194
|
+
) === "exceeded"
|
|
195
|
+
? "bg-red-500"
|
|
196
|
+
: getQuotaStatus(
|
|
197
|
+
quotaInfo.usage.totalImages,
|
|
198
|
+
quotaInfo.limits.totalImages,
|
|
199
|
+
) === "warning"
|
|
200
|
+
? "bg-orange-500"
|
|
201
|
+
: getQuotaStatus(
|
|
202
|
+
quotaInfo.usage.totalImages,
|
|
203
|
+
quotaInfo.limits.totalImages,
|
|
204
|
+
) === "caution"
|
|
205
|
+
? "bg-yellow-500"
|
|
206
|
+
: "bg-green-500"
|
|
207
|
+
}`}
|
|
208
|
+
style={{
|
|
209
|
+
width: `${getProgressWidth(quotaInfo.usage.totalImages, quotaInfo.limits.totalImages)}%`,
|
|
210
|
+
}}
|
|
211
|
+
></div>
|
|
212
|
+
</div>
|
|
213
|
+
)}
|
|
214
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
215
|
+
{formatQuotaPercentage(
|
|
216
|
+
quotaInfo.usage.totalImages,
|
|
217
|
+
quotaInfo.limits.totalImages,
|
|
218
|
+
)}{" "}
|
|
219
|
+
used
|
|
220
|
+
</p>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
{/* Daily Image Usage */}
|
|
224
|
+
<div>
|
|
225
|
+
<div className="mb-2 flex items-center justify-between">
|
|
226
|
+
<label className="text-sm font-medium text-gray-500">
|
|
227
|
+
Daily Usage
|
|
228
|
+
</label>
|
|
229
|
+
<span className="text-sm text-gray-600">
|
|
230
|
+
{quotaInfo.usage.dailyImages.toLocaleString()} /{" "}
|
|
231
|
+
{quotaInfo.limits.dailyImages &&
|
|
232
|
+
quotaInfo.limits.dailyImages > 0
|
|
233
|
+
? quotaInfo.limits.dailyImages.toLocaleString()
|
|
234
|
+
: "∞"}
|
|
235
|
+
</span>
|
|
236
|
+
</div>
|
|
237
|
+
{quotaInfo.limits.dailyImages &&
|
|
238
|
+
quotaInfo.limits.dailyImages > 0 && (
|
|
239
|
+
<div className="h-2 w-full rounded-full bg-gray-200">
|
|
240
|
+
<div
|
|
241
|
+
className={`h-2 rounded-full ${
|
|
242
|
+
getQuotaStatus(
|
|
243
|
+
quotaInfo.usage.dailyImages,
|
|
244
|
+
quotaInfo.limits.dailyImages,
|
|
245
|
+
) === "exceeded"
|
|
246
|
+
? "bg-red-500"
|
|
247
|
+
: getQuotaStatus(
|
|
248
|
+
quotaInfo.usage.dailyImages,
|
|
249
|
+
quotaInfo.limits.dailyImages,
|
|
250
|
+
) === "warning"
|
|
251
|
+
? "bg-orange-500"
|
|
252
|
+
: getQuotaStatus(
|
|
253
|
+
quotaInfo.usage.dailyImages,
|
|
254
|
+
quotaInfo.limits.dailyImages,
|
|
255
|
+
) === "caution"
|
|
256
|
+
? "bg-yellow-500"
|
|
257
|
+
: "bg-green-500"
|
|
258
|
+
}`}
|
|
259
|
+
style={{
|
|
260
|
+
width: `${getProgressWidth(quotaInfo.usage.dailyImages, quotaInfo.limits.dailyImages)}%`,
|
|
261
|
+
}}
|
|
262
|
+
></div>
|
|
263
|
+
</div>
|
|
264
|
+
)}
|
|
265
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
266
|
+
{quotaInfo.limits.dailyImages === -1
|
|
267
|
+
? "No daily limit"
|
|
268
|
+
: `${formatQuotaPercentage(
|
|
269
|
+
quotaInfo.usage.dailyImages,
|
|
270
|
+
quotaInfo.limits.dailyImages,
|
|
271
|
+
)} used today`}
|
|
272
|
+
</p>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
{/* Quota Status Summary */}
|
|
278
|
+
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
|
279
|
+
<h4 className="mb-2 text-sm font-medium text-gray-700">Quota Status</h4>
|
|
280
|
+
<div className="space-y-1 text-sm">
|
|
281
|
+
{editContext?.isQuotaExceeded && (
|
|
282
|
+
<p className="font-medium text-red-600">
|
|
283
|
+
⚠️ Quota limits have been exceeded
|
|
284
|
+
</p>
|
|
285
|
+
)}
|
|
286
|
+
{editContext?.getQuotaWarningMessage &&
|
|
287
|
+
editContext.getQuotaWarningMessage() &&
|
|
288
|
+
!editContext.isQuotaExceeded && (
|
|
289
|
+
<p className="text-orange-600">
|
|
290
|
+
⚠️ {editContext.getQuotaWarningMessage()}
|
|
291
|
+
</p>
|
|
292
|
+
)}
|
|
293
|
+
{!editContext?.isQuotaExceeded &&
|
|
294
|
+
!editContext?.getQuotaWarningMessage?.() && (
|
|
295
|
+
<p className="text-green-600">✅ All quotas are within limits</p>
|
|
296
|
+
)}
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
@@ -1,7 +1,113 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useRouter } from "next/navigation";
|
|
3
|
+
import { usePathname, useSearchParams } from "next/navigation";
|
|
4
|
+
import { useEditContext } from "../client/editContext";
|
|
5
|
+
import { SimpleMenu } from "../ui/SimpleMenu";
|
|
6
|
+
import { Splitter, SplitterPanel } from "../ui/Splitter";
|
|
7
|
+
|
|
1
8
|
export function Status() {
|
|
9
|
+
const editContext = useEditContext();
|
|
10
|
+
const config = editContext?.configuration;
|
|
11
|
+
const searchParams = useSearchParams();
|
|
12
|
+
const urlActiveItemKey = searchParams.get("ccpanel");
|
|
13
|
+
|
|
14
|
+
// Get the first available panel as default
|
|
15
|
+
const defaultActiveItemKey = config?.controlCenter.groups?.flatMap(
|
|
16
|
+
(x) => x.panels,
|
|
17
|
+
)?.[0]?.id;
|
|
18
|
+
|
|
19
|
+
const [activeItemKey, setActiveItemKey] = useState<string | null>(
|
|
20
|
+
urlActiveItemKey || defaultActiveItemKey || null,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const router = useRouter();
|
|
24
|
+
const pathname = usePathname();
|
|
25
|
+
|
|
26
|
+
const updateUrl = (key: string | null) => {
|
|
27
|
+
if (urlActiveItemKey === key) return;
|
|
28
|
+
|
|
29
|
+
const current = new URLSearchParams(Array.from(searchParams.entries()));
|
|
30
|
+
|
|
31
|
+
if (key) {
|
|
32
|
+
current.set("ccpanel", key);
|
|
33
|
+
} else {
|
|
34
|
+
current.delete("ccpanel");
|
|
35
|
+
}
|
|
36
|
+
router.push(`${pathname}?${current.toString()}`, { scroll: false });
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Set default active item when config loads and no active item is set
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!activeItemKey && defaultActiveItemKey) {
|
|
42
|
+
setActiveItemKey(defaultActiveItemKey);
|
|
43
|
+
}
|
|
44
|
+
}, [defaultActiveItemKey, activeItemKey]);
|
|
45
|
+
|
|
46
|
+
const items = config?.controlCenter.groups.map((group) => {
|
|
47
|
+
return {
|
|
48
|
+
id: group.title,
|
|
49
|
+
label: group.title,
|
|
50
|
+
icon: group.icon,
|
|
51
|
+
items:
|
|
52
|
+
group.panels.map((panel) => ({
|
|
53
|
+
id: panel.id,
|
|
54
|
+
label: panel.title,
|
|
55
|
+
})) || [],
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Find the currently selected panel content
|
|
60
|
+
const selectedPanel = config?.controlCenter.groups
|
|
61
|
+
?.flatMap((x) => x.panels)
|
|
62
|
+
?.find((item) => item.id === activeItemKey);
|
|
63
|
+
|
|
64
|
+
if (!items) {
|
|
65
|
+
return (
|
|
66
|
+
<div className="flex h-full flex-col items-center justify-center">
|
|
67
|
+
Loading...
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const panels: SplitterPanel[] = [
|
|
73
|
+
{
|
|
74
|
+
name: "menu",
|
|
75
|
+
defaultSize: 300,
|
|
76
|
+
content: (
|
|
77
|
+
<div className="h-full border-r border-gray-200">
|
|
78
|
+
<SimpleMenu
|
|
79
|
+
items={items}
|
|
80
|
+
activeItemKey={activeItemKey}
|
|
81
|
+
onItemClick={(item) => {
|
|
82
|
+
setActiveItemKey(item.id);
|
|
83
|
+
// Only update URL when user explicitly clicks on an item
|
|
84
|
+
updateUrl(item.id);
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "content",
|
|
92
|
+
defaultSize: "auto",
|
|
93
|
+
content: (
|
|
94
|
+
<div className="absolute inset-0 overflow-auto">
|
|
95
|
+
{selectedPanel ? (
|
|
96
|
+
selectedPanel.content
|
|
97
|
+
) : (
|
|
98
|
+
<div className="flex h-full flex-col items-center justify-center text-gray-500">
|
|
99
|
+
<i className="pi pi-info-circle mb-4 text-4xl"></i>
|
|
100
|
+
<p>Select a panel from the menu to view its content</p>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
),
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
2
108
|
return (
|
|
3
|
-
<div className="
|
|
4
|
-
|
|
109
|
+
<div className="h-full">
|
|
110
|
+
<Splitter panels={panels} localStorageKey="control-center-splitter" />
|
|
5
111
|
</div>
|
|
6
112
|
);
|
|
7
113
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from "react";
|
|
2
|
+
import { useEditContext } from "../client/editContext";
|
|
3
|
+
import { WebSocketMessage } from "../client/EditorClient";
|
|
4
|
+
|
|
5
|
+
export function WebSocketMessages() {
|
|
6
|
+
const editContext = useEditContext();
|
|
7
|
+
const [expandedMessages, setExpandedMessages] = useState<Set<string>>(
|
|
8
|
+
new Set(),
|
|
9
|
+
);
|
|
10
|
+
const [autoScroll, setAutoScroll] = useState(true);
|
|
11
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
12
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
13
|
+
|
|
14
|
+
// Get messages from central store
|
|
15
|
+
const messages = editContext?.webSocketMessages || [];
|
|
16
|
+
|
|
17
|
+
// Auto-scroll to bottom when new messages arrive
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (autoScroll && messagesEndRef.current) {
|
|
20
|
+
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
|
|
21
|
+
}
|
|
22
|
+
}, [messages, autoScroll]);
|
|
23
|
+
|
|
24
|
+
const toggleExpanded = (messageId: string) => {
|
|
25
|
+
setExpandedMessages((prev) => {
|
|
26
|
+
const newSet = new Set(prev);
|
|
27
|
+
if (newSet.has(messageId)) {
|
|
28
|
+
newSet.delete(messageId);
|
|
29
|
+
} else {
|
|
30
|
+
newSet.add(messageId);
|
|
31
|
+
}
|
|
32
|
+
return newSet;
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const formatTimestamp = (isoString: string) => {
|
|
37
|
+
const date = new Date(isoString);
|
|
38
|
+
return (
|
|
39
|
+
date.toLocaleTimeString() +
|
|
40
|
+
"." +
|
|
41
|
+
date.getMilliseconds().toString().padStart(3, "0")
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const getMessageTypeColor = (type: string) => {
|
|
46
|
+
const colors: { [key: string]: string } = {
|
|
47
|
+
"active-sessions": "bg-blue-100 text-blue-800",
|
|
48
|
+
"item-deleted": "bg-red-100 text-red-800",
|
|
49
|
+
"item-changed": "bg-yellow-100 text-yellow-800",
|
|
50
|
+
"item-version-added": "bg-green-100 text-green-800",
|
|
51
|
+
"edit-operation": "bg-purple-100 text-purple-800",
|
|
52
|
+
"executing-field-action": "bg-orange-100 text-orange-800",
|
|
53
|
+
"comment-updated": "bg-cyan-100 text-cyan-800",
|
|
54
|
+
"comment-deleted": "bg-red-100 text-red-800",
|
|
55
|
+
"suggested-edit-updated": "bg-indigo-100 text-indigo-800",
|
|
56
|
+
"suggested-edit-deleted": "bg-red-100 text-red-800",
|
|
57
|
+
"update-quota": "bg-gray-100 text-gray-800",
|
|
58
|
+
};
|
|
59
|
+
return colors[type] || "bg-gray-100 text-gray-800";
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const clearMessages = () => {
|
|
63
|
+
editContext?.clearWebSocketMessages();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleScroll = () => {
|
|
67
|
+
if (!containerRef.current) return;
|
|
68
|
+
|
|
69
|
+
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
|
70
|
+
const isAtBottom = scrollHeight - scrollTop <= clientHeight + 10;
|
|
71
|
+
setAutoScroll(isAtBottom);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div className="flex h-full flex-col">
|
|
76
|
+
{/* Header */}
|
|
77
|
+
<div className="flex items-center justify-between border-b border-gray-200 p-4">
|
|
78
|
+
<div className="flex items-center gap-2">
|
|
79
|
+
<i className="pi pi-comments text-lg" />
|
|
80
|
+
<h3 className="text-lg font-semibold">WebSocket Messages</h3>
|
|
81
|
+
<span className="rounded bg-gray-100 px-2 py-1 text-sm text-gray-800">
|
|
82
|
+
{messages.length}
|
|
83
|
+
</span>
|
|
84
|
+
</div>
|
|
85
|
+
<div className="flex items-center gap-2">
|
|
86
|
+
<label className="flex items-center gap-1 text-sm">
|
|
87
|
+
<input
|
|
88
|
+
type="checkbox"
|
|
89
|
+
checked={autoScroll}
|
|
90
|
+
onChange={(e) => setAutoScroll(e.target.checked)}
|
|
91
|
+
className="rounded"
|
|
92
|
+
/>
|
|
93
|
+
Auto-scroll
|
|
94
|
+
</label>
|
|
95
|
+
<button
|
|
96
|
+
onClick={clearMessages}
|
|
97
|
+
className="rounded bg-red-500 px-3 py-1 text-sm text-white hover:bg-red-600"
|
|
98
|
+
>
|
|
99
|
+
Clear
|
|
100
|
+
</button>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
{/* Messages List */}
|
|
105
|
+
<div
|
|
106
|
+
ref={containerRef}
|
|
107
|
+
className="flex-1 space-y-2 overflow-y-auto p-4"
|
|
108
|
+
onScroll={handleScroll}
|
|
109
|
+
>
|
|
110
|
+
{messages.length === 0 ? (
|
|
111
|
+
<div className="py-8 text-center text-gray-500">
|
|
112
|
+
No WebSocket messages received yet
|
|
113
|
+
</div>
|
|
114
|
+
) : (
|
|
115
|
+
messages.map((message) => (
|
|
116
|
+
<div key={message.id} className="rounded-lg border border-gray-200">
|
|
117
|
+
<div
|
|
118
|
+
className="flex cursor-pointer items-center justify-between p-3 hover:bg-gray-50"
|
|
119
|
+
onClick={() => toggleExpanded(message.id)}
|
|
120
|
+
>
|
|
121
|
+
<div className="flex min-w-0 flex-1 items-center gap-3">
|
|
122
|
+
<span className="font-mono text-xs text-gray-500">
|
|
123
|
+
{formatTimestamp(message.timestamp)}
|
|
124
|
+
</span>
|
|
125
|
+
<span
|
|
126
|
+
className={`rounded px-2 py-1 text-xs font-medium ${getMessageTypeColor(message.type)}`}
|
|
127
|
+
>
|
|
128
|
+
{message.type}
|
|
129
|
+
</span>
|
|
130
|
+
<span className="truncate text-sm text-gray-600">
|
|
131
|
+
{typeof message.payload === "object"
|
|
132
|
+
? Object.keys(message.payload).join(", ")
|
|
133
|
+
: String(message.payload)}
|
|
134
|
+
</span>
|
|
135
|
+
</div>
|
|
136
|
+
<i
|
|
137
|
+
className={`pi ${expandedMessages.has(message.id) ? "pi-chevron-up" : "pi-chevron-down"} text-gray-400`}
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
{expandedMessages.has(message.id) && (
|
|
142
|
+
<div className="border-t border-gray-200 bg-gray-50 p-3">
|
|
143
|
+
<pre className="overflow-x-auto font-mono text-xs break-words whitespace-pre-wrap">
|
|
144
|
+
{message.rawMessage}
|
|
145
|
+
</pre>
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
</div>
|
|
149
|
+
))
|
|
150
|
+
)}
|
|
151
|
+
<div ref={messagesEndRef} />
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ProgressSpinner } from "primereact/progressspinner";
|
|
1
2
|
import { FieldAction } from "../client/EditorClient";
|
|
2
3
|
import { useEditContext } from "../client/editContext";
|
|
3
4
|
|
|
@@ -9,7 +10,7 @@ export function FieldActionIndicator({ action }: { action: FieldAction }) {
|
|
|
9
10
|
const field = action.field;
|
|
10
11
|
const fieldElements =
|
|
11
12
|
pageViewContext.editorIframeRef.current?.contentWindow?.document.querySelectorAll(
|
|
12
|
-
`[data-fieldid="${field.fieldId}"][data-itemid="${field.item.id}"][data-language="${field.item.language}"]
|
|
13
|
+
`[data-fieldid="${field.fieldId}"][data-itemid="${field.item.id}"][data-language="${field.item.language}"][data-version="${field.item.version}"]`,
|
|
13
14
|
);
|
|
14
15
|
|
|
15
16
|
if (!fieldElements) return null;
|
|
@@ -19,19 +20,25 @@ export function FieldActionIndicator({ action }: { action: FieldAction }) {
|
|
|
19
20
|
<SingleIndicator
|
|
20
21
|
element={element}
|
|
21
22
|
key={action.field.fieldId + action.field + index}
|
|
23
|
+
action={action}
|
|
22
24
|
/>
|
|
23
25
|
))}
|
|
24
26
|
</>
|
|
25
27
|
);
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
function SingleIndicator({
|
|
30
|
+
function SingleIndicator({
|
|
31
|
+
element,
|
|
32
|
+
action,
|
|
33
|
+
}: {
|
|
34
|
+
element: Element;
|
|
35
|
+
action: FieldAction;
|
|
36
|
+
}) {
|
|
29
37
|
const rect = element.getBoundingClientRect();
|
|
30
|
-
|
|
31
38
|
const indicatorRect = rect;
|
|
32
39
|
return (
|
|
33
40
|
<div
|
|
34
|
-
className={`pointer-events-none absolute
|
|
41
|
+
className={`focus-shadow executing-action pointer-events-none absolute flex items-center justify-center bg-blue-500/20`}
|
|
35
42
|
style={{
|
|
36
43
|
left: indicatorRect.x,
|
|
37
44
|
top: indicatorRect.y,
|
|
@@ -39,6 +46,14 @@ function SingleIndicator({ element }: { element: Element }) {
|
|
|
39
46
|
height: indicatorRect.height,
|
|
40
47
|
zIndex: 800,
|
|
41
48
|
}}
|
|
42
|
-
|
|
49
|
+
>
|
|
50
|
+
<div className="flex flex-col items-center justify-center gap-1.5 rounded-md bg-gray-100 p-3 text-sm font-bold text-blue-500">
|
|
51
|
+
<div className="flex items-center gap-1">
|
|
52
|
+
<ProgressSpinner style={{ width: "1rem", height: "1rem" }} />
|
|
53
|
+
{action.label}
|
|
54
|
+
</div>
|
|
55
|
+
<div className="text-xs">{action.message}</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
43
58
|
);
|
|
44
59
|
}
|
|
@@ -5,7 +5,7 @@ import { PageViewerFrame } from "./PageViewerFrame";
|
|
|
5
5
|
import { useEffect, useState } from "react";
|
|
6
6
|
import { useRef } from "react";
|
|
7
7
|
import { SimpleIconButton } from "../ui/SimpleIconButton";
|
|
8
|
-
import {
|
|
8
|
+
import { useEditContext } from "../client/editContext";
|
|
9
9
|
import { useDebouncedCallback } from "use-debounce";
|
|
10
10
|
import { Ellipsis, PanelLeftClose, PanelLeftOpen } from "lucide-react";
|
|
11
11
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AiContext } from "../ai/AiTerminal";
|
|
2
2
|
import { EditContextType } from "../client/editContext";
|
|
3
|
-
import { ItemDescriptor } from "../pageModel";
|
|
4
|
-
|
|
3
|
+
import { FieldDescriptor, ItemDescriptor } from "../pageModel";
|
|
4
|
+
|
|
5
|
+
import { ExecutionResult, get, post } from "./serviceHelper";
|
|
5
6
|
|
|
6
7
|
export type AiProfile = {
|
|
7
8
|
id: string;
|
|
@@ -157,3 +158,17 @@ export async function executeSearch({
|
|
|
157
158
|
|
|
158
159
|
return { type: "success", response, data: await response.json() };
|
|
159
160
|
}
|
|
161
|
+
|
|
162
|
+
export async function generateImage(
|
|
163
|
+
options: FieldDescriptor & {
|
|
164
|
+
prompt: string;
|
|
165
|
+
sessionId: string;
|
|
166
|
+
pageItem: ItemDescriptor;
|
|
167
|
+
},
|
|
168
|
+
): Promise<ExecutionResult<any>> {
|
|
169
|
+
const response = await post("/alpaca/editor/ai/generateImage", options);
|
|
170
|
+
return response;
|
|
171
|
+
}
|
|
172
|
+
export async function requestQuota() {
|
|
173
|
+
await get("/alpaca/editor/ai/requestQuota");
|
|
174
|
+
}
|
|
@@ -474,7 +474,7 @@ export function ComponentTree({}) {
|
|
|
474
474
|
function renderNode(node: CustomTreeNode) {
|
|
475
475
|
return (
|
|
476
476
|
<div>
|
|
477
|
-
<div className="
|
|
477
|
+
<div className="flex items-center gap-2 text-[12px] text-gray-600">
|
|
478
478
|
{typeof node.icon === "string" ? (
|
|
479
479
|
<i className={node.icon}></i>
|
|
480
480
|
) : (
|