@orangecatai/adgen-canvas 0.0.2 → 0.0.3
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/dev/{chunk-X2WWSILD.js → chunk-EP27W34X.js} +2 -2
- package/dist/dev/{chunk-UV7ECD7A.js → chunk-YOQUQ6O5.js} +3 -3
- package/dist/dev/data/{image-H4O52A73.js → image-IZS5VEYX.js} +3 -3
- package/dist/dev/index.css +731 -0
- package/dist/dev/index.css.map +3 -3
- package/dist/dev/index.js +2068 -140
- package/dist/dev/index.js.map +4 -4
- package/dist/dev/subset-shared.chunk.js +1 -1
- package/dist/dev/subset-worker.chunk.js +1 -1
- package/dist/prod/{chunk-DOJFO2UO.js → chunk-2ALGWY4R.js} +2 -2
- package/dist/prod/{chunk-O3WJMHIE.js → chunk-I4SUBR2Z.js} +1 -1
- package/dist/prod/data/image-PKF4YK4A.js +1 -0
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +64 -19
- package/dist/prod/subset-shared.chunk.js +1 -1
- package/dist/prod/subset-worker.chunk.js +1 -1
- package/dist/types/excalidraw/index.d.ts +2 -0
- package/package.json +1 -1
- package/dist/prod/data/image-43FV5SMF.js +0 -1
- /package/dist/dev/{chunk-X2WWSILD.js.map → chunk-EP27W34X.js.map} +0 -0
- /package/dist/dev/{chunk-UV7ECD7A.js.map → chunk-YOQUQ6O5.js.map} +0 -0
- /package/dist/dev/data/{image-H4O52A73.js.map → image-IZS5VEYX.js.map} +0 -0
package/dist/dev/index.js
CHANGED
|
@@ -66,10 +66,10 @@ import {
|
|
|
66
66
|
serializeAsJSON,
|
|
67
67
|
serializeLibraryAsJSON,
|
|
68
68
|
strokeRectWithRotation_simple
|
|
69
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-YOQUQ6O5.js";
|
|
70
70
|
import {
|
|
71
71
|
define_import_meta_env_default
|
|
72
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-EP27W34X.js";
|
|
73
73
|
import {
|
|
74
74
|
en_default
|
|
75
75
|
} from "./chunk-IFMURN5W.js";
|
|
@@ -83,7 +83,7 @@ import {
|
|
|
83
83
|
} from "./chunk-XDFCUUT6.js";
|
|
84
84
|
|
|
85
85
|
// index.tsx
|
|
86
|
-
import
|
|
86
|
+
import React58, { useEffect as useEffect56 } from "react";
|
|
87
87
|
import { DEFAULT_UI_OPTIONS, isShallowEqual as isShallowEqual10 } from "@orangecatai/common";
|
|
88
88
|
|
|
89
89
|
// components/App.tsx
|
|
@@ -6242,20 +6242,20 @@ var changeFontSize = (elements, appState, app, getNewFontSize, fallbackValue) =>
|
|
|
6242
6242
|
if (isTextElement2(oldElement)) {
|
|
6243
6243
|
const newFontSize = getNewFontSize(oldElement);
|
|
6244
6244
|
newFontSizes.add(newFontSize);
|
|
6245
|
-
let
|
|
6245
|
+
let newElement6 = newElementWith2(oldElement, {
|
|
6246
6246
|
fontSize: newFontSize
|
|
6247
6247
|
});
|
|
6248
6248
|
redrawTextBoundingBox(
|
|
6249
|
-
|
|
6249
|
+
newElement6,
|
|
6250
6250
|
app.scene.getContainerElement(oldElement),
|
|
6251
6251
|
app.scene
|
|
6252
6252
|
);
|
|
6253
|
-
|
|
6253
|
+
newElement6 = offsetElementAfterFontResize(
|
|
6254
6254
|
oldElement,
|
|
6255
|
-
|
|
6255
|
+
newElement6,
|
|
6256
6256
|
app.scene
|
|
6257
6257
|
);
|
|
6258
|
-
return
|
|
6258
|
+
return newElement6;
|
|
6259
6259
|
}
|
|
6260
6260
|
return oldElement;
|
|
6261
6261
|
},
|
|
@@ -6794,10 +6794,10 @@ var actionChangeFontFamily = register({
|
|
|
6794
6794
|
(element) => {
|
|
6795
6795
|
const cachedElement = cachedElements?.get(element.id);
|
|
6796
6796
|
if (cachedElement) {
|
|
6797
|
-
const
|
|
6797
|
+
const newElement6 = newElementWith2(element, {
|
|
6798
6798
|
...cachedElement
|
|
6799
6799
|
});
|
|
6800
|
-
return
|
|
6800
|
+
return newElement6;
|
|
6801
6801
|
}
|
|
6802
6802
|
return element;
|
|
6803
6803
|
},
|
|
@@ -6865,7 +6865,7 @@ var actionChangeFontFamily = register({
|
|
|
6865
6865
|
appState,
|
|
6866
6866
|
(oldElement) => {
|
|
6867
6867
|
if (isTextElement2(oldElement) && (oldElement.fontFamily !== nextFontFamily || currentItemFontFamily)) {
|
|
6868
|
-
const
|
|
6868
|
+
const newElement6 = newElementWith2(
|
|
6869
6869
|
oldElement,
|
|
6870
6870
|
{
|
|
6871
6871
|
fontFamily: nextFontFamily,
|
|
@@ -6880,11 +6880,11 @@ var actionChangeFontFamily = register({
|
|
|
6880
6880
|
if (!skipFontFaceCheck) {
|
|
6881
6881
|
uniqueChars = /* @__PURE__ */ new Set([
|
|
6882
6882
|
...uniqueChars,
|
|
6883
|
-
...Array.from(
|
|
6883
|
+
...Array.from(newElement6.originalText)
|
|
6884
6884
|
]);
|
|
6885
6885
|
}
|
|
6886
|
-
elementContainerMapping.set(
|
|
6887
|
-
return
|
|
6886
|
+
elementContainerMapping.set(newElement6, container);
|
|
6887
|
+
return newElement6;
|
|
6888
6888
|
}
|
|
6889
6889
|
return oldElement;
|
|
6890
6890
|
},
|
|
@@ -7070,16 +7070,16 @@ var actionChangeTextAlign = register({
|
|
|
7070
7070
|
appState,
|
|
7071
7071
|
(oldElement) => {
|
|
7072
7072
|
if (isTextElement2(oldElement)) {
|
|
7073
|
-
const
|
|
7073
|
+
const newElement6 = newElementWith2(
|
|
7074
7074
|
oldElement,
|
|
7075
7075
|
{ textAlign: value }
|
|
7076
7076
|
);
|
|
7077
7077
|
redrawTextBoundingBox(
|
|
7078
|
-
|
|
7078
|
+
newElement6,
|
|
7079
7079
|
app.scene.getContainerElement(oldElement),
|
|
7080
7080
|
app.scene
|
|
7081
7081
|
);
|
|
7082
|
-
return
|
|
7082
|
+
return newElement6;
|
|
7083
7083
|
}
|
|
7084
7084
|
return oldElement;
|
|
7085
7085
|
},
|
|
@@ -7164,16 +7164,16 @@ var actionChangeVerticalAlign = register({
|
|
|
7164
7164
|
appState,
|
|
7165
7165
|
(oldElement) => {
|
|
7166
7166
|
if (isTextElement2(oldElement)) {
|
|
7167
|
-
const
|
|
7167
|
+
const newElement6 = newElementWith2(
|
|
7168
7168
|
oldElement,
|
|
7169
7169
|
{ verticalAlign: value }
|
|
7170
7170
|
);
|
|
7171
7171
|
redrawTextBoundingBox(
|
|
7172
|
-
|
|
7172
|
+
newElement6,
|
|
7173
7173
|
app.scene.getContainerElement(oldElement),
|
|
7174
7174
|
app.scene
|
|
7175
7175
|
);
|
|
7176
|
-
return
|
|
7176
|
+
return newElement6;
|
|
7177
7177
|
}
|
|
7178
7178
|
return oldElement;
|
|
7179
7179
|
},
|
|
@@ -7493,7 +7493,7 @@ var actionChangeArrowType = register({
|
|
|
7493
7493
|
-1,
|
|
7494
7494
|
elementsMap
|
|
7495
7495
|
);
|
|
7496
|
-
let
|
|
7496
|
+
let newElement6 = newElementWith2(el, {
|
|
7497
7497
|
x: value === ARROW_TYPE.elbow ? startPoint[0] : el.x,
|
|
7498
7498
|
y: value === ARROW_TYPE.elbow ? startPoint[1] : el.y,
|
|
7499
7499
|
roundness: value === ARROW_TYPE.round ? {
|
|
@@ -7524,53 +7524,53 @@ var actionChangeArrowType = register({
|
|
|
7524
7524
|
)
|
|
7525
7525
|
] : el.points
|
|
7526
7526
|
});
|
|
7527
|
-
if (isElbowArrow2(
|
|
7528
|
-
|
|
7527
|
+
if (isElbowArrow2(newElement6)) {
|
|
7528
|
+
newElement6.fixedSegments = null;
|
|
7529
7529
|
const elementsMap2 = app.scene.getNonDeletedElementsMap();
|
|
7530
7530
|
app.dismissLinearEditor();
|
|
7531
7531
|
const startGlobalPoint = LinearElementEditor4.getPointAtIndexGlobalCoordinates(
|
|
7532
|
-
|
|
7532
|
+
newElement6,
|
|
7533
7533
|
0,
|
|
7534
7534
|
elementsMap2
|
|
7535
7535
|
);
|
|
7536
7536
|
const endGlobalPoint = LinearElementEditor4.getPointAtIndexGlobalCoordinates(
|
|
7537
|
-
|
|
7537
|
+
newElement6,
|
|
7538
7538
|
-1,
|
|
7539
7539
|
elementsMap2
|
|
7540
7540
|
);
|
|
7541
|
-
const startElement =
|
|
7542
|
-
|
|
7541
|
+
const startElement = newElement6.startBinding && elementsMap2.get(
|
|
7542
|
+
newElement6.startBinding.elementId
|
|
7543
7543
|
);
|
|
7544
|
-
const endElement =
|
|
7545
|
-
|
|
7544
|
+
const endElement = newElement6.endBinding && elementsMap2.get(
|
|
7545
|
+
newElement6.endBinding.elementId
|
|
7546
7546
|
);
|
|
7547
|
-
const startBinding = startElement &&
|
|
7547
|
+
const startBinding = startElement && newElement6.startBinding ? {
|
|
7548
7548
|
// @ts-ignore TS cannot discern check above
|
|
7549
|
-
...
|
|
7549
|
+
...newElement6.startBinding,
|
|
7550
7550
|
...calculateFixedPointForElbowArrowBinding(
|
|
7551
|
-
|
|
7551
|
+
newElement6,
|
|
7552
7552
|
startElement,
|
|
7553
7553
|
"start",
|
|
7554
7554
|
elementsMap2
|
|
7555
7555
|
)
|
|
7556
7556
|
} : null;
|
|
7557
|
-
const endBinding = endElement &&
|
|
7557
|
+
const endBinding = endElement && newElement6.endBinding ? {
|
|
7558
7558
|
// @ts-ignore TS cannot discern check above
|
|
7559
|
-
...
|
|
7559
|
+
...newElement6.endBinding,
|
|
7560
7560
|
...calculateFixedPointForElbowArrowBinding(
|
|
7561
|
-
|
|
7561
|
+
newElement6,
|
|
7562
7562
|
endElement,
|
|
7563
7563
|
"end",
|
|
7564
7564
|
elementsMap2
|
|
7565
7565
|
)
|
|
7566
7566
|
} : null;
|
|
7567
|
-
|
|
7568
|
-
...
|
|
7567
|
+
newElement6 = {
|
|
7568
|
+
...newElement6,
|
|
7569
7569
|
startBinding,
|
|
7570
7570
|
endBinding,
|
|
7571
|
-
...updateElbowArrowPoints(
|
|
7571
|
+
...updateElbowArrowPoints(newElement6, elementsMap2, {
|
|
7572
7572
|
points: [startGlobalPoint, endGlobalPoint].map(
|
|
7573
|
-
(p) => pointFrom(p[0] -
|
|
7573
|
+
(p) => pointFrom(p[0] - newElement6.x, p[1] - newElement6.y)
|
|
7574
7574
|
),
|
|
7575
7575
|
startBinding,
|
|
7576
7576
|
endBinding,
|
|
@@ -7579,13 +7579,13 @@ var actionChangeArrowType = register({
|
|
|
7579
7579
|
};
|
|
7580
7580
|
} else {
|
|
7581
7581
|
const elementsMap2 = app.scene.getNonDeletedElementsMap();
|
|
7582
|
-
if (
|
|
7582
|
+
if (newElement6.startBinding) {
|
|
7583
7583
|
const startElement = elementsMap2.get(
|
|
7584
|
-
|
|
7584
|
+
newElement6.startBinding.elementId
|
|
7585
7585
|
);
|
|
7586
7586
|
if (startElement) {
|
|
7587
7587
|
bindBindingElement(
|
|
7588
|
-
|
|
7588
|
+
newElement6,
|
|
7589
7589
|
startElement,
|
|
7590
7590
|
appState.bindMode === "inside" ? "inside" : "orbit",
|
|
7591
7591
|
"start",
|
|
@@ -7593,13 +7593,13 @@ var actionChangeArrowType = register({
|
|
|
7593
7593
|
);
|
|
7594
7594
|
}
|
|
7595
7595
|
}
|
|
7596
|
-
if (
|
|
7596
|
+
if (newElement6.endBinding) {
|
|
7597
7597
|
const endElement = elementsMap2.get(
|
|
7598
|
-
|
|
7598
|
+
newElement6.endBinding.elementId
|
|
7599
7599
|
);
|
|
7600
7600
|
if (endElement) {
|
|
7601
7601
|
bindBindingElement(
|
|
7602
|
-
|
|
7602
|
+
newElement6,
|
|
7603
7603
|
endElement,
|
|
7604
7604
|
appState.bindMode === "inside" ? "inside" : "orbit",
|
|
7605
7605
|
"end",
|
|
@@ -7608,7 +7608,7 @@ var actionChangeArrowType = register({
|
|
|
7608
7608
|
}
|
|
7609
7609
|
}
|
|
7610
7610
|
}
|
|
7611
|
-
return
|
|
7611
|
+
return newElement6;
|
|
7612
7612
|
});
|
|
7613
7613
|
const newState = {
|
|
7614
7614
|
...appState,
|
|
@@ -9626,7 +9626,7 @@ var exportCanvas = async (type, elements, appState, files, {
|
|
|
9626
9626
|
let blob = canvasToBlob(tempCanvas);
|
|
9627
9627
|
if (appState.exportEmbedScene) {
|
|
9628
9628
|
blob = blob.then(
|
|
9629
|
-
(blob2) => import("./data/image-
|
|
9629
|
+
(blob2) => import("./data/image-IZS5VEYX.js").then(
|
|
9630
9630
|
({ encodePngMetadata: encodePngMetadata2 }) => encodePngMetadata2({
|
|
9631
9631
|
blob: blob2,
|
|
9632
9632
|
metadata: serializeAsJSON(elements, appState, files, "local")
|
|
@@ -10038,7 +10038,7 @@ var actionPasteStyles = register({
|
|
|
10038
10038
|
if (!elementStylesToCopyFrom) {
|
|
10039
10039
|
return element;
|
|
10040
10040
|
}
|
|
10041
|
-
let
|
|
10041
|
+
let newElement6 = newElementWith5(element, {
|
|
10042
10042
|
backgroundColor: elementStylesToCopyFrom?.backgroundColor,
|
|
10043
10043
|
strokeWidth: elementStylesToCopyFrom?.strokeWidth,
|
|
10044
10044
|
strokeColor: elementStylesToCopyFrom?.strokeColor,
|
|
@@ -10051,36 +10051,36 @@ var actionPasteStyles = register({
|
|
|
10051
10051
|
element
|
|
10052
10052
|
) ? elementStylesToCopyFrom.roundness : getDefaultRoundnessTypeForElement(element) : null
|
|
10053
10053
|
});
|
|
10054
|
-
if (isTextElement3(
|
|
10054
|
+
if (isTextElement3(newElement6)) {
|
|
10055
10055
|
const fontSize = elementStylesToCopyFrom.fontSize || DEFAULT_FONT_SIZE3;
|
|
10056
10056
|
const fontFamily = elementStylesToCopyFrom.fontFamily || DEFAULT_FONT_FAMILY3;
|
|
10057
|
-
|
|
10057
|
+
newElement6 = newElementWith5(newElement6, {
|
|
10058
10058
|
fontSize,
|
|
10059
10059
|
fontFamily,
|
|
10060
10060
|
textAlign: elementStylesToCopyFrom.textAlign || DEFAULT_TEXT_ALIGN,
|
|
10061
10061
|
lineHeight: elementStylesToCopyFrom.lineHeight || getLineHeight2(fontFamily)
|
|
10062
10062
|
});
|
|
10063
10063
|
let container = null;
|
|
10064
|
-
if (
|
|
10064
|
+
if (newElement6.containerId) {
|
|
10065
10065
|
container = selectedElements.find(
|
|
10066
|
-
(element2) => isTextElement3(
|
|
10066
|
+
(element2) => isTextElement3(newElement6) && element2.id === newElement6.containerId
|
|
10067
10067
|
) || null;
|
|
10068
10068
|
}
|
|
10069
|
-
redrawTextBoundingBox2(
|
|
10069
|
+
redrawTextBoundingBox2(newElement6, container, app.scene);
|
|
10070
10070
|
}
|
|
10071
|
-
if (
|
|
10072
|
-
|
|
10071
|
+
if (newElement6.type === "arrow" && isArrowElement2(elementStylesToCopyFrom)) {
|
|
10072
|
+
newElement6 = newElementWith5(newElement6, {
|
|
10073
10073
|
startArrowhead: elementStylesToCopyFrom.startArrowhead,
|
|
10074
10074
|
endArrowhead: elementStylesToCopyFrom.endArrowhead
|
|
10075
10075
|
});
|
|
10076
10076
|
}
|
|
10077
10077
|
if (isFrameLikeElement4(element)) {
|
|
10078
|
-
|
|
10078
|
+
newElement6 = newElementWith5(newElement6, {
|
|
10079
10079
|
roundness: null,
|
|
10080
10080
|
backgroundColor: "transparent"
|
|
10081
10081
|
});
|
|
10082
10082
|
}
|
|
10083
|
-
return
|
|
10083
|
+
return newElement6;
|
|
10084
10084
|
}
|
|
10085
10085
|
return element;
|
|
10086
10086
|
}),
|
|
@@ -21838,8 +21838,8 @@ var snapResizingElements = (selectedElements, selectedOriginalElements, app, eve
|
|
|
21838
21838
|
snapLines: pointSnapLines
|
|
21839
21839
|
};
|
|
21840
21840
|
};
|
|
21841
|
-
var snapNewElement = (
|
|
21842
|
-
if (!isSnappingEnabled({ event, selectedElements: [
|
|
21841
|
+
var snapNewElement = (newElement6, app, event, origin, dragOffset, elementsMap) => {
|
|
21842
|
+
if (!isSnappingEnabled({ event, selectedElements: [newElement6], app })) {
|
|
21843
21843
|
return {
|
|
21844
21844
|
snapOffset: { x: 0, y: 0 },
|
|
21845
21845
|
snapLines: []
|
|
@@ -21856,7 +21856,7 @@ var snapNewElement = (newElement5, app, event, origin, dragOffset, elementsMap)
|
|
|
21856
21856
|
const nearestSnapsX = [];
|
|
21857
21857
|
const nearestSnapsY = [];
|
|
21858
21858
|
getPointSnaps(
|
|
21859
|
-
[
|
|
21859
|
+
[newElement6],
|
|
21860
21860
|
selectionSnapPoints,
|
|
21861
21861
|
app,
|
|
21862
21862
|
event,
|
|
@@ -21872,12 +21872,12 @@ var snapNewElement = (newElement5, app, event, origin, dragOffset, elementsMap)
|
|
|
21872
21872
|
minOffset.y = 0;
|
|
21873
21873
|
nearestSnapsX.length = 0;
|
|
21874
21874
|
nearestSnapsY.length = 0;
|
|
21875
|
-
const corners = getElementsCorners([
|
|
21875
|
+
const corners = getElementsCorners([newElement6], elementsMap, {
|
|
21876
21876
|
boundingBoxCorners: true,
|
|
21877
21877
|
omitCenter: true
|
|
21878
21878
|
});
|
|
21879
21879
|
getPointSnaps(
|
|
21880
|
-
[
|
|
21880
|
+
[newElement6],
|
|
21881
21881
|
corners,
|
|
21882
21882
|
app,
|
|
21883
21883
|
event,
|
|
@@ -36860,7 +36860,7 @@ import {
|
|
|
36860
36860
|
var _renderNewElementScene = ({
|
|
36861
36861
|
canvas,
|
|
36862
36862
|
rc,
|
|
36863
|
-
newElement:
|
|
36863
|
+
newElement: newElement6,
|
|
36864
36864
|
elementsMap,
|
|
36865
36865
|
allElementsMap,
|
|
36866
36866
|
scale,
|
|
@@ -36880,19 +36880,19 @@ var _renderNewElementScene = ({
|
|
|
36880
36880
|
});
|
|
36881
36881
|
context.save();
|
|
36882
36882
|
context.scale(appState.zoom.value, appState.zoom.value);
|
|
36883
|
-
if (
|
|
36884
|
-
if (isInvisiblySmallElement2(
|
|
36883
|
+
if (newElement6 && newElement6.type !== "selection") {
|
|
36884
|
+
if (isInvisiblySmallElement2(newElement6)) {
|
|
36885
36885
|
return;
|
|
36886
36886
|
}
|
|
36887
|
-
const frameId =
|
|
36887
|
+
const frameId = newElement6.frameId || appState.frameToHighlight?.id;
|
|
36888
36888
|
if (frameId && appState.frameRendering.enabled && appState.frameRendering.clip) {
|
|
36889
|
-
const frame = getTargetFrame(
|
|
36890
|
-
if (frame && shouldApplyFrameClip(
|
|
36889
|
+
const frame = getTargetFrame(newElement6, elementsMap, appState);
|
|
36890
|
+
if (frame && shouldApplyFrameClip(newElement6, frame, appState, elementsMap)) {
|
|
36891
36891
|
frameClip(frame, context, renderConfig, appState);
|
|
36892
36892
|
}
|
|
36893
36893
|
}
|
|
36894
36894
|
renderElement(
|
|
36895
|
-
|
|
36895
|
+
newElement6,
|
|
36896
36896
|
elementsMap,
|
|
36897
36897
|
allElementsMap,
|
|
36898
36898
|
rc,
|
|
@@ -38157,13 +38157,13 @@ var App = class _App extends React52.Component {
|
|
|
38157
38157
|
);
|
|
38158
38158
|
}
|
|
38159
38159
|
this.scene.replaceAllElements(nextElements);
|
|
38160
|
-
duplicatedElements.forEach((
|
|
38161
|
-
if (isTextElement19(
|
|
38160
|
+
duplicatedElements.forEach((newElement6) => {
|
|
38161
|
+
if (isTextElement19(newElement6) && isBoundToContainer9(newElement6)) {
|
|
38162
38162
|
const container = getContainerElement5(
|
|
38163
|
-
|
|
38163
|
+
newElement6,
|
|
38164
38164
|
this.scene.getElementsMapIncludingDeleted()
|
|
38165
38165
|
);
|
|
38166
|
-
redrawTextBoundingBox8(
|
|
38166
|
+
redrawTextBoundingBox8(newElement6, container, this.scene);
|
|
38167
38167
|
}
|
|
38168
38168
|
});
|
|
38169
38169
|
if (isSafari2) {
|
|
@@ -39705,8 +39705,8 @@ var App = class _App extends React52.Component {
|
|
|
39705
39705
|
}
|
|
39706
39706
|
}
|
|
39707
39707
|
if (isBindingElementType(this.state.activeTool.type)) {
|
|
39708
|
-
const { newElement:
|
|
39709
|
-
if (!
|
|
39708
|
+
const { newElement: newElement6 } = this.state;
|
|
39709
|
+
if (!newElement6 && isBindingEnabled2(this.state)) {
|
|
39710
39710
|
const globalPoint = pointFrom29(
|
|
39711
39711
|
scenePointerX,
|
|
39712
39712
|
scenePointerY
|
|
@@ -41850,8 +41850,8 @@ var App = class _App extends React52.Component {
|
|
|
41850
41850
|
});
|
|
41851
41851
|
return;
|
|
41852
41852
|
}
|
|
41853
|
-
const
|
|
41854
|
-
if (!
|
|
41853
|
+
const newElement6 = this.state.newElement;
|
|
41854
|
+
if (!newElement6) {
|
|
41855
41855
|
return;
|
|
41856
41856
|
}
|
|
41857
41857
|
let [gridX, gridY] = getGridPoint2(
|
|
@@ -41859,11 +41859,11 @@ var App = class _App extends React52.Component {
|
|
|
41859
41859
|
pointerCoords.y,
|
|
41860
41860
|
event[KEYS55.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
|
|
41861
41861
|
);
|
|
41862
|
-
const image = isInitializedImageElement3(
|
|
41862
|
+
const image = isInitializedImageElement3(newElement6) && this.imageCache.get(newElement6.fileId)?.image;
|
|
41863
41863
|
const aspectRatio = image && !(image instanceof Promise) ? image.width / image.height : null;
|
|
41864
|
-
this.maybeCacheReferenceSnapPoints(event, [
|
|
41864
|
+
this.maybeCacheReferenceSnapPoints(event, [newElement6]);
|
|
41865
41865
|
const { snapOffset, snapLines } = snapNewElement(
|
|
41866
|
-
|
|
41866
|
+
newElement6,
|
|
41867
41867
|
this,
|
|
41868
41868
|
event,
|
|
41869
41869
|
{
|
|
@@ -41881,9 +41881,9 @@ var App = class _App extends React52.Component {
|
|
|
41881
41881
|
this.setState({
|
|
41882
41882
|
snapLines
|
|
41883
41883
|
});
|
|
41884
|
-
if (!isBindingElement4(
|
|
41884
|
+
if (!isBindingElement4(newElement6)) {
|
|
41885
41885
|
dragNewElement({
|
|
41886
|
-
newElement:
|
|
41886
|
+
newElement: newElement6,
|
|
41887
41887
|
elementType: this.state.activeTool.type,
|
|
41888
41888
|
originX: pointerDownState.originInGrid.x,
|
|
41889
41889
|
originY: pointerDownState.originInGrid.y,
|
|
@@ -41891,7 +41891,7 @@ var App = class _App extends React52.Component {
|
|
|
41891
41891
|
y: gridY,
|
|
41892
41892
|
width: distance2(pointerDownState.originInGrid.x, gridX),
|
|
41893
41893
|
height: distance2(pointerDownState.originInGrid.y, gridY),
|
|
41894
|
-
shouldMaintainAspectRatio: isImageElement9(
|
|
41894
|
+
shouldMaintainAspectRatio: isImageElement9(newElement6) ? !shouldMaintainAspectRatio(event) : shouldMaintainAspectRatio(event),
|
|
41895
41895
|
shouldResizeFromCenter: shouldResizeFromCenter(event),
|
|
41896
41896
|
zoom: this.state.zoom.value,
|
|
41897
41897
|
scene: this.scene,
|
|
@@ -41901,13 +41901,13 @@ var App = class _App extends React52.Component {
|
|
|
41901
41901
|
});
|
|
41902
41902
|
}
|
|
41903
41903
|
this.setState({
|
|
41904
|
-
newElement:
|
|
41904
|
+
newElement: newElement6
|
|
41905
41905
|
});
|
|
41906
41906
|
if (this.state.activeTool.type === TOOL_TYPE3.frame || this.state.activeTool.type === TOOL_TYPE3.magicframe) {
|
|
41907
41907
|
this.setState({
|
|
41908
41908
|
elementsToHighlight: getElementsInResizingFrame4(
|
|
41909
41909
|
this.scene.getNonDeletedElements(),
|
|
41910
|
-
|
|
41910
|
+
newElement6,
|
|
41911
41911
|
this.state,
|
|
41912
41912
|
this.scene.getNonDeletedElementsMap()
|
|
41913
41913
|
)
|
|
@@ -44926,20 +44926,20 @@ var App = class _App extends React52.Component {
|
|
|
44926
44926
|
);
|
|
44927
44927
|
}
|
|
44928
44928
|
} else {
|
|
44929
|
-
const
|
|
44930
|
-
if (!
|
|
44929
|
+
const newElement6 = this.state.newElement;
|
|
44930
|
+
if (!newElement6) {
|
|
44931
44931
|
return;
|
|
44932
44932
|
}
|
|
44933
|
-
if (
|
|
44934
|
-
const points =
|
|
44935
|
-
const dx = pointerCoords.x -
|
|
44936
|
-
const dy = pointerCoords.y -
|
|
44933
|
+
if (newElement6.type === "freedraw") {
|
|
44934
|
+
const points = newElement6.points;
|
|
44935
|
+
const dx = pointerCoords.x - newElement6.x;
|
|
44936
|
+
const dy = pointerCoords.y - newElement6.y;
|
|
44937
44937
|
const lastPoint = points.length > 0 && points[points.length - 1];
|
|
44938
44938
|
const discardPoint = lastPoint && lastPoint[0] === dx && lastPoint[1] === dy;
|
|
44939
44939
|
if (!discardPoint) {
|
|
44940
|
-
const pressures =
|
|
44940
|
+
const pressures = newElement6.simulatePressure ? newElement6.pressures : [...newElement6.pressures, event.pressure];
|
|
44941
44941
|
this.scene.mutateElement(
|
|
44942
|
-
|
|
44942
|
+
newElement6,
|
|
44943
44943
|
{
|
|
44944
44944
|
points: [...points, pointFrom29(dx, dy)],
|
|
44945
44945
|
pressures
|
|
@@ -44950,12 +44950,12 @@ var App = class _App extends React52.Component {
|
|
|
44950
44950
|
}
|
|
44951
44951
|
);
|
|
44952
44952
|
this.setState({
|
|
44953
|
-
newElement:
|
|
44953
|
+
newElement: newElement6
|
|
44954
44954
|
});
|
|
44955
44955
|
}
|
|
44956
|
-
} else if (isLinearElement12(
|
|
44956
|
+
} else if (isLinearElement12(newElement6) && !newElement6.isDeleted) {
|
|
44957
44957
|
pointerDownState.drag.hasOccurred = true;
|
|
44958
|
-
const points =
|
|
44958
|
+
const points = newElement6.points;
|
|
44959
44959
|
invariant16(
|
|
44960
44960
|
points.length > 1,
|
|
44961
44961
|
"Do not create linear elements with less than 2 points"
|
|
@@ -44963,7 +44963,7 @@ var App = class _App extends React52.Component {
|
|
|
44963
44963
|
let linearElementEditor = this.state.selectedLinearElement;
|
|
44964
44964
|
if (!linearElementEditor) {
|
|
44965
44965
|
linearElementEditor = new LinearElementEditor11(
|
|
44966
|
-
|
|
44966
|
+
newElement6,
|
|
44967
44967
|
this.scene.getNonDeletedElementsMap()
|
|
44968
44968
|
);
|
|
44969
44969
|
linearElementEditor = {
|
|
@@ -44976,7 +44976,7 @@ var App = class _App extends React52.Component {
|
|
|
44976
44976
|
};
|
|
44977
44977
|
}
|
|
44978
44978
|
this.setState({
|
|
44979
|
-
newElement:
|
|
44979
|
+
newElement: newElement6,
|
|
44980
44980
|
...LinearElementEditor11.handlePointDragging(
|
|
44981
44981
|
event,
|
|
44982
44982
|
this,
|
|
@@ -45100,7 +45100,7 @@ var App = class _App extends React52.Component {
|
|
|
45100
45100
|
pointerDownState.eventListeners.onMove.flush();
|
|
45101
45101
|
}
|
|
45102
45102
|
const {
|
|
45103
|
-
newElement:
|
|
45103
|
+
newElement: newElement6,
|
|
45104
45104
|
resizingElement,
|
|
45105
45105
|
croppingElementId,
|
|
45106
45106
|
multiElement,
|
|
@@ -45264,28 +45264,28 @@ var App = class _App extends React52.Component {
|
|
|
45264
45264
|
pointerDownState,
|
|
45265
45265
|
childEvent
|
|
45266
45266
|
);
|
|
45267
|
-
if (
|
|
45267
|
+
if (newElement6?.type === "freedraw") {
|
|
45268
45268
|
const pointerCoords = viewportCoordsToSceneCoords3(
|
|
45269
45269
|
childEvent,
|
|
45270
45270
|
this.state
|
|
45271
45271
|
);
|
|
45272
|
-
const points =
|
|
45273
|
-
let dx = pointerCoords.x -
|
|
45274
|
-
let dy = pointerCoords.y -
|
|
45272
|
+
const points = newElement6.points;
|
|
45273
|
+
let dx = pointerCoords.x - newElement6.x;
|
|
45274
|
+
let dy = pointerCoords.y - newElement6.y;
|
|
45275
45275
|
if (dx === points[0][0] && dy === points[0][1]) {
|
|
45276
45276
|
dy += 1e-4;
|
|
45277
45277
|
dx += 1e-4;
|
|
45278
45278
|
}
|
|
45279
|
-
const pressures =
|
|
45280
|
-
this.scene.mutateElement(
|
|
45279
|
+
const pressures = newElement6.simulatePressure ? [] : [...newElement6.pressures, childEvent.pressure];
|
|
45280
|
+
this.scene.mutateElement(newElement6, {
|
|
45281
45281
|
points: [...points, pointFrom29(dx, dy)],
|
|
45282
45282
|
pressures
|
|
45283
45283
|
});
|
|
45284
45284
|
this.actionManager.executeAction(actionFinalize);
|
|
45285
45285
|
return;
|
|
45286
45286
|
}
|
|
45287
|
-
if (isLinearElement12(
|
|
45288
|
-
if (
|
|
45287
|
+
if (isLinearElement12(newElement6)) {
|
|
45288
|
+
if (newElement6.points.length > 1 && newElement6.points[1][0] !== 0 && newElement6.points[1][1] !== 0) {
|
|
45289
45289
|
this.store.scheduleCapture();
|
|
45290
45290
|
}
|
|
45291
45291
|
const pointerCoords = viewportCoordsToSceneCoords3(
|
|
@@ -45296,16 +45296,16 @@ var App = class _App extends React52.Component {
|
|
|
45296
45296
|
pointFrom29(pointerCoords.x, pointerCoords.y),
|
|
45297
45297
|
pointFrom29(pointerDownState.origin.x, pointerDownState.origin.y)
|
|
45298
45298
|
) * this.state.zoom.value;
|
|
45299
|
-
if ((!pointerDownState.drag.hasOccurred || dragDistance < MINIMUM_ARROW_SIZE) &&
|
|
45299
|
+
if ((!pointerDownState.drag.hasOccurred || dragDistance < MINIMUM_ARROW_SIZE) && newElement6 && !multiElement) {
|
|
45300
45300
|
if (this.editorInterface.isTouchScreen) {
|
|
45301
45301
|
const FIXED_DELTA_X = Math.min(
|
|
45302
45302
|
this.state.width * 0.7 / this.state.zoom.value,
|
|
45303
45303
|
100
|
|
45304
45304
|
);
|
|
45305
45305
|
this.scene.mutateElement(
|
|
45306
|
-
|
|
45306
|
+
newElement6,
|
|
45307
45307
|
{
|
|
45308
|
-
x:
|
|
45308
|
+
x: newElement6.x - FIXED_DELTA_X / 2,
|
|
45309
45309
|
points: [
|
|
45310
45310
|
pointFrom29(0, 0),
|
|
45311
45311
|
pointFrom29(FIXED_DELTA_X, 0)
|
|
@@ -45315,22 +45315,22 @@ var App = class _App extends React52.Component {
|
|
|
45315
45315
|
);
|
|
45316
45316
|
this.actionManager.executeAction(actionFinalize);
|
|
45317
45317
|
} else {
|
|
45318
|
-
const dx = pointerCoords.x -
|
|
45319
|
-
const dy = pointerCoords.y -
|
|
45318
|
+
const dx = pointerCoords.x - newElement6.x;
|
|
45319
|
+
const dy = pointerCoords.y - newElement6.y;
|
|
45320
45320
|
this.scene.mutateElement(
|
|
45321
|
-
|
|
45321
|
+
newElement6,
|
|
45322
45322
|
{
|
|
45323
|
-
points: [
|
|
45323
|
+
points: [newElement6.points[0], pointFrom29(dx, dy)]
|
|
45324
45324
|
},
|
|
45325
45325
|
{ informMutation: false, isDragging: false }
|
|
45326
45326
|
);
|
|
45327
45327
|
this.setState({
|
|
45328
|
-
multiElement:
|
|
45329
|
-
newElement:
|
|
45328
|
+
multiElement: newElement6,
|
|
45329
|
+
newElement: newElement6
|
|
45330
45330
|
});
|
|
45331
45331
|
}
|
|
45332
45332
|
} else if (pointerDownState.drag.hasOccurred && !multiElement) {
|
|
45333
|
-
if (isLinearElement12(
|
|
45333
|
+
if (isLinearElement12(newElement6)) {
|
|
45334
45334
|
this.actionManager.executeAction(actionFinalize, "ui", {
|
|
45335
45335
|
event: childEvent,
|
|
45336
45336
|
sceneCoords
|
|
@@ -45347,12 +45347,12 @@ var App = class _App extends React52.Component {
|
|
|
45347
45347
|
selectedElementIds: makeNextSelectedElementIds2(
|
|
45348
45348
|
{
|
|
45349
45349
|
...prevState.selectedElementIds,
|
|
45350
|
-
[
|
|
45350
|
+
[newElement6.id]: true
|
|
45351
45351
|
},
|
|
45352
45352
|
prevState
|
|
45353
45353
|
),
|
|
45354
45354
|
selectedLinearElement: new LinearElementEditor11(
|
|
45355
|
-
|
|
45355
|
+
newElement6,
|
|
45356
45356
|
this.scene.getNonDeletedElementsMap()
|
|
45357
45357
|
)
|
|
45358
45358
|
}));
|
|
@@ -45365,27 +45365,27 @@ var App = class _App extends React52.Component {
|
|
|
45365
45365
|
}
|
|
45366
45366
|
return;
|
|
45367
45367
|
}
|
|
45368
|
-
if (isTextElement19(
|
|
45368
|
+
if (isTextElement19(newElement6)) {
|
|
45369
45369
|
const minWidth = getMinTextElementWidth(
|
|
45370
45370
|
getFontString9({
|
|
45371
|
-
fontSize:
|
|
45372
|
-
fontFamily:
|
|
45371
|
+
fontSize: newElement6.fontSize,
|
|
45372
|
+
fontFamily: newElement6.fontFamily
|
|
45373
45373
|
}),
|
|
45374
|
-
|
|
45374
|
+
newElement6.lineHeight
|
|
45375
45375
|
);
|
|
45376
|
-
if (
|
|
45377
|
-
this.scene.mutateElement(
|
|
45376
|
+
if (newElement6.width < minWidth) {
|
|
45377
|
+
this.scene.mutateElement(newElement6, {
|
|
45378
45378
|
autoResize: true
|
|
45379
45379
|
});
|
|
45380
45380
|
}
|
|
45381
45381
|
this.resetCursor();
|
|
45382
|
-
this.handleTextWysiwyg(
|
|
45382
|
+
this.handleTextWysiwyg(newElement6, {
|
|
45383
45383
|
isExistingElement: true
|
|
45384
45384
|
});
|
|
45385
45385
|
}
|
|
45386
|
-
if (activeTool.type !== "selection" &&
|
|
45386
|
+
if (activeTool.type !== "selection" && newElement6 && isInvisiblySmallElement3(newElement6)) {
|
|
45387
45387
|
this.updateScene({
|
|
45388
|
-
elements: this.scene.getElementsIncludingDeleted().filter((el) => el.id !==
|
|
45388
|
+
elements: this.scene.getElementsIncludingDeleted().filter((el) => el.id !== newElement6.id),
|
|
45389
45389
|
appState: {
|
|
45390
45390
|
newElement: null
|
|
45391
45391
|
},
|
|
@@ -45393,25 +45393,25 @@ var App = class _App extends React52.Component {
|
|
|
45393
45393
|
});
|
|
45394
45394
|
return;
|
|
45395
45395
|
}
|
|
45396
|
-
if (isFrameLikeElement16(
|
|
45396
|
+
if (isFrameLikeElement16(newElement6)) {
|
|
45397
45397
|
const elementsInsideFrame = getElementsInNewFrame(
|
|
45398
45398
|
this.scene.getElementsIncludingDeleted(),
|
|
45399
|
-
|
|
45399
|
+
newElement6,
|
|
45400
45400
|
this.scene.getNonDeletedElementsMap()
|
|
45401
45401
|
);
|
|
45402
45402
|
this.scene.replaceAllElements(
|
|
45403
45403
|
addElementsToFrame2(
|
|
45404
45404
|
this.scene.getElementsMapIncludingDeleted(),
|
|
45405
45405
|
elementsInsideFrame,
|
|
45406
|
-
|
|
45406
|
+
newElement6,
|
|
45407
45407
|
this.state
|
|
45408
45408
|
)
|
|
45409
45409
|
);
|
|
45410
45410
|
}
|
|
45411
|
-
if (
|
|
45411
|
+
if (newElement6) {
|
|
45412
45412
|
this.scene.mutateElement(
|
|
45413
|
-
|
|
45414
|
-
getNormalizedDimensions(
|
|
45413
|
+
newElement6,
|
|
45414
|
+
getNormalizedDimensions(newElement6),
|
|
45415
45415
|
{
|
|
45416
45416
|
informMutation: false,
|
|
45417
45417
|
isDragging: false
|
|
@@ -45741,16 +45741,16 @@ var App = class _App extends React52.Component {
|
|
|
45741
45741
|
setCursor(this.interactiveCanvas, CURSOR_TYPE4.AUTO);
|
|
45742
45742
|
return;
|
|
45743
45743
|
}
|
|
45744
|
-
if (!activeTool.locked && activeTool.type !== "freedraw" &&
|
|
45744
|
+
if (!activeTool.locked && activeTool.type !== "freedraw" && newElement6) {
|
|
45745
45745
|
this.setState((prevState) => ({
|
|
45746
45746
|
selectedElementIds: makeNextSelectedElementIds2(
|
|
45747
45747
|
{
|
|
45748
45748
|
...prevState.selectedElementIds,
|
|
45749
|
-
[
|
|
45749
|
+
[newElement6.id]: true
|
|
45750
45750
|
},
|
|
45751
45751
|
prevState
|
|
45752
45752
|
),
|
|
45753
|
-
showHyperlinkPopup: isEmbeddableElement4(
|
|
45753
|
+
showHyperlinkPopup: isEmbeddableElement4(newElement6) && !newElement6.link ? "editor" : prevState.showHyperlinkPopup
|
|
45754
45754
|
}));
|
|
45755
45755
|
}
|
|
45756
45756
|
if (activeTool.type !== "selection" || isSomeElementSelected(this.scene.getNonDeletedElements(), this.state) || !isShallowEqual9(
|
|
@@ -46518,7 +46518,1934 @@ var DiagramToCodePlugin = (props) => {
|
|
|
46518
46518
|
// index.tsx
|
|
46519
46519
|
import { isElementLink as isElementLink3 } from "@orangecatai/element";
|
|
46520
46520
|
import { setCustomTextMetricsProvider } from "@orangecatai/element";
|
|
46521
|
+
|
|
46522
|
+
// components/AIChatPanel.tsx
|
|
46523
|
+
import {
|
|
46524
|
+
useCallback as useCallback24,
|
|
46525
|
+
useEffect as useEffect55,
|
|
46526
|
+
useRef as useRef49,
|
|
46527
|
+
useState as useState49
|
|
46528
|
+
} from "react";
|
|
46529
|
+
import {
|
|
46530
|
+
ArrowUp as ArrowUp3,
|
|
46531
|
+
AtSign,
|
|
46532
|
+
ChevronDown as ChevronDown2,
|
|
46533
|
+
ChevronRight,
|
|
46534
|
+
Copy,
|
|
46535
|
+
Globe,
|
|
46536
|
+
MessageSquare,
|
|
46537
|
+
Mic,
|
|
46538
|
+
MoreHorizontal,
|
|
46539
|
+
Pencil,
|
|
46540
|
+
Plus,
|
|
46541
|
+
Search,
|
|
46542
|
+
ThumbsDown,
|
|
46543
|
+
ThumbsUp,
|
|
46544
|
+
Trash,
|
|
46545
|
+
Wrench,
|
|
46546
|
+
X as X2
|
|
46547
|
+
} from "lucide-react";
|
|
46548
|
+
|
|
46549
|
+
// components/ai-chat/canvasTools.ts
|
|
46550
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
46551
|
+
import {
|
|
46552
|
+
newFrameElement as newFrameElement3,
|
|
46553
|
+
newElement as newElement5,
|
|
46554
|
+
newTextElement as newTextElement4,
|
|
46555
|
+
newImageElement as newImageElement2,
|
|
46556
|
+
getFrameChildren as getFrameChildren7,
|
|
46557
|
+
isFrameLikeElement as isFrameLikeElement17
|
|
46558
|
+
} from "@orangecatai/element";
|
|
46559
|
+
import { FRAME_STYLE as FRAME_STYLE5 } from "@orangecatai/common";
|
|
46560
|
+
var CANVAS_TOOLS = [
|
|
46561
|
+
{
|
|
46562
|
+
type: "function",
|
|
46563
|
+
function: {
|
|
46564
|
+
name: "create_frame",
|
|
46565
|
+
description: "Create a new frame on the canvas. Frames act as artboards that clip their children. Default size is 512x512.",
|
|
46566
|
+
parameters: {
|
|
46567
|
+
type: "object",
|
|
46568
|
+
properties: {
|
|
46569
|
+
width: {
|
|
46570
|
+
type: "number",
|
|
46571
|
+
description: "Frame width in pixels (default 512)"
|
|
46572
|
+
},
|
|
46573
|
+
height: {
|
|
46574
|
+
type: "number",
|
|
46575
|
+
description: "Frame height in pixels (default 512)"
|
|
46576
|
+
},
|
|
46577
|
+
name: {
|
|
46578
|
+
type: "string",
|
|
46579
|
+
description: "Optional display name for the frame"
|
|
46580
|
+
}
|
|
46581
|
+
},
|
|
46582
|
+
required: [],
|
|
46583
|
+
additionalProperties: false
|
|
46584
|
+
}
|
|
46585
|
+
}
|
|
46586
|
+
},
|
|
46587
|
+
{
|
|
46588
|
+
type: "function",
|
|
46589
|
+
function: {
|
|
46590
|
+
name: "add_rectangle",
|
|
46591
|
+
description: "Add a filled rectangle inside a frame. Commonly used for solid background fills behind images and text.",
|
|
46592
|
+
parameters: {
|
|
46593
|
+
type: "object",
|
|
46594
|
+
properties: {
|
|
46595
|
+
frameId: {
|
|
46596
|
+
type: "string",
|
|
46597
|
+
description: "ID of the parent frame"
|
|
46598
|
+
},
|
|
46599
|
+
x: {
|
|
46600
|
+
type: "number",
|
|
46601
|
+
description: "X position relative to canvas (use the frame's x for full coverage)"
|
|
46602
|
+
},
|
|
46603
|
+
y: {
|
|
46604
|
+
type: "number",
|
|
46605
|
+
description: "Y position relative to canvas (use the frame's y for full coverage)"
|
|
46606
|
+
},
|
|
46607
|
+
width: {
|
|
46608
|
+
type: "number",
|
|
46609
|
+
description: "Rectangle width in pixels"
|
|
46610
|
+
},
|
|
46611
|
+
height: {
|
|
46612
|
+
type: "number",
|
|
46613
|
+
description: "Rectangle height in pixels"
|
|
46614
|
+
},
|
|
46615
|
+
backgroundColor: {
|
|
46616
|
+
type: "string",
|
|
46617
|
+
description: 'Fill color as hex string e.g. "#1a1a2e". Use "transparent" for no fill.'
|
|
46618
|
+
},
|
|
46619
|
+
fillStyle: {
|
|
46620
|
+
type: "string",
|
|
46621
|
+
enum: ["solid", "hachure", "cross-hatch"],
|
|
46622
|
+
description: 'Fill style (default "solid")'
|
|
46623
|
+
},
|
|
46624
|
+
strokeColor: {
|
|
46625
|
+
type: "string",
|
|
46626
|
+
description: 'Stroke/border color as hex (default "transparent")'
|
|
46627
|
+
},
|
|
46628
|
+
strokeWidth: {
|
|
46629
|
+
type: "number",
|
|
46630
|
+
description: "Stroke width in pixels (default 0)"
|
|
46631
|
+
},
|
|
46632
|
+
opacity: {
|
|
46633
|
+
type: "number",
|
|
46634
|
+
description: "Opacity 0-100 (default 100)"
|
|
46635
|
+
}
|
|
46636
|
+
},
|
|
46637
|
+
required: ["frameId", "x", "y", "width", "height", "backgroundColor"],
|
|
46638
|
+
additionalProperties: false
|
|
46639
|
+
}
|
|
46640
|
+
}
|
|
46641
|
+
},
|
|
46642
|
+
{
|
|
46643
|
+
type: "function",
|
|
46644
|
+
function: {
|
|
46645
|
+
name: "add_text",
|
|
46646
|
+
description: "Add a text element inside a frame. Use for headlines, subheads, body copy, CTAs etc.",
|
|
46647
|
+
parameters: {
|
|
46648
|
+
type: "object",
|
|
46649
|
+
properties: {
|
|
46650
|
+
frameId: {
|
|
46651
|
+
type: "string",
|
|
46652
|
+
description: "ID of the parent frame"
|
|
46653
|
+
},
|
|
46654
|
+
x: {
|
|
46655
|
+
type: "number",
|
|
46656
|
+
description: "X position (canvas coordinates)"
|
|
46657
|
+
},
|
|
46658
|
+
y: {
|
|
46659
|
+
type: "number",
|
|
46660
|
+
description: "Y position (canvas coordinates)"
|
|
46661
|
+
},
|
|
46662
|
+
text: {
|
|
46663
|
+
type: "string",
|
|
46664
|
+
description: "The text content"
|
|
46665
|
+
},
|
|
46666
|
+
fontSize: {
|
|
46667
|
+
type: "number",
|
|
46668
|
+
description: "Font size in pixels (default 20)"
|
|
46669
|
+
},
|
|
46670
|
+
fontFamily: {
|
|
46671
|
+
type: "number",
|
|
46672
|
+
enum: [1, 2, 3, 4, 5],
|
|
46673
|
+
description: "Font family: 1=Excalifont(hand), 2=Nunito(sans), 3=Comic Shanns(code), 4=Liberation Sans(clean sans), 5=cascadia(code). Default 2."
|
|
46674
|
+
},
|
|
46675
|
+
strokeColor: {
|
|
46676
|
+
type: "string",
|
|
46677
|
+
description: 'Text color as hex string (default "#000000")'
|
|
46678
|
+
},
|
|
46679
|
+
textAlign: {
|
|
46680
|
+
type: "string",
|
|
46681
|
+
enum: ["left", "center", "right"],
|
|
46682
|
+
description: "Horizontal alignment (default left)"
|
|
46683
|
+
},
|
|
46684
|
+
width: {
|
|
46685
|
+
type: "number",
|
|
46686
|
+
description: "Optional fixed width for text wrapping"
|
|
46687
|
+
}
|
|
46688
|
+
},
|
|
46689
|
+
required: ["frameId", "x", "y", "text"],
|
|
46690
|
+
additionalProperties: false
|
|
46691
|
+
}
|
|
46692
|
+
}
|
|
46693
|
+
},
|
|
46694
|
+
{
|
|
46695
|
+
type: "function",
|
|
46696
|
+
function: {
|
|
46697
|
+
name: "generate_image",
|
|
46698
|
+
description: "Generate an image using AI (Gemini) from a text prompt and place it inside a frame. Use for hero images, product shots, backgrounds, illustrations etc.",
|
|
46699
|
+
parameters: {
|
|
46700
|
+
type: "object",
|
|
46701
|
+
properties: {
|
|
46702
|
+
frameId: {
|
|
46703
|
+
type: "string",
|
|
46704
|
+
description: "ID of the parent frame to insert the image into"
|
|
46705
|
+
},
|
|
46706
|
+
prompt: {
|
|
46707
|
+
type: "string",
|
|
46708
|
+
description: "Detailed image generation prompt describing what to create"
|
|
46709
|
+
},
|
|
46710
|
+
x: {
|
|
46711
|
+
type: "number",
|
|
46712
|
+
description: "X position for the image (canvas coordinates)"
|
|
46713
|
+
},
|
|
46714
|
+
y: {
|
|
46715
|
+
type: "number",
|
|
46716
|
+
description: "Y position for the image (canvas coordinates)"
|
|
46717
|
+
},
|
|
46718
|
+
width: {
|
|
46719
|
+
type: "number",
|
|
46720
|
+
description: "Image width in pixels"
|
|
46721
|
+
},
|
|
46722
|
+
height: {
|
|
46723
|
+
type: "number",
|
|
46724
|
+
description: "Image height in pixels"
|
|
46725
|
+
}
|
|
46726
|
+
},
|
|
46727
|
+
required: ["frameId", "prompt", "x", "y", "width", "height"],
|
|
46728
|
+
additionalProperties: false
|
|
46729
|
+
}
|
|
46730
|
+
}
|
|
46731
|
+
},
|
|
46732
|
+
{
|
|
46733
|
+
type: "function",
|
|
46734
|
+
function: {
|
|
46735
|
+
name: "update_element",
|
|
46736
|
+
description: "Update properties of an existing element (move, resize, recolor, change text, etc.)",
|
|
46737
|
+
parameters: {
|
|
46738
|
+
type: "object",
|
|
46739
|
+
properties: {
|
|
46740
|
+
elementId: {
|
|
46741
|
+
type: "string",
|
|
46742
|
+
description: "ID of the element to update"
|
|
46743
|
+
},
|
|
46744
|
+
x: { type: "number", description: "New X position" },
|
|
46745
|
+
y: { type: "number", description: "New Y position" },
|
|
46746
|
+
width: { type: "number", description: "New width" },
|
|
46747
|
+
height: { type: "number", description: "New height" },
|
|
46748
|
+
backgroundColor: {
|
|
46749
|
+
type: "string",
|
|
46750
|
+
description: "New background/fill color"
|
|
46751
|
+
},
|
|
46752
|
+
strokeColor: {
|
|
46753
|
+
type: "string",
|
|
46754
|
+
description: "New stroke/text color"
|
|
46755
|
+
},
|
|
46756
|
+
opacity: { type: "number", description: "New opacity 0-100" },
|
|
46757
|
+
fontSize: {
|
|
46758
|
+
type: "number",
|
|
46759
|
+
description: "New font size (text elements only)"
|
|
46760
|
+
},
|
|
46761
|
+
text: {
|
|
46762
|
+
type: "string",
|
|
46763
|
+
description: "New text content (text elements only)"
|
|
46764
|
+
},
|
|
46765
|
+
fillStyle: {
|
|
46766
|
+
type: "string",
|
|
46767
|
+
enum: ["solid", "hachure", "cross-hatch"],
|
|
46768
|
+
description: "New fill style"
|
|
46769
|
+
}
|
|
46770
|
+
},
|
|
46771
|
+
required: ["elementId"],
|
|
46772
|
+
additionalProperties: false
|
|
46773
|
+
}
|
|
46774
|
+
}
|
|
46775
|
+
},
|
|
46776
|
+
{
|
|
46777
|
+
type: "function",
|
|
46778
|
+
function: {
|
|
46779
|
+
name: "delete_element",
|
|
46780
|
+
description: "Delete an element from the canvas",
|
|
46781
|
+
parameters: {
|
|
46782
|
+
type: "object",
|
|
46783
|
+
properties: {
|
|
46784
|
+
elementId: {
|
|
46785
|
+
type: "string",
|
|
46786
|
+
description: "ID of the element to delete"
|
|
46787
|
+
}
|
|
46788
|
+
},
|
|
46789
|
+
required: ["elementId"],
|
|
46790
|
+
additionalProperties: false
|
|
46791
|
+
}
|
|
46792
|
+
}
|
|
46793
|
+
},
|
|
46794
|
+
{
|
|
46795
|
+
type: "function",
|
|
46796
|
+
function: {
|
|
46797
|
+
name: "get_frame_elements",
|
|
46798
|
+
description: "Get all child elements inside a frame. Returns their IDs, types, positions, sizes, colors, and text content. Use this to understand the current layout before making changes.",
|
|
46799
|
+
parameters: {
|
|
46800
|
+
type: "object",
|
|
46801
|
+
properties: {
|
|
46802
|
+
frameId: {
|
|
46803
|
+
type: "string",
|
|
46804
|
+
description: "ID of the frame to inspect"
|
|
46805
|
+
}
|
|
46806
|
+
},
|
|
46807
|
+
required: ["frameId"],
|
|
46808
|
+
additionalProperties: false
|
|
46809
|
+
}
|
|
46810
|
+
}
|
|
46811
|
+
}
|
|
46812
|
+
];
|
|
46813
|
+
function serializeElement(el) {
|
|
46814
|
+
const base = {
|
|
46815
|
+
id: el.id,
|
|
46816
|
+
type: el.type,
|
|
46817
|
+
x: Math.round(el.x),
|
|
46818
|
+
y: Math.round(el.y),
|
|
46819
|
+
width: Math.round(el.width),
|
|
46820
|
+
height: Math.round(el.height)
|
|
46821
|
+
};
|
|
46822
|
+
if (el.backgroundColor && el.backgroundColor !== "transparent") {
|
|
46823
|
+
base.backgroundColor = el.backgroundColor;
|
|
46824
|
+
}
|
|
46825
|
+
if (el.strokeColor && el.strokeColor !== "#1e1e1e") {
|
|
46826
|
+
base.strokeColor = el.strokeColor;
|
|
46827
|
+
}
|
|
46828
|
+
if (el.opacity !== 100) {
|
|
46829
|
+
base.opacity = el.opacity;
|
|
46830
|
+
}
|
|
46831
|
+
if (el.fillStyle && el.fillStyle !== "solid") {
|
|
46832
|
+
base.fillStyle = el.fillStyle;
|
|
46833
|
+
}
|
|
46834
|
+
if (el.type === "text") {
|
|
46835
|
+
const textEl = el;
|
|
46836
|
+
base.text = textEl.text;
|
|
46837
|
+
base.fontSize = textEl.fontSize;
|
|
46838
|
+
base.fontFamily = textEl.fontFamily;
|
|
46839
|
+
base.textAlign = textEl.textAlign;
|
|
46840
|
+
}
|
|
46841
|
+
if (el.type === "image") {
|
|
46842
|
+
base.type = "image";
|
|
46843
|
+
}
|
|
46844
|
+
if (el.type === "frame") {
|
|
46845
|
+
const frameEl = el;
|
|
46846
|
+
if (frameEl.name) {
|
|
46847
|
+
base.name = frameEl.name;
|
|
46848
|
+
}
|
|
46849
|
+
}
|
|
46850
|
+
return base;
|
|
46851
|
+
}
|
|
46852
|
+
function serializeElements(elements) {
|
|
46853
|
+
return elements.map(serializeElement);
|
|
46854
|
+
}
|
|
46855
|
+
function listFrames(api) {
|
|
46856
|
+
const elements = api.getSceneElements();
|
|
46857
|
+
const frames = [];
|
|
46858
|
+
for (const el of elements) {
|
|
46859
|
+
if (isFrameLikeElement17(el)) {
|
|
46860
|
+
const children = getFrameChildren7(elements, el.id);
|
|
46861
|
+
const frameLike = el;
|
|
46862
|
+
frames.push({
|
|
46863
|
+
id: el.id,
|
|
46864
|
+
name: frameLike.name || `Frame ${Math.round(el.width)}\xD7${Math.round(el.height)}`,
|
|
46865
|
+
width: Math.round(el.width),
|
|
46866
|
+
height: Math.round(el.height),
|
|
46867
|
+
childCount: children.length
|
|
46868
|
+
});
|
|
46869
|
+
}
|
|
46870
|
+
}
|
|
46871
|
+
return frames;
|
|
46872
|
+
}
|
|
46873
|
+
async function captureFrameScreenshot(api, frameId) {
|
|
46874
|
+
const elements = api.getSceneElements();
|
|
46875
|
+
const frame = elements.find((el) => el.id === frameId);
|
|
46876
|
+
if (!frame || !isFrameLikeElement17(frame)) {
|
|
46877
|
+
return null;
|
|
46878
|
+
}
|
|
46879
|
+
const appState = api.getAppState();
|
|
46880
|
+
const files = api.getFiles();
|
|
46881
|
+
const children = getFrameChildren7(elements, frameId);
|
|
46882
|
+
try {
|
|
46883
|
+
const canvas = await exportToCanvas(
|
|
46884
|
+
[...children, frame],
|
|
46885
|
+
appState,
|
|
46886
|
+
files,
|
|
46887
|
+
{
|
|
46888
|
+
exportBackground: true,
|
|
46889
|
+
viewBackgroundColor: appState.viewBackgroundColor,
|
|
46890
|
+
exportingFrame: frame,
|
|
46891
|
+
exportPadding: 0
|
|
46892
|
+
}
|
|
46893
|
+
);
|
|
46894
|
+
return canvas.toDataURL("image/jpeg", 0.7);
|
|
46895
|
+
} catch {
|
|
46896
|
+
return null;
|
|
46897
|
+
}
|
|
46898
|
+
}
|
|
46899
|
+
function getFrameContext(api, frameId) {
|
|
46900
|
+
const elements = api.getSceneElements();
|
|
46901
|
+
const frame = elements.find((el) => el.id === frameId);
|
|
46902
|
+
if (!frame || !isFrameLikeElement17(frame)) {
|
|
46903
|
+
return null;
|
|
46904
|
+
}
|
|
46905
|
+
const children = getFrameChildren7(elements, frameId);
|
|
46906
|
+
const frameLike = frame;
|
|
46907
|
+
const frameInfo = {
|
|
46908
|
+
id: frame.id,
|
|
46909
|
+
name: frameLike.name || `Frame ${Math.round(frame.width)}\xD7${Math.round(frame.height)}`,
|
|
46910
|
+
width: Math.round(frame.width),
|
|
46911
|
+
height: Math.round(frame.height),
|
|
46912
|
+
childCount: children.length
|
|
46913
|
+
};
|
|
46914
|
+
const data = {
|
|
46915
|
+
frame: serializeElement(frame),
|
|
46916
|
+
children: serializeElements(children)
|
|
46917
|
+
};
|
|
46918
|
+
return {
|
|
46919
|
+
serialized: JSON.stringify(data, null, 2),
|
|
46920
|
+
frameInfo
|
|
46921
|
+
};
|
|
46922
|
+
}
|
|
46923
|
+
async function executeCanvasTool(toolName, rawArgs, ctx) {
|
|
46924
|
+
let args;
|
|
46925
|
+
try {
|
|
46926
|
+
args = JSON.parse(rawArgs);
|
|
46927
|
+
} catch {
|
|
46928
|
+
return {
|
|
46929
|
+
success: false,
|
|
46930
|
+
error: "Invalid JSON arguments",
|
|
46931
|
+
statusMessage: "Failed to parse tool arguments"
|
|
46932
|
+
};
|
|
46933
|
+
}
|
|
46934
|
+
switch (toolName) {
|
|
46935
|
+
case "create_frame":
|
|
46936
|
+
return execCreateFrame(args, ctx);
|
|
46937
|
+
case "add_rectangle":
|
|
46938
|
+
return execAddRectangle(args, ctx);
|
|
46939
|
+
case "add_text":
|
|
46940
|
+
return execAddText(args, ctx);
|
|
46941
|
+
case "generate_image":
|
|
46942
|
+
return execGenerateImage(args, ctx);
|
|
46943
|
+
case "update_element":
|
|
46944
|
+
return execUpdateElement(args, ctx);
|
|
46945
|
+
case "delete_element":
|
|
46946
|
+
return execDeleteElement(args, ctx);
|
|
46947
|
+
case "get_frame_elements":
|
|
46948
|
+
return execGetFrameElements(args, ctx);
|
|
46949
|
+
default:
|
|
46950
|
+
return {
|
|
46951
|
+
success: false,
|
|
46952
|
+
error: `Unknown tool: ${toolName}`,
|
|
46953
|
+
statusMessage: `Unknown tool: ${toolName}`
|
|
46954
|
+
};
|
|
46955
|
+
}
|
|
46956
|
+
}
|
|
46957
|
+
function getViewportCenter(api) {
|
|
46958
|
+
const appState = api.getAppState();
|
|
46959
|
+
const x = -appState.scrollX + appState.width / 2 / appState.zoom.value;
|
|
46960
|
+
const y = -appState.scrollY + appState.height / 2 / appState.zoom.value;
|
|
46961
|
+
return { x, y };
|
|
46962
|
+
}
|
|
46963
|
+
var COVER_TOLERANCE = 10;
|
|
46964
|
+
function coversFrame(elX, elY, elW, elH, frameEl) {
|
|
46965
|
+
return Math.abs(elX - frameEl.x) < COVER_TOLERANCE && Math.abs(elY - frameEl.y) < COVER_TOLERANCE && Math.abs(elW - frameEl.width) < COVER_TOLERANCE && Math.abs(elH - frameEl.height) < COVER_TOLERANCE;
|
|
46966
|
+
}
|
|
46967
|
+
function getElementTier(type, x, y, w, h, frame) {
|
|
46968
|
+
if (type === "text")
|
|
46969
|
+
return 3 /* TEXT */;
|
|
46970
|
+
if (type === "image")
|
|
46971
|
+
return 1 /* IMAGE */;
|
|
46972
|
+
if (coversFrame(x, y, w, h, frame))
|
|
46973
|
+
return 0 /* BG_FILL */;
|
|
46974
|
+
return 2 /* SHAPE */;
|
|
46975
|
+
}
|
|
46976
|
+
function findInsertIndexByTier(elements, frameIndex, tier) {
|
|
46977
|
+
const frame = elements[frameIndex];
|
|
46978
|
+
const frameId = frame.id;
|
|
46979
|
+
let insertAt = frameIndex + 1;
|
|
46980
|
+
for (let i = frameIndex + 1; i < elements.length; i++) {
|
|
46981
|
+
const el = elements[i];
|
|
46982
|
+
if (el.frameId !== frameId)
|
|
46983
|
+
continue;
|
|
46984
|
+
const elTier = getElementTier(el.type, el.x, el.y, el.width, el.height, frame);
|
|
46985
|
+
if (elTier > tier) {
|
|
46986
|
+
return i;
|
|
46987
|
+
}
|
|
46988
|
+
insertAt = i + 1;
|
|
46989
|
+
}
|
|
46990
|
+
return insertAt;
|
|
46991
|
+
}
|
|
46992
|
+
function execCreateFrame(args, ctx) {
|
|
46993
|
+
const width = args.width || 512;
|
|
46994
|
+
const height = args.height || 512;
|
|
46995
|
+
const name = args.name || null;
|
|
46996
|
+
const center = getViewportCenter(ctx.excalidrawAPI);
|
|
46997
|
+
const frame = newFrameElement3({
|
|
46998
|
+
...FRAME_STYLE5,
|
|
46999
|
+
x: center.x - width / 2,
|
|
47000
|
+
y: center.y - height / 2,
|
|
47001
|
+
width,
|
|
47002
|
+
height,
|
|
47003
|
+
opacity: 100,
|
|
47004
|
+
locked: false,
|
|
47005
|
+
name: name ?? void 0
|
|
47006
|
+
});
|
|
47007
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47008
|
+
ctx.excalidrawAPI.updateScene({
|
|
47009
|
+
elements: [...elements, frame]
|
|
47010
|
+
});
|
|
47011
|
+
ctx.excalidrawAPI.scrollToContent(frame, { fitToViewport: false });
|
|
47012
|
+
return {
|
|
47013
|
+
success: true,
|
|
47014
|
+
data: {
|
|
47015
|
+
frameId: frame.id,
|
|
47016
|
+
x: Math.round(frame.x),
|
|
47017
|
+
y: Math.round(frame.y),
|
|
47018
|
+
width,
|
|
47019
|
+
height
|
|
47020
|
+
},
|
|
47021
|
+
statusMessage: `Created frame "${name || "Ad Frame"}" (${width}\xD7${height})`
|
|
47022
|
+
};
|
|
47023
|
+
}
|
|
47024
|
+
function execAddRectangle(args, ctx) {
|
|
47025
|
+
const frameId = args.frameId;
|
|
47026
|
+
if (!frameId) {
|
|
47027
|
+
return {
|
|
47028
|
+
success: false,
|
|
47029
|
+
error: "frameId is required",
|
|
47030
|
+
statusMessage: "Failed: missing frameId"
|
|
47031
|
+
};
|
|
47032
|
+
}
|
|
47033
|
+
const rect = newElement5({
|
|
47034
|
+
type: "rectangle",
|
|
47035
|
+
x: args.x,
|
|
47036
|
+
y: args.y,
|
|
47037
|
+
width: args.width,
|
|
47038
|
+
height: args.height,
|
|
47039
|
+
backgroundColor: args.backgroundColor || "transparent",
|
|
47040
|
+
fillStyle: args.fillStyle || "solid",
|
|
47041
|
+
strokeColor: args.strokeColor || "transparent",
|
|
47042
|
+
strokeWidth: args.strokeWidth ?? 0,
|
|
47043
|
+
opacity: args.opacity ?? 100
|
|
47044
|
+
});
|
|
47045
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47046
|
+
const frameIndex = elements.findIndex((el) => el.id === frameId);
|
|
47047
|
+
if (frameIndex === -1) {
|
|
47048
|
+
return {
|
|
47049
|
+
success: false,
|
|
47050
|
+
error: `Frame ${frameId} not found`,
|
|
47051
|
+
statusMessage: "Failed: frame not found"
|
|
47052
|
+
};
|
|
47053
|
+
}
|
|
47054
|
+
const frame = elements[frameIndex];
|
|
47055
|
+
const tier = getElementTier(
|
|
47056
|
+
"rectangle",
|
|
47057
|
+
args.x,
|
|
47058
|
+
args.y,
|
|
47059
|
+
args.width,
|
|
47060
|
+
args.height,
|
|
47061
|
+
frame
|
|
47062
|
+
);
|
|
47063
|
+
const updatedRect = { ...rect, frameId };
|
|
47064
|
+
const newElements = [...elements];
|
|
47065
|
+
const insertAt = findInsertIndexByTier(newElements, frameIndex, tier);
|
|
47066
|
+
newElements.splice(insertAt, 0, updatedRect);
|
|
47067
|
+
ctx.excalidrawAPI.updateScene({ elements: newElements });
|
|
47068
|
+
const isBg = tier === 0 /* BG_FILL */;
|
|
47069
|
+
return {
|
|
47070
|
+
success: true,
|
|
47071
|
+
data: {
|
|
47072
|
+
elementId: rect.id,
|
|
47073
|
+
type: "rectangle",
|
|
47074
|
+
x: Math.round(rect.x),
|
|
47075
|
+
y: Math.round(rect.y),
|
|
47076
|
+
width: Math.round(rect.width),
|
|
47077
|
+
height: Math.round(rect.height),
|
|
47078
|
+
backgroundColor: rect.backgroundColor,
|
|
47079
|
+
isBackground: isBg
|
|
47080
|
+
},
|
|
47081
|
+
statusMessage: `Added ${isBg ? "background " : ""}rectangle (${Math.round(
|
|
47082
|
+
rect.width
|
|
47083
|
+
)}\xD7${Math.round(rect.height)}) with fill ${rect.backgroundColor}`
|
|
47084
|
+
};
|
|
47085
|
+
}
|
|
47086
|
+
function execAddText(args, ctx) {
|
|
47087
|
+
const frameId = args.frameId;
|
|
47088
|
+
if (!frameId) {
|
|
47089
|
+
return {
|
|
47090
|
+
success: false,
|
|
47091
|
+
error: "frameId is required",
|
|
47092
|
+
statusMessage: "Failed: missing frameId"
|
|
47093
|
+
};
|
|
47094
|
+
}
|
|
47095
|
+
const textEl = newTextElement4({
|
|
47096
|
+
x: args.x,
|
|
47097
|
+
y: args.y,
|
|
47098
|
+
text: args.text,
|
|
47099
|
+
fontSize: args.fontSize || 20,
|
|
47100
|
+
fontFamily: args.fontFamily || 2,
|
|
47101
|
+
strokeColor: args.strokeColor || "#000000",
|
|
47102
|
+
textAlign: args.textAlign || "left",
|
|
47103
|
+
width: args.width
|
|
47104
|
+
});
|
|
47105
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47106
|
+
const frameIndex = elements.findIndex((el) => el.id === frameId);
|
|
47107
|
+
if (frameIndex === -1) {
|
|
47108
|
+
return {
|
|
47109
|
+
success: false,
|
|
47110
|
+
error: `Frame ${frameId} not found`,
|
|
47111
|
+
statusMessage: "Failed: frame not found"
|
|
47112
|
+
};
|
|
47113
|
+
}
|
|
47114
|
+
const updatedText = { ...textEl, frameId };
|
|
47115
|
+
const newElements = [...elements];
|
|
47116
|
+
const insertAt = findInsertIndexByTier(newElements, frameIndex, 3 /* TEXT */);
|
|
47117
|
+
newElements.splice(insertAt, 0, updatedText);
|
|
47118
|
+
ctx.excalidrawAPI.updateScene({ elements: newElements });
|
|
47119
|
+
return {
|
|
47120
|
+
success: true,
|
|
47121
|
+
data: {
|
|
47122
|
+
elementId: textEl.id,
|
|
47123
|
+
type: "text",
|
|
47124
|
+
x: Math.round(textEl.x),
|
|
47125
|
+
y: Math.round(textEl.y),
|
|
47126
|
+
text: args.text,
|
|
47127
|
+
fontSize: args.fontSize || 20
|
|
47128
|
+
},
|
|
47129
|
+
statusMessage: `Added text "${args.text.slice(0, 40)}${args.text.length > 40 ? "\u2026" : ""}"`
|
|
47130
|
+
};
|
|
47131
|
+
}
|
|
47132
|
+
async function execGenerateImage(args, ctx) {
|
|
47133
|
+
const frameId = args.frameId;
|
|
47134
|
+
const prompt = args.prompt;
|
|
47135
|
+
const x = args.x;
|
|
47136
|
+
const y = args.y;
|
|
47137
|
+
const width = args.width;
|
|
47138
|
+
const height = args.height;
|
|
47139
|
+
if (!frameId || !prompt) {
|
|
47140
|
+
return {
|
|
47141
|
+
success: false,
|
|
47142
|
+
error: "frameId and prompt are required",
|
|
47143
|
+
statusMessage: "Failed: missing required parameters"
|
|
47144
|
+
};
|
|
47145
|
+
}
|
|
47146
|
+
if (!ctx.geminiApiKey) {
|
|
47147
|
+
return {
|
|
47148
|
+
success: false,
|
|
47149
|
+
error: "No Gemini API key configured. Set geminiApiKey prop or VITE_APP_GEMINI_API_KEY.",
|
|
47150
|
+
statusMessage: "Failed: no Gemini API key"
|
|
47151
|
+
};
|
|
47152
|
+
}
|
|
47153
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47154
|
+
const frameIndex = elements.findIndex((el) => el.id === frameId);
|
|
47155
|
+
if (frameIndex === -1) {
|
|
47156
|
+
return {
|
|
47157
|
+
success: false,
|
|
47158
|
+
error: `Frame ${frameId} not found`,
|
|
47159
|
+
statusMessage: "Failed: frame not found"
|
|
47160
|
+
};
|
|
47161
|
+
}
|
|
47162
|
+
const aspectRatio = width >= height ? width / height > 1.3 ? "16:9" : "1:1" : "9:16";
|
|
47163
|
+
try {
|
|
47164
|
+
const imageDataUrl = await callGeminiAPI(
|
|
47165
|
+
prompt,
|
|
47166
|
+
"gemini-2.5-flash-image",
|
|
47167
|
+
aspectRatio,
|
|
47168
|
+
"1K",
|
|
47169
|
+
false,
|
|
47170
|
+
false,
|
|
47171
|
+
ctx.geminiApiKey,
|
|
47172
|
+
void 0,
|
|
47173
|
+
ctx.signal
|
|
47174
|
+
);
|
|
47175
|
+
if (ctx.signal?.aborted) {
|
|
47176
|
+
return {
|
|
47177
|
+
success: false,
|
|
47178
|
+
error: "Cancelled",
|
|
47179
|
+
statusMessage: "Image generation cancelled"
|
|
47180
|
+
};
|
|
47181
|
+
}
|
|
47182
|
+
const fileId = nanoid2();
|
|
47183
|
+
const imageEl = newImageElement2({
|
|
47184
|
+
type: "image",
|
|
47185
|
+
x,
|
|
47186
|
+
y,
|
|
47187
|
+
width,
|
|
47188
|
+
height,
|
|
47189
|
+
fileId,
|
|
47190
|
+
status: "pending",
|
|
47191
|
+
scale: [1, 1]
|
|
47192
|
+
});
|
|
47193
|
+
const currentElements = ctx.excalidrawAPI.getSceneElements();
|
|
47194
|
+
const currentFrameIndex = currentElements.findIndex(
|
|
47195
|
+
(el) => el.id === frameId
|
|
47196
|
+
);
|
|
47197
|
+
if (currentFrameIndex === -1) {
|
|
47198
|
+
return {
|
|
47199
|
+
success: false,
|
|
47200
|
+
error: `Frame ${frameId} was removed during image generation`,
|
|
47201
|
+
statusMessage: "Failed: frame no longer exists"
|
|
47202
|
+
};
|
|
47203
|
+
}
|
|
47204
|
+
const updatedImage = { ...imageEl, frameId };
|
|
47205
|
+
const newElements = [...currentElements];
|
|
47206
|
+
const insertAt = findInsertIndexByTier(newElements, currentFrameIndex, 1 /* IMAGE */);
|
|
47207
|
+
newElements.splice(insertAt, 0, updatedImage);
|
|
47208
|
+
ctx.excalidrawAPI.updateScene({ elements: newElements });
|
|
47209
|
+
ctx.excalidrawAPI.addFiles([
|
|
47210
|
+
{
|
|
47211
|
+
id: fileId,
|
|
47212
|
+
dataURL: imageDataUrl,
|
|
47213
|
+
mimeType: getMimeTypeFromDataURL(imageDataUrl),
|
|
47214
|
+
created: Date.now(),
|
|
47215
|
+
lastRetrieved: Date.now()
|
|
47216
|
+
}
|
|
47217
|
+
]);
|
|
47218
|
+
return {
|
|
47219
|
+
success: true,
|
|
47220
|
+
data: {
|
|
47221
|
+
elementId: imageEl.id,
|
|
47222
|
+
type: "image",
|
|
47223
|
+
x: Math.round(x),
|
|
47224
|
+
y: Math.round(y),
|
|
47225
|
+
width: Math.round(width),
|
|
47226
|
+
height: Math.round(height)
|
|
47227
|
+
},
|
|
47228
|
+
statusMessage: `Generated image from prompt "${prompt.slice(0, 50)}${prompt.length > 50 ? "\u2026" : ""}"`
|
|
47229
|
+
};
|
|
47230
|
+
} catch (err) {
|
|
47231
|
+
const message = err instanceof Error ? err.message : "Image generation failed";
|
|
47232
|
+
return {
|
|
47233
|
+
success: false,
|
|
47234
|
+
error: message,
|
|
47235
|
+
statusMessage: `Image generation failed: ${message}`
|
|
47236
|
+
};
|
|
47237
|
+
}
|
|
47238
|
+
}
|
|
47239
|
+
function execUpdateElement(args, ctx) {
|
|
47240
|
+
const elementId = args.elementId;
|
|
47241
|
+
if (!elementId) {
|
|
47242
|
+
return {
|
|
47243
|
+
success: false,
|
|
47244
|
+
error: "elementId is required",
|
|
47245
|
+
statusMessage: "Failed: missing elementId"
|
|
47246
|
+
};
|
|
47247
|
+
}
|
|
47248
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47249
|
+
const element = elements.find((el) => el.id === elementId);
|
|
47250
|
+
if (!element) {
|
|
47251
|
+
return {
|
|
47252
|
+
success: false,
|
|
47253
|
+
error: `Element ${elementId} not found`,
|
|
47254
|
+
statusMessage: "Failed: element not found"
|
|
47255
|
+
};
|
|
47256
|
+
}
|
|
47257
|
+
const updates = {};
|
|
47258
|
+
const allowedKeys = [
|
|
47259
|
+
"x",
|
|
47260
|
+
"y",
|
|
47261
|
+
"width",
|
|
47262
|
+
"height",
|
|
47263
|
+
"backgroundColor",
|
|
47264
|
+
"strokeColor",
|
|
47265
|
+
"opacity",
|
|
47266
|
+
"fontSize",
|
|
47267
|
+
"text",
|
|
47268
|
+
"fillStyle"
|
|
47269
|
+
];
|
|
47270
|
+
for (const key of allowedKeys) {
|
|
47271
|
+
if (args[key] !== void 0) {
|
|
47272
|
+
updates[key] = args[key];
|
|
47273
|
+
}
|
|
47274
|
+
}
|
|
47275
|
+
if (Object.keys(updates).length === 0) {
|
|
47276
|
+
return {
|
|
47277
|
+
success: false,
|
|
47278
|
+
error: "No valid properties to update",
|
|
47279
|
+
statusMessage: "Failed: nothing to update"
|
|
47280
|
+
};
|
|
47281
|
+
}
|
|
47282
|
+
if (updates.text !== void 0 && element.type === "text" && (updates.width === void 0 || updates.height === void 0)) {
|
|
47283
|
+
return {
|
|
47284
|
+
success: false,
|
|
47285
|
+
error: "Updating text content requires providing new width and height to avoid clipping.",
|
|
47286
|
+
statusMessage: "Failed: text update requires width and height"
|
|
47287
|
+
};
|
|
47288
|
+
}
|
|
47289
|
+
ctx.excalidrawAPI.mutateElement(element, updates);
|
|
47290
|
+
return {
|
|
47291
|
+
success: true,
|
|
47292
|
+
data: { elementId, updated: Object.keys(updates) },
|
|
47293
|
+
statusMessage: `Updated element (${Object.keys(updates).join(", ")})`
|
|
47294
|
+
};
|
|
47295
|
+
}
|
|
47296
|
+
function execDeleteElement(args, ctx) {
|
|
47297
|
+
const elementId = args.elementId;
|
|
47298
|
+
if (!elementId) {
|
|
47299
|
+
return {
|
|
47300
|
+
success: false,
|
|
47301
|
+
error: "elementId is required",
|
|
47302
|
+
statusMessage: "Failed: missing elementId"
|
|
47303
|
+
};
|
|
47304
|
+
}
|
|
47305
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47306
|
+
const element = elements.find((el) => el.id === elementId);
|
|
47307
|
+
if (!element) {
|
|
47308
|
+
return {
|
|
47309
|
+
success: false,
|
|
47310
|
+
error: `Element ${elementId} not found`,
|
|
47311
|
+
statusMessage: "Failed: element not found"
|
|
47312
|
+
};
|
|
47313
|
+
}
|
|
47314
|
+
ctx.excalidrawAPI.mutateElement(element, { isDeleted: true });
|
|
47315
|
+
return {
|
|
47316
|
+
success: true,
|
|
47317
|
+
data: { elementId },
|
|
47318
|
+
statusMessage: `Deleted element ${elementId}`
|
|
47319
|
+
};
|
|
47320
|
+
}
|
|
47321
|
+
function execGetFrameElements(args, ctx) {
|
|
47322
|
+
const frameId = args.frameId;
|
|
47323
|
+
if (!frameId) {
|
|
47324
|
+
return {
|
|
47325
|
+
success: false,
|
|
47326
|
+
error: "frameId is required",
|
|
47327
|
+
statusMessage: "Failed: missing frameId"
|
|
47328
|
+
};
|
|
47329
|
+
}
|
|
47330
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47331
|
+
const frame = elements.find((el) => el.id === frameId);
|
|
47332
|
+
if (!frame) {
|
|
47333
|
+
return {
|
|
47334
|
+
success: false,
|
|
47335
|
+
error: `Frame ${frameId} not found`,
|
|
47336
|
+
statusMessage: "Failed: frame not found"
|
|
47337
|
+
};
|
|
47338
|
+
}
|
|
47339
|
+
const children = getFrameChildren7(elements, frameId);
|
|
47340
|
+
const serialized = serializeElements(children);
|
|
47341
|
+
return {
|
|
47342
|
+
success: true,
|
|
47343
|
+
data: {
|
|
47344
|
+
frameId,
|
|
47345
|
+
frame: serializeElement(frame),
|
|
47346
|
+
children: serialized,
|
|
47347
|
+
childCount: serialized.length
|
|
47348
|
+
},
|
|
47349
|
+
statusMessage: `Found ${serialized.length} elements in frame`
|
|
47350
|
+
};
|
|
47351
|
+
}
|
|
47352
|
+
|
|
47353
|
+
// components/ai-chat/agentLoop.ts
|
|
47354
|
+
var SYSTEM_PROMPT = `You are an AI ad designer assistant embedded in a canvas editor (Excalidraw-based). You create advertisements, graphics, and visual compositions by calling tools that manipulate canvas elements.
|
|
47355
|
+
|
|
47356
|
+
## Your Capabilities
|
|
47357
|
+
You have tools to: create frames, add rectangles (for backgrounds/shapes), add text, generate AI images, update elements, delete elements, and inspect frame contents.
|
|
47358
|
+
|
|
47359
|
+
## Design Process
|
|
47360
|
+
When asked to create an ad or visual from scratch:
|
|
47361
|
+
1. **Create a frame** first (default 512\xD7512 unless the user specifies a size). This is the artboard.
|
|
47362
|
+
2. **Add a background** \u2014 use add_rectangle at the frame's exact position and size with a solid fill color. This is the base layer.
|
|
47363
|
+
3. **Generate a hero image** \u2014 use generate_image to create the main visual. Position it within the frame. It can be full-bleed (same size/position as frame) or partial.
|
|
47364
|
+
4. **Add headline text** \u2014 large, bold text. Position it where it's readable against the background/image. Use contrasting colors (white text on dark images, dark text on light backgrounds).
|
|
47365
|
+
5. **Add subhead text** \u2014 smaller supporting text below the headline.
|
|
47366
|
+
|
|
47367
|
+
## Layout & Z-ordering Rules
|
|
47368
|
+
- Elements are rendered in insertion order. Add background rectangle FIRST, then images, then text ON TOP.
|
|
47369
|
+
- All element positions use absolute canvas coordinates. The frame's x,y is the top-left corner.
|
|
47370
|
+
- To place an element at a frame's top-left: use the frame's x,y values.
|
|
47371
|
+
- To center text horizontally in a frame: set text x = frame.x + (frame.width - estimated_text_width) / 2
|
|
47372
|
+
- Leave padding from edges (at least 20px from frame borders for text).
|
|
47373
|
+
- Headline font size: 28-48px depending on frame size. Subhead: 16-24px.
|
|
47374
|
+
|
|
47375
|
+
## Color & Typography
|
|
47376
|
+
- Use font family 2 (Nunito/clean sans-serif) for professional ads.
|
|
47377
|
+
- For dark backgrounds: use white (#ffffff) or light text.
|
|
47378
|
+
- For light backgrounds: use dark (#1a1a2e or #000000) text.
|
|
47379
|
+
- Popular ad background colors: deep navy (#1a1a2e), warm red (#c0392b), forest green (#27ae60), elegant black (#111111), soft cream (#fdf6e3).
|
|
47380
|
+
|
|
47381
|
+
## When modifying existing elements (@ context provided)
|
|
47382
|
+
- The user may reference existing elements via @ mentions. Their current state will be described in the message.
|
|
47383
|
+
- Use get_frame_elements to see the full current state of a frame before making changes.
|
|
47384
|
+
- Use update_element to modify properties. Use delete_element to remove things.
|
|
47385
|
+
- Preserve existing elements unless explicitly asked to change them.
|
|
47386
|
+
|
|
47387
|
+
## Image Generation Tips
|
|
47388
|
+
- Write detailed, descriptive prompts for generate_image. Include style, mood, composition, colors.
|
|
47389
|
+
- Example: "A steaming bowl of creamy pasta on a rustic wooden table, warm golden lighting, top-down view, food photography style, appetizing, high quality"
|
|
47390
|
+
- The image will be generated by Gemini AI and placed at the coordinates you specify.
|
|
47391
|
+
|
|
47392
|
+
## Important
|
|
47393
|
+
- Always respond with a brief summary of what you created/modified after completing the tools.
|
|
47394
|
+
- If the user's request is unclear, use the available tools to create a reasonable default and explain what you did.
|
|
47395
|
+
- Coordinates: frame x,y is the top-left corner. Width extends right, height extends down.`;
|
|
47396
|
+
var MAX_ITERATIONS = 15;
|
|
47397
|
+
async function callOpenRouter(messages, apiKey, signal) {
|
|
47398
|
+
const response = await fetch(
|
|
47399
|
+
"https://openrouter.ai/api/v1/chat/completions",
|
|
47400
|
+
{
|
|
47401
|
+
method: "POST",
|
|
47402
|
+
signal,
|
|
47403
|
+
headers: {
|
|
47404
|
+
Authorization: `Bearer ${apiKey}`,
|
|
47405
|
+
"Content-Type": "application/json"
|
|
47406
|
+
},
|
|
47407
|
+
body: JSON.stringify({
|
|
47408
|
+
model: "openai/gpt-4.1-mini",
|
|
47409
|
+
messages: messages.map((m) => {
|
|
47410
|
+
if (m.role === "assistant" && m.tool_calls) {
|
|
47411
|
+
return {
|
|
47412
|
+
role: m.role,
|
|
47413
|
+
content: m.content,
|
|
47414
|
+
tool_calls: m.tool_calls
|
|
47415
|
+
};
|
|
47416
|
+
}
|
|
47417
|
+
if (m.role === "tool") {
|
|
47418
|
+
return {
|
|
47419
|
+
role: m.role,
|
|
47420
|
+
tool_call_id: m.tool_call_id,
|
|
47421
|
+
content: m.content
|
|
47422
|
+
};
|
|
47423
|
+
}
|
|
47424
|
+
return { role: m.role, content: m.content };
|
|
47425
|
+
}),
|
|
47426
|
+
tools: CANVAS_TOOLS,
|
|
47427
|
+
tool_choice: "auto"
|
|
47428
|
+
})
|
|
47429
|
+
}
|
|
47430
|
+
);
|
|
47431
|
+
if (!response.ok) {
|
|
47432
|
+
let message = `OpenRouter error ${response.status}`;
|
|
47433
|
+
try {
|
|
47434
|
+
const err = await response.json();
|
|
47435
|
+
if (err?.error?.message) {
|
|
47436
|
+
message = err.error.message;
|
|
47437
|
+
}
|
|
47438
|
+
} catch {
|
|
47439
|
+
}
|
|
47440
|
+
throw new Error(message);
|
|
47441
|
+
}
|
|
47442
|
+
const data = await response.json();
|
|
47443
|
+
const choice = data?.choices?.[0]?.message;
|
|
47444
|
+
if (!choice) {
|
|
47445
|
+
throw new Error("No response returned by the API.");
|
|
47446
|
+
}
|
|
47447
|
+
return {
|
|
47448
|
+
content: choice.content ?? null,
|
|
47449
|
+
tool_calls: choice.tool_calls ?? void 0
|
|
47450
|
+
};
|
|
47451
|
+
}
|
|
47452
|
+
async function runAgentLoop(opts) {
|
|
47453
|
+
const {
|
|
47454
|
+
userMessages,
|
|
47455
|
+
elementContext,
|
|
47456
|
+
frameScreenshot,
|
|
47457
|
+
apiKey,
|
|
47458
|
+
toolCtx,
|
|
47459
|
+
onUpdate,
|
|
47460
|
+
signal
|
|
47461
|
+
} = opts;
|
|
47462
|
+
const messages = [{ role: "system", content: SYSTEM_PROMPT }];
|
|
47463
|
+
if (elementContext) {
|
|
47464
|
+
messages.push({
|
|
47465
|
+
role: "system",
|
|
47466
|
+
content: `The user has referenced a frame (via @ mention). Here are the frame details and its child elements. Use these IDs and properties when modifying or working with these elements:
|
|
47467
|
+
|
|
47468
|
+
${elementContext}`
|
|
47469
|
+
});
|
|
47470
|
+
}
|
|
47471
|
+
for (let i = 0; i < userMessages.length; i++) {
|
|
47472
|
+
const msg = userMessages[i];
|
|
47473
|
+
const isLast = i === userMessages.length - 1;
|
|
47474
|
+
if (isLast && msg.role === "user" && frameScreenshot) {
|
|
47475
|
+
messages.push({
|
|
47476
|
+
role: "user",
|
|
47477
|
+
content: [
|
|
47478
|
+
{
|
|
47479
|
+
type: "image_url",
|
|
47480
|
+
image_url: { url: frameScreenshot }
|
|
47481
|
+
},
|
|
47482
|
+
{
|
|
47483
|
+
type: "text",
|
|
47484
|
+
text: `[Screenshot of the referenced frame is attached above]
|
|
47485
|
+
|
|
47486
|
+
${msg.content}`
|
|
47487
|
+
}
|
|
47488
|
+
]
|
|
47489
|
+
});
|
|
47490
|
+
} else {
|
|
47491
|
+
messages.push({ role: msg.role, content: msg.content });
|
|
47492
|
+
}
|
|
47493
|
+
}
|
|
47494
|
+
const toolActions = [];
|
|
47495
|
+
let iterations = 0;
|
|
47496
|
+
while (iterations < MAX_ITERATIONS) {
|
|
47497
|
+
iterations++;
|
|
47498
|
+
onUpdate({
|
|
47499
|
+
type: "status",
|
|
47500
|
+
message: iterations === 1 ? "Thinking\u2026" : "Processing tool results\u2026"
|
|
47501
|
+
});
|
|
47502
|
+
const response = await callOpenRouter(messages, apiKey, signal);
|
|
47503
|
+
if (!response.tool_calls || response.tool_calls.length === 0) {
|
|
47504
|
+
const reply = response.content || "Done!";
|
|
47505
|
+
onUpdate({ type: "final", message: reply });
|
|
47506
|
+
return { reply, toolActions };
|
|
47507
|
+
}
|
|
47508
|
+
messages.push({
|
|
47509
|
+
role: "assistant",
|
|
47510
|
+
content: response.content,
|
|
47511
|
+
tool_calls: response.tool_calls
|
|
47512
|
+
});
|
|
47513
|
+
for (const toolCall of response.tool_calls) {
|
|
47514
|
+
const { name, arguments: rawArgs } = toolCall.function;
|
|
47515
|
+
let parsedArgs = {};
|
|
47516
|
+
try {
|
|
47517
|
+
parsedArgs = JSON.parse(rawArgs);
|
|
47518
|
+
} catch {
|
|
47519
|
+
}
|
|
47520
|
+
onUpdate({
|
|
47521
|
+
type: "tool_start",
|
|
47522
|
+
message: formatToolStartMessage(name, parsedArgs)
|
|
47523
|
+
});
|
|
47524
|
+
const result = await executeCanvasTool(name, rawArgs, toolCtx);
|
|
47525
|
+
const action = {
|
|
47526
|
+
toolName: name,
|
|
47527
|
+
args: parsedArgs,
|
|
47528
|
+
result
|
|
47529
|
+
};
|
|
47530
|
+
toolActions.push(action);
|
|
47531
|
+
onUpdate({
|
|
47532
|
+
type: "tool_done",
|
|
47533
|
+
message: result.statusMessage,
|
|
47534
|
+
toolAction: action
|
|
47535
|
+
});
|
|
47536
|
+
messages.push({
|
|
47537
|
+
role: "tool",
|
|
47538
|
+
tool_call_id: toolCall.id,
|
|
47539
|
+
content: JSON.stringify(
|
|
47540
|
+
result.success ? { success: true, ...result.data } : { success: false, error: result.error }
|
|
47541
|
+
)
|
|
47542
|
+
});
|
|
47543
|
+
}
|
|
47544
|
+
}
|
|
47545
|
+
const fallbackReply = "I've completed the available operations. Let me know if you'd like any adjustments!";
|
|
47546
|
+
onUpdate({ type: "final", message: fallbackReply });
|
|
47547
|
+
return { reply: fallbackReply, toolActions };
|
|
47548
|
+
}
|
|
47549
|
+
function formatToolStartMessage(name, args) {
|
|
47550
|
+
switch (name) {
|
|
47551
|
+
case "create_frame":
|
|
47552
|
+
return `Creating frame (${args.width || 512}\xD7${args.height || 512})\u2026`;
|
|
47553
|
+
case "add_rectangle":
|
|
47554
|
+
return `Adding background rectangle\u2026`;
|
|
47555
|
+
case "add_text":
|
|
47556
|
+
return `Adding text "${String(args.text || "").slice(0, 30)}"\u2026`;
|
|
47557
|
+
case "generate_image":
|
|
47558
|
+
return `Generating image \u2014 this may take a moment\u2026`;
|
|
47559
|
+
case "update_element":
|
|
47560
|
+
return `Updating element\u2026`;
|
|
47561
|
+
case "delete_element":
|
|
47562
|
+
return `Deleting element\u2026`;
|
|
47563
|
+
case "get_frame_elements":
|
|
47564
|
+
return `Inspecting frame contents\u2026`;
|
|
47565
|
+
default:
|
|
47566
|
+
return `Running ${name}\u2026`;
|
|
47567
|
+
}
|
|
47568
|
+
}
|
|
47569
|
+
|
|
47570
|
+
// components/ui/chat-container.tsx
|
|
47571
|
+
import { StickToBottom } from "use-stick-to-bottom";
|
|
46521
47572
|
import { jsx as jsx177 } from "react/jsx-runtime";
|
|
47573
|
+
function ChatContainerRoot({
|
|
47574
|
+
children,
|
|
47575
|
+
className,
|
|
47576
|
+
...props
|
|
47577
|
+
}) {
|
|
47578
|
+
return /* @__PURE__ */ jsx177(
|
|
47579
|
+
StickToBottom,
|
|
47580
|
+
{
|
|
47581
|
+
className: cn("flex overflow-y-auto", className),
|
|
47582
|
+
resize: "smooth",
|
|
47583
|
+
initial: "instant",
|
|
47584
|
+
role: "log",
|
|
47585
|
+
...props,
|
|
47586
|
+
children
|
|
47587
|
+
}
|
|
47588
|
+
);
|
|
47589
|
+
}
|
|
47590
|
+
function ChatContainerContent({
|
|
47591
|
+
children,
|
|
47592
|
+
className,
|
|
47593
|
+
...props
|
|
47594
|
+
}) {
|
|
47595
|
+
return /* @__PURE__ */ jsx177(
|
|
47596
|
+
StickToBottom.Content,
|
|
47597
|
+
{
|
|
47598
|
+
className: cn("flex w-full flex-col", className),
|
|
47599
|
+
...props,
|
|
47600
|
+
children
|
|
47601
|
+
}
|
|
47602
|
+
);
|
|
47603
|
+
}
|
|
47604
|
+
|
|
47605
|
+
// components/ui/markdown.tsx
|
|
47606
|
+
import { marked } from "marked";
|
|
47607
|
+
import { memo as memo6, useId, useMemo as useMemo13 } from "react";
|
|
47608
|
+
import ReactMarkdown from "react-markdown";
|
|
47609
|
+
import remarkBreaks from "remark-breaks";
|
|
47610
|
+
import remarkGfm from "remark-gfm";
|
|
47611
|
+
|
|
47612
|
+
// components/ui/code-block.tsx
|
|
47613
|
+
import { useEffect as useEffect54, useState as useState48 } from "react";
|
|
47614
|
+
import { codeToHtml } from "shiki";
|
|
47615
|
+
import { jsx as jsx178 } from "react/jsx-runtime";
|
|
47616
|
+
function CodeBlock({ children, className, ...props }) {
|
|
47617
|
+
return /* @__PURE__ */ jsx178(
|
|
47618
|
+
"div",
|
|
47619
|
+
{
|
|
47620
|
+
className: cn(
|
|
47621
|
+
"not-prose flex w-full flex-col overflow-clip border",
|
|
47622
|
+
"border-zinc-200 bg-white text-zinc-950 rounded-xl dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50",
|
|
47623
|
+
className
|
|
47624
|
+
),
|
|
47625
|
+
...props,
|
|
47626
|
+
children
|
|
47627
|
+
}
|
|
47628
|
+
);
|
|
47629
|
+
}
|
|
47630
|
+
function CodeBlockCode({
|
|
47631
|
+
code,
|
|
47632
|
+
language = "tsx",
|
|
47633
|
+
theme = "github-light",
|
|
47634
|
+
className,
|
|
47635
|
+
...props
|
|
47636
|
+
}) {
|
|
47637
|
+
const [highlightedHtml, setHighlightedHtml] = useState48(null);
|
|
47638
|
+
useEffect54(() => {
|
|
47639
|
+
async function highlight() {
|
|
47640
|
+
if (!code) {
|
|
47641
|
+
setHighlightedHtml("<pre><code></code></pre>");
|
|
47642
|
+
return;
|
|
47643
|
+
}
|
|
47644
|
+
const html = await codeToHtml(code, { lang: language, theme });
|
|
47645
|
+
setHighlightedHtml(html);
|
|
47646
|
+
}
|
|
47647
|
+
highlight();
|
|
47648
|
+
}, [code, language, theme]);
|
|
47649
|
+
const classNames = cn(
|
|
47650
|
+
"w-full overflow-x-auto text-[13px] [&>pre]:px-4 [&>pre]:py-4",
|
|
47651
|
+
className
|
|
47652
|
+
);
|
|
47653
|
+
return highlightedHtml ? /* @__PURE__ */ jsx178(
|
|
47654
|
+
"div",
|
|
47655
|
+
{
|
|
47656
|
+
className: classNames,
|
|
47657
|
+
dangerouslySetInnerHTML: { __html: highlightedHtml },
|
|
47658
|
+
...props
|
|
47659
|
+
}
|
|
47660
|
+
) : /* @__PURE__ */ jsx178("div", { className: classNames, ...props, children: /* @__PURE__ */ jsx178("pre", { children: /* @__PURE__ */ jsx178("code", { children: code }) }) });
|
|
47661
|
+
}
|
|
47662
|
+
|
|
47663
|
+
// components/ui/markdown.tsx
|
|
47664
|
+
import { Fragment as Fragment33, jsx as jsx179 } from "react/jsx-runtime";
|
|
47665
|
+
function parseMarkdownIntoBlocks(markdown) {
|
|
47666
|
+
const tokens = marked.lexer(markdown);
|
|
47667
|
+
return tokens.map((token) => token.raw);
|
|
47668
|
+
}
|
|
47669
|
+
function extractLanguage(className) {
|
|
47670
|
+
if (!className) {
|
|
47671
|
+
return "plaintext";
|
|
47672
|
+
}
|
|
47673
|
+
const match = className.match(/language-(\w+)/);
|
|
47674
|
+
return match ? match[1] : "plaintext";
|
|
47675
|
+
}
|
|
47676
|
+
var INITIAL_COMPONENTS = {
|
|
47677
|
+
code: function CodeComponent({ className, children, ...props }) {
|
|
47678
|
+
const isInline = !props.node?.position?.start.line || props.node?.position?.start.line === props.node?.position?.end.line;
|
|
47679
|
+
if (isInline) {
|
|
47680
|
+
return /* @__PURE__ */ jsx179(
|
|
47681
|
+
"span",
|
|
47682
|
+
{
|
|
47683
|
+
className: cn(
|
|
47684
|
+
"bg-zinc-50 rounded-sm px-1 font-mono text-sm dark:bg-zinc-900",
|
|
47685
|
+
className
|
|
47686
|
+
),
|
|
47687
|
+
...props,
|
|
47688
|
+
children
|
|
47689
|
+
}
|
|
47690
|
+
);
|
|
47691
|
+
}
|
|
47692
|
+
const language = extractLanguage(className);
|
|
47693
|
+
return /* @__PURE__ */ jsx179(CodeBlock, { className, children: /* @__PURE__ */ jsx179(CodeBlockCode, { code: children, language }) });
|
|
47694
|
+
},
|
|
47695
|
+
pre: function PreComponent({ children }) {
|
|
47696
|
+
return /* @__PURE__ */ jsx179(Fragment33, { children });
|
|
47697
|
+
}
|
|
47698
|
+
};
|
|
47699
|
+
var MemoizedMarkdownBlock = memo6(
|
|
47700
|
+
({
|
|
47701
|
+
content,
|
|
47702
|
+
components = INITIAL_COMPONENTS
|
|
47703
|
+
}) => {
|
|
47704
|
+
return /* @__PURE__ */ jsx179(
|
|
47705
|
+
ReactMarkdown,
|
|
47706
|
+
{
|
|
47707
|
+
remarkPlugins: [remarkGfm, remarkBreaks],
|
|
47708
|
+
components,
|
|
47709
|
+
children: content
|
|
47710
|
+
}
|
|
47711
|
+
);
|
|
47712
|
+
},
|
|
47713
|
+
(prevProps, nextProps) => {
|
|
47714
|
+
return prevProps.content === nextProps.content;
|
|
47715
|
+
}
|
|
47716
|
+
);
|
|
47717
|
+
MemoizedMarkdownBlock.displayName = "MemoizedMarkdownBlock";
|
|
47718
|
+
function MarkdownComponent({
|
|
47719
|
+
children,
|
|
47720
|
+
id,
|
|
47721
|
+
className,
|
|
47722
|
+
components = INITIAL_COMPONENTS
|
|
47723
|
+
}) {
|
|
47724
|
+
const generatedId = useId();
|
|
47725
|
+
const blockId = id ?? generatedId;
|
|
47726
|
+
const blocks = useMemo13(() => parseMarkdownIntoBlocks(children), [children]);
|
|
47727
|
+
return /* @__PURE__ */ jsx179("div", { className, children: blocks.map((block, index) => /* @__PURE__ */ jsx179(
|
|
47728
|
+
MemoizedMarkdownBlock,
|
|
47729
|
+
{
|
|
47730
|
+
content: block,
|
|
47731
|
+
components
|
|
47732
|
+
},
|
|
47733
|
+
`${blockId}-block-${index}`
|
|
47734
|
+
)) });
|
|
47735
|
+
}
|
|
47736
|
+
var Markdown = memo6(MarkdownComponent);
|
|
47737
|
+
Markdown.displayName = "Markdown";
|
|
47738
|
+
|
|
47739
|
+
// components/ui/avatar.tsx
|
|
47740
|
+
import * as React55 from "react";
|
|
47741
|
+
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
|
47742
|
+
import { jsx as jsx180 } from "react/jsx-runtime";
|
|
47743
|
+
var Avatar2 = React55.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx180(
|
|
47744
|
+
AvatarPrimitive.Root,
|
|
47745
|
+
{
|
|
47746
|
+
ref,
|
|
47747
|
+
className: cn(
|
|
47748
|
+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
|
47749
|
+
className
|
|
47750
|
+
),
|
|
47751
|
+
...props
|
|
47752
|
+
}
|
|
47753
|
+
));
|
|
47754
|
+
Avatar2.displayName = AvatarPrimitive.Root.displayName;
|
|
47755
|
+
var AvatarImage = React55.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx180(
|
|
47756
|
+
AvatarPrimitive.Image,
|
|
47757
|
+
{
|
|
47758
|
+
ref,
|
|
47759
|
+
className: cn("aspect-square h-full w-full", className),
|
|
47760
|
+
...props
|
|
47761
|
+
}
|
|
47762
|
+
));
|
|
47763
|
+
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
|
|
47764
|
+
var AvatarFallback = React55.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx180(
|
|
47765
|
+
AvatarPrimitive.Fallback,
|
|
47766
|
+
{
|
|
47767
|
+
ref,
|
|
47768
|
+
className: cn(
|
|
47769
|
+
"flex h-full w-full items-center justify-center rounded-full bg-zinc-100 dark:bg-zinc-800",
|
|
47770
|
+
className
|
|
47771
|
+
),
|
|
47772
|
+
...props
|
|
47773
|
+
}
|
|
47774
|
+
));
|
|
47775
|
+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
|
47776
|
+
|
|
47777
|
+
// components/ui/message.tsx
|
|
47778
|
+
import { jsx as jsx181, jsxs as jsxs99 } from "react/jsx-runtime";
|
|
47779
|
+
var Message = ({ children, className, ...props }) => /* @__PURE__ */ jsx181("div", { className: cn("flex gap-3", className), ...props, children });
|
|
47780
|
+
var MessageContent = ({
|
|
47781
|
+
children,
|
|
47782
|
+
markdown = false,
|
|
47783
|
+
className,
|
|
47784
|
+
...props
|
|
47785
|
+
}) => {
|
|
47786
|
+
const classNames = cn(
|
|
47787
|
+
"rounded-lg p-2 text-zinc-950 bg-zinc-100 prose break-words whitespace-normal dark:text-zinc-50 dark:bg-zinc-800",
|
|
47788
|
+
className
|
|
47789
|
+
);
|
|
47790
|
+
return markdown ? /* @__PURE__ */ jsx181(Markdown, { className: classNames, ...props, children }) : /* @__PURE__ */ jsx181("div", { className: classNames, ...props, children });
|
|
47791
|
+
};
|
|
47792
|
+
var MessageActions = ({
|
|
47793
|
+
children,
|
|
47794
|
+
className,
|
|
47795
|
+
...props
|
|
47796
|
+
}) => /* @__PURE__ */ jsx181(
|
|
47797
|
+
"div",
|
|
47798
|
+
{
|
|
47799
|
+
className: cn(
|
|
47800
|
+
"text-zinc-500 flex items-center gap-2 dark:text-zinc-400",
|
|
47801
|
+
className
|
|
47802
|
+
),
|
|
47803
|
+
...props,
|
|
47804
|
+
children
|
|
47805
|
+
}
|
|
47806
|
+
);
|
|
47807
|
+
var MessageAction = ({
|
|
47808
|
+
tooltip,
|
|
47809
|
+
children,
|
|
47810
|
+
className,
|
|
47811
|
+
side = "top",
|
|
47812
|
+
...props
|
|
47813
|
+
}) => {
|
|
47814
|
+
return /* @__PURE__ */ jsx181(TooltipProvider, { children: /* @__PURE__ */ jsxs99(Tooltip2, { ...props, children: [
|
|
47815
|
+
/* @__PURE__ */ jsx181(TooltipTrigger, { asChild: true, children }),
|
|
47816
|
+
/* @__PURE__ */ jsx181(TooltipContent, { side, className, children: tooltip })
|
|
47817
|
+
] }) });
|
|
47818
|
+
};
|
|
47819
|
+
|
|
47820
|
+
// components/ui/scroll-button.tsx
|
|
47821
|
+
import { ChevronDown } from "lucide-react";
|
|
47822
|
+
import { useStickToBottomContext } from "use-stick-to-bottom";
|
|
47823
|
+
|
|
47824
|
+
// components/ui/button.tsx
|
|
47825
|
+
import * as React56 from "react";
|
|
47826
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
47827
|
+
import { cva } from "class-variance-authority";
|
|
47828
|
+
import { jsx as jsx182 } from "react/jsx-runtime";
|
|
47829
|
+
var buttonVariants = cva(
|
|
47830
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-zinc-950 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 dark:focus-visible:ring-zinc-300",
|
|
47831
|
+
{
|
|
47832
|
+
variants: {
|
|
47833
|
+
variant: {
|
|
47834
|
+
default: "bg-zinc-900 text-zinc-50 shadow hover:bg-zinc-900/90 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/90",
|
|
47835
|
+
destructive: "bg-red-500 text-zinc-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/90",
|
|
47836
|
+
outline: "border border-zinc-200 bg-white shadow-sm hover:bg-zinc-100 hover:text-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-800 dark:hover:text-zinc-50",
|
|
47837
|
+
secondary: "bg-zinc-100 text-zinc-900 shadow-sm hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80",
|
|
47838
|
+
ghost: "hover:bg-zinc-100 hover:text-zinc-900 dark:hover:bg-zinc-800 dark:hover:text-zinc-50",
|
|
47839
|
+
link: "text-zinc-900 underline-offset-4 hover:underline dark:text-zinc-50"
|
|
47840
|
+
},
|
|
47841
|
+
size: {
|
|
47842
|
+
default: "h-9 px-4 py-2",
|
|
47843
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
47844
|
+
lg: "h-10 rounded-md px-8",
|
|
47845
|
+
icon: "h-9 w-9"
|
|
47846
|
+
}
|
|
47847
|
+
},
|
|
47848
|
+
defaultVariants: {
|
|
47849
|
+
variant: "default",
|
|
47850
|
+
size: "default"
|
|
47851
|
+
}
|
|
47852
|
+
}
|
|
47853
|
+
);
|
|
47854
|
+
var Button2 = React56.forwardRef(
|
|
47855
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
47856
|
+
const Comp = asChild ? Slot : "button";
|
|
47857
|
+
return /* @__PURE__ */ jsx182(
|
|
47858
|
+
Comp,
|
|
47859
|
+
{
|
|
47860
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
47861
|
+
ref,
|
|
47862
|
+
...props
|
|
47863
|
+
}
|
|
47864
|
+
);
|
|
47865
|
+
}
|
|
47866
|
+
);
|
|
47867
|
+
Button2.displayName = "Button";
|
|
47868
|
+
|
|
47869
|
+
// components/ui/scroll-button.tsx
|
|
47870
|
+
import { jsx as jsx183 } from "react/jsx-runtime";
|
|
47871
|
+
function ScrollButton({
|
|
47872
|
+
className,
|
|
47873
|
+
variant = "outline",
|
|
47874
|
+
size = "sm",
|
|
47875
|
+
...props
|
|
47876
|
+
}) {
|
|
47877
|
+
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
|
47878
|
+
return /* @__PURE__ */ jsx183(
|
|
47879
|
+
Button2,
|
|
47880
|
+
{
|
|
47881
|
+
variant,
|
|
47882
|
+
size,
|
|
47883
|
+
className: cn(
|
|
47884
|
+
"h-10 w-10 rounded-full transition-all duration-150 ease-out",
|
|
47885
|
+
!isAtBottom ? "translate-y-0 scale-100 opacity-100" : "pointer-events-none translate-y-4 scale-95 opacity-0",
|
|
47886
|
+
className
|
|
47887
|
+
),
|
|
47888
|
+
onClick: () => scrollToBottom(),
|
|
47889
|
+
...props,
|
|
47890
|
+
children: /* @__PURE__ */ jsx183(ChevronDown, { className: "h-5 w-5" })
|
|
47891
|
+
}
|
|
47892
|
+
);
|
|
47893
|
+
}
|
|
47894
|
+
|
|
47895
|
+
// components/AIChatPanel.tsx
|
|
47896
|
+
import { jsx as jsx184, jsxs as jsxs100 } from "react/jsx-runtime";
|
|
47897
|
+
function genId() {
|
|
47898
|
+
return Math.random().toString(36).slice(2, 10);
|
|
47899
|
+
}
|
|
47900
|
+
var SUGGESTIONS = [
|
|
47901
|
+
"Create a pasta ad",
|
|
47902
|
+
"Design a sale banner",
|
|
47903
|
+
"Make a product showcase",
|
|
47904
|
+
"Help me design"
|
|
47905
|
+
];
|
|
47906
|
+
var AIChatPanel = ({
|
|
47907
|
+
isOpen,
|
|
47908
|
+
onClose,
|
|
47909
|
+
apiKey,
|
|
47910
|
+
excalidrawAPI,
|
|
47911
|
+
geminiApiKey
|
|
47912
|
+
}) => {
|
|
47913
|
+
const [prompt, setPrompt] = useState49("");
|
|
47914
|
+
const [isLoading, setIsLoading] = useState49(false);
|
|
47915
|
+
const [statusText, setStatusText] = useState49("");
|
|
47916
|
+
const [messages, setMessages] = useState49([]);
|
|
47917
|
+
const [sessions, setSessions] = useState49([]);
|
|
47918
|
+
const [currentSessionId, setCurrentSessionId] = useState49(genId);
|
|
47919
|
+
const [historyOpen, setHistoryOpen] = useState49(false);
|
|
47920
|
+
const [historySearch, setHistorySearch] = useState49("");
|
|
47921
|
+
const [error, setError] = useState49(null);
|
|
47922
|
+
const [frameRef, setFrameRef] = useState49(null);
|
|
47923
|
+
const [mentionMenuOpen, setMentionMenuOpen] = useState49(false);
|
|
47924
|
+
const [availableFrames, setAvailableFrames] = useState49([]);
|
|
47925
|
+
const historyRef = useRef49(null);
|
|
47926
|
+
const abortControllerRef = useRef49(null);
|
|
47927
|
+
const mentionMenuRef = useRef49(null);
|
|
47928
|
+
useEffect55(() => {
|
|
47929
|
+
const handler = (e) => {
|
|
47930
|
+
if (historyRef.current && !historyRef.current.contains(e.target)) {
|
|
47931
|
+
setHistoryOpen(false);
|
|
47932
|
+
}
|
|
47933
|
+
if (mentionMenuRef.current && !mentionMenuRef.current.contains(e.target)) {
|
|
47934
|
+
setMentionMenuOpen(false);
|
|
47935
|
+
}
|
|
47936
|
+
};
|
|
47937
|
+
document.addEventListener("mousedown", handler);
|
|
47938
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
47939
|
+
}, []);
|
|
47940
|
+
useEffect55(() => {
|
|
47941
|
+
if (!isOpen) {
|
|
47942
|
+
abortControllerRef.current?.abort();
|
|
47943
|
+
}
|
|
47944
|
+
}, [isOpen]);
|
|
47945
|
+
const currentTitle = messages.length > 0 ? messages[0].content.slice(0, 30) + (messages[0].content.length > 30 ? "\u2026" : "") : "New chat";
|
|
47946
|
+
const saveCurrentSession = useCallback24(() => {
|
|
47947
|
+
if (messages.length === 0) {
|
|
47948
|
+
return;
|
|
47949
|
+
}
|
|
47950
|
+
const title = messages[0].content.slice(0, 40) + (messages[0].content.length > 40 ? "\u2026" : "");
|
|
47951
|
+
setSessions((prev) => {
|
|
47952
|
+
const existing = prev.findIndex((s) => s.id === currentSessionId);
|
|
47953
|
+
const updated = { id: currentSessionId, title, messages };
|
|
47954
|
+
if (existing >= 0) {
|
|
47955
|
+
const next = [...prev];
|
|
47956
|
+
next[existing] = updated;
|
|
47957
|
+
return next;
|
|
47958
|
+
}
|
|
47959
|
+
return [updated, ...prev];
|
|
47960
|
+
});
|
|
47961
|
+
}, [messages, currentSessionId]);
|
|
47962
|
+
const handleNewChat = useCallback24(() => {
|
|
47963
|
+
saveCurrentSession();
|
|
47964
|
+
setMessages([]);
|
|
47965
|
+
setCurrentSessionId(genId());
|
|
47966
|
+
setHistoryOpen(false);
|
|
47967
|
+
setError(null);
|
|
47968
|
+
setPrompt("");
|
|
47969
|
+
setFrameRef(null);
|
|
47970
|
+
setStatusText("");
|
|
47971
|
+
}, [saveCurrentSession]);
|
|
47972
|
+
const handleSwitchSession = useCallback24(
|
|
47973
|
+
(session) => {
|
|
47974
|
+
saveCurrentSession();
|
|
47975
|
+
setMessages(session.messages);
|
|
47976
|
+
setCurrentSessionId(session.id);
|
|
47977
|
+
setHistoryOpen(false);
|
|
47978
|
+
setError(null);
|
|
47979
|
+
},
|
|
47980
|
+
[saveCurrentSession]
|
|
47981
|
+
);
|
|
47982
|
+
const handleAtMention = useCallback24(() => {
|
|
47983
|
+
if (!excalidrawAPI) {
|
|
47984
|
+
return;
|
|
47985
|
+
}
|
|
47986
|
+
setAvailableFrames(listFrames(excalidrawAPI));
|
|
47987
|
+
setMentionMenuOpen(true);
|
|
47988
|
+
}, [excalidrawAPI]);
|
|
47989
|
+
const handlePickFrame = useCallback24(
|
|
47990
|
+
async (frame) => {
|
|
47991
|
+
if (!excalidrawAPI) {
|
|
47992
|
+
return;
|
|
47993
|
+
}
|
|
47994
|
+
setMentionMenuOpen(false);
|
|
47995
|
+
const ctx = getFrameContext(excalidrawAPI, frame.id);
|
|
47996
|
+
if (!ctx) {
|
|
47997
|
+
return;
|
|
47998
|
+
}
|
|
47999
|
+
const screenshot = await captureFrameScreenshot(
|
|
48000
|
+
excalidrawAPI,
|
|
48001
|
+
frame.id
|
|
48002
|
+
);
|
|
48003
|
+
setFrameRef({
|
|
48004
|
+
frameId: frame.id,
|
|
48005
|
+
label: ctx.frameInfo.name,
|
|
48006
|
+
serialized: ctx.serialized,
|
|
48007
|
+
screenshot: screenshot ?? void 0
|
|
48008
|
+
});
|
|
48009
|
+
},
|
|
48010
|
+
[excalidrawAPI]
|
|
48011
|
+
);
|
|
48012
|
+
const handleRemoveRef = useCallback24(() => {
|
|
48013
|
+
setFrameRef(null);
|
|
48014
|
+
}, []);
|
|
48015
|
+
const handleSend = useCallback24(async () => {
|
|
48016
|
+
const text = prompt.trim();
|
|
48017
|
+
const normalizedKey = apiKey.trim();
|
|
48018
|
+
if (!text || isLoading) {
|
|
48019
|
+
return;
|
|
48020
|
+
}
|
|
48021
|
+
if (!normalizedKey) {
|
|
48022
|
+
setError(
|
|
48023
|
+
"No OpenRouter API key. Set VITE_APP_OPENROUTER_API_KEY in .env or pass the key via the `apiKey` prop."
|
|
48024
|
+
);
|
|
48025
|
+
return;
|
|
48026
|
+
}
|
|
48027
|
+
const userMsg = {
|
|
48028
|
+
id: genId(),
|
|
48029
|
+
role: "user",
|
|
48030
|
+
content: text,
|
|
48031
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
48032
|
+
};
|
|
48033
|
+
const nextMessages = [...messages, userMsg];
|
|
48034
|
+
setMessages(nextMessages);
|
|
48035
|
+
setPrompt("");
|
|
48036
|
+
const controller = new AbortController();
|
|
48037
|
+
abortControllerRef.current = controller;
|
|
48038
|
+
setIsLoading(true);
|
|
48039
|
+
setError(null);
|
|
48040
|
+
setStatusText("Thinking\u2026");
|
|
48041
|
+
const capturedFrameRef = frameRef;
|
|
48042
|
+
setFrameRef(null);
|
|
48043
|
+
if (!excalidrawAPI) {
|
|
48044
|
+
try {
|
|
48045
|
+
const reply = await callPlainChatAPI(
|
|
48046
|
+
nextMessages,
|
|
48047
|
+
normalizedKey,
|
|
48048
|
+
controller.signal
|
|
48049
|
+
);
|
|
48050
|
+
setMessages((prev) => [
|
|
48051
|
+
...prev,
|
|
48052
|
+
{
|
|
48053
|
+
id: genId(),
|
|
48054
|
+
role: "assistant",
|
|
48055
|
+
content: reply,
|
|
48056
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
48057
|
+
}
|
|
48058
|
+
]);
|
|
48059
|
+
} catch (err) {
|
|
48060
|
+
if (err instanceof Error && err.name !== "AbortError") {
|
|
48061
|
+
setError(err.message);
|
|
48062
|
+
}
|
|
48063
|
+
} finally {
|
|
48064
|
+
abortControllerRef.current = null;
|
|
48065
|
+
setIsLoading(false);
|
|
48066
|
+
setStatusText("");
|
|
48067
|
+
}
|
|
48068
|
+
return;
|
|
48069
|
+
}
|
|
48070
|
+
try {
|
|
48071
|
+
const result = await runAgentLoop({
|
|
48072
|
+
userMessages: nextMessages.map((m) => ({
|
|
48073
|
+
role: m.role,
|
|
48074
|
+
content: m.content
|
|
48075
|
+
})),
|
|
48076
|
+
elementContext: capturedFrameRef?.serialized,
|
|
48077
|
+
frameScreenshot: capturedFrameRef?.screenshot,
|
|
48078
|
+
apiKey: normalizedKey,
|
|
48079
|
+
toolCtx: {
|
|
48080
|
+
excalidrawAPI,
|
|
48081
|
+
geminiApiKey: geminiApiKey || "",
|
|
48082
|
+
signal: controller.signal
|
|
48083
|
+
},
|
|
48084
|
+
onUpdate: (update) => {
|
|
48085
|
+
if (update.type !== "final") {
|
|
48086
|
+
setStatusText(update.message);
|
|
48087
|
+
}
|
|
48088
|
+
},
|
|
48089
|
+
signal: controller.signal
|
|
48090
|
+
});
|
|
48091
|
+
setMessages((prev) => [
|
|
48092
|
+
...prev,
|
|
48093
|
+
{
|
|
48094
|
+
id: genId(),
|
|
48095
|
+
role: "assistant",
|
|
48096
|
+
content: result.reply,
|
|
48097
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
48098
|
+
toolActions: result.toolActions.length > 0 ? result.toolActions : void 0
|
|
48099
|
+
}
|
|
48100
|
+
]);
|
|
48101
|
+
} catch (err) {
|
|
48102
|
+
if (err instanceof Error && err.name !== "AbortError") {
|
|
48103
|
+
setError(err.message);
|
|
48104
|
+
}
|
|
48105
|
+
} finally {
|
|
48106
|
+
abortControllerRef.current = null;
|
|
48107
|
+
setIsLoading(false);
|
|
48108
|
+
setStatusText("");
|
|
48109
|
+
}
|
|
48110
|
+
}, [
|
|
48111
|
+
prompt,
|
|
48112
|
+
isLoading,
|
|
48113
|
+
messages,
|
|
48114
|
+
apiKey,
|
|
48115
|
+
excalidrawAPI,
|
|
48116
|
+
geminiApiKey,
|
|
48117
|
+
frameRef
|
|
48118
|
+
]);
|
|
48119
|
+
const handleStop = useCallback24(() => {
|
|
48120
|
+
abortControllerRef.current?.abort();
|
|
48121
|
+
}, []);
|
|
48122
|
+
const handleChip = useCallback24((chip) => {
|
|
48123
|
+
setPrompt(chip);
|
|
48124
|
+
}, []);
|
|
48125
|
+
const filteredSessions = sessions.filter(
|
|
48126
|
+
(s) => historySearch ? s.title.toLowerCase().includes(historySearch.toLowerCase()) : true
|
|
48127
|
+
);
|
|
48128
|
+
if (!isOpen) {
|
|
48129
|
+
return null;
|
|
48130
|
+
}
|
|
48131
|
+
return /* @__PURE__ */ jsx184(
|
|
48132
|
+
"div",
|
|
48133
|
+
{
|
|
48134
|
+
className: "acp",
|
|
48135
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
48136
|
+
onClick: (e) => e.stopPropagation(),
|
|
48137
|
+
children: /* @__PURE__ */ jsxs100("div", { className: "acp-panel", children: [
|
|
48138
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-header", ref: historyRef, children: [
|
|
48139
|
+
/* @__PURE__ */ jsxs100(
|
|
48140
|
+
"button",
|
|
48141
|
+
{
|
|
48142
|
+
className: "acp-title-btn",
|
|
48143
|
+
onClick: () => setHistoryOpen((v) => !v),
|
|
48144
|
+
title: "Chat history",
|
|
48145
|
+
children: [
|
|
48146
|
+
/* @__PURE__ */ jsx184("span", { children: currentTitle }),
|
|
48147
|
+
/* @__PURE__ */ jsx184(ChevronDown2, { size: 13 })
|
|
48148
|
+
]
|
|
48149
|
+
}
|
|
48150
|
+
),
|
|
48151
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-header-right", children: [
|
|
48152
|
+
/* @__PURE__ */ jsx184(
|
|
48153
|
+
"button",
|
|
48154
|
+
{
|
|
48155
|
+
className: "acp-icon-btn",
|
|
48156
|
+
onClick: handleNewChat,
|
|
48157
|
+
title: "New chat",
|
|
48158
|
+
children: /* @__PURE__ */ jsx184(Plus, { size: 15 })
|
|
48159
|
+
}
|
|
48160
|
+
),
|
|
48161
|
+
/* @__PURE__ */ jsx184("button", { className: "acp-icon-btn", onClick: onClose, title: "Close", children: /* @__PURE__ */ jsx184(X2, { size: 15 }) })
|
|
48162
|
+
] }),
|
|
48163
|
+
historyOpen && /* @__PURE__ */ jsxs100("div", { className: "acp-history-dropdown", children: [
|
|
48164
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-history-search", children: [
|
|
48165
|
+
/* @__PURE__ */ jsx184(Search, { size: 13 }),
|
|
48166
|
+
/* @__PURE__ */ jsx184(
|
|
48167
|
+
"input",
|
|
48168
|
+
{
|
|
48169
|
+
type: "search",
|
|
48170
|
+
placeholder: "Search chats\u2026",
|
|
48171
|
+
value: historySearch,
|
|
48172
|
+
onChange: (e) => setHistorySearch(e.target.value),
|
|
48173
|
+
autoFocus: true
|
|
48174
|
+
}
|
|
48175
|
+
)
|
|
48176
|
+
] }),
|
|
48177
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-history-list", children: [
|
|
48178
|
+
/* @__PURE__ */ jsxs100(
|
|
48179
|
+
"button",
|
|
48180
|
+
{
|
|
48181
|
+
className: "acp-history-item acp-history-item--new",
|
|
48182
|
+
onClick: handleNewChat,
|
|
48183
|
+
children: [
|
|
48184
|
+
/* @__PURE__ */ jsx184(Plus, { size: 13 }),
|
|
48185
|
+
/* @__PURE__ */ jsx184("span", { children: "New chat" })
|
|
48186
|
+
]
|
|
48187
|
+
}
|
|
48188
|
+
),
|
|
48189
|
+
filteredSessions.map((session) => /* @__PURE__ */ jsxs100(
|
|
48190
|
+
"button",
|
|
48191
|
+
{
|
|
48192
|
+
className: "acp-history-item",
|
|
48193
|
+
onClick: () => handleSwitchSession(session),
|
|
48194
|
+
children: [
|
|
48195
|
+
/* @__PURE__ */ jsx184(MessageSquare, { size: 13 }),
|
|
48196
|
+
/* @__PURE__ */ jsx184("span", { children: session.title })
|
|
48197
|
+
]
|
|
48198
|
+
},
|
|
48199
|
+
session.id
|
|
48200
|
+
)),
|
|
48201
|
+
filteredSessions.length === 0 && historySearch && /* @__PURE__ */ jsx184("div", { className: "acp-history-empty", children: "No matching chats" })
|
|
48202
|
+
] })
|
|
48203
|
+
] })
|
|
48204
|
+
] }),
|
|
48205
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-messages-wrap", children: messages.length === 0 && !isLoading ? /* @__PURE__ */ jsxs100("div", { className: "acp-empty", children: [
|
|
48206
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-empty-icon", children: /* @__PURE__ */ jsx184(MessageSquare, { size: 22 }) }),
|
|
48207
|
+
/* @__PURE__ */ jsxs100("div", { children: [
|
|
48208
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-empty-title", children: "AI Ad Designer" }),
|
|
48209
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-empty-sub", children: "Describe an ad and I'll create it on the canvas. Use @ to reference selected elements." })
|
|
48210
|
+
] }),
|
|
48211
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-chips", children: SUGGESTIONS.map((chip) => /* @__PURE__ */ jsx184(
|
|
48212
|
+
"button",
|
|
48213
|
+
{
|
|
48214
|
+
className: "acp-chip",
|
|
48215
|
+
onClick: () => handleChip(chip),
|
|
48216
|
+
children: chip
|
|
48217
|
+
},
|
|
48218
|
+
chip
|
|
48219
|
+
)) })
|
|
48220
|
+
] }) : /* @__PURE__ */ jsxs100(ChatContainerRoot, { className: "acp-chat-root", children: [
|
|
48221
|
+
/* @__PURE__ */ jsxs100(ChatContainerContent, { className: "acp-chat-content", children: [
|
|
48222
|
+
messages.map((msg, index) => {
|
|
48223
|
+
const isAssistant = msg.role === "assistant";
|
|
48224
|
+
const isLast = index === messages.length - 1;
|
|
48225
|
+
return /* @__PURE__ */ jsx184(
|
|
48226
|
+
Message,
|
|
48227
|
+
{
|
|
48228
|
+
className: `acp-msg ${isAssistant ? "acp-msg--assistant" : "acp-msg--user"}`,
|
|
48229
|
+
children: isAssistant ? /* @__PURE__ */ jsxs100("div", { className: "acp-msg-inner", children: [
|
|
48230
|
+
msg.toolActions && msg.toolActions.length > 0 && /* @__PURE__ */ jsx184(ToolActionsDisplay, { actions: msg.toolActions }),
|
|
48231
|
+
/* @__PURE__ */ jsx184(
|
|
48232
|
+
MessageContent,
|
|
48233
|
+
{
|
|
48234
|
+
markdown: true,
|
|
48235
|
+
className: "acp-content-assistant",
|
|
48236
|
+
children: msg.content
|
|
48237
|
+
}
|
|
48238
|
+
),
|
|
48239
|
+
/* @__PURE__ */ jsxs100(
|
|
48240
|
+
MessageActions,
|
|
48241
|
+
{
|
|
48242
|
+
className: `acp-msg-actions${isLast ? " acp-msg-actions--visible" : ""}`,
|
|
48243
|
+
children: [
|
|
48244
|
+
/* @__PURE__ */ jsx184(MessageAction, { tooltip: "Copy", delayDuration: 100, children: /* @__PURE__ */ jsx184("button", { className: "acp-action-btn", children: /* @__PURE__ */ jsx184(Copy, { size: 14 }) }) }),
|
|
48245
|
+
/* @__PURE__ */ jsx184(MessageAction, { tooltip: "Upvote", delayDuration: 100, children: /* @__PURE__ */ jsx184("button", { className: "acp-action-btn", children: /* @__PURE__ */ jsx184(ThumbsUp, { size: 14 }) }) }),
|
|
48246
|
+
/* @__PURE__ */ jsx184(
|
|
48247
|
+
MessageAction,
|
|
48248
|
+
{
|
|
48249
|
+
tooltip: "Downvote",
|
|
48250
|
+
delayDuration: 100,
|
|
48251
|
+
children: /* @__PURE__ */ jsx184("button", { className: "acp-action-btn", children: /* @__PURE__ */ jsx184(ThumbsDown, { size: 14 }) })
|
|
48252
|
+
}
|
|
48253
|
+
)
|
|
48254
|
+
]
|
|
48255
|
+
}
|
|
48256
|
+
)
|
|
48257
|
+
] }) : /* @__PURE__ */ jsxs100("div", { className: "acp-msg-inner", children: [
|
|
48258
|
+
/* @__PURE__ */ jsx184(MessageContent, { className: "acp-content-user", children: msg.content }),
|
|
48259
|
+
/* @__PURE__ */ jsxs100(MessageActions, { className: "acp-msg-actions", children: [
|
|
48260
|
+
/* @__PURE__ */ jsx184(MessageAction, { tooltip: "Edit", delayDuration: 100, children: /* @__PURE__ */ jsx184("button", { className: "acp-action-btn", children: /* @__PURE__ */ jsx184(Pencil, { size: 14 }) }) }),
|
|
48261
|
+
/* @__PURE__ */ jsx184(MessageAction, { tooltip: "Delete", delayDuration: 100, children: /* @__PURE__ */ jsx184("button", { className: "acp-action-btn", children: /* @__PURE__ */ jsx184(Trash, { size: 14 }) }) }),
|
|
48262
|
+
/* @__PURE__ */ jsx184(MessageAction, { tooltip: "Copy", delayDuration: 100, children: /* @__PURE__ */ jsx184("button", { className: "acp-action-btn", children: /* @__PURE__ */ jsx184(Copy, { size: 14 }) }) })
|
|
48263
|
+
] })
|
|
48264
|
+
] })
|
|
48265
|
+
},
|
|
48266
|
+
msg.id
|
|
48267
|
+
);
|
|
48268
|
+
}),
|
|
48269
|
+
isLoading && /* @__PURE__ */ jsx184(Message, { className: "acp-msg acp-msg--assistant", children: /* @__PURE__ */ jsx184("div", { className: "acp-msg-inner", children: /* @__PURE__ */ jsxs100("div", { className: "acp-status-indicator", children: [
|
|
48270
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-loading-dots", children: [
|
|
48271
|
+
/* @__PURE__ */ jsx184("span", {}),
|
|
48272
|
+
/* @__PURE__ */ jsx184("span", {}),
|
|
48273
|
+
/* @__PURE__ */ jsx184("span", {})
|
|
48274
|
+
] }),
|
|
48275
|
+
statusText && /* @__PURE__ */ jsx184("span", { className: "acp-status-text", children: statusText })
|
|
48276
|
+
] }) }) })
|
|
48277
|
+
] }),
|
|
48278
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-scroll-anchor", children: /* @__PURE__ */ jsx184(ScrollButton, { className: "acp-scroll-btn" }) })
|
|
48279
|
+
] }) }),
|
|
48280
|
+
error && /* @__PURE__ */ jsx184("div", { className: "acp-error", children: error }),
|
|
48281
|
+
frameRef && /* @__PURE__ */ jsx184("div", { className: "acp-ref-area", children: /* @__PURE__ */ jsxs100("div", { className: "acp-ref-pill", children: [
|
|
48282
|
+
/* @__PURE__ */ jsx184(AtSign, { size: 12 }),
|
|
48283
|
+
/* @__PURE__ */ jsx184("span", { children: frameRef.label }),
|
|
48284
|
+
/* @__PURE__ */ jsx184(
|
|
48285
|
+
"button",
|
|
48286
|
+
{
|
|
48287
|
+
className: "acp-ref-remove",
|
|
48288
|
+
onClick: handleRemoveRef,
|
|
48289
|
+
title: "Remove reference",
|
|
48290
|
+
children: /* @__PURE__ */ jsx184(X2, { size: 10 })
|
|
48291
|
+
}
|
|
48292
|
+
)
|
|
48293
|
+
] }) }),
|
|
48294
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-input-area", children: /* @__PURE__ */ jsxs100("div", { className: "acp-input-wrapper", ref: mentionMenuRef, children: [
|
|
48295
|
+
mentionMenuOpen && /* @__PURE__ */ jsx184("div", { className: "acp-mention-menu", children: availableFrames.length > 0 ? availableFrames.map((f) => /* @__PURE__ */ jsxs100(
|
|
48296
|
+
"button",
|
|
48297
|
+
{
|
|
48298
|
+
className: "acp-mention-item",
|
|
48299
|
+
onClick: () => handlePickFrame(f),
|
|
48300
|
+
children: [
|
|
48301
|
+
/* @__PURE__ */ jsx184(AtSign, { size: 14 }),
|
|
48302
|
+
/* @__PURE__ */ jsxs100("span", { children: [
|
|
48303
|
+
f.name,
|
|
48304
|
+
" ",
|
|
48305
|
+
/* @__PURE__ */ jsxs100("span", { className: "acp-mention-dim", children: [
|
|
48306
|
+
"(",
|
|
48307
|
+
f.width,
|
|
48308
|
+
"x",
|
|
48309
|
+
f.height,
|
|
48310
|
+
", ",
|
|
48311
|
+
f.childCount,
|
|
48312
|
+
" items)"
|
|
48313
|
+
] })
|
|
48314
|
+
] })
|
|
48315
|
+
]
|
|
48316
|
+
},
|
|
48317
|
+
f.id
|
|
48318
|
+
)) : /* @__PURE__ */ jsx184("div", { className: "acp-mention-empty", children: "No frames on the canvas. Create a frame first, then use @ to reference it." }) }),
|
|
48319
|
+
/* @__PURE__ */ jsxs100(
|
|
48320
|
+
PromptInput,
|
|
48321
|
+
{
|
|
48322
|
+
isLoading,
|
|
48323
|
+
value: prompt,
|
|
48324
|
+
onValueChange: setPrompt,
|
|
48325
|
+
onSubmit: handleSend,
|
|
48326
|
+
className: "acp-prompt-input",
|
|
48327
|
+
children: [
|
|
48328
|
+
/* @__PURE__ */ jsx184(
|
|
48329
|
+
PromptInputTextarea,
|
|
48330
|
+
{
|
|
48331
|
+
placeholder: excalidrawAPI ? "Describe an ad to create\u2026" : "Ask anything",
|
|
48332
|
+
className: "acp-textarea"
|
|
48333
|
+
}
|
|
48334
|
+
),
|
|
48335
|
+
/* @__PURE__ */ jsxs100(PromptInputActions, { className: "acp-actions-bar", children: [
|
|
48336
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-actions-left", children: [
|
|
48337
|
+
excalidrawAPI && /* @__PURE__ */ jsx184(PromptInputAction, { tooltip: "Reference selection (@)", children: /* @__PURE__ */ jsx184(
|
|
48338
|
+
"button",
|
|
48339
|
+
{
|
|
48340
|
+
className: "acp-pill-btn acp-pill-btn--icon",
|
|
48341
|
+
onClick: handleAtMention,
|
|
48342
|
+
children: /* @__PURE__ */ jsx184(AtSign, { size: 16 })
|
|
48343
|
+
}
|
|
48344
|
+
) }),
|
|
48345
|
+
/* @__PURE__ */ jsx184(PromptInputAction, { tooltip: "Search", children: /* @__PURE__ */ jsxs100("button", { className: "acp-pill-btn", children: [
|
|
48346
|
+
/* @__PURE__ */ jsx184(Globe, { size: 15 }),
|
|
48347
|
+
/* @__PURE__ */ jsx184("span", { children: "Search" })
|
|
48348
|
+
] }) }),
|
|
48349
|
+
/* @__PURE__ */ jsx184(PromptInputAction, { tooltip: "More", children: /* @__PURE__ */ jsx184("button", { className: "acp-pill-btn acp-pill-btn--icon", children: /* @__PURE__ */ jsx184(MoreHorizontal, { size: 16 }) }) })
|
|
48350
|
+
] }),
|
|
48351
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-actions-right", children: [
|
|
48352
|
+
/* @__PURE__ */ jsx184(PromptInputAction, { tooltip: "Voice input", children: /* @__PURE__ */ jsx184("button", { className: "acp-pill-btn acp-pill-btn--icon", children: /* @__PURE__ */ jsx184(Mic, { size: 16 }) }) }),
|
|
48353
|
+
/* @__PURE__ */ jsx184(
|
|
48354
|
+
"button",
|
|
48355
|
+
{
|
|
48356
|
+
className: "acp-send-btn",
|
|
48357
|
+
onClick: isLoading ? handleStop : handleSend,
|
|
48358
|
+
disabled: !isLoading && !prompt.trim(),
|
|
48359
|
+
title: isLoading ? "Stop" : "Send",
|
|
48360
|
+
children: isLoading ? /* @__PURE__ */ jsx184("span", { className: "acp-stop-square" }) : /* @__PURE__ */ jsx184(ArrowUp3, { size: 16 })
|
|
48361
|
+
}
|
|
48362
|
+
)
|
|
48363
|
+
] })
|
|
48364
|
+
] })
|
|
48365
|
+
]
|
|
48366
|
+
}
|
|
48367
|
+
)
|
|
48368
|
+
] }) })
|
|
48369
|
+
] })
|
|
48370
|
+
}
|
|
48371
|
+
);
|
|
48372
|
+
};
|
|
48373
|
+
function ToolActionsDisplay({ actions: actions2 }) {
|
|
48374
|
+
const [expanded, setExpanded] = useState49(false);
|
|
48375
|
+
return /* @__PURE__ */ jsxs100("div", { className: "acp-tool-actions", children: [
|
|
48376
|
+
/* @__PURE__ */ jsxs100(
|
|
48377
|
+
"button",
|
|
48378
|
+
{
|
|
48379
|
+
className: "acp-tool-actions-toggle",
|
|
48380
|
+
onClick: () => setExpanded((v) => !v),
|
|
48381
|
+
children: [
|
|
48382
|
+
/* @__PURE__ */ jsx184(Wrench, { size: 13 }),
|
|
48383
|
+
/* @__PURE__ */ jsxs100("span", { children: [
|
|
48384
|
+
actions2.length,
|
|
48385
|
+
" action",
|
|
48386
|
+
actions2.length !== 1 ? "s" : "",
|
|
48387
|
+
" performed"
|
|
48388
|
+
] }),
|
|
48389
|
+
/* @__PURE__ */ jsx184(
|
|
48390
|
+
ChevronRight,
|
|
48391
|
+
{
|
|
48392
|
+
size: 12,
|
|
48393
|
+
className: `acp-tool-chevron ${expanded ? "acp-tool-chevron--open" : ""}`
|
|
48394
|
+
}
|
|
48395
|
+
)
|
|
48396
|
+
]
|
|
48397
|
+
}
|
|
48398
|
+
),
|
|
48399
|
+
expanded && /* @__PURE__ */ jsx184("div", { className: "acp-tool-actions-list", children: actions2.map((action, i) => /* @__PURE__ */ jsxs100(
|
|
48400
|
+
"div",
|
|
48401
|
+
{
|
|
48402
|
+
className: `acp-tool-action-item ${action.result.success ? "acp-tool-action-item--ok" : "acp-tool-action-item--err"}`,
|
|
48403
|
+
children: [
|
|
48404
|
+
/* @__PURE__ */ jsx184("span", { className: "acp-tool-action-dot" }),
|
|
48405
|
+
/* @__PURE__ */ jsx184("span", { children: action.result.statusMessage })
|
|
48406
|
+
]
|
|
48407
|
+
},
|
|
48408
|
+
i
|
|
48409
|
+
)) })
|
|
48410
|
+
] });
|
|
48411
|
+
}
|
|
48412
|
+
async function callPlainChatAPI(messages, apiKey, signal) {
|
|
48413
|
+
const response = await fetch(
|
|
48414
|
+
"https://openrouter.ai/api/v1/chat/completions",
|
|
48415
|
+
{
|
|
48416
|
+
method: "POST",
|
|
48417
|
+
signal,
|
|
48418
|
+
headers: {
|
|
48419
|
+
Authorization: `Bearer ${apiKey}`,
|
|
48420
|
+
"Content-Type": "application/json"
|
|
48421
|
+
},
|
|
48422
|
+
body: JSON.stringify({
|
|
48423
|
+
model: "openai/gpt-4.1-mini",
|
|
48424
|
+
messages: messages.map((m) => ({ role: m.role, content: m.content }))
|
|
48425
|
+
})
|
|
48426
|
+
}
|
|
48427
|
+
);
|
|
48428
|
+
if (!response.ok) {
|
|
48429
|
+
let message = `OpenRouter error ${response.status}`;
|
|
48430
|
+
try {
|
|
48431
|
+
const err = await response.json();
|
|
48432
|
+
if (err?.error?.message) {
|
|
48433
|
+
message = err.error.message;
|
|
48434
|
+
}
|
|
48435
|
+
} catch {
|
|
48436
|
+
}
|
|
48437
|
+
throw new Error(message);
|
|
48438
|
+
}
|
|
48439
|
+
const data = await response.json();
|
|
48440
|
+
const content = data?.choices?.[0]?.message?.content;
|
|
48441
|
+
if (!content) {
|
|
48442
|
+
throw new Error("No response returned by the API.");
|
|
48443
|
+
}
|
|
48444
|
+
return content;
|
|
48445
|
+
}
|
|
48446
|
+
|
|
48447
|
+
// index.tsx
|
|
48448
|
+
import { jsx as jsx185 } from "react/jsx-runtime";
|
|
46522
48449
|
polyfill_default();
|
|
46523
48450
|
var ExcalidrawBase = (props) => {
|
|
46524
48451
|
const {
|
|
@@ -46574,7 +48501,7 @@ var ExcalidrawBase = (props) => {
|
|
|
46574
48501
|
if (UIOptions.canvasActions.toggleTheme === null && typeof theme === "undefined") {
|
|
46575
48502
|
UIOptions.canvasActions.toggleTheme = true;
|
|
46576
48503
|
}
|
|
46577
|
-
|
|
48504
|
+
useEffect56(() => {
|
|
46578
48505
|
const importPolyfill = async () => {
|
|
46579
48506
|
await import("canvas-roundrect-polyfill");
|
|
46580
48507
|
};
|
|
@@ -46591,7 +48518,7 @@ var ExcalidrawBase = (props) => {
|
|
|
46591
48518
|
document.removeEventListener("touchmove", handleTouchMove);
|
|
46592
48519
|
};
|
|
46593
48520
|
}, []);
|
|
46594
|
-
return /* @__PURE__ */
|
|
48521
|
+
return /* @__PURE__ */ jsx185(EditorJotaiProvider, { store: editorJotaiStore, children: /* @__PURE__ */ jsx185(InitializeApp, { langCode, theme, children: /* @__PURE__ */ jsx185(
|
|
46595
48522
|
App_default,
|
|
46596
48523
|
{
|
|
46597
48524
|
onChange,
|
|
@@ -46670,9 +48597,10 @@ var areEqual5 = (prevProps, nextProps) => {
|
|
|
46670
48597
|
});
|
|
46671
48598
|
return isUIOptionsSame && isShallowEqual10(prev, next);
|
|
46672
48599
|
};
|
|
46673
|
-
var Excalidraw =
|
|
48600
|
+
var Excalidraw = React58.memo(ExcalidrawBase, areEqual5);
|
|
46674
48601
|
Excalidraw.displayName = "Excalidraw";
|
|
46675
48602
|
export {
|
|
48603
|
+
AIChatPanel,
|
|
46676
48604
|
Button,
|
|
46677
48605
|
CaptureUpdateAction39 as CaptureUpdateAction,
|
|
46678
48606
|
CommandPalette,
|