@orangecatai/adgen-canvas 0.0.2 → 0.0.4
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-UV7ECD7A.js → chunk-3FGOYDLK.js} +3 -3
- package/dist/dev/{chunk-X2WWSILD.js → chunk-IRIUFXMO.js} +2 -2
- package/dist/dev/data/{image-H4O52A73.js → image-HH4XNQRO.js} +3 -3
- package/dist/dev/index.css +880 -1
- package/dist/dev/index.css.map +3 -3
- package/dist/dev/index.js +2970 -232
- 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-LUZI7MFZ.js} +2 -2
- package/dist/prod/{chunk-O3WJMHIE.js → chunk-SG4RCQVC.js} +1 -1
- package/dist/prod/data/image-J2ZJZU4A.js +1 -0
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +84 -29
- package/dist/prod/subset-shared.chunk.js +1 -1
- package/dist/prod/subset-worker.chunk.js +1 -1
- package/dist/types/excalidraw/actions/actionAddToLibrary.d.ts +1 -1
- package/dist/types/excalidraw/actions/actionCanvas.d.ts +177 -0
- package/dist/types/excalidraw/actions/actionClipboard.d.ts +7 -7
- package/dist/types/excalidraw/actions/actionCropEditor.d.ts +2 -2
- package/dist/types/excalidraw/actions/actionDuplicateSelection.d.ts +2 -2
- package/dist/types/excalidraw/actions/actionElementLink.d.ts +6 -6
- package/dist/types/excalidraw/actions/actionEmbeddable.d.ts +1 -1
- package/dist/types/excalidraw/actions/actionLinearEditor.d.ts +7 -7
- package/dist/types/excalidraw/actions/actionLink.d.ts +3 -3
- package/dist/types/excalidraw/actions/actionMenu.d.ts +1 -1
- package/dist/types/excalidraw/actions/actionSelectAll.d.ts +4 -4
- package/dist/types/excalidraw/actions/actionStyles.d.ts +2 -2
- package/dist/types/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +4 -4
- package/dist/types/excalidraw/actions/actionToggleShapeSwitch.d.ts +3 -3
- package/dist/types/excalidraw/actions/actionToggleStats.d.ts +2 -2
- package/dist/types/excalidraw/actions/actionToggleViewMode.d.ts +4 -4
- package/dist/types/excalidraw/actions/actionToggleZenMode.d.ts +4 -4
- package/dist/types/excalidraw/actions/actionZindex.d.ts +8 -8
- package/dist/types/excalidraw/actions/index.d.ts +1 -1
- package/dist/types/excalidraw/actions/types.d.ts +1 -1
- package/dist/types/excalidraw/components/AIChatPanel.d.ts +49 -3
- package/dist/types/excalidraw/components/Actions.d.ts +3 -0
- package/dist/types/excalidraw/components/DefaultSidebar.d.ts +1 -1
- package/dist/types/excalidraw/components/ImageGeneratorPanel.d.ts +6 -1
- package/dist/types/excalidraw/components/ImageQuickEditPanel.d.ts +6 -1
- package/dist/types/excalidraw/components/Sidebar/Sidebar.d.ts +2 -2
- package/dist/types/excalidraw/components/ai-chat/agentLoop.d.ts +13 -0
- package/dist/types/excalidraw/components/ai-chat/audioUtils.d.ts +10 -0
- package/dist/types/excalidraw/index.d.ts +5 -0
- package/dist/types/excalidraw/types.d.ts +14 -0
- package/dist/types/excalidraw/utils/openRouterApiKey.d.ts +1 -0
- package/package.json +1 -1
- package/dist/prod/data/image-43FV5SMF.js +0 -1
- /package/dist/dev/{chunk-UV7ECD7A.js.map → chunk-3FGOYDLK.js.map} +0 -0
- /package/dist/dev/{chunk-X2WWSILD.js.map → chunk-IRIUFXMO.js.map} +0 -0
- /package/dist/dev/data/{image-H4O52A73.js.map → image-HH4XNQRO.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-3FGOYDLK.js";
|
|
70
70
|
import {
|
|
71
71
|
define_import_meta_env_default
|
|
72
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-IRIUFXMO.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
|
|
@@ -158,7 +158,7 @@ import {
|
|
|
158
158
|
isWritableElement as isWritableElement5,
|
|
159
159
|
sceneCoordsToViewportCoords as sceneCoordsToViewportCoords10,
|
|
160
160
|
tupleToCoors,
|
|
161
|
-
viewportCoordsToSceneCoords as
|
|
161
|
+
viewportCoordsToSceneCoords as viewportCoordsToSceneCoords4,
|
|
162
162
|
wrapEvent as wrapEvent2,
|
|
163
163
|
updateObject as updateObject2,
|
|
164
164
|
updateActiveTool as updateActiveTool7,
|
|
@@ -197,7 +197,7 @@ import {
|
|
|
197
197
|
import {
|
|
198
198
|
getObservedAppState,
|
|
199
199
|
getCommonBounds as getCommonBounds11,
|
|
200
|
-
getElementAbsoluteCoords as
|
|
200
|
+
getElementAbsoluteCoords as getElementAbsoluteCoords12,
|
|
201
201
|
bindOrUnbindBindingElements as bindOrUnbindBindingElements2,
|
|
202
202
|
fixBindingsAfterDeletion as fixBindingsAfterDeletion2,
|
|
203
203
|
getHoveredElementForBinding as getHoveredElementForBinding2,
|
|
@@ -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,
|
|
@@ -7687,9 +7687,14 @@ import {
|
|
|
7687
7687
|
ZOOM_STEP,
|
|
7688
7688
|
updateActiveTool as updateActiveTool2,
|
|
7689
7689
|
CODES as CODES2,
|
|
7690
|
-
KEYS as KEYS12
|
|
7690
|
+
KEYS as KEYS12,
|
|
7691
|
+
viewportCoordsToSceneCoords
|
|
7691
7692
|
} from "@orangecatai/common";
|
|
7692
|
-
import {
|
|
7693
|
+
import {
|
|
7694
|
+
getElementAbsoluteCoords,
|
|
7695
|
+
getNonDeletedElements as getNonDeletedElements5,
|
|
7696
|
+
getVisibleElements
|
|
7697
|
+
} from "@orangecatai/element";
|
|
7693
7698
|
import { newElementWith as newElementWith3 } from "@orangecatai/element";
|
|
7694
7699
|
import { getCommonBounds } from "@orangecatai/element";
|
|
7695
7700
|
import { CaptureUpdateAction as CaptureUpdateAction6 } from "@orangecatai/element";
|
|
@@ -8070,6 +8075,77 @@ var zoomValueToFitBoundsOnViewport = (bounds, viewportDimensions, viewportZoomFa
|
|
|
8070
8075
|
const adjustedZoomValue = smallestZoomValue * clamp(viewportZoomFactor, 0.1, 1);
|
|
8071
8076
|
return Math.min(adjustedZoomValue, 1);
|
|
8072
8077
|
};
|
|
8078
|
+
var COMFORTABLE_VIEWPORT_ZOOM_FACTOR = 0.8;
|
|
8079
|
+
var intersectsBounds = (a, b) => a[0] <= b[2] && a[2] >= b[0] && a[1] <= b[3] && a[3] >= b[1];
|
|
8080
|
+
var expandBounds = (bounds, margin) => [
|
|
8081
|
+
bounds[0] - margin,
|
|
8082
|
+
bounds[1] - margin,
|
|
8083
|
+
bounds[2] + margin,
|
|
8084
|
+
bounds[3] + margin
|
|
8085
|
+
];
|
|
8086
|
+
var getBoundsCenterDistance = (bounds, point) => {
|
|
8087
|
+
const centerX = (bounds[0] + bounds[2]) / 2;
|
|
8088
|
+
const centerY = (bounds[1] + bounds[3]) / 2;
|
|
8089
|
+
return Math.hypot(centerX - point.x, centerY - point.y);
|
|
8090
|
+
};
|
|
8091
|
+
var toSceneBounds = (bounds) => [bounds[0], bounds[1], bounds[2], bounds[3]];
|
|
8092
|
+
var getNearestContentCluster = (elements, appState) => {
|
|
8093
|
+
const nonDeletedElements = getNonDeletedElements5(elements);
|
|
8094
|
+
if (!nonDeletedElements.length) {
|
|
8095
|
+
return [];
|
|
8096
|
+
}
|
|
8097
|
+
const candidateElements = getVisibleElements(nonDeletedElements);
|
|
8098
|
+
const clusterCandidates = candidateElements.length ? candidateElements : nonDeletedElements;
|
|
8099
|
+
const elementsMap = new Map(
|
|
8100
|
+
nonDeletedElements.map((element) => [element.id, element])
|
|
8101
|
+
);
|
|
8102
|
+
const viewportCenter = viewportCoordsToSceneCoords(
|
|
8103
|
+
{
|
|
8104
|
+
clientX: appState.offsetLeft + appState.width / 2,
|
|
8105
|
+
clientY: appState.offsetTop + appState.height / 2
|
|
8106
|
+
},
|
|
8107
|
+
appState
|
|
8108
|
+
);
|
|
8109
|
+
const anchor = clusterCandidates.reduce((closest, element) => {
|
|
8110
|
+
const bounds = toSceneBounds(
|
|
8111
|
+
getElementAbsoluteCoords(element, elementsMap)
|
|
8112
|
+
);
|
|
8113
|
+
if (!closest) {
|
|
8114
|
+
return { element, bounds };
|
|
8115
|
+
}
|
|
8116
|
+
return getBoundsCenterDistance(bounds, viewportCenter) < getBoundsCenterDistance(closest.bounds, viewportCenter) ? { element, bounds } : closest;
|
|
8117
|
+
}, null);
|
|
8118
|
+
if (!anchor) {
|
|
8119
|
+
return [];
|
|
8120
|
+
}
|
|
8121
|
+
const proximityMargin = Math.max(
|
|
8122
|
+
160 / appState.zoom.value,
|
|
8123
|
+
0.12 * Math.min(appState.width, appState.height) / appState.zoom.value
|
|
8124
|
+
);
|
|
8125
|
+
const cluster = /* @__PURE__ */ new Map([
|
|
8126
|
+
[anchor.element.id, anchor.element]
|
|
8127
|
+
]);
|
|
8128
|
+
let clusterBounds = anchor.bounds;
|
|
8129
|
+
let changed = true;
|
|
8130
|
+
while (changed) {
|
|
8131
|
+
changed = false;
|
|
8132
|
+
const expandedBounds = expandBounds(clusterBounds, proximityMargin);
|
|
8133
|
+
for (const element of clusterCandidates) {
|
|
8134
|
+
if (cluster.has(element.id)) {
|
|
8135
|
+
continue;
|
|
8136
|
+
}
|
|
8137
|
+
const elementBounds = toSceneBounds(
|
|
8138
|
+
getElementAbsoluteCoords(element, elementsMap)
|
|
8139
|
+
);
|
|
8140
|
+
if (intersectsBounds(elementBounds, expandedBounds)) {
|
|
8141
|
+
cluster.set(element.id, element);
|
|
8142
|
+
clusterBounds = getCommonBounds([...cluster.values()]);
|
|
8143
|
+
changed = true;
|
|
8144
|
+
}
|
|
8145
|
+
}
|
|
8146
|
+
}
|
|
8147
|
+
return [...cluster.values()];
|
|
8148
|
+
};
|
|
8073
8149
|
var zoomToFitBounds = ({
|
|
8074
8150
|
bounds,
|
|
8075
8151
|
appState,
|
|
@@ -8207,6 +8283,50 @@ var actionZoomToFit = register({
|
|
|
8207
8283
|
}),
|
|
8208
8284
|
keyTest: (event) => event.code === CODES2.ONE && event.shiftKey && !event.altKey && !event[KEYS12.CTRL_OR_CMD]
|
|
8209
8285
|
});
|
|
8286
|
+
var actionScrollBackToContent = register({
|
|
8287
|
+
name: "scrollBackToContent",
|
|
8288
|
+
label: "buttons.scrollBackToContent",
|
|
8289
|
+
icon: zoomAreaIcon,
|
|
8290
|
+
viewMode: true,
|
|
8291
|
+
trackEvent: { category: "canvas" },
|
|
8292
|
+
perform: (elements, appState, _, app) => {
|
|
8293
|
+
const selectedElements = app.scene.getSelectedElements(appState);
|
|
8294
|
+
const targetElements = selectedElements.length ? selectedElements : getNearestContentCluster(elements, appState);
|
|
8295
|
+
if (!targetElements.length) {
|
|
8296
|
+
return false;
|
|
8297
|
+
}
|
|
8298
|
+
return zoomToFit({
|
|
8299
|
+
targetElements,
|
|
8300
|
+
appState: {
|
|
8301
|
+
...appState,
|
|
8302
|
+
userToFollow: null
|
|
8303
|
+
},
|
|
8304
|
+
fitToViewport: true,
|
|
8305
|
+
viewportZoomFactor: COMFORTABLE_VIEWPORT_ZOOM_FACTOR,
|
|
8306
|
+
canvasOffsets: app.getEditorUIOffsets()
|
|
8307
|
+
});
|
|
8308
|
+
},
|
|
8309
|
+
PanelComponent: ({ updateData }) => /* @__PURE__ */ jsx36(
|
|
8310
|
+
Tooltip,
|
|
8311
|
+
{
|
|
8312
|
+
label: t("buttons.scrollBackToContent"),
|
|
8313
|
+
style: { height: "100%" },
|
|
8314
|
+
children: /* @__PURE__ */ jsx36(
|
|
8315
|
+
ToolButton,
|
|
8316
|
+
{
|
|
8317
|
+
type: "button",
|
|
8318
|
+
className: "scroll-back-to-content-button zoom-button",
|
|
8319
|
+
icon: zoomAreaIcon,
|
|
8320
|
+
title: t("buttons.scrollBackToContent"),
|
|
8321
|
+
"aria-label": t("buttons.scrollBackToContent"),
|
|
8322
|
+
onClick: () => {
|
|
8323
|
+
updateData(null);
|
|
8324
|
+
}
|
|
8325
|
+
}
|
|
8326
|
+
)
|
|
8327
|
+
}
|
|
8328
|
+
)
|
|
8329
|
+
});
|
|
8210
8330
|
var actionToggleTheme = register({
|
|
8211
8331
|
name: "toggleTheme",
|
|
8212
8332
|
label: (_, appState) => {
|
|
@@ -9626,7 +9746,7 @@ var exportCanvas = async (type, elements, appState, files, {
|
|
|
9626
9746
|
let blob = canvasToBlob(tempCanvas);
|
|
9627
9747
|
if (appState.exportEmbedScene) {
|
|
9628
9748
|
blob = blob.then(
|
|
9629
|
-
(blob2) => import("./data/image-
|
|
9749
|
+
(blob2) => import("./data/image-HH4XNQRO.js").then(
|
|
9630
9750
|
({ encodePngMetadata: encodePngMetadata2 }) => encodePngMetadata2({
|
|
9631
9751
|
blob: blob2,
|
|
9632
9752
|
metadata: serializeAsJSON(elements, appState, files, "local")
|
|
@@ -10038,7 +10158,7 @@ var actionPasteStyles = register({
|
|
|
10038
10158
|
if (!elementStylesToCopyFrom) {
|
|
10039
10159
|
return element;
|
|
10040
10160
|
}
|
|
10041
|
-
let
|
|
10161
|
+
let newElement6 = newElementWith5(element, {
|
|
10042
10162
|
backgroundColor: elementStylesToCopyFrom?.backgroundColor,
|
|
10043
10163
|
strokeWidth: elementStylesToCopyFrom?.strokeWidth,
|
|
10044
10164
|
strokeColor: elementStylesToCopyFrom?.strokeColor,
|
|
@@ -10051,36 +10171,36 @@ var actionPasteStyles = register({
|
|
|
10051
10171
|
element
|
|
10052
10172
|
) ? elementStylesToCopyFrom.roundness : getDefaultRoundnessTypeForElement(element) : null
|
|
10053
10173
|
});
|
|
10054
|
-
if (isTextElement3(
|
|
10174
|
+
if (isTextElement3(newElement6)) {
|
|
10055
10175
|
const fontSize = elementStylesToCopyFrom.fontSize || DEFAULT_FONT_SIZE3;
|
|
10056
10176
|
const fontFamily = elementStylesToCopyFrom.fontFamily || DEFAULT_FONT_FAMILY3;
|
|
10057
|
-
|
|
10177
|
+
newElement6 = newElementWith5(newElement6, {
|
|
10058
10178
|
fontSize,
|
|
10059
10179
|
fontFamily,
|
|
10060
10180
|
textAlign: elementStylesToCopyFrom.textAlign || DEFAULT_TEXT_ALIGN,
|
|
10061
10181
|
lineHeight: elementStylesToCopyFrom.lineHeight || getLineHeight2(fontFamily)
|
|
10062
10182
|
});
|
|
10063
10183
|
let container = null;
|
|
10064
|
-
if (
|
|
10184
|
+
if (newElement6.containerId) {
|
|
10065
10185
|
container = selectedElements.find(
|
|
10066
|
-
(element2) => isTextElement3(
|
|
10186
|
+
(element2) => isTextElement3(newElement6) && element2.id === newElement6.containerId
|
|
10067
10187
|
) || null;
|
|
10068
10188
|
}
|
|
10069
|
-
redrawTextBoundingBox2(
|
|
10189
|
+
redrawTextBoundingBox2(newElement6, container, app.scene);
|
|
10070
10190
|
}
|
|
10071
|
-
if (
|
|
10072
|
-
|
|
10191
|
+
if (newElement6.type === "arrow" && isArrowElement2(elementStylesToCopyFrom)) {
|
|
10192
|
+
newElement6 = newElementWith5(newElement6, {
|
|
10073
10193
|
startArrowhead: elementStylesToCopyFrom.startArrowhead,
|
|
10074
10194
|
endArrowhead: elementStylesToCopyFrom.endArrowhead
|
|
10075
10195
|
});
|
|
10076
10196
|
}
|
|
10077
10197
|
if (isFrameLikeElement4(element)) {
|
|
10078
|
-
|
|
10198
|
+
newElement6 = newElementWith5(newElement6, {
|
|
10079
10199
|
roundness: null,
|
|
10080
10200
|
backgroundColor: "transparent"
|
|
10081
10201
|
});
|
|
10082
10202
|
}
|
|
10083
|
-
return
|
|
10203
|
+
return newElement6;
|
|
10084
10204
|
}
|
|
10085
10205
|
return element;
|
|
10086
10206
|
}),
|
|
@@ -11863,13 +11983,13 @@ import {
|
|
|
11863
11983
|
useState as useState10
|
|
11864
11984
|
} from "react";
|
|
11865
11985
|
import { EVENT as EVENT5, HYPERLINK_TOOLTIP_DELAY, KEYS as KEYS27 } from "@orangecatai/common";
|
|
11866
|
-
import { getElementAbsoluteCoords } from "@orangecatai/element";
|
|
11986
|
+
import { getElementAbsoluteCoords as getElementAbsoluteCoords2 } from "@orangecatai/element";
|
|
11867
11987
|
import { hitElementBoundingBox } from "@orangecatai/element";
|
|
11868
11988
|
import { isElementLink } from "@orangecatai/element";
|
|
11869
11989
|
import { getEmbedLink, embeddableURLValidator } from "@orangecatai/element";
|
|
11870
11990
|
import {
|
|
11871
11991
|
sceneCoordsToViewportCoords,
|
|
11872
|
-
viewportCoordsToSceneCoords,
|
|
11992
|
+
viewportCoordsToSceneCoords as viewportCoordsToSceneCoords2,
|
|
11873
11993
|
wrapEvent,
|
|
11874
11994
|
isLocalLink,
|
|
11875
11995
|
normalizeLink
|
|
@@ -12122,7 +12242,7 @@ var Hyperlink = ({
|
|
|
12122
12242
|
);
|
|
12123
12243
|
};
|
|
12124
12244
|
var getCoordsForPopover = (element, appState, elementsMap) => {
|
|
12125
|
-
const [x1, y1] =
|
|
12245
|
+
const [x1, y1] = getElementAbsoluteCoords2(element, elementsMap);
|
|
12126
12246
|
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
|
12127
12247
|
{ sceneX: x1 + element.width / 2, sceneY: y1 },
|
|
12128
12248
|
appState
|
|
@@ -12154,7 +12274,7 @@ var renderTooltip = (element, appState, elementsMap) => {
|
|
|
12154
12274
|
tooltipDiv.classList.add("excalidraw-tooltip--visible");
|
|
12155
12275
|
tooltipDiv.style.maxWidth = "20rem";
|
|
12156
12276
|
tooltipDiv.textContent = isElementLink(element.link) ? t("labels.link.goToElement") : element.link;
|
|
12157
|
-
const [x1, y1, x2, y2] =
|
|
12277
|
+
const [x1, y1, x2, y2] = getElementAbsoluteCoords2(element, elementsMap);
|
|
12158
12278
|
const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords(
|
|
12159
12279
|
[x1, y1, x2, y2],
|
|
12160
12280
|
element.angle,
|
|
@@ -12187,7 +12307,7 @@ var hideHyperlinkToolip = () => {
|
|
|
12187
12307
|
}
|
|
12188
12308
|
};
|
|
12189
12309
|
var shouldHideLinkPopup = (element, elementsMap, appState, [clientX, clientY]) => {
|
|
12190
|
-
const { x: sceneX, y: sceneY } =
|
|
12310
|
+
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords2(
|
|
12191
12311
|
{ clientX, clientY },
|
|
12192
12312
|
appState
|
|
12193
12313
|
);
|
|
@@ -12195,7 +12315,7 @@ var shouldHideLinkPopup = (element, elementsMap, appState, [clientX, clientY]) =
|
|
|
12195
12315
|
if (hitElementBoundingBox(pointFrom4(sceneX, sceneY), element, elementsMap)) {
|
|
12196
12316
|
return false;
|
|
12197
12317
|
}
|
|
12198
|
-
const [x1, y1, x2] =
|
|
12318
|
+
const [x1, y1, x2] = getElementAbsoluteCoords2(element, elementsMap);
|
|
12199
12319
|
if (sceneX >= x1 && sceneX <= x2 && sceneY >= y1 - SPACE_BOTTOM && sceneY <= y1) {
|
|
12200
12320
|
return false;
|
|
12201
12321
|
}
|
|
@@ -12444,7 +12564,7 @@ import {
|
|
|
12444
12564
|
} from "@orangecatai/element";
|
|
12445
12565
|
import {
|
|
12446
12566
|
getCommonBoundingBox as getCommonBoundingBox2,
|
|
12447
|
-
getElementAbsoluteCoords as
|
|
12567
|
+
getElementAbsoluteCoords as getElementAbsoluteCoords3
|
|
12448
12568
|
} from "@orangecatai/element";
|
|
12449
12569
|
import {
|
|
12450
12570
|
getBoundTextElement as getBoundTextElement4,
|
|
@@ -12547,7 +12667,7 @@ var Panel = ({
|
|
|
12547
12667
|
positionRef.current = newPositionRef;
|
|
12548
12668
|
let bottomLeft;
|
|
12549
12669
|
if (elements2.length === 1) {
|
|
12550
|
-
const [x1, , , y2, cx, cy] =
|
|
12670
|
+
const [x1, , , y2, cx, cy] = getElementAbsoluteCoords3(
|
|
12551
12671
|
elements2[0],
|
|
12552
12672
|
app.scene.getNonDeletedElementsMap()
|
|
12553
12673
|
);
|
|
@@ -17769,14 +17889,17 @@ var ShapesSwitcher = ({
|
|
|
17769
17889
|
] })
|
|
17770
17890
|
] });
|
|
17771
17891
|
};
|
|
17892
|
+
var ScrollBackToContentAction = ({
|
|
17893
|
+
renderAction
|
|
17894
|
+
}) => renderAction("scrollBackToContent");
|
|
17772
17895
|
var ZoomActions = ({
|
|
17773
17896
|
renderAction,
|
|
17774
17897
|
zoom
|
|
17775
|
-
}) => /* @__PURE__ */
|
|
17898
|
+
}) => /* @__PURE__ */ jsxs42(Stack_default.Row, { align: "center", className: CLASSES5.ZOOM_ACTIONS, children: [
|
|
17776
17899
|
renderAction("zoomOut"),
|
|
17777
17900
|
renderAction("resetZoom"),
|
|
17778
17901
|
renderAction("zoomIn")
|
|
17779
|
-
] })
|
|
17902
|
+
] });
|
|
17780
17903
|
var UndoRedoActions = ({
|
|
17781
17904
|
renderAction,
|
|
17782
17905
|
className
|
|
@@ -18679,7 +18802,7 @@ import {
|
|
|
18679
18802
|
vectorFromPoint,
|
|
18680
18803
|
vectorScale
|
|
18681
18804
|
} from "@orangecatai/math";
|
|
18682
|
-
import { getElementAbsoluteCoords as
|
|
18805
|
+
import { getElementAbsoluteCoords as getElementAbsoluteCoords4 } from "@orangecatai/element";
|
|
18683
18806
|
var getCurvePathOps = (shape) => {
|
|
18684
18807
|
if (!shape) {
|
|
18685
18808
|
return [];
|
|
@@ -19011,7 +19134,7 @@ var FOCUS_POINT_SIZE = 10 / 1.5;
|
|
|
19011
19134
|
// ../element/src/sizeHelpers.ts
|
|
19012
19135
|
import {
|
|
19013
19136
|
SHIFT_LOCKING_ANGLE,
|
|
19014
|
-
viewportCoordsToSceneCoords as
|
|
19137
|
+
viewportCoordsToSceneCoords as viewportCoordsToSceneCoords3
|
|
19015
19138
|
} from "@orangecatai/common";
|
|
19016
19139
|
import {
|
|
19017
19140
|
normalizeRadians,
|
|
@@ -20988,7 +21111,7 @@ import { TOOL_TYPE, KEYS as KEYS43 } from "@orangecatai/common";
|
|
|
20988
21111
|
import {
|
|
20989
21112
|
getCommonBounds as getCommonBounds4,
|
|
20990
21113
|
getDraggedElementsBounds,
|
|
20991
|
-
getElementAbsoluteCoords as
|
|
21114
|
+
getElementAbsoluteCoords as getElementAbsoluteCoords6
|
|
20992
21115
|
} from "@orangecatai/element";
|
|
20993
21116
|
import { isBoundToContainer as isBoundToContainer6 } from "@orangecatai/element";
|
|
20994
21117
|
import { getMaximumGroups } from "@orangecatai/element";
|
|
@@ -21051,7 +21174,7 @@ var getElementsCorners = (elements, elementsMap, {
|
|
|
21051
21174
|
let result = [];
|
|
21052
21175
|
if (elements.length === 1) {
|
|
21053
21176
|
const element = elements[0];
|
|
21054
|
-
let [x1, y1, x2, y2, cx, cy] =
|
|
21177
|
+
let [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords6(
|
|
21055
21178
|
element,
|
|
21056
21179
|
elementsMap
|
|
21057
21180
|
);
|
|
@@ -21838,8 +21961,8 @@ var snapResizingElements = (selectedElements, selectedOriginalElements, app, eve
|
|
|
21838
21961
|
snapLines: pointSnapLines
|
|
21839
21962
|
};
|
|
21840
21963
|
};
|
|
21841
|
-
var snapNewElement = (
|
|
21842
|
-
if (!isSnappingEnabled({ event, selectedElements: [
|
|
21964
|
+
var snapNewElement = (newElement6, app, event, origin, dragOffset, elementsMap) => {
|
|
21965
|
+
if (!isSnappingEnabled({ event, selectedElements: [newElement6], app })) {
|
|
21843
21966
|
return {
|
|
21844
21967
|
snapOffset: { x: 0, y: 0 },
|
|
21845
21968
|
snapLines: []
|
|
@@ -21856,7 +21979,7 @@ var snapNewElement = (newElement5, app, event, origin, dragOffset, elementsMap)
|
|
|
21856
21979
|
const nearestSnapsX = [];
|
|
21857
21980
|
const nearestSnapsY = [];
|
|
21858
21981
|
getPointSnaps(
|
|
21859
|
-
[
|
|
21982
|
+
[newElement6],
|
|
21860
21983
|
selectionSnapPoints,
|
|
21861
21984
|
app,
|
|
21862
21985
|
event,
|
|
@@ -21872,12 +21995,12 @@ var snapNewElement = (newElement5, app, event, origin, dragOffset, elementsMap)
|
|
|
21872
21995
|
minOffset.y = 0;
|
|
21873
21996
|
nearestSnapsX.length = 0;
|
|
21874
21997
|
nearestSnapsY.length = 0;
|
|
21875
|
-
const corners = getElementsCorners([
|
|
21998
|
+
const corners = getElementsCorners([newElement6], elementsMap, {
|
|
21876
21999
|
boundingBoxCorners: true,
|
|
21877
22000
|
omitCenter: true
|
|
21878
22001
|
});
|
|
21879
22002
|
getPointSnaps(
|
|
21880
|
-
[
|
|
22003
|
+
[newElement6],
|
|
21881
22004
|
corners,
|
|
21882
22005
|
app,
|
|
21883
22006
|
event,
|
|
@@ -22051,11 +22174,11 @@ var Renderer = class {
|
|
|
22051
22174
|
|
|
22052
22175
|
// components/ElementCanvasButtons.tsx
|
|
22053
22176
|
import { sceneCoordsToViewportCoords as sceneCoordsToViewportCoords3 } from "@orangecatai/common";
|
|
22054
|
-
import { getElementAbsoluteCoords as
|
|
22177
|
+
import { getElementAbsoluteCoords as getElementAbsoluteCoords7 } from "@orangecatai/element";
|
|
22055
22178
|
import { jsx as jsx82 } from "react/jsx-runtime";
|
|
22056
22179
|
var CONTAINER_PADDING = 5;
|
|
22057
22180
|
var getContainerCoords2 = (element, appState, elementsMap) => {
|
|
22058
|
-
const [x1, y1] =
|
|
22181
|
+
const [x1, y1] = getElementAbsoluteCoords7(element, elementsMap);
|
|
22059
22182
|
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords3(
|
|
22060
22183
|
{ sceneX: x1 + element.width, sceneY: y1 },
|
|
22061
22184
|
appState
|
|
@@ -22092,7 +22215,7 @@ var ElementCanvasButtons = ({
|
|
|
22092
22215
|
// components/FrameToolbar.tsx
|
|
22093
22216
|
import { useCallback as useCallback14, useEffect as useEffect29, useRef as useRef24, useState as useState27 } from "react";
|
|
22094
22217
|
import { sceneCoordsToViewportCoords as sceneCoordsToViewportCoords4 } from "@orangecatai/common";
|
|
22095
|
-
import { getElementAbsoluteCoords as
|
|
22218
|
+
import { getElementAbsoluteCoords as getElementAbsoluteCoords8 } from "@orangecatai/element";
|
|
22096
22219
|
import { isFrameLikeElement as isFrameLikeElement9 } from "@orangecatai/element";
|
|
22097
22220
|
import { Fragment as Fragment11, jsx as jsx83, jsxs as jsxs45 } from "react/jsx-runtime";
|
|
22098
22221
|
var ASPECT_RATIOS = [
|
|
@@ -22289,7 +22412,7 @@ var FrameToolbar = ({
|
|
|
22289
22412
|
Math.round(element.width),
|
|
22290
22413
|
Math.round(element.height)
|
|
22291
22414
|
);
|
|
22292
|
-
const [x1, y1] =
|
|
22415
|
+
const [x1, y1] = getElementAbsoluteCoords8(
|
|
22293
22416
|
element,
|
|
22294
22417
|
app.scene.getNonDeletedElementsMap()
|
|
22295
22418
|
);
|
|
@@ -22482,13 +22605,14 @@ import { useCallback as useCallback15, useEffect as useEffect30, useRef as useRe
|
|
|
22482
22605
|
import { sceneCoordsToViewportCoords as sceneCoordsToViewportCoords5 } from "@orangecatai/common";
|
|
22483
22606
|
import {
|
|
22484
22607
|
CaptureUpdateAction as CaptureUpdateAction36,
|
|
22485
|
-
getElementAbsoluteCoords as
|
|
22608
|
+
getElementAbsoluteCoords as getElementAbsoluteCoords9
|
|
22486
22609
|
} from "@orangecatai/element";
|
|
22487
22610
|
import { ArrowUp, ImagePlus, X } from "lucide-react";
|
|
22488
22611
|
|
|
22489
22612
|
// utils/geminiApiKey.ts
|
|
22490
22613
|
function resolveGeminiApiKey(propKey) {
|
|
22491
|
-
|
|
22614
|
+
const normalizedPropKey = propKey?.trim();
|
|
22615
|
+
return (normalizedPropKey ? normalizedPropKey : void 0) ?? (typeof import.meta !== "undefined" && define_import_meta_env_default?.VITE_APP_GEMINI_API_KEY ? define_import_meta_env_default.VITE_APP_GEMINI_API_KEY : "") ?? "";
|
|
22492
22616
|
}
|
|
22493
22617
|
|
|
22494
22618
|
// components/image-generation/pendingGenerations.ts
|
|
@@ -23144,14 +23268,28 @@ var RatioShapeIcon = ({ label }) => {
|
|
|
23144
23268
|
};
|
|
23145
23269
|
var ImageGeneratorPanel = ({
|
|
23146
23270
|
element,
|
|
23147
|
-
app
|
|
23271
|
+
app,
|
|
23272
|
+
onBeforeImageGen,
|
|
23273
|
+
onAfterImageGen
|
|
23148
23274
|
}) => {
|
|
23275
|
+
const getStoredSettings = (frame) => {
|
|
23276
|
+
const stored = frame.customData?.imageGenerator;
|
|
23277
|
+
if (!stored) {
|
|
23278
|
+
return null;
|
|
23279
|
+
}
|
|
23280
|
+
return {
|
|
23281
|
+
model: stored.model,
|
|
23282
|
+
ratio: stored.ratio,
|
|
23283
|
+
resolution: stored.resolution
|
|
23284
|
+
};
|
|
23285
|
+
};
|
|
23149
23286
|
const appState = app.state;
|
|
23150
23287
|
const resolvedApiKey = resolveGeminiApiKey(app.props.geminiApiKey);
|
|
23151
23288
|
const [prompt, setPrompt] = useState29(() => getPendingPrompt(element.id));
|
|
23152
23289
|
const [selectedModel, setSelectedModel] = useState29(() => {
|
|
23153
|
-
const
|
|
23154
|
-
|
|
23290
|
+
const stored = getStoredSettings(element);
|
|
23291
|
+
const pending = getPendingSettings(element.id);
|
|
23292
|
+
return stored?.model ?? pending?.model ?? "Gemini 3.1 Flash";
|
|
23155
23293
|
});
|
|
23156
23294
|
const modelCfg = MODEL_CONFIG[selectedModel];
|
|
23157
23295
|
const availableRatios = modelCfg.supportedRatios;
|
|
@@ -23159,15 +23297,39 @@ var ImageGeneratorPanel = ({
|
|
|
23159
23297
|
const defaultRatio = availableRatios.includes("2:3") ? "2:3" : availableRatios[0];
|
|
23160
23298
|
const defaultResolution = availableResolutions.includes("2K") ? "2K" : availableResolutions[availableResolutions.length - 1];
|
|
23161
23299
|
const [selectedRatio, setSelectedRatio] = useState29(() => {
|
|
23162
|
-
const
|
|
23163
|
-
|
|
23300
|
+
const stored = getStoredSettings(element);
|
|
23301
|
+
const pending = getPendingSettings(element.id);
|
|
23302
|
+
return stored?.ratio ?? pending?.ratio ?? defaultRatio;
|
|
23164
23303
|
});
|
|
23165
23304
|
const [selectedResolution, setSelectedResolution] = useState29(
|
|
23166
23305
|
() => {
|
|
23167
|
-
const
|
|
23168
|
-
|
|
23306
|
+
const stored = getStoredSettings(element);
|
|
23307
|
+
const pending = getPendingSettings(element.id);
|
|
23308
|
+
return stored?.resolution ?? pending?.resolution ?? defaultResolution;
|
|
23169
23309
|
}
|
|
23170
23310
|
);
|
|
23311
|
+
const persistFrameSettings = useCallback15(
|
|
23312
|
+
(settings) => {
|
|
23313
|
+
app.scene.mutateElement(element, {
|
|
23314
|
+
customData: {
|
|
23315
|
+
...element.customData ?? {},
|
|
23316
|
+
imageGenerator: settings
|
|
23317
|
+
}
|
|
23318
|
+
});
|
|
23319
|
+
},
|
|
23320
|
+
[app.scene, element]
|
|
23321
|
+
);
|
|
23322
|
+
const fitFrameIntoView = useCallback15(
|
|
23323
|
+
(frame, animate = false) => {
|
|
23324
|
+
app.scrollToContent(frame, {
|
|
23325
|
+
fitToViewport: true,
|
|
23326
|
+
viewportZoomFactor: 0.8,
|
|
23327
|
+
animate,
|
|
23328
|
+
canvasOffsets: app.getEditorUIOffsets()
|
|
23329
|
+
});
|
|
23330
|
+
},
|
|
23331
|
+
[app]
|
|
23332
|
+
);
|
|
23171
23333
|
const isInitialModelMount = useRef26(true);
|
|
23172
23334
|
useEffect30(() => {
|
|
23173
23335
|
if (isInitialModelMount.current) {
|
|
@@ -23196,8 +23358,13 @@ var ImageGeneratorPanel = ({
|
|
|
23196
23358
|
width: dims.width,
|
|
23197
23359
|
height: dims.height
|
|
23198
23360
|
});
|
|
23361
|
+
persistFrameSettings({
|
|
23362
|
+
model: selectedModel,
|
|
23363
|
+
ratio: newRatio,
|
|
23364
|
+
resolution: newRes
|
|
23365
|
+
});
|
|
23199
23366
|
app.syncActionResult({ captureUpdate: CaptureUpdateAction36.IMMEDIATELY });
|
|
23200
|
-
}, [selectedModel]);
|
|
23367
|
+
}, [selectedModel, app, element, persistFrameSettings]);
|
|
23201
23368
|
const [isGenerating, setIsGenerating] = useState29(
|
|
23202
23369
|
() => hasPendingGeneration(element.id)
|
|
23203
23370
|
);
|
|
@@ -23217,6 +23384,32 @@ var ImageGeneratorPanel = ({
|
|
|
23217
23384
|
elementRef.current = element;
|
|
23218
23385
|
appRef.current = app;
|
|
23219
23386
|
});
|
|
23387
|
+
useEffect30(() => {
|
|
23388
|
+
if (!element.customData?.imageGeneratorAutoFitPending) {
|
|
23389
|
+
return;
|
|
23390
|
+
}
|
|
23391
|
+
let frameId = 0;
|
|
23392
|
+
let cancelled = false;
|
|
23393
|
+
const run = () => {
|
|
23394
|
+
if (cancelled) {
|
|
23395
|
+
return;
|
|
23396
|
+
}
|
|
23397
|
+
fitFrameIntoView(element);
|
|
23398
|
+
app.scene.mutateElement(element, {
|
|
23399
|
+
customData: {
|
|
23400
|
+
...element.customData ?? {},
|
|
23401
|
+
imageGeneratorAutoFitPending: false
|
|
23402
|
+
}
|
|
23403
|
+
});
|
|
23404
|
+
};
|
|
23405
|
+
frameId = window.requestAnimationFrame(() => {
|
|
23406
|
+
frameId = window.requestAnimationFrame(run);
|
|
23407
|
+
});
|
|
23408
|
+
return () => {
|
|
23409
|
+
cancelled = true;
|
|
23410
|
+
window.cancelAnimationFrame(frameId);
|
|
23411
|
+
};
|
|
23412
|
+
}, [app.scene, element, fitFrameIntoView]);
|
|
23220
23413
|
const applyRatio = useCallback15(
|
|
23221
23414
|
(ratioLabel) => {
|
|
23222
23415
|
setSelectedRatio(ratioLabel);
|
|
@@ -23224,7 +23417,15 @@ var ImageGeneratorPanel = ({
|
|
|
23224
23417
|
const dims = getDimensions(selectedModel, ratioLabel, selectedResolution);
|
|
23225
23418
|
appRef.current.scene.mutateElement(elementRef.current, {
|
|
23226
23419
|
width: dims.width,
|
|
23227
|
-
height: dims.height
|
|
23420
|
+
height: dims.height,
|
|
23421
|
+
customData: {
|
|
23422
|
+
...elementRef.current.customData ?? {},
|
|
23423
|
+
imageGenerator: {
|
|
23424
|
+
model: selectedModel,
|
|
23425
|
+
ratio: ratioLabel,
|
|
23426
|
+
resolution: selectedResolution
|
|
23427
|
+
}
|
|
23428
|
+
}
|
|
23228
23429
|
});
|
|
23229
23430
|
appRef.current.syncActionResult({
|
|
23230
23431
|
captureUpdate: CaptureUpdateAction36.IMMEDIATELY
|
|
@@ -23239,7 +23440,15 @@ var ImageGeneratorPanel = ({
|
|
|
23239
23440
|
const dims = getDimensions(selectedModel, selectedRatio, res);
|
|
23240
23441
|
appRef.current.scene.mutateElement(elementRef.current, {
|
|
23241
23442
|
width: dims.width,
|
|
23242
|
-
height: dims.height
|
|
23443
|
+
height: dims.height,
|
|
23444
|
+
customData: {
|
|
23445
|
+
...elementRef.current.customData ?? {},
|
|
23446
|
+
imageGenerator: {
|
|
23447
|
+
model: selectedModel,
|
|
23448
|
+
ratio: selectedRatio,
|
|
23449
|
+
resolution: res
|
|
23450
|
+
}
|
|
23451
|
+
}
|
|
23243
23452
|
});
|
|
23244
23453
|
appRef.current.syncActionResult({
|
|
23245
23454
|
captureUpdate: CaptureUpdateAction36.IMMEDIATELY
|
|
@@ -23301,6 +23510,20 @@ var ImageGeneratorPanel = ({
|
|
|
23301
23510
|
);
|
|
23302
23511
|
return;
|
|
23303
23512
|
}
|
|
23513
|
+
if (onBeforeImageGen) {
|
|
23514
|
+
try {
|
|
23515
|
+
const { allowed, error } = await onBeforeImageGen();
|
|
23516
|
+
if (!allowed) {
|
|
23517
|
+
setGenerateError(
|
|
23518
|
+
error || "Insufficient credits for image generation"
|
|
23519
|
+
);
|
|
23520
|
+
return;
|
|
23521
|
+
}
|
|
23522
|
+
} catch {
|
|
23523
|
+
setGenerateError("Credit check failed");
|
|
23524
|
+
return;
|
|
23525
|
+
}
|
|
23526
|
+
}
|
|
23304
23527
|
const controller = new AbortController();
|
|
23305
23528
|
abortControllerRef.current = controller;
|
|
23306
23529
|
startPendingGeneration(
|
|
@@ -23330,6 +23553,7 @@ var ImageGeneratorPanel = ({
|
|
|
23330
23553
|
controller.signal
|
|
23331
23554
|
);
|
|
23332
23555
|
await app.insertGeneratedImageIntoFrame(imageDataUrl, element);
|
|
23556
|
+
onAfterImageGen?.();
|
|
23333
23557
|
} catch (err) {
|
|
23334
23558
|
if (err instanceof Error) {
|
|
23335
23559
|
if (err.name === "AbortError") {
|
|
@@ -23359,12 +23583,14 @@ var ImageGeneratorPanel = ({
|
|
|
23359
23583
|
selectedResolution,
|
|
23360
23584
|
referenceImage,
|
|
23361
23585
|
app,
|
|
23362
|
-
element
|
|
23586
|
+
element,
|
|
23587
|
+
onBeforeImageGen,
|
|
23588
|
+
onAfterImageGen
|
|
23363
23589
|
]);
|
|
23364
23590
|
if (appState.contextMenu || appState.newElement || appState.resizingElement || appState.isRotating || appState.openMenu || appState.viewModeEnabled || appState.selectedElementsAreBeingDragged) {
|
|
23365
23591
|
return null;
|
|
23366
23592
|
}
|
|
23367
|
-
const [x1, , , y2] =
|
|
23593
|
+
const [x1, , , y2] = getElementAbsoluteCoords9(
|
|
23368
23594
|
element,
|
|
23369
23595
|
app.scene.getNonDeletedElementsMap()
|
|
23370
23596
|
);
|
|
@@ -23440,6 +23666,11 @@ var ImageGeneratorPanel = ({
|
|
|
23440
23666
|
{
|
|
23441
23667
|
className: "igp-dropdown-item",
|
|
23442
23668
|
onClick: () => {
|
|
23669
|
+
persistFrameSettings({
|
|
23670
|
+
model: m,
|
|
23671
|
+
ratio: selectedRatio,
|
|
23672
|
+
resolution: selectedResolution
|
|
23673
|
+
});
|
|
23443
23674
|
setSelectedModel(m);
|
|
23444
23675
|
setModelOpen(false);
|
|
23445
23676
|
},
|
|
@@ -23568,7 +23799,7 @@ var ImageGeneratorPanel = ({
|
|
|
23568
23799
|
// components/ImageQuickEditPanel.tsx
|
|
23569
23800
|
import { useCallback as useCallback16, useEffect as useEffect31, useRef as useRef27, useState as useState30 } from "react";
|
|
23570
23801
|
import { sceneCoordsToViewportCoords as sceneCoordsToViewportCoords6 } from "@orangecatai/common";
|
|
23571
|
-
import { getElementAbsoluteCoords as
|
|
23802
|
+
import { getElementAbsoluteCoords as getElementAbsoluteCoords10 } from "@orangecatai/element";
|
|
23572
23803
|
import { ArrowUp as ArrowUp2 } from "lucide-react";
|
|
23573
23804
|
import { Fragment as Fragment12, jsx as jsx88, jsxs as jsxs48 } from "react/jsx-runtime";
|
|
23574
23805
|
var ChevronDownIcon3 = () => /* @__PURE__ */ jsx88("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx88(
|
|
@@ -23671,7 +23902,9 @@ var RatioShapeIcon2 = ({ label }) => {
|
|
|
23671
23902
|
};
|
|
23672
23903
|
var ImageQuickEditPanel = ({
|
|
23673
23904
|
element,
|
|
23674
|
-
app
|
|
23905
|
+
app,
|
|
23906
|
+
onBeforeImageGen,
|
|
23907
|
+
onAfterImageGen
|
|
23675
23908
|
}) => {
|
|
23676
23909
|
const appState = app.state;
|
|
23677
23910
|
const resolvedApiKey = resolveGeminiApiKey(app.props.geminiApiKey);
|
|
@@ -23770,6 +24003,20 @@ var ImageQuickEditPanel = ({
|
|
|
23770
24003
|
);
|
|
23771
24004
|
return;
|
|
23772
24005
|
}
|
|
24006
|
+
if (onBeforeImageGen) {
|
|
24007
|
+
try {
|
|
24008
|
+
const { allowed, error } = await onBeforeImageGen();
|
|
24009
|
+
if (!allowed) {
|
|
24010
|
+
setGenerateError(
|
|
24011
|
+
error || "Insufficient credits for image generation"
|
|
24012
|
+
);
|
|
24013
|
+
return;
|
|
24014
|
+
}
|
|
24015
|
+
} catch {
|
|
24016
|
+
setGenerateError("Credit check failed");
|
|
24017
|
+
return;
|
|
24018
|
+
}
|
|
24019
|
+
}
|
|
23773
24020
|
const controller = new AbortController();
|
|
23774
24021
|
abortControllerRef.current = controller;
|
|
23775
24022
|
startPendingGeneration(
|
|
@@ -23810,6 +24057,7 @@ var ImageQuickEditPanel = ({
|
|
|
23810
24057
|
dims.width,
|
|
23811
24058
|
dims.height
|
|
23812
24059
|
);
|
|
24060
|
+
onAfterImageGen?.();
|
|
23813
24061
|
} catch (err) {
|
|
23814
24062
|
if (err instanceof Error) {
|
|
23815
24063
|
if (err.name === "AbortError") {
|
|
@@ -23838,13 +24086,15 @@ var ImageQuickEditPanel = ({
|
|
|
23838
24086
|
selectedRatio,
|
|
23839
24087
|
selectedResolution,
|
|
23840
24088
|
app,
|
|
23841
|
-
element
|
|
24089
|
+
element,
|
|
24090
|
+
onBeforeImageGen,
|
|
24091
|
+
onAfterImageGen
|
|
23842
24092
|
]);
|
|
23843
24093
|
if (appState.contextMenu || appState.newElement || appState.resizingElement || appState.isRotating || appState.openMenu || appState.viewModeEnabled || appState.selectedElementsAreBeingDragged) {
|
|
23844
24094
|
return null;
|
|
23845
24095
|
}
|
|
23846
24096
|
const elementsMap = app.scene.getNonDeletedElementsMap();
|
|
23847
|
-
const [x1, y1, , y2] =
|
|
24097
|
+
const [x1, y1, , y2] = getElementAbsoluteCoords10(element, elementsMap);
|
|
23848
24098
|
const { x: triggerVpX, y: triggerVpY } = sceneCoordsToViewportCoords6(
|
|
23849
24099
|
{ sceneX: x1 + element.width / 2, sceneY: y1 },
|
|
23850
24100
|
appState
|
|
@@ -26767,24 +27017,40 @@ var Footer = ({
|
|
|
26767
27017
|
className: clsx47("layer-ui__wrapper__footer-left zen-mode-transition", {
|
|
26768
27018
|
"layer-ui__wrapper__footer-left--transition-left": appState.zenModeEnabled
|
|
26769
27019
|
}),
|
|
26770
|
-
children: /* @__PURE__ */ jsx104(Stack_default.Col, { gap: 2, children: /* @__PURE__ */
|
|
27020
|
+
children: /* @__PURE__ */ jsx104(Stack_default.Col, { gap: 2, children: /* @__PURE__ */ jsx104(Section, { heading: "canvasActions", children: /* @__PURE__ */ jsxs60(Stack_default.Col, { gap: 1, className: "footer-canvas-actions", children: [
|
|
26771
27021
|
/* @__PURE__ */ jsx104(
|
|
26772
|
-
|
|
27022
|
+
ScrollBackToContentAction,
|
|
26773
27023
|
{
|
|
26774
|
-
renderAction: actionManager.renderAction
|
|
26775
|
-
zoom: appState.zoom
|
|
27024
|
+
renderAction: actionManager.renderAction
|
|
26776
27025
|
}
|
|
26777
27026
|
),
|
|
26778
|
-
|
|
26779
|
-
|
|
27027
|
+
/* @__PURE__ */ jsxs60(
|
|
27028
|
+
Stack_default.Row,
|
|
26780
27029
|
{
|
|
26781
|
-
|
|
26782
|
-
|
|
26783
|
-
|
|
26784
|
-
|
|
27030
|
+
align: "center",
|
|
27031
|
+
gap: 1,
|
|
27032
|
+
className: "footer-canvas-actions-row",
|
|
27033
|
+
children: [
|
|
27034
|
+
/* @__PURE__ */ jsx104(
|
|
27035
|
+
ZoomActions,
|
|
27036
|
+
{
|
|
27037
|
+
renderAction: actionManager.renderAction,
|
|
27038
|
+
zoom: appState.zoom
|
|
27039
|
+
}
|
|
27040
|
+
),
|
|
27041
|
+
!appState.viewModeEnabled && /* @__PURE__ */ jsx104(
|
|
27042
|
+
UndoRedoActions,
|
|
27043
|
+
{
|
|
27044
|
+
renderAction: actionManager.renderAction,
|
|
27045
|
+
className: clsx47("zen-mode-transition", {
|
|
27046
|
+
"layer-ui__wrapper__footer-left--transition-bottom": appState.zenModeEnabled
|
|
27047
|
+
})
|
|
27048
|
+
}
|
|
27049
|
+
)
|
|
27050
|
+
]
|
|
26785
27051
|
}
|
|
26786
27052
|
)
|
|
26787
|
-
] }) })
|
|
27053
|
+
] }) }) })
|
|
26788
27054
|
}
|
|
26789
27055
|
),
|
|
26790
27056
|
/* @__PURE__ */ jsx104(FooterCenterTunnel.Out, {}),
|
|
@@ -35050,7 +35316,7 @@ import {
|
|
|
35050
35316
|
} from "@orangecatai/element";
|
|
35051
35317
|
import {
|
|
35052
35318
|
getCommonBounds as getCommonBounds9,
|
|
35053
|
-
getElementAbsoluteCoords as
|
|
35319
|
+
getElementAbsoluteCoords as getElementAbsoluteCoords11
|
|
35054
35320
|
} from "@orangecatai/element";
|
|
35055
35321
|
import {
|
|
35056
35322
|
getGlobalFixedPointForBindableElement as getGlobalFixedPointForBindableElement2,
|
|
@@ -35837,7 +36103,7 @@ var renderSelectionBorder = (context, appState, elementProperties) => {
|
|
|
35837
36103
|
context.restore();
|
|
35838
36104
|
};
|
|
35839
36105
|
var renderFrameHighlight = (context, appState, frame, elementsMap) => {
|
|
35840
|
-
const [x1, y1, x2, y2] =
|
|
36106
|
+
const [x1, y1, x2, y2] = getElementAbsoluteCoords11(frame, elementsMap);
|
|
35841
36107
|
const width = x2 - x1;
|
|
35842
36108
|
const height = y2 - y1;
|
|
35843
36109
|
context.strokeStyle = "rgb(0,118,255)";
|
|
@@ -36098,7 +36364,7 @@ var renderTransformHandles = (context, renderConfig, appState, transformHandles,
|
|
|
36098
36364
|
});
|
|
36099
36365
|
};
|
|
36100
36366
|
var renderCropHandles = (context, renderConfig, appState, croppingElement, elementsMap) => {
|
|
36101
|
-
const [x1, y1, , , cx, cy] =
|
|
36367
|
+
const [x1, y1, , , cx, cy] = getElementAbsoluteCoords11(
|
|
36102
36368
|
croppingElement,
|
|
36103
36369
|
elementsMap
|
|
36104
36370
|
);
|
|
@@ -36379,7 +36645,7 @@ var _renderInteractiveScene = ({
|
|
|
36379
36645
|
}
|
|
36380
36646
|
}
|
|
36381
36647
|
if (selectionColors.length) {
|
|
36382
|
-
const [x1, y1, x2, y2, cx, cy] =
|
|
36648
|
+
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords11(
|
|
36383
36649
|
element,
|
|
36384
36650
|
elementsMap,
|
|
36385
36651
|
true
|
|
@@ -36506,7 +36772,7 @@ var _renderInteractiveScene = ({
|
|
|
36506
36772
|
appState.searchMatches?.matches.forEach(({ id, focus, matchedLines }) => {
|
|
36507
36773
|
const element = elementsMap.get(id);
|
|
36508
36774
|
if (element) {
|
|
36509
|
-
const [elementX1, elementY1, , , cx, cy] =
|
|
36775
|
+
const [elementX1, elementY1, , , cx, cy] = getElementAbsoluteCoords11(
|
|
36510
36776
|
element,
|
|
36511
36777
|
elementsMap,
|
|
36512
36778
|
true
|
|
@@ -36860,7 +37126,7 @@ import {
|
|
|
36860
37126
|
var _renderNewElementScene = ({
|
|
36861
37127
|
canvas,
|
|
36862
37128
|
rc,
|
|
36863
|
-
newElement:
|
|
37129
|
+
newElement: newElement6,
|
|
36864
37130
|
elementsMap,
|
|
36865
37131
|
allElementsMap,
|
|
36866
37132
|
scale,
|
|
@@ -36880,19 +37146,19 @@ var _renderNewElementScene = ({
|
|
|
36880
37146
|
});
|
|
36881
37147
|
context.save();
|
|
36882
37148
|
context.scale(appState.zoom.value, appState.zoom.value);
|
|
36883
|
-
if (
|
|
36884
|
-
if (isInvisiblySmallElement2(
|
|
37149
|
+
if (newElement6 && newElement6.type !== "selection") {
|
|
37150
|
+
if (isInvisiblySmallElement2(newElement6)) {
|
|
36885
37151
|
return;
|
|
36886
37152
|
}
|
|
36887
|
-
const frameId =
|
|
37153
|
+
const frameId = newElement6.frameId || appState.frameToHighlight?.id;
|
|
36888
37154
|
if (frameId && appState.frameRendering.enabled && appState.frameRendering.clip) {
|
|
36889
|
-
const frame = getTargetFrame(
|
|
36890
|
-
if (frame && shouldApplyFrameClip(
|
|
37155
|
+
const frame = getTargetFrame(newElement6, elementsMap, appState);
|
|
37156
|
+
if (frame && shouldApplyFrameClip(newElement6, frame, appState, elementsMap)) {
|
|
36891
37157
|
frameClip(frame, context, renderConfig, appState);
|
|
36892
37158
|
}
|
|
36893
37159
|
}
|
|
36894
37160
|
renderElement(
|
|
36895
|
-
|
|
37161
|
+
newElement6,
|
|
36896
37162
|
elementsMap,
|
|
36897
37163
|
allElementsMap,
|
|
36898
37164
|
rc,
|
|
@@ -37275,11 +37541,11 @@ var App = class _App extends React52.Component {
|
|
|
37275
37541
|
);
|
|
37276
37542
|
if (frameNameDiv) {
|
|
37277
37543
|
const box = frameNameDiv.getBoundingClientRect();
|
|
37278
|
-
const boxSceneTopLeft =
|
|
37544
|
+
const boxSceneTopLeft = viewportCoordsToSceneCoords4(
|
|
37279
37545
|
{ clientX: box.x, clientY: box.y },
|
|
37280
37546
|
this.state
|
|
37281
37547
|
);
|
|
37282
|
-
const boxSceneBottomRight =
|
|
37548
|
+
const boxSceneBottomRight = viewportCoordsToSceneCoords4(
|
|
37283
37549
|
{ clientX: box.right, clientY: box.bottom },
|
|
37284
37550
|
this.state
|
|
37285
37551
|
);
|
|
@@ -37569,7 +37835,10 @@ var App = class _App extends React52.Component {
|
|
|
37569
37835
|
locked: false,
|
|
37570
37836
|
backgroundColor: "#cce8f5",
|
|
37571
37837
|
fillStyle: "solid",
|
|
37572
|
-
customData: {
|
|
37838
|
+
customData: {
|
|
37839
|
+
type: "image-generator",
|
|
37840
|
+
imageGeneratorAutoFitPending: true
|
|
37841
|
+
}
|
|
37573
37842
|
});
|
|
37574
37843
|
this.scene.insertElement(frame);
|
|
37575
37844
|
this.setActiveTool({ type: "selection" });
|
|
@@ -37614,7 +37883,7 @@ var App = class _App extends React52.Component {
|
|
|
37614
37883
|
const CLEARANCE = 25;
|
|
37615
37884
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
37616
37885
|
const allElements = this.scene.getNonDeletedElements().filter((el) => el.id !== sourceElement.id);
|
|
37617
|
-
const [sx1, sy1, sx2, sy2] =
|
|
37886
|
+
const [sx1, sy1, sx2, sy2] = getElementAbsoluteCoords12(
|
|
37618
37887
|
sourceElement,
|
|
37619
37888
|
elementsMap
|
|
37620
37889
|
);
|
|
@@ -37632,7 +37901,7 @@ var App = class _App extends React52.Component {
|
|
|
37632
37901
|
const right = cRight + CLEARANCE;
|
|
37633
37902
|
const bottom = cBottom + CLEARANCE;
|
|
37634
37903
|
return allElements.some((el) => {
|
|
37635
|
-
const [ex1, ey1, ex2, ey2] =
|
|
37904
|
+
const [ex1, ey1, ex2, ey2] = getElementAbsoluteCoords12(el, elementsMap);
|
|
37636
37905
|
return ex1 < right && ex2 > left && ey1 < bottom && ey2 > top;
|
|
37637
37906
|
});
|
|
37638
37907
|
};
|
|
@@ -38118,7 +38387,7 @@ var App = class _App extends React52.Component {
|
|
|
38118
38387
|
const elementsCenterY = distance2(minY, maxY) / 2;
|
|
38119
38388
|
const clientX = typeof opts.position === "object" ? opts.position.clientX : opts.position === "cursor" ? this.lastViewportPosition.x : this.state.width / 2 + this.state.offsetLeft;
|
|
38120
38389
|
const clientY = typeof opts.position === "object" ? opts.position.clientY : opts.position === "cursor" ? this.lastViewportPosition.y : this.state.height / 2 + this.state.offsetTop;
|
|
38121
|
-
const { x, y } =
|
|
38390
|
+
const { x, y } = viewportCoordsToSceneCoords4(
|
|
38122
38391
|
{ clientX, clientY },
|
|
38123
38392
|
this.state
|
|
38124
38393
|
);
|
|
@@ -38157,13 +38426,13 @@ var App = class _App extends React52.Component {
|
|
|
38157
38426
|
);
|
|
38158
38427
|
}
|
|
38159
38428
|
this.scene.replaceAllElements(nextElements);
|
|
38160
|
-
duplicatedElements.forEach((
|
|
38161
|
-
if (isTextElement19(
|
|
38429
|
+
duplicatedElements.forEach((newElement6) => {
|
|
38430
|
+
if (isTextElement19(newElement6) && isBoundToContainer9(newElement6)) {
|
|
38162
38431
|
const container = getContainerElement5(
|
|
38163
|
-
|
|
38432
|
+
newElement6,
|
|
38164
38433
|
this.scene.getElementsMapIncludingDeleted()
|
|
38165
38434
|
);
|
|
38166
|
-
redrawTextBoundingBox8(
|
|
38435
|
+
redrawTextBoundingBox8(newElement6, container, this.scene);
|
|
38167
38436
|
}
|
|
38168
38437
|
});
|
|
38169
38438
|
if (isSafari2) {
|
|
@@ -38970,7 +39239,7 @@ var App = class _App extends React52.Component {
|
|
|
38970
39239
|
bindMode: "orbit"
|
|
38971
39240
|
});
|
|
38972
39241
|
if (this.lastPointerMoveEvent && getFeatureFlag4("COMPLEX_BINDINGS")) {
|
|
38973
|
-
const scenePointer =
|
|
39242
|
+
const scenePointer = viewportCoordsToSceneCoords4(
|
|
38974
39243
|
{
|
|
38975
39244
|
clientX: this.lastPointerMoveEvent.clientX,
|
|
38976
39245
|
clientY: this.lastPointerMoveEvent.clientY
|
|
@@ -39371,7 +39640,7 @@ var App = class _App extends React52.Component {
|
|
|
39371
39640
|
return;
|
|
39372
39641
|
}
|
|
39373
39642
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
39374
|
-
let { x: sceneX, y: sceneY } =
|
|
39643
|
+
let { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords4(
|
|
39375
39644
|
event,
|
|
39376
39645
|
this.state
|
|
39377
39646
|
);
|
|
@@ -39536,7 +39805,7 @@ var App = class _App extends React52.Component {
|
|
|
39536
39805
|
if (!this.hitLinkElement || draggedDistance > DRAGGING_THRESHOLD3) {
|
|
39537
39806
|
return;
|
|
39538
39807
|
}
|
|
39539
|
-
const lastPointerDownCoords =
|
|
39808
|
+
const lastPointerDownCoords = viewportCoordsToSceneCoords4(
|
|
39540
39809
|
this.lastPointerDownEvent,
|
|
39541
39810
|
this.state
|
|
39542
39811
|
);
|
|
@@ -39548,7 +39817,7 @@ var App = class _App extends React52.Component {
|
|
|
39548
39817
|
pointFrom29(lastPointerDownCoords.x, lastPointerDownCoords.y),
|
|
39549
39818
|
this.editorInterface.formFactor === "phone"
|
|
39550
39819
|
);
|
|
39551
|
-
const lastPointerUpCoords =
|
|
39820
|
+
const lastPointerUpCoords = viewportCoordsToSceneCoords4(
|
|
39552
39821
|
this.lastPointerUpEvent,
|
|
39553
39822
|
this.state
|
|
39554
39823
|
);
|
|
@@ -39596,7 +39865,7 @@ var App = class _App extends React52.Component {
|
|
|
39596
39865
|
__publicField(this, "handleCanvasPointerMove", (event) => {
|
|
39597
39866
|
this.savePointer(event.clientX, event.clientY, this.state.cursorButton);
|
|
39598
39867
|
this.lastPointerMoveEvent = event.nativeEvent;
|
|
39599
|
-
const scenePointer =
|
|
39868
|
+
const scenePointer = viewportCoordsToSceneCoords4(event, this.state);
|
|
39600
39869
|
const { x: scenePointerX, y: scenePointerY } = scenePointer;
|
|
39601
39870
|
this.lastPointerMoveCoords = {
|
|
39602
39871
|
x: scenePointerX,
|
|
@@ -39705,8 +39974,8 @@ var App = class _App extends React52.Component {
|
|
|
39705
39974
|
}
|
|
39706
39975
|
}
|
|
39707
39976
|
if (isBindingElementType(this.state.activeTool.type)) {
|
|
39708
|
-
const { newElement:
|
|
39709
|
-
if (!
|
|
39977
|
+
const { newElement: newElement6 } = this.state;
|
|
39978
|
+
if (!newElement6 && isBindingEnabled2(this.state)) {
|
|
39710
39979
|
const globalPoint = pointFrom29(
|
|
39711
39980
|
scenePointerX,
|
|
39712
39981
|
scenePointerY
|
|
@@ -40072,7 +40341,7 @@ var App = class _App extends React52.Component {
|
|
|
40072
40341
|
invalidateContextMenu = true;
|
|
40073
40342
|
});
|
|
40074
40343
|
__publicField(this, "handleCanvasPointerDown", (event) => {
|
|
40075
|
-
const scenePointer =
|
|
40344
|
+
const scenePointer = viewportCoordsToSceneCoords4(event, this.state);
|
|
40076
40345
|
const { x: scenePointerX, y: scenePointerY } = scenePointer;
|
|
40077
40346
|
this.lastPointerMoveCoords = {
|
|
40078
40347
|
x: scenePointerX,
|
|
@@ -40364,7 +40633,7 @@ var App = class _App extends React52.Component {
|
|
|
40364
40633
|
}
|
|
40365
40634
|
this.removePointer(event);
|
|
40366
40635
|
this.lastPointerUpEvent = event;
|
|
40367
|
-
const scenePointer =
|
|
40636
|
+
const scenePointer = viewportCoordsToSceneCoords4(
|
|
40368
40637
|
{ clientX: event.clientX, clientY: event.clientY },
|
|
40369
40638
|
this.state
|
|
40370
40639
|
);
|
|
@@ -41444,7 +41713,7 @@ var App = class _App extends React52.Component {
|
|
|
41444
41713
|
try {
|
|
41445
41714
|
const clientX = this.state.width / 2 + this.state.offsetLeft;
|
|
41446
41715
|
const clientY = this.state.height / 2 + this.state.offsetTop;
|
|
41447
|
-
const { x, y } =
|
|
41716
|
+
const { x, y } = viewportCoordsToSceneCoords4(
|
|
41448
41717
|
{ clientX, clientY },
|
|
41449
41718
|
this.state
|
|
41450
41719
|
);
|
|
@@ -41619,7 +41888,7 @@ var App = class _App extends React52.Component {
|
|
|
41619
41888
|
});
|
|
41620
41889
|
});
|
|
41621
41890
|
__publicField(this, "handleAppOnDrop", async (event) => {
|
|
41622
|
-
const { x: sceneX, y: sceneY } =
|
|
41891
|
+
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords4(
|
|
41623
41892
|
event,
|
|
41624
41893
|
this.state
|
|
41625
41894
|
);
|
|
@@ -41786,7 +42055,7 @@ var App = class _App extends React52.Component {
|
|
|
41786
42055
|
event.button !== POINTER_BUTTON2.SECONDARY) && this.state.activeTool.type !== this.state.preferredSelectionTool.type) {
|
|
41787
42056
|
return;
|
|
41788
42057
|
}
|
|
41789
|
-
const { x, y } =
|
|
42058
|
+
const { x, y } = viewportCoordsToSceneCoords4(event, this.state);
|
|
41790
42059
|
const element = this.getElementAtPosition(x, y, {
|
|
41791
42060
|
preferSelected: true,
|
|
41792
42061
|
includeLockedElements: true
|
|
@@ -41850,8 +42119,8 @@ var App = class _App extends React52.Component {
|
|
|
41850
42119
|
});
|
|
41851
42120
|
return;
|
|
41852
42121
|
}
|
|
41853
|
-
const
|
|
41854
|
-
if (!
|
|
42122
|
+
const newElement6 = this.state.newElement;
|
|
42123
|
+
if (!newElement6) {
|
|
41855
42124
|
return;
|
|
41856
42125
|
}
|
|
41857
42126
|
let [gridX, gridY] = getGridPoint2(
|
|
@@ -41859,11 +42128,11 @@ var App = class _App extends React52.Component {
|
|
|
41859
42128
|
pointerCoords.y,
|
|
41860
42129
|
event[KEYS55.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
|
|
41861
42130
|
);
|
|
41862
|
-
const image = isInitializedImageElement3(
|
|
42131
|
+
const image = isInitializedImageElement3(newElement6) && this.imageCache.get(newElement6.fileId)?.image;
|
|
41863
42132
|
const aspectRatio = image && !(image instanceof Promise) ? image.width / image.height : null;
|
|
41864
|
-
this.maybeCacheReferenceSnapPoints(event, [
|
|
42133
|
+
this.maybeCacheReferenceSnapPoints(event, [newElement6]);
|
|
41865
42134
|
const { snapOffset, snapLines } = snapNewElement(
|
|
41866
|
-
|
|
42135
|
+
newElement6,
|
|
41867
42136
|
this,
|
|
41868
42137
|
event,
|
|
41869
42138
|
{
|
|
@@ -41881,9 +42150,9 @@ var App = class _App extends React52.Component {
|
|
|
41881
42150
|
this.setState({
|
|
41882
42151
|
snapLines
|
|
41883
42152
|
});
|
|
41884
|
-
if (!isBindingElement4(
|
|
42153
|
+
if (!isBindingElement4(newElement6)) {
|
|
41885
42154
|
dragNewElement({
|
|
41886
|
-
newElement:
|
|
42155
|
+
newElement: newElement6,
|
|
41887
42156
|
elementType: this.state.activeTool.type,
|
|
41888
42157
|
originX: pointerDownState.originInGrid.x,
|
|
41889
42158
|
originY: pointerDownState.originInGrid.y,
|
|
@@ -41891,7 +42160,7 @@ var App = class _App extends React52.Component {
|
|
|
41891
42160
|
y: gridY,
|
|
41892
42161
|
width: distance2(pointerDownState.originInGrid.x, gridX),
|
|
41893
42162
|
height: distance2(pointerDownState.originInGrid.y, gridY),
|
|
41894
|
-
shouldMaintainAspectRatio: isImageElement9(
|
|
42163
|
+
shouldMaintainAspectRatio: isImageElement9(newElement6) ? !shouldMaintainAspectRatio(event) : shouldMaintainAspectRatio(event),
|
|
41895
42164
|
shouldResizeFromCenter: shouldResizeFromCenter(event),
|
|
41896
42165
|
zoom: this.state.zoom.value,
|
|
41897
42166
|
scene: this.scene,
|
|
@@ -41901,13 +42170,13 @@ var App = class _App extends React52.Component {
|
|
|
41901
42170
|
});
|
|
41902
42171
|
}
|
|
41903
42172
|
this.setState({
|
|
41904
|
-
newElement:
|
|
42173
|
+
newElement: newElement6
|
|
41905
42174
|
});
|
|
41906
42175
|
if (this.state.activeTool.type === TOOL_TYPE3.frame || this.state.activeTool.type === TOOL_TYPE3.magicframe) {
|
|
41907
42176
|
this.setState({
|
|
41908
42177
|
elementsToHighlight: getElementsInResizingFrame4(
|
|
41909
42178
|
this.scene.getNonDeletedElements(),
|
|
41910
|
-
|
|
42179
|
+
newElement6,
|
|
41911
42180
|
this.state,
|
|
41912
42181
|
this.scene.getNonDeletedElementsMap()
|
|
41913
42182
|
)
|
|
@@ -42206,7 +42475,7 @@ var App = class _App extends React52.Component {
|
|
|
42206
42475
|
if (!x || !y) {
|
|
42207
42476
|
return;
|
|
42208
42477
|
}
|
|
42209
|
-
const { x: sceneX, y: sceneY } =
|
|
42478
|
+
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords4(
|
|
42210
42479
|
{ clientX: x, clientY: y },
|
|
42211
42480
|
this.state
|
|
42212
42481
|
);
|
|
@@ -42631,7 +42900,7 @@ var App = class _App extends React52.Component {
|
|
|
42631
42900
|
return false;
|
|
42632
42901
|
}
|
|
42633
42902
|
const viewportClickStart_scenePoint = pointFrom29(
|
|
42634
|
-
|
|
42903
|
+
viewportCoordsToSceneCoords4(
|
|
42635
42904
|
{
|
|
42636
42905
|
clientX: this.lastPointerDownEvent.clientX,
|
|
42637
42906
|
clientY: this.lastPointerDownEvent.clientY
|
|
@@ -42640,7 +42909,7 @@ var App = class _App extends React52.Component {
|
|
|
42640
42909
|
)
|
|
42641
42910
|
);
|
|
42642
42911
|
const viewportClickEnd_scenePoint = pointFrom29(
|
|
42643
|
-
|
|
42912
|
+
viewportCoordsToSceneCoords4(
|
|
42644
42913
|
{
|
|
42645
42914
|
clientX: this.lastPointerUpEvent.clientX,
|
|
42646
42915
|
clientY: this.lastPointerUpEvent.clientY
|
|
@@ -43161,14 +43430,18 @@ var App = class _App extends React52.Component {
|
|
|
43161
43430
|
ImageGeneratorPanel,
|
|
43162
43431
|
{
|
|
43163
43432
|
element: firstSelectedElement,
|
|
43164
|
-
app: this
|
|
43433
|
+
app: this,
|
|
43434
|
+
onBeforeImageGen: this.props.onBeforeImageGen,
|
|
43435
|
+
onAfterImageGen: this.props.onAfterImageGen
|
|
43165
43436
|
}
|
|
43166
43437
|
),
|
|
43167
43438
|
selectedElements.length === 1 && isImageElement9(firstSelectedElement) && !this.state.viewModeEnabled && /* @__PURE__ */ jsx168(
|
|
43168
43439
|
ImageQuickEditPanel,
|
|
43169
43440
|
{
|
|
43170
43441
|
element: firstSelectedElement,
|
|
43171
|
-
app: this
|
|
43442
|
+
app: this,
|
|
43443
|
+
onBeforeImageGen: this.props.onBeforeImageGen,
|
|
43444
|
+
onAfterImageGen: this.props.onAfterImageGen
|
|
43172
43445
|
}
|
|
43173
43446
|
),
|
|
43174
43447
|
this.state.toast !== null && /* @__PURE__ */ jsx168(
|
|
@@ -43702,7 +43975,7 @@ var App = class _App extends React52.Component {
|
|
|
43702
43975
|
}
|
|
43703
43976
|
// TODO: Cover with tests
|
|
43704
43977
|
async insertClipboardContent(data, dataTransferFiles, isPlainPaste) {
|
|
43705
|
-
const { x: sceneX, y: sceneY } =
|
|
43978
|
+
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords4(
|
|
43706
43979
|
{
|
|
43707
43980
|
clientX: this.lastViewportPosition.x,
|
|
43708
43981
|
clientY: this.lastViewportPosition.y
|
|
@@ -43849,7 +44122,7 @@ var App = class _App extends React52.Component {
|
|
|
43849
44122
|
}
|
|
43850
44123
|
}
|
|
43851
44124
|
addTextFromPaste(text, isPlainPaste = false) {
|
|
43852
|
-
const { x, y } =
|
|
44125
|
+
const { x, y } = viewportCoordsToSceneCoords4(
|
|
43853
44126
|
{
|
|
43854
44127
|
clientX: this.lastViewportPosition.x,
|
|
43855
44128
|
clientY: this.lastViewportPosition.y
|
|
@@ -44153,7 +44426,7 @@ var App = class _App extends React52.Component {
|
|
|
44153
44426
|
if (elements[index].isDeleted) {
|
|
44154
44427
|
continue;
|
|
44155
44428
|
}
|
|
44156
|
-
const [x1, y1, x2, y2] =
|
|
44429
|
+
const [x1, y1, x2, y2] = getElementAbsoluteCoords12(
|
|
44157
44430
|
elements[index],
|
|
44158
44431
|
this.scene.getNonDeletedElementsMap()
|
|
44159
44432
|
);
|
|
@@ -44286,7 +44559,7 @@ var App = class _App extends React52.Component {
|
|
|
44286
44559
|
}
|
|
44287
44560
|
}
|
|
44288
44561
|
initialPointerDownState(event) {
|
|
44289
|
-
const origin =
|
|
44562
|
+
const origin = viewportCoordsToSceneCoords4(event, this.state);
|
|
44290
44563
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
44291
44564
|
const [minX, minY, maxX, maxY] = getCommonBounds11(selectedElements);
|
|
44292
44565
|
const isElbowArrowOnly = selectedElements.findIndex(isElbowArrow10) === 0;
|
|
@@ -44450,7 +44723,7 @@ var App = class _App extends React52.Component {
|
|
|
44450
44723
|
if (this.state.openDialog?.name === "elementLinkSelector") {
|
|
44451
44724
|
return;
|
|
44452
44725
|
}
|
|
44453
|
-
const pointerCoords =
|
|
44726
|
+
const pointerCoords = viewportCoordsToSceneCoords4(event, this.state);
|
|
44454
44727
|
if (this.state.activeLockedId) {
|
|
44455
44728
|
this.setState({
|
|
44456
44729
|
activeLockedId: null
|
|
@@ -44699,7 +44972,7 @@ var App = class _App extends React52.Component {
|
|
|
44699
44972
|
);
|
|
44700
44973
|
instantDragOffset[0] *= image.naturalWidth / uncroppedSize.width;
|
|
44701
44974
|
instantDragOffset[1] *= image.naturalHeight / uncroppedSize.height;
|
|
44702
|
-
const [x1, y1, x2, y2, cx, cy] =
|
|
44975
|
+
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords12(
|
|
44703
44976
|
croppingElement,
|
|
44704
44977
|
elementsMap
|
|
44705
44978
|
);
|
|
@@ -44867,7 +45140,7 @@ var App = class _App extends React52.Component {
|
|
|
44867
45140
|
},
|
|
44868
45141
|
[]
|
|
44869
45142
|
);
|
|
44870
|
-
pointerDownState.drag.origin =
|
|
45143
|
+
pointerDownState.drag.origin = viewportCoordsToSceneCoords4(
|
|
44871
45144
|
event,
|
|
44872
45145
|
this.state
|
|
44873
45146
|
);
|
|
@@ -44926,20 +45199,20 @@ var App = class _App extends React52.Component {
|
|
|
44926
45199
|
);
|
|
44927
45200
|
}
|
|
44928
45201
|
} else {
|
|
44929
|
-
const
|
|
44930
|
-
if (!
|
|
45202
|
+
const newElement6 = this.state.newElement;
|
|
45203
|
+
if (!newElement6) {
|
|
44931
45204
|
return;
|
|
44932
45205
|
}
|
|
44933
|
-
if (
|
|
44934
|
-
const points =
|
|
44935
|
-
const dx = pointerCoords.x -
|
|
44936
|
-
const dy = pointerCoords.y -
|
|
45206
|
+
if (newElement6.type === "freedraw") {
|
|
45207
|
+
const points = newElement6.points;
|
|
45208
|
+
const dx = pointerCoords.x - newElement6.x;
|
|
45209
|
+
const dy = pointerCoords.y - newElement6.y;
|
|
44937
45210
|
const lastPoint = points.length > 0 && points[points.length - 1];
|
|
44938
45211
|
const discardPoint = lastPoint && lastPoint[0] === dx && lastPoint[1] === dy;
|
|
44939
45212
|
if (!discardPoint) {
|
|
44940
|
-
const pressures =
|
|
45213
|
+
const pressures = newElement6.simulatePressure ? newElement6.pressures : [...newElement6.pressures, event.pressure];
|
|
44941
45214
|
this.scene.mutateElement(
|
|
44942
|
-
|
|
45215
|
+
newElement6,
|
|
44943
45216
|
{
|
|
44944
45217
|
points: [...points, pointFrom29(dx, dy)],
|
|
44945
45218
|
pressures
|
|
@@ -44950,12 +45223,12 @@ var App = class _App extends React52.Component {
|
|
|
44950
45223
|
}
|
|
44951
45224
|
);
|
|
44952
45225
|
this.setState({
|
|
44953
|
-
newElement:
|
|
45226
|
+
newElement: newElement6
|
|
44954
45227
|
});
|
|
44955
45228
|
}
|
|
44956
|
-
} else if (isLinearElement12(
|
|
45229
|
+
} else if (isLinearElement12(newElement6) && !newElement6.isDeleted) {
|
|
44957
45230
|
pointerDownState.drag.hasOccurred = true;
|
|
44958
|
-
const points =
|
|
45231
|
+
const points = newElement6.points;
|
|
44959
45232
|
invariant16(
|
|
44960
45233
|
points.length > 1,
|
|
44961
45234
|
"Do not create linear elements with less than 2 points"
|
|
@@ -44963,7 +45236,7 @@ var App = class _App extends React52.Component {
|
|
|
44963
45236
|
let linearElementEditor = this.state.selectedLinearElement;
|
|
44964
45237
|
if (!linearElementEditor) {
|
|
44965
45238
|
linearElementEditor = new LinearElementEditor11(
|
|
44966
|
-
|
|
45239
|
+
newElement6,
|
|
44967
45240
|
this.scene.getNonDeletedElementsMap()
|
|
44968
45241
|
);
|
|
44969
45242
|
linearElementEditor = {
|
|
@@ -44976,7 +45249,7 @@ var App = class _App extends React52.Component {
|
|
|
44976
45249
|
};
|
|
44977
45250
|
}
|
|
44978
45251
|
this.setState({
|
|
44979
|
-
newElement:
|
|
45252
|
+
newElement: newElement6,
|
|
44980
45253
|
...LinearElementEditor11.handlePointDragging(
|
|
44981
45254
|
event,
|
|
44982
45255
|
this,
|
|
@@ -45100,7 +45373,7 @@ var App = class _App extends React52.Component {
|
|
|
45100
45373
|
pointerDownState.eventListeners.onMove.flush();
|
|
45101
45374
|
}
|
|
45102
45375
|
const {
|
|
45103
|
-
newElement:
|
|
45376
|
+
newElement: newElement6,
|
|
45104
45377
|
resizingElement,
|
|
45105
45378
|
croppingElementId,
|
|
45106
45379
|
multiElement,
|
|
@@ -45127,7 +45400,7 @@ var App = class _App extends React52.Component {
|
|
|
45127
45400
|
SnapCache.setVisibleGaps(null);
|
|
45128
45401
|
this.savePointer(childEvent.clientX, childEvent.clientY, "up");
|
|
45129
45402
|
const hitElements = pointerDownState.hit.allHitElements;
|
|
45130
|
-
const sceneCoords =
|
|
45403
|
+
const sceneCoords = viewportCoordsToSceneCoords4(
|
|
45131
45404
|
{ clientX: childEvent.clientX, clientY: childEvent.clientY },
|
|
45132
45405
|
this.state
|
|
45133
45406
|
);
|
|
@@ -45264,31 +45537,31 @@ var App = class _App extends React52.Component {
|
|
|
45264
45537
|
pointerDownState,
|
|
45265
45538
|
childEvent
|
|
45266
45539
|
);
|
|
45267
|
-
if (
|
|
45268
|
-
const pointerCoords =
|
|
45540
|
+
if (newElement6?.type === "freedraw") {
|
|
45541
|
+
const pointerCoords = viewportCoordsToSceneCoords4(
|
|
45269
45542
|
childEvent,
|
|
45270
45543
|
this.state
|
|
45271
45544
|
);
|
|
45272
|
-
const points =
|
|
45273
|
-
let dx = pointerCoords.x -
|
|
45274
|
-
let dy = pointerCoords.y -
|
|
45545
|
+
const points = newElement6.points;
|
|
45546
|
+
let dx = pointerCoords.x - newElement6.x;
|
|
45547
|
+
let dy = pointerCoords.y - newElement6.y;
|
|
45275
45548
|
if (dx === points[0][0] && dy === points[0][1]) {
|
|
45276
45549
|
dy += 1e-4;
|
|
45277
45550
|
dx += 1e-4;
|
|
45278
45551
|
}
|
|
45279
|
-
const pressures =
|
|
45280
|
-
this.scene.mutateElement(
|
|
45552
|
+
const pressures = newElement6.simulatePressure ? [] : [...newElement6.pressures, childEvent.pressure];
|
|
45553
|
+
this.scene.mutateElement(newElement6, {
|
|
45281
45554
|
points: [...points, pointFrom29(dx, dy)],
|
|
45282
45555
|
pressures
|
|
45283
45556
|
});
|
|
45284
45557
|
this.actionManager.executeAction(actionFinalize);
|
|
45285
45558
|
return;
|
|
45286
45559
|
}
|
|
45287
|
-
if (isLinearElement12(
|
|
45288
|
-
if (
|
|
45560
|
+
if (isLinearElement12(newElement6)) {
|
|
45561
|
+
if (newElement6.points.length > 1 && newElement6.points[1][0] !== 0 && newElement6.points[1][1] !== 0) {
|
|
45289
45562
|
this.store.scheduleCapture();
|
|
45290
45563
|
}
|
|
45291
|
-
const pointerCoords =
|
|
45564
|
+
const pointerCoords = viewportCoordsToSceneCoords4(
|
|
45292
45565
|
childEvent,
|
|
45293
45566
|
this.state
|
|
45294
45567
|
);
|
|
@@ -45296,16 +45569,16 @@ var App = class _App extends React52.Component {
|
|
|
45296
45569
|
pointFrom29(pointerCoords.x, pointerCoords.y),
|
|
45297
45570
|
pointFrom29(pointerDownState.origin.x, pointerDownState.origin.y)
|
|
45298
45571
|
) * this.state.zoom.value;
|
|
45299
|
-
if ((!pointerDownState.drag.hasOccurred || dragDistance < MINIMUM_ARROW_SIZE) &&
|
|
45572
|
+
if ((!pointerDownState.drag.hasOccurred || dragDistance < MINIMUM_ARROW_SIZE) && newElement6 && !multiElement) {
|
|
45300
45573
|
if (this.editorInterface.isTouchScreen) {
|
|
45301
45574
|
const FIXED_DELTA_X = Math.min(
|
|
45302
45575
|
this.state.width * 0.7 / this.state.zoom.value,
|
|
45303
45576
|
100
|
|
45304
45577
|
);
|
|
45305
45578
|
this.scene.mutateElement(
|
|
45306
|
-
|
|
45579
|
+
newElement6,
|
|
45307
45580
|
{
|
|
45308
|
-
x:
|
|
45581
|
+
x: newElement6.x - FIXED_DELTA_X / 2,
|
|
45309
45582
|
points: [
|
|
45310
45583
|
pointFrom29(0, 0),
|
|
45311
45584
|
pointFrom29(FIXED_DELTA_X, 0)
|
|
@@ -45315,22 +45588,22 @@ var App = class _App extends React52.Component {
|
|
|
45315
45588
|
);
|
|
45316
45589
|
this.actionManager.executeAction(actionFinalize);
|
|
45317
45590
|
} else {
|
|
45318
|
-
const dx = pointerCoords.x -
|
|
45319
|
-
const dy = pointerCoords.y -
|
|
45591
|
+
const dx = pointerCoords.x - newElement6.x;
|
|
45592
|
+
const dy = pointerCoords.y - newElement6.y;
|
|
45320
45593
|
this.scene.mutateElement(
|
|
45321
|
-
|
|
45594
|
+
newElement6,
|
|
45322
45595
|
{
|
|
45323
|
-
points: [
|
|
45596
|
+
points: [newElement6.points[0], pointFrom29(dx, dy)]
|
|
45324
45597
|
},
|
|
45325
45598
|
{ informMutation: false, isDragging: false }
|
|
45326
45599
|
);
|
|
45327
45600
|
this.setState({
|
|
45328
|
-
multiElement:
|
|
45329
|
-
newElement:
|
|
45601
|
+
multiElement: newElement6,
|
|
45602
|
+
newElement: newElement6
|
|
45330
45603
|
});
|
|
45331
45604
|
}
|
|
45332
45605
|
} else if (pointerDownState.drag.hasOccurred && !multiElement) {
|
|
45333
|
-
if (isLinearElement12(
|
|
45606
|
+
if (isLinearElement12(newElement6)) {
|
|
45334
45607
|
this.actionManager.executeAction(actionFinalize, "ui", {
|
|
45335
45608
|
event: childEvent,
|
|
45336
45609
|
sceneCoords
|
|
@@ -45347,12 +45620,12 @@ var App = class _App extends React52.Component {
|
|
|
45347
45620
|
selectedElementIds: makeNextSelectedElementIds2(
|
|
45348
45621
|
{
|
|
45349
45622
|
...prevState.selectedElementIds,
|
|
45350
|
-
[
|
|
45623
|
+
[newElement6.id]: true
|
|
45351
45624
|
},
|
|
45352
45625
|
prevState
|
|
45353
45626
|
),
|
|
45354
45627
|
selectedLinearElement: new LinearElementEditor11(
|
|
45355
|
-
|
|
45628
|
+
newElement6,
|
|
45356
45629
|
this.scene.getNonDeletedElementsMap()
|
|
45357
45630
|
)
|
|
45358
45631
|
}));
|
|
@@ -45365,27 +45638,27 @@ var App = class _App extends React52.Component {
|
|
|
45365
45638
|
}
|
|
45366
45639
|
return;
|
|
45367
45640
|
}
|
|
45368
|
-
if (isTextElement19(
|
|
45641
|
+
if (isTextElement19(newElement6)) {
|
|
45369
45642
|
const minWidth = getMinTextElementWidth(
|
|
45370
45643
|
getFontString9({
|
|
45371
|
-
fontSize:
|
|
45372
|
-
fontFamily:
|
|
45644
|
+
fontSize: newElement6.fontSize,
|
|
45645
|
+
fontFamily: newElement6.fontFamily
|
|
45373
45646
|
}),
|
|
45374
|
-
|
|
45647
|
+
newElement6.lineHeight
|
|
45375
45648
|
);
|
|
45376
|
-
if (
|
|
45377
|
-
this.scene.mutateElement(
|
|
45649
|
+
if (newElement6.width < minWidth) {
|
|
45650
|
+
this.scene.mutateElement(newElement6, {
|
|
45378
45651
|
autoResize: true
|
|
45379
45652
|
});
|
|
45380
45653
|
}
|
|
45381
45654
|
this.resetCursor();
|
|
45382
|
-
this.handleTextWysiwyg(
|
|
45655
|
+
this.handleTextWysiwyg(newElement6, {
|
|
45383
45656
|
isExistingElement: true
|
|
45384
45657
|
});
|
|
45385
45658
|
}
|
|
45386
|
-
if (activeTool.type !== "selection" &&
|
|
45659
|
+
if (activeTool.type !== "selection" && newElement6 && isInvisiblySmallElement3(newElement6)) {
|
|
45387
45660
|
this.updateScene({
|
|
45388
|
-
elements: this.scene.getElementsIncludingDeleted().filter((el) => el.id !==
|
|
45661
|
+
elements: this.scene.getElementsIncludingDeleted().filter((el) => el.id !== newElement6.id),
|
|
45389
45662
|
appState: {
|
|
45390
45663
|
newElement: null
|
|
45391
45664
|
},
|
|
@@ -45393,25 +45666,25 @@ var App = class _App extends React52.Component {
|
|
|
45393
45666
|
});
|
|
45394
45667
|
return;
|
|
45395
45668
|
}
|
|
45396
|
-
if (isFrameLikeElement16(
|
|
45669
|
+
if (isFrameLikeElement16(newElement6)) {
|
|
45397
45670
|
const elementsInsideFrame = getElementsInNewFrame(
|
|
45398
45671
|
this.scene.getElementsIncludingDeleted(),
|
|
45399
|
-
|
|
45672
|
+
newElement6,
|
|
45400
45673
|
this.scene.getNonDeletedElementsMap()
|
|
45401
45674
|
);
|
|
45402
45675
|
this.scene.replaceAllElements(
|
|
45403
45676
|
addElementsToFrame2(
|
|
45404
45677
|
this.scene.getElementsMapIncludingDeleted(),
|
|
45405
45678
|
elementsInsideFrame,
|
|
45406
|
-
|
|
45679
|
+
newElement6,
|
|
45407
45680
|
this.state
|
|
45408
45681
|
)
|
|
45409
45682
|
);
|
|
45410
45683
|
}
|
|
45411
|
-
if (
|
|
45684
|
+
if (newElement6) {
|
|
45412
45685
|
this.scene.mutateElement(
|
|
45413
|
-
|
|
45414
|
-
getNormalizedDimensions(
|
|
45686
|
+
newElement6,
|
|
45687
|
+
getNormalizedDimensions(newElement6),
|
|
45415
45688
|
{
|
|
45416
45689
|
informMutation: false,
|
|
45417
45690
|
isDragging: false
|
|
@@ -45420,7 +45693,7 @@ var App = class _App extends React52.Component {
|
|
|
45420
45693
|
this.scene.triggerUpdate();
|
|
45421
45694
|
}
|
|
45422
45695
|
if (pointerDownState.drag.hasOccurred) {
|
|
45423
|
-
const sceneCoords2 =
|
|
45696
|
+
const sceneCoords2 = viewportCoordsToSceneCoords4(childEvent, this.state);
|
|
45424
45697
|
if (this.state.selectedLinearElement && this.state.selectedLinearElement.isDragging) {
|
|
45425
45698
|
const linearElement = this.scene.getElement(
|
|
45426
45699
|
this.state.selectedLinearElement.elementId
|
|
@@ -45573,7 +45846,7 @@ var App = class _App extends React52.Component {
|
|
|
45573
45846
|
pointFrom29(pointerEnd.clientX, pointerEnd.clientY)
|
|
45574
45847
|
);
|
|
45575
45848
|
if (draggedDistance === 0) {
|
|
45576
|
-
const scenePointer =
|
|
45849
|
+
const scenePointer = viewportCoordsToSceneCoords4(
|
|
45577
45850
|
{
|
|
45578
45851
|
clientX: pointerEnd.clientX,
|
|
45579
45852
|
clientY: pointerEnd.clientY
|
|
@@ -45741,16 +46014,16 @@ var App = class _App extends React52.Component {
|
|
|
45741
46014
|
setCursor(this.interactiveCanvas, CURSOR_TYPE4.AUTO);
|
|
45742
46015
|
return;
|
|
45743
46016
|
}
|
|
45744
|
-
if (!activeTool.locked && activeTool.type !== "freedraw" &&
|
|
46017
|
+
if (!activeTool.locked && activeTool.type !== "freedraw" && newElement6) {
|
|
45745
46018
|
this.setState((prevState) => ({
|
|
45746
46019
|
selectedElementIds: makeNextSelectedElementIds2(
|
|
45747
46020
|
{
|
|
45748
46021
|
...prevState.selectedElementIds,
|
|
45749
|
-
[
|
|
46022
|
+
[newElement6.id]: true
|
|
45750
46023
|
},
|
|
45751
46024
|
prevState
|
|
45752
46025
|
),
|
|
45753
|
-
showHyperlinkPopup: isEmbeddableElement4(
|
|
46026
|
+
showHyperlinkPopup: isEmbeddableElement4(newElement6) && !newElement6.link ? "editor" : prevState.showHyperlinkPopup
|
|
45754
46027
|
}));
|
|
45755
46028
|
}
|
|
45756
46029
|
if (activeTool.type !== "selection" || isSomeElementSelected(this.scene.getNonDeletedElements(), this.state) || !isShallowEqual9(
|
|
@@ -46300,7 +46573,7 @@ import {
|
|
|
46300
46573
|
UserIdleState as UserIdleState2,
|
|
46301
46574
|
normalizeLink as normalizeLink4,
|
|
46302
46575
|
sceneCoordsToViewportCoords as sceneCoordsToViewportCoords2,
|
|
46303
|
-
viewportCoordsToSceneCoords as
|
|
46576
|
+
viewportCoordsToSceneCoords as viewportCoordsToSceneCoords5,
|
|
46304
46577
|
getFormFactor as getFormFactor2
|
|
46305
46578
|
} from "@orangecatai/common";
|
|
46306
46579
|
import {
|
|
@@ -46518,7 +46791,2465 @@ var DiagramToCodePlugin = (props) => {
|
|
|
46518
46791
|
// index.tsx
|
|
46519
46792
|
import { isElementLink as isElementLink3 } from "@orangecatai/element";
|
|
46520
46793
|
import { setCustomTextMetricsProvider } from "@orangecatai/element";
|
|
46794
|
+
|
|
46795
|
+
// components/AIChatPanel.tsx
|
|
46796
|
+
import React57, {
|
|
46797
|
+
useCallback as useCallback24,
|
|
46798
|
+
useEffect as useEffect55,
|
|
46799
|
+
useImperativeHandle as useImperativeHandle4,
|
|
46800
|
+
useRef as useRef49,
|
|
46801
|
+
useState as useState49
|
|
46802
|
+
} from "react";
|
|
46803
|
+
import {
|
|
46804
|
+
ArrowUp as ArrowUp3,
|
|
46805
|
+
AtSign,
|
|
46806
|
+
Check,
|
|
46807
|
+
ChevronDown as ChevronDown2,
|
|
46808
|
+
ChevronRight,
|
|
46809
|
+
Copy,
|
|
46810
|
+
Globe,
|
|
46811
|
+
MessageSquare,
|
|
46812
|
+
Mic,
|
|
46813
|
+
MicOff,
|
|
46814
|
+
Paperclip,
|
|
46815
|
+
Plus,
|
|
46816
|
+
Search,
|
|
46817
|
+
Wrench,
|
|
46818
|
+
X as X2
|
|
46819
|
+
} from "lucide-react";
|
|
46820
|
+
|
|
46821
|
+
// utils/openRouterApiKey.ts
|
|
46822
|
+
function resolveOpenRouterApiKey(propKey) {
|
|
46823
|
+
const normalizedPropKey = propKey?.trim();
|
|
46824
|
+
return (normalizedPropKey ? normalizedPropKey : void 0) ?? (typeof import.meta !== "undefined" && define_import_meta_env_default?.VITE_APP_OPENROUTER_API_KEY ? define_import_meta_env_default.VITE_APP_OPENROUTER_API_KEY : "") ?? "";
|
|
46825
|
+
}
|
|
46826
|
+
|
|
46827
|
+
// components/ai-chat/canvasTools.ts
|
|
46828
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
46829
|
+
import {
|
|
46830
|
+
newFrameElement as newFrameElement3,
|
|
46831
|
+
newElement as newElement5,
|
|
46832
|
+
newTextElement as newTextElement4,
|
|
46833
|
+
newImageElement as newImageElement2,
|
|
46834
|
+
getFrameChildren as getFrameChildren7,
|
|
46835
|
+
isFrameLikeElement as isFrameLikeElement17
|
|
46836
|
+
} from "@orangecatai/element";
|
|
46837
|
+
import { FRAME_STYLE as FRAME_STYLE5 } from "@orangecatai/common";
|
|
46838
|
+
var CANVAS_TOOLS = [
|
|
46839
|
+
{
|
|
46840
|
+
type: "function",
|
|
46841
|
+
function: {
|
|
46842
|
+
name: "create_frame",
|
|
46843
|
+
description: "Create a new frame on the canvas. Frames act as artboards that clip their children. Default size is 512x512.",
|
|
46844
|
+
parameters: {
|
|
46845
|
+
type: "object",
|
|
46846
|
+
properties: {
|
|
46847
|
+
width: {
|
|
46848
|
+
type: "number",
|
|
46849
|
+
description: "Frame width in pixels (default 512)"
|
|
46850
|
+
},
|
|
46851
|
+
height: {
|
|
46852
|
+
type: "number",
|
|
46853
|
+
description: "Frame height in pixels (default 512)"
|
|
46854
|
+
},
|
|
46855
|
+
name: {
|
|
46856
|
+
type: "string",
|
|
46857
|
+
description: "Optional display name for the frame"
|
|
46858
|
+
}
|
|
46859
|
+
},
|
|
46860
|
+
required: [],
|
|
46861
|
+
additionalProperties: false
|
|
46862
|
+
}
|
|
46863
|
+
}
|
|
46864
|
+
},
|
|
46865
|
+
{
|
|
46866
|
+
type: "function",
|
|
46867
|
+
function: {
|
|
46868
|
+
name: "add_rectangle",
|
|
46869
|
+
description: "Add a filled rectangle inside a frame. Commonly used for solid background fills behind images and text.",
|
|
46870
|
+
parameters: {
|
|
46871
|
+
type: "object",
|
|
46872
|
+
properties: {
|
|
46873
|
+
frameId: {
|
|
46874
|
+
type: "string",
|
|
46875
|
+
description: "ID of the parent frame"
|
|
46876
|
+
},
|
|
46877
|
+
x: {
|
|
46878
|
+
type: "number",
|
|
46879
|
+
description: "X position relative to canvas (use the frame's x for full coverage)"
|
|
46880
|
+
},
|
|
46881
|
+
y: {
|
|
46882
|
+
type: "number",
|
|
46883
|
+
description: "Y position relative to canvas (use the frame's y for full coverage)"
|
|
46884
|
+
},
|
|
46885
|
+
width: {
|
|
46886
|
+
type: "number",
|
|
46887
|
+
description: "Rectangle width in pixels"
|
|
46888
|
+
},
|
|
46889
|
+
height: {
|
|
46890
|
+
type: "number",
|
|
46891
|
+
description: "Rectangle height in pixels"
|
|
46892
|
+
},
|
|
46893
|
+
backgroundColor: {
|
|
46894
|
+
type: "string",
|
|
46895
|
+
description: 'Fill color as hex string e.g. "#1a1a2e". Use "transparent" for no fill.'
|
|
46896
|
+
},
|
|
46897
|
+
fillStyle: {
|
|
46898
|
+
type: "string",
|
|
46899
|
+
enum: ["solid", "hachure", "cross-hatch"],
|
|
46900
|
+
description: 'Fill style (default "solid")'
|
|
46901
|
+
},
|
|
46902
|
+
strokeColor: {
|
|
46903
|
+
type: "string",
|
|
46904
|
+
description: 'Stroke/border color as hex (default "transparent")'
|
|
46905
|
+
},
|
|
46906
|
+
strokeWidth: {
|
|
46907
|
+
type: "number",
|
|
46908
|
+
description: "Stroke width in pixels (default 0)"
|
|
46909
|
+
},
|
|
46910
|
+
opacity: {
|
|
46911
|
+
type: "number",
|
|
46912
|
+
description: "Opacity 0-100 (default 100)"
|
|
46913
|
+
}
|
|
46914
|
+
},
|
|
46915
|
+
required: ["frameId", "x", "y", "width", "height", "backgroundColor"],
|
|
46916
|
+
additionalProperties: false
|
|
46917
|
+
}
|
|
46918
|
+
}
|
|
46919
|
+
},
|
|
46920
|
+
{
|
|
46921
|
+
type: "function",
|
|
46922
|
+
function: {
|
|
46923
|
+
name: "add_text",
|
|
46924
|
+
description: "Add a text element inside a frame. Use for headlines, subheads, body copy, CTAs etc.",
|
|
46925
|
+
parameters: {
|
|
46926
|
+
type: "object",
|
|
46927
|
+
properties: {
|
|
46928
|
+
frameId: {
|
|
46929
|
+
type: "string",
|
|
46930
|
+
description: "ID of the parent frame"
|
|
46931
|
+
},
|
|
46932
|
+
x: {
|
|
46933
|
+
type: "number",
|
|
46934
|
+
description: "X position (canvas coordinates)"
|
|
46935
|
+
},
|
|
46936
|
+
y: {
|
|
46937
|
+
type: "number",
|
|
46938
|
+
description: "Y position (canvas coordinates)"
|
|
46939
|
+
},
|
|
46940
|
+
text: {
|
|
46941
|
+
type: "string",
|
|
46942
|
+
description: "The text content"
|
|
46943
|
+
},
|
|
46944
|
+
fontSize: {
|
|
46945
|
+
type: "number",
|
|
46946
|
+
description: "Font size in pixels (default 20)"
|
|
46947
|
+
},
|
|
46948
|
+
fontFamily: {
|
|
46949
|
+
type: "number",
|
|
46950
|
+
enum: [1, 2, 3, 4, 5],
|
|
46951
|
+
description: "Font family: 1=Excalifont(hand), 2=Nunito(sans), 3=Comic Shanns(code), 4=Liberation Sans(clean sans), 5=cascadia(code). Default 2."
|
|
46952
|
+
},
|
|
46953
|
+
strokeColor: {
|
|
46954
|
+
type: "string",
|
|
46955
|
+
description: 'Text color as hex string (default "#000000")'
|
|
46956
|
+
},
|
|
46957
|
+
textAlign: {
|
|
46958
|
+
type: "string",
|
|
46959
|
+
enum: ["left", "center", "right"],
|
|
46960
|
+
description: "Horizontal alignment (default left)"
|
|
46961
|
+
},
|
|
46962
|
+
width: {
|
|
46963
|
+
type: "number",
|
|
46964
|
+
description: "Optional fixed width for text wrapping"
|
|
46965
|
+
}
|
|
46966
|
+
},
|
|
46967
|
+
required: ["frameId", "x", "y", "text"],
|
|
46968
|
+
additionalProperties: false
|
|
46969
|
+
}
|
|
46970
|
+
}
|
|
46971
|
+
},
|
|
46972
|
+
{
|
|
46973
|
+
type: "function",
|
|
46974
|
+
function: {
|
|
46975
|
+
name: "generate_image",
|
|
46976
|
+
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.",
|
|
46977
|
+
parameters: {
|
|
46978
|
+
type: "object",
|
|
46979
|
+
properties: {
|
|
46980
|
+
frameId: {
|
|
46981
|
+
type: "string",
|
|
46982
|
+
description: "ID of the parent frame to insert the image into"
|
|
46983
|
+
},
|
|
46984
|
+
prompt: {
|
|
46985
|
+
type: "string",
|
|
46986
|
+
description: "Detailed image generation prompt describing what to create"
|
|
46987
|
+
},
|
|
46988
|
+
x: {
|
|
46989
|
+
type: "number",
|
|
46990
|
+
description: "X position for the image (canvas coordinates)"
|
|
46991
|
+
},
|
|
46992
|
+
y: {
|
|
46993
|
+
type: "number",
|
|
46994
|
+
description: "Y position for the image (canvas coordinates)"
|
|
46995
|
+
},
|
|
46996
|
+
width: {
|
|
46997
|
+
type: "number",
|
|
46998
|
+
description: "Image width in pixels"
|
|
46999
|
+
},
|
|
47000
|
+
height: {
|
|
47001
|
+
type: "number",
|
|
47002
|
+
description: "Image height in pixels"
|
|
47003
|
+
}
|
|
47004
|
+
},
|
|
47005
|
+
required: ["frameId", "prompt", "x", "y", "width", "height"],
|
|
47006
|
+
additionalProperties: false
|
|
47007
|
+
}
|
|
47008
|
+
}
|
|
47009
|
+
},
|
|
47010
|
+
{
|
|
47011
|
+
type: "function",
|
|
47012
|
+
function: {
|
|
47013
|
+
name: "update_element",
|
|
47014
|
+
description: "Update properties of an existing element (move, resize, recolor, change text, etc.)",
|
|
47015
|
+
parameters: {
|
|
47016
|
+
type: "object",
|
|
47017
|
+
properties: {
|
|
47018
|
+
elementId: {
|
|
47019
|
+
type: "string",
|
|
47020
|
+
description: "ID of the element to update"
|
|
47021
|
+
},
|
|
47022
|
+
x: { type: "number", description: "New X position" },
|
|
47023
|
+
y: { type: "number", description: "New Y position" },
|
|
47024
|
+
width: { type: "number", description: "New width" },
|
|
47025
|
+
height: { type: "number", description: "New height" },
|
|
47026
|
+
backgroundColor: {
|
|
47027
|
+
type: "string",
|
|
47028
|
+
description: "New background/fill color"
|
|
47029
|
+
},
|
|
47030
|
+
strokeColor: {
|
|
47031
|
+
type: "string",
|
|
47032
|
+
description: "New stroke/text color"
|
|
47033
|
+
},
|
|
47034
|
+
opacity: { type: "number", description: "New opacity 0-100" },
|
|
47035
|
+
fontSize: {
|
|
47036
|
+
type: "number",
|
|
47037
|
+
description: "New font size (text elements only)"
|
|
47038
|
+
},
|
|
47039
|
+
text: {
|
|
47040
|
+
type: "string",
|
|
47041
|
+
description: "New text content (text elements only)"
|
|
47042
|
+
},
|
|
47043
|
+
fillStyle: {
|
|
47044
|
+
type: "string",
|
|
47045
|
+
enum: ["solid", "hachure", "cross-hatch"],
|
|
47046
|
+
description: "New fill style"
|
|
47047
|
+
}
|
|
47048
|
+
},
|
|
47049
|
+
required: ["elementId"],
|
|
47050
|
+
additionalProperties: false
|
|
47051
|
+
}
|
|
47052
|
+
}
|
|
47053
|
+
},
|
|
47054
|
+
{
|
|
47055
|
+
type: "function",
|
|
47056
|
+
function: {
|
|
47057
|
+
name: "delete_element",
|
|
47058
|
+
description: "Delete an element from the canvas",
|
|
47059
|
+
parameters: {
|
|
47060
|
+
type: "object",
|
|
47061
|
+
properties: {
|
|
47062
|
+
elementId: {
|
|
47063
|
+
type: "string",
|
|
47064
|
+
description: "ID of the element to delete"
|
|
47065
|
+
}
|
|
47066
|
+
},
|
|
47067
|
+
required: ["elementId"],
|
|
47068
|
+
additionalProperties: false
|
|
47069
|
+
}
|
|
47070
|
+
}
|
|
47071
|
+
},
|
|
47072
|
+
{
|
|
47073
|
+
type: "function",
|
|
47074
|
+
function: {
|
|
47075
|
+
name: "get_frame_elements",
|
|
47076
|
+
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.",
|
|
47077
|
+
parameters: {
|
|
47078
|
+
type: "object",
|
|
47079
|
+
properties: {
|
|
47080
|
+
frameId: {
|
|
47081
|
+
type: "string",
|
|
47082
|
+
description: "ID of the frame to inspect"
|
|
47083
|
+
}
|
|
47084
|
+
},
|
|
47085
|
+
required: ["frameId"],
|
|
47086
|
+
additionalProperties: false
|
|
47087
|
+
}
|
|
47088
|
+
}
|
|
47089
|
+
}
|
|
47090
|
+
];
|
|
47091
|
+
function serializeElement(el) {
|
|
47092
|
+
const base = {
|
|
47093
|
+
id: el.id,
|
|
47094
|
+
type: el.type,
|
|
47095
|
+
x: Math.round(el.x),
|
|
47096
|
+
y: Math.round(el.y),
|
|
47097
|
+
width: Math.round(el.width),
|
|
47098
|
+
height: Math.round(el.height)
|
|
47099
|
+
};
|
|
47100
|
+
if (el.backgroundColor && el.backgroundColor !== "transparent") {
|
|
47101
|
+
base.backgroundColor = el.backgroundColor;
|
|
47102
|
+
}
|
|
47103
|
+
if (el.strokeColor && el.strokeColor !== "#1e1e1e") {
|
|
47104
|
+
base.strokeColor = el.strokeColor;
|
|
47105
|
+
}
|
|
47106
|
+
if (el.opacity !== 100) {
|
|
47107
|
+
base.opacity = el.opacity;
|
|
47108
|
+
}
|
|
47109
|
+
if (el.fillStyle && el.fillStyle !== "solid") {
|
|
47110
|
+
base.fillStyle = el.fillStyle;
|
|
47111
|
+
}
|
|
47112
|
+
if (el.type === "text") {
|
|
47113
|
+
const textEl = el;
|
|
47114
|
+
base.text = textEl.text;
|
|
47115
|
+
base.fontSize = textEl.fontSize;
|
|
47116
|
+
base.fontFamily = textEl.fontFamily;
|
|
47117
|
+
base.textAlign = textEl.textAlign;
|
|
47118
|
+
}
|
|
47119
|
+
if (el.type === "image") {
|
|
47120
|
+
base.type = "image";
|
|
47121
|
+
}
|
|
47122
|
+
if (el.type === "frame") {
|
|
47123
|
+
const frameEl = el;
|
|
47124
|
+
if (frameEl.name) {
|
|
47125
|
+
base.name = frameEl.name;
|
|
47126
|
+
}
|
|
47127
|
+
}
|
|
47128
|
+
return base;
|
|
47129
|
+
}
|
|
47130
|
+
function serializeElements(elements) {
|
|
47131
|
+
return elements.map(serializeElement);
|
|
47132
|
+
}
|
|
47133
|
+
function listFrames(api) {
|
|
47134
|
+
const elements = api.getSceneElements();
|
|
47135
|
+
const frames = [];
|
|
47136
|
+
for (const el of elements) {
|
|
47137
|
+
if (isFrameLikeElement17(el)) {
|
|
47138
|
+
const children = getFrameChildren7(elements, el.id);
|
|
47139
|
+
const frameLike = el;
|
|
47140
|
+
frames.push({
|
|
47141
|
+
id: el.id,
|
|
47142
|
+
name: frameLike.name || `Frame ${Math.round(el.width)}\xD7${Math.round(el.height)}`,
|
|
47143
|
+
width: Math.round(el.width),
|
|
47144
|
+
height: Math.round(el.height),
|
|
47145
|
+
childCount: children.length
|
|
47146
|
+
});
|
|
47147
|
+
}
|
|
47148
|
+
}
|
|
47149
|
+
return frames;
|
|
47150
|
+
}
|
|
47151
|
+
async function captureFrameScreenshot(api, frameId) {
|
|
47152
|
+
const elements = api.getSceneElements();
|
|
47153
|
+
const frame = elements.find((el) => el.id === frameId);
|
|
47154
|
+
if (!frame || !isFrameLikeElement17(frame)) {
|
|
47155
|
+
return null;
|
|
47156
|
+
}
|
|
47157
|
+
const appState = api.getAppState();
|
|
47158
|
+
const files = api.getFiles();
|
|
47159
|
+
const children = getFrameChildren7(elements, frameId);
|
|
47160
|
+
try {
|
|
47161
|
+
const canvas = await exportToCanvas([...children, frame], appState, files, {
|
|
47162
|
+
exportBackground: true,
|
|
47163
|
+
viewBackgroundColor: appState.viewBackgroundColor,
|
|
47164
|
+
exportingFrame: frame,
|
|
47165
|
+
exportPadding: 0
|
|
47166
|
+
});
|
|
47167
|
+
return canvas.toDataURL("image/jpeg", 0.7);
|
|
47168
|
+
} catch {
|
|
47169
|
+
return null;
|
|
47170
|
+
}
|
|
47171
|
+
}
|
|
47172
|
+
function getFrameContext(api, frameId) {
|
|
47173
|
+
const elements = api.getSceneElements();
|
|
47174
|
+
const frame = elements.find((el) => el.id === frameId);
|
|
47175
|
+
if (!frame || !isFrameLikeElement17(frame)) {
|
|
47176
|
+
return null;
|
|
47177
|
+
}
|
|
47178
|
+
const children = getFrameChildren7(elements, frameId);
|
|
47179
|
+
const frameLike = frame;
|
|
47180
|
+
const frameInfo = {
|
|
47181
|
+
id: frame.id,
|
|
47182
|
+
name: frameLike.name || `Frame ${Math.round(frame.width)}\xD7${Math.round(frame.height)}`,
|
|
47183
|
+
width: Math.round(frame.width),
|
|
47184
|
+
height: Math.round(frame.height),
|
|
47185
|
+
childCount: children.length
|
|
47186
|
+
};
|
|
47187
|
+
const data = {
|
|
47188
|
+
frame: serializeElement(frame),
|
|
47189
|
+
children: serializeElements(children)
|
|
47190
|
+
};
|
|
47191
|
+
return {
|
|
47192
|
+
serialized: JSON.stringify(data, null, 2),
|
|
47193
|
+
frameInfo
|
|
47194
|
+
};
|
|
47195
|
+
}
|
|
47196
|
+
async function executeCanvasTool(toolName, rawArgs, ctx) {
|
|
47197
|
+
let args;
|
|
47198
|
+
try {
|
|
47199
|
+
args = JSON.parse(rawArgs);
|
|
47200
|
+
} catch {
|
|
47201
|
+
return {
|
|
47202
|
+
success: false,
|
|
47203
|
+
error: "Invalid JSON arguments",
|
|
47204
|
+
statusMessage: "Failed to parse tool arguments"
|
|
47205
|
+
};
|
|
47206
|
+
}
|
|
47207
|
+
switch (toolName) {
|
|
47208
|
+
case "create_frame":
|
|
47209
|
+
return execCreateFrame(args, ctx);
|
|
47210
|
+
case "add_rectangle":
|
|
47211
|
+
return execAddRectangle(args, ctx);
|
|
47212
|
+
case "add_text":
|
|
47213
|
+
return execAddText(args, ctx);
|
|
47214
|
+
case "generate_image":
|
|
47215
|
+
return execGenerateImage(args, ctx);
|
|
47216
|
+
case "update_element":
|
|
47217
|
+
return execUpdateElement(args, ctx);
|
|
47218
|
+
case "delete_element":
|
|
47219
|
+
return execDeleteElement(args, ctx);
|
|
47220
|
+
case "get_frame_elements":
|
|
47221
|
+
return execGetFrameElements(args, ctx);
|
|
47222
|
+
default:
|
|
47223
|
+
return {
|
|
47224
|
+
success: false,
|
|
47225
|
+
error: `Unknown tool: ${toolName}`,
|
|
47226
|
+
statusMessage: `Unknown tool: ${toolName}`
|
|
47227
|
+
};
|
|
47228
|
+
}
|
|
47229
|
+
}
|
|
47230
|
+
function getViewportCenter(api) {
|
|
47231
|
+
const appState = api.getAppState();
|
|
47232
|
+
const x = -appState.scrollX + appState.width / 2 / appState.zoom.value;
|
|
47233
|
+
const y = -appState.scrollY + appState.height / 2 / appState.zoom.value;
|
|
47234
|
+
return { x, y };
|
|
47235
|
+
}
|
|
47236
|
+
var COVER_TOLERANCE = 10;
|
|
47237
|
+
function coversFrame(elX, elY, elW, elH, frameEl) {
|
|
47238
|
+
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;
|
|
47239
|
+
}
|
|
47240
|
+
function getElementTier(type, x, y, w, h, frame) {
|
|
47241
|
+
if (type === "text") {
|
|
47242
|
+
return 3 /* TEXT */;
|
|
47243
|
+
}
|
|
47244
|
+
if (type === "image") {
|
|
47245
|
+
return 1 /* IMAGE */;
|
|
47246
|
+
}
|
|
47247
|
+
if (coversFrame(x, y, w, h, frame)) {
|
|
47248
|
+
return 0 /* BG_FILL */;
|
|
47249
|
+
}
|
|
47250
|
+
return 2 /* SHAPE */;
|
|
47251
|
+
}
|
|
47252
|
+
function findInsertIndexByTier(elements, frameIndex, tier) {
|
|
47253
|
+
const frame = elements[frameIndex];
|
|
47254
|
+
const frameId = frame.id;
|
|
47255
|
+
let insertAt = frameIndex + 1;
|
|
47256
|
+
for (let i = frameIndex + 1; i < elements.length; i++) {
|
|
47257
|
+
const el = elements[i];
|
|
47258
|
+
if (el.frameId !== frameId) {
|
|
47259
|
+
continue;
|
|
47260
|
+
}
|
|
47261
|
+
const elTier = getElementTier(
|
|
47262
|
+
el.type,
|
|
47263
|
+
el.x,
|
|
47264
|
+
el.y,
|
|
47265
|
+
el.width,
|
|
47266
|
+
el.height,
|
|
47267
|
+
frame
|
|
47268
|
+
);
|
|
47269
|
+
if (elTier > tier) {
|
|
47270
|
+
return i;
|
|
47271
|
+
}
|
|
47272
|
+
insertAt = i + 1;
|
|
47273
|
+
}
|
|
47274
|
+
return insertAt;
|
|
47275
|
+
}
|
|
47276
|
+
function execCreateFrame(args, ctx) {
|
|
47277
|
+
const width = args.width || 512;
|
|
47278
|
+
const height = args.height || 512;
|
|
47279
|
+
const name = args.name || null;
|
|
47280
|
+
const center = getViewportCenter(ctx.excalidrawAPI);
|
|
47281
|
+
const frame = newFrameElement3({
|
|
47282
|
+
...FRAME_STYLE5,
|
|
47283
|
+
x: center.x - width / 2,
|
|
47284
|
+
y: center.y - height / 2,
|
|
47285
|
+
width,
|
|
47286
|
+
height,
|
|
47287
|
+
opacity: 100,
|
|
47288
|
+
locked: false,
|
|
47289
|
+
name: name ?? void 0
|
|
47290
|
+
});
|
|
47291
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47292
|
+
ctx.excalidrawAPI.updateScene({
|
|
47293
|
+
elements: [...elements, frame]
|
|
47294
|
+
});
|
|
47295
|
+
ctx.excalidrawAPI.scrollToContent(frame, { fitToViewport: false });
|
|
47296
|
+
return {
|
|
47297
|
+
success: true,
|
|
47298
|
+
data: {
|
|
47299
|
+
frameId: frame.id,
|
|
47300
|
+
x: Math.round(frame.x),
|
|
47301
|
+
y: Math.round(frame.y),
|
|
47302
|
+
width,
|
|
47303
|
+
height
|
|
47304
|
+
},
|
|
47305
|
+
statusMessage: `Created frame "${name || "Ad Frame"}" (${width}\xD7${height})`
|
|
47306
|
+
};
|
|
47307
|
+
}
|
|
47308
|
+
function execAddRectangle(args, ctx) {
|
|
47309
|
+
const frameId = args.frameId;
|
|
47310
|
+
if (!frameId) {
|
|
47311
|
+
return {
|
|
47312
|
+
success: false,
|
|
47313
|
+
error: "frameId is required",
|
|
47314
|
+
statusMessage: "Failed: missing frameId"
|
|
47315
|
+
};
|
|
47316
|
+
}
|
|
47317
|
+
const rect = newElement5({
|
|
47318
|
+
type: "rectangle",
|
|
47319
|
+
x: args.x,
|
|
47320
|
+
y: args.y,
|
|
47321
|
+
width: args.width,
|
|
47322
|
+
height: args.height,
|
|
47323
|
+
backgroundColor: args.backgroundColor || "transparent",
|
|
47324
|
+
fillStyle: args.fillStyle || "solid",
|
|
47325
|
+
strokeColor: args.strokeColor || "transparent",
|
|
47326
|
+
strokeWidth: args.strokeWidth ?? 0,
|
|
47327
|
+
opacity: args.opacity ?? 100
|
|
47328
|
+
});
|
|
47329
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47330
|
+
const frameIndex = elements.findIndex((el) => el.id === frameId);
|
|
47331
|
+
if (frameIndex === -1) {
|
|
47332
|
+
return {
|
|
47333
|
+
success: false,
|
|
47334
|
+
error: `Frame ${frameId} not found`,
|
|
47335
|
+
statusMessage: "Failed: frame not found"
|
|
47336
|
+
};
|
|
47337
|
+
}
|
|
47338
|
+
const frame = elements[frameIndex];
|
|
47339
|
+
const tier = getElementTier(
|
|
47340
|
+
"rectangle",
|
|
47341
|
+
args.x,
|
|
47342
|
+
args.y,
|
|
47343
|
+
args.width,
|
|
47344
|
+
args.height,
|
|
47345
|
+
frame
|
|
47346
|
+
);
|
|
47347
|
+
const updatedRect = { ...rect, frameId };
|
|
47348
|
+
const newElements = [...elements];
|
|
47349
|
+
const insertAt = findInsertIndexByTier(newElements, frameIndex, tier);
|
|
47350
|
+
newElements.splice(insertAt, 0, updatedRect);
|
|
47351
|
+
ctx.excalidrawAPI.updateScene({ elements: newElements });
|
|
47352
|
+
const isBg = tier === 0 /* BG_FILL */;
|
|
47353
|
+
return {
|
|
47354
|
+
success: true,
|
|
47355
|
+
data: {
|
|
47356
|
+
elementId: rect.id,
|
|
47357
|
+
type: "rectangle",
|
|
47358
|
+
x: Math.round(rect.x),
|
|
47359
|
+
y: Math.round(rect.y),
|
|
47360
|
+
width: Math.round(rect.width),
|
|
47361
|
+
height: Math.round(rect.height),
|
|
47362
|
+
backgroundColor: rect.backgroundColor,
|
|
47363
|
+
isBackground: isBg
|
|
47364
|
+
},
|
|
47365
|
+
statusMessage: `Added ${isBg ? "background " : ""}rectangle (${Math.round(
|
|
47366
|
+
rect.width
|
|
47367
|
+
)}\xD7${Math.round(rect.height)}) with fill ${rect.backgroundColor}`
|
|
47368
|
+
};
|
|
47369
|
+
}
|
|
47370
|
+
function execAddText(args, ctx) {
|
|
47371
|
+
const frameId = args.frameId;
|
|
47372
|
+
if (!frameId) {
|
|
47373
|
+
return {
|
|
47374
|
+
success: false,
|
|
47375
|
+
error: "frameId is required",
|
|
47376
|
+
statusMessage: "Failed: missing frameId"
|
|
47377
|
+
};
|
|
47378
|
+
}
|
|
47379
|
+
const textEl = newTextElement4({
|
|
47380
|
+
x: args.x,
|
|
47381
|
+
y: args.y,
|
|
47382
|
+
text: args.text,
|
|
47383
|
+
fontSize: args.fontSize || 20,
|
|
47384
|
+
fontFamily: args.fontFamily || 2,
|
|
47385
|
+
strokeColor: args.strokeColor || "#000000",
|
|
47386
|
+
textAlign: args.textAlign || "left",
|
|
47387
|
+
width: args.width
|
|
47388
|
+
});
|
|
47389
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47390
|
+
const frameIndex = elements.findIndex((el) => el.id === frameId);
|
|
47391
|
+
if (frameIndex === -1) {
|
|
47392
|
+
return {
|
|
47393
|
+
success: false,
|
|
47394
|
+
error: `Frame ${frameId} not found`,
|
|
47395
|
+
statusMessage: "Failed: frame not found"
|
|
47396
|
+
};
|
|
47397
|
+
}
|
|
47398
|
+
const updatedText = { ...textEl, frameId };
|
|
47399
|
+
const newElements = [...elements];
|
|
47400
|
+
const insertAt = findInsertIndexByTier(
|
|
47401
|
+
newElements,
|
|
47402
|
+
frameIndex,
|
|
47403
|
+
3 /* TEXT */
|
|
47404
|
+
);
|
|
47405
|
+
newElements.splice(insertAt, 0, updatedText);
|
|
47406
|
+
ctx.excalidrawAPI.updateScene({ elements: newElements });
|
|
47407
|
+
return {
|
|
47408
|
+
success: true,
|
|
47409
|
+
data: {
|
|
47410
|
+
elementId: textEl.id,
|
|
47411
|
+
type: "text",
|
|
47412
|
+
x: Math.round(textEl.x),
|
|
47413
|
+
y: Math.round(textEl.y),
|
|
47414
|
+
text: args.text,
|
|
47415
|
+
fontSize: args.fontSize || 20
|
|
47416
|
+
},
|
|
47417
|
+
statusMessage: `Added text "${args.text.slice(0, 40)}${args.text.length > 40 ? "\u2026" : ""}"`
|
|
47418
|
+
};
|
|
47419
|
+
}
|
|
47420
|
+
async function execGenerateImage(args, ctx) {
|
|
47421
|
+
const frameId = args.frameId;
|
|
47422
|
+
const prompt = args.prompt;
|
|
47423
|
+
const x = args.x;
|
|
47424
|
+
const y = args.y;
|
|
47425
|
+
const width = args.width;
|
|
47426
|
+
const height = args.height;
|
|
47427
|
+
if (!frameId || !prompt) {
|
|
47428
|
+
return {
|
|
47429
|
+
success: false,
|
|
47430
|
+
error: "frameId and prompt are required",
|
|
47431
|
+
statusMessage: "Failed: missing required parameters"
|
|
47432
|
+
};
|
|
47433
|
+
}
|
|
47434
|
+
if (!ctx.geminiApiKey) {
|
|
47435
|
+
return {
|
|
47436
|
+
success: false,
|
|
47437
|
+
error: "No Gemini API key configured. Set geminiApiKey prop or VITE_APP_GEMINI_API_KEY.",
|
|
47438
|
+
statusMessage: "Failed: no Gemini API key"
|
|
47439
|
+
};
|
|
47440
|
+
}
|
|
47441
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47442
|
+
const frameIndex = elements.findIndex((el) => el.id === frameId);
|
|
47443
|
+
if (frameIndex === -1) {
|
|
47444
|
+
return {
|
|
47445
|
+
success: false,
|
|
47446
|
+
error: `Frame ${frameId} not found`,
|
|
47447
|
+
statusMessage: "Failed: frame not found"
|
|
47448
|
+
};
|
|
47449
|
+
}
|
|
47450
|
+
const aspectRatio = width >= height ? width / height > 1.3 ? "16:9" : "1:1" : "9:16";
|
|
47451
|
+
try {
|
|
47452
|
+
const imageDataUrl = await callGeminiAPI(
|
|
47453
|
+
prompt,
|
|
47454
|
+
"gemini-2.5-flash-image",
|
|
47455
|
+
aspectRatio,
|
|
47456
|
+
"1K",
|
|
47457
|
+
false,
|
|
47458
|
+
false,
|
|
47459
|
+
ctx.geminiApiKey,
|
|
47460
|
+
void 0,
|
|
47461
|
+
ctx.signal
|
|
47462
|
+
);
|
|
47463
|
+
if (ctx.signal?.aborted) {
|
|
47464
|
+
return {
|
|
47465
|
+
success: false,
|
|
47466
|
+
error: "Cancelled",
|
|
47467
|
+
statusMessage: "Image generation cancelled"
|
|
47468
|
+
};
|
|
47469
|
+
}
|
|
47470
|
+
const fileId = nanoid2();
|
|
47471
|
+
const imageEl = newImageElement2({
|
|
47472
|
+
type: "image",
|
|
47473
|
+
x,
|
|
47474
|
+
y,
|
|
47475
|
+
width,
|
|
47476
|
+
height,
|
|
47477
|
+
fileId,
|
|
47478
|
+
status: "pending",
|
|
47479
|
+
scale: [1, 1]
|
|
47480
|
+
});
|
|
47481
|
+
const currentElements = ctx.excalidrawAPI.getSceneElements();
|
|
47482
|
+
const currentFrameIndex = currentElements.findIndex(
|
|
47483
|
+
(el) => el.id === frameId
|
|
47484
|
+
);
|
|
47485
|
+
if (currentFrameIndex === -1) {
|
|
47486
|
+
return {
|
|
47487
|
+
success: false,
|
|
47488
|
+
error: `Frame ${frameId} was removed during image generation`,
|
|
47489
|
+
statusMessage: "Failed: frame no longer exists"
|
|
47490
|
+
};
|
|
47491
|
+
}
|
|
47492
|
+
const updatedImage = { ...imageEl, frameId };
|
|
47493
|
+
const newElements = [...currentElements];
|
|
47494
|
+
const insertAt = findInsertIndexByTier(
|
|
47495
|
+
newElements,
|
|
47496
|
+
currentFrameIndex,
|
|
47497
|
+
1 /* IMAGE */
|
|
47498
|
+
);
|
|
47499
|
+
newElements.splice(insertAt, 0, updatedImage);
|
|
47500
|
+
ctx.excalidrawAPI.updateScene({ elements: newElements });
|
|
47501
|
+
ctx.excalidrawAPI.addFiles([
|
|
47502
|
+
{
|
|
47503
|
+
id: fileId,
|
|
47504
|
+
dataURL: imageDataUrl,
|
|
47505
|
+
mimeType: getMimeTypeFromDataURL(imageDataUrl),
|
|
47506
|
+
created: Date.now(),
|
|
47507
|
+
lastRetrieved: Date.now()
|
|
47508
|
+
}
|
|
47509
|
+
]);
|
|
47510
|
+
return {
|
|
47511
|
+
success: true,
|
|
47512
|
+
data: {
|
|
47513
|
+
elementId: imageEl.id,
|
|
47514
|
+
type: "image",
|
|
47515
|
+
x: Math.round(x),
|
|
47516
|
+
y: Math.round(y),
|
|
47517
|
+
width: Math.round(width),
|
|
47518
|
+
height: Math.round(height)
|
|
47519
|
+
},
|
|
47520
|
+
statusMessage: `Generated image from prompt "${prompt.slice(0, 50)}${prompt.length > 50 ? "\u2026" : ""}"`
|
|
47521
|
+
};
|
|
47522
|
+
} catch (err) {
|
|
47523
|
+
const message = err instanceof Error ? err.message : "Image generation failed";
|
|
47524
|
+
return {
|
|
47525
|
+
success: false,
|
|
47526
|
+
error: message,
|
|
47527
|
+
statusMessage: `Image generation failed: ${message}`
|
|
47528
|
+
};
|
|
47529
|
+
}
|
|
47530
|
+
}
|
|
47531
|
+
function execUpdateElement(args, ctx) {
|
|
47532
|
+
const elementId = args.elementId;
|
|
47533
|
+
if (!elementId) {
|
|
47534
|
+
return {
|
|
47535
|
+
success: false,
|
|
47536
|
+
error: "elementId is required",
|
|
47537
|
+
statusMessage: "Failed: missing elementId"
|
|
47538
|
+
};
|
|
47539
|
+
}
|
|
47540
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47541
|
+
const element = elements.find((el) => el.id === elementId);
|
|
47542
|
+
if (!element) {
|
|
47543
|
+
return {
|
|
47544
|
+
success: false,
|
|
47545
|
+
error: `Element ${elementId} not found`,
|
|
47546
|
+
statusMessage: "Failed: element not found"
|
|
47547
|
+
};
|
|
47548
|
+
}
|
|
47549
|
+
const updates = {};
|
|
47550
|
+
const allowedKeys = [
|
|
47551
|
+
"x",
|
|
47552
|
+
"y",
|
|
47553
|
+
"width",
|
|
47554
|
+
"height",
|
|
47555
|
+
"backgroundColor",
|
|
47556
|
+
"strokeColor",
|
|
47557
|
+
"opacity",
|
|
47558
|
+
"fontSize",
|
|
47559
|
+
"text",
|
|
47560
|
+
"fillStyle"
|
|
47561
|
+
];
|
|
47562
|
+
for (const key of allowedKeys) {
|
|
47563
|
+
if (args[key] !== void 0) {
|
|
47564
|
+
updates[key] = args[key];
|
|
47565
|
+
}
|
|
47566
|
+
}
|
|
47567
|
+
if (Object.keys(updates).length === 0) {
|
|
47568
|
+
return {
|
|
47569
|
+
success: false,
|
|
47570
|
+
error: "No valid properties to update",
|
|
47571
|
+
statusMessage: "Failed: nothing to update"
|
|
47572
|
+
};
|
|
47573
|
+
}
|
|
47574
|
+
if (updates.text !== void 0 && element.type === "text" && (updates.width === void 0 || updates.height === void 0)) {
|
|
47575
|
+
return {
|
|
47576
|
+
success: false,
|
|
47577
|
+
error: "Updating text content requires providing new width and height to avoid clipping.",
|
|
47578
|
+
statusMessage: "Failed: text update requires width and height"
|
|
47579
|
+
};
|
|
47580
|
+
}
|
|
47581
|
+
ctx.excalidrawAPI.mutateElement(element, updates);
|
|
47582
|
+
return {
|
|
47583
|
+
success: true,
|
|
47584
|
+
data: { elementId, updated: Object.keys(updates) },
|
|
47585
|
+
statusMessage: `Updated element (${Object.keys(updates).join(", ")})`
|
|
47586
|
+
};
|
|
47587
|
+
}
|
|
47588
|
+
function execDeleteElement(args, ctx) {
|
|
47589
|
+
const elementId = args.elementId;
|
|
47590
|
+
if (!elementId) {
|
|
47591
|
+
return {
|
|
47592
|
+
success: false,
|
|
47593
|
+
error: "elementId is required",
|
|
47594
|
+
statusMessage: "Failed: missing elementId"
|
|
47595
|
+
};
|
|
47596
|
+
}
|
|
47597
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47598
|
+
const element = elements.find((el) => el.id === elementId);
|
|
47599
|
+
if (!element) {
|
|
47600
|
+
return {
|
|
47601
|
+
success: false,
|
|
47602
|
+
error: `Element ${elementId} not found`,
|
|
47603
|
+
statusMessage: "Failed: element not found"
|
|
47604
|
+
};
|
|
47605
|
+
}
|
|
47606
|
+
ctx.excalidrawAPI.mutateElement(element, { isDeleted: true });
|
|
47607
|
+
return {
|
|
47608
|
+
success: true,
|
|
47609
|
+
data: { elementId },
|
|
47610
|
+
statusMessage: `Deleted element ${elementId}`
|
|
47611
|
+
};
|
|
47612
|
+
}
|
|
47613
|
+
function execGetFrameElements(args, ctx) {
|
|
47614
|
+
const frameId = args.frameId;
|
|
47615
|
+
if (!frameId) {
|
|
47616
|
+
return {
|
|
47617
|
+
success: false,
|
|
47618
|
+
error: "frameId is required",
|
|
47619
|
+
statusMessage: "Failed: missing frameId"
|
|
47620
|
+
};
|
|
47621
|
+
}
|
|
47622
|
+
const elements = ctx.excalidrawAPI.getSceneElements();
|
|
47623
|
+
const frame = elements.find((el) => el.id === frameId);
|
|
47624
|
+
if (!frame) {
|
|
47625
|
+
return {
|
|
47626
|
+
success: false,
|
|
47627
|
+
error: `Frame ${frameId} not found`,
|
|
47628
|
+
statusMessage: "Failed: frame not found"
|
|
47629
|
+
};
|
|
47630
|
+
}
|
|
47631
|
+
const children = getFrameChildren7(elements, frameId);
|
|
47632
|
+
const serialized = serializeElements(children);
|
|
47633
|
+
return {
|
|
47634
|
+
success: true,
|
|
47635
|
+
data: {
|
|
47636
|
+
frameId,
|
|
47637
|
+
frame: serializeElement(frame),
|
|
47638
|
+
children: serialized,
|
|
47639
|
+
childCount: serialized.length
|
|
47640
|
+
},
|
|
47641
|
+
statusMessage: `Found ${serialized.length} elements in frame`
|
|
47642
|
+
};
|
|
47643
|
+
}
|
|
47644
|
+
|
|
47645
|
+
// components/ai-chat/agentLoop.ts
|
|
47646
|
+
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.
|
|
47647
|
+
|
|
47648
|
+
## Your Capabilities
|
|
47649
|
+
You have tools to: create frames, add rectangles (for backgrounds/shapes), add text, generate AI images, update elements, delete elements, and inspect frame contents.
|
|
47650
|
+
|
|
47651
|
+
## Design Process
|
|
47652
|
+
When asked to create an ad or visual from scratch:
|
|
47653
|
+
1. **Create a frame** first (default 512\xD7512 unless the user specifies a size). This is the artboard.
|
|
47654
|
+
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.
|
|
47655
|
+
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.
|
|
47656
|
+
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).
|
|
47657
|
+
5. **Add subhead text** \u2014 smaller supporting text below the headline.
|
|
47658
|
+
|
|
47659
|
+
## Layout & Z-ordering Rules
|
|
47660
|
+
- Elements are rendered in insertion order. Add background rectangle FIRST, then images, then text ON TOP.
|
|
47661
|
+
- All element positions use absolute canvas coordinates. The frame's x,y is the top-left corner.
|
|
47662
|
+
- To place an element at a frame's top-left: use the frame's x,y values.
|
|
47663
|
+
- To center text horizontally in a frame: set text x = frame.x + (frame.width - estimated_text_width) / 2
|
|
47664
|
+
- Leave padding from edges (at least 20px from frame borders for text).
|
|
47665
|
+
- Headline font size: 28-48px depending on frame size. Subhead: 16-24px.
|
|
47666
|
+
|
|
47667
|
+
## Color & Typography
|
|
47668
|
+
- Use font family 2 (Nunito/clean sans-serif) for professional ads.
|
|
47669
|
+
- For dark backgrounds: use white (#ffffff) or light text.
|
|
47670
|
+
- For light backgrounds: use dark (#1a1a2e or #000000) text.
|
|
47671
|
+
- Popular ad background colors: deep navy (#1a1a2e), warm red (#c0392b), forest green (#27ae60), elegant black (#111111), soft cream (#fdf6e3).
|
|
47672
|
+
|
|
47673
|
+
## When modifying existing elements (@ context provided)
|
|
47674
|
+
- The user may reference existing elements via @ mentions. Their current state will be described in the message.
|
|
47675
|
+
- Use get_frame_elements to see the full current state of a frame before making changes.
|
|
47676
|
+
- Use update_element to modify properties. Use delete_element to remove things.
|
|
47677
|
+
- Preserve existing elements unless explicitly asked to change them.
|
|
47678
|
+
|
|
47679
|
+
## Image Generation Tips
|
|
47680
|
+
- Write detailed, descriptive prompts for generate_image. Include style, mood, composition, colors.
|
|
47681
|
+
- Example: "A steaming bowl of creamy pasta on a rustic wooden table, warm golden lighting, top-down view, food photography style, appetizing, high quality"
|
|
47682
|
+
- The image will be generated by Gemini AI and placed at the coordinates you specify.
|
|
47683
|
+
|
|
47684
|
+
## Important
|
|
47685
|
+
- Always respond with a brief summary of what you created/modified after completing the tools.
|
|
47686
|
+
- If the user's request is unclear, use the available tools to create a reasonable default and explain what you did.
|
|
47687
|
+
- Coordinates: frame x,y is the top-left corner. Width extends right, height extends down.`;
|
|
47688
|
+
var MAX_ITERATIONS = 15;
|
|
47689
|
+
async function callOpenRouter(messages, apiKey, signal, webSearchEnabled) {
|
|
47690
|
+
const model = webSearchEnabled ? "openai/gpt-4.1-mini:online" : "openai/gpt-4.1-mini";
|
|
47691
|
+
const body = {
|
|
47692
|
+
model,
|
|
47693
|
+
messages: messages.map((m) => {
|
|
47694
|
+
if (m.role === "assistant" && m.tool_calls) {
|
|
47695
|
+
return {
|
|
47696
|
+
role: m.role,
|
|
47697
|
+
content: m.content,
|
|
47698
|
+
tool_calls: m.tool_calls
|
|
47699
|
+
};
|
|
47700
|
+
}
|
|
47701
|
+
if (m.role === "tool") {
|
|
47702
|
+
return {
|
|
47703
|
+
role: m.role,
|
|
47704
|
+
tool_call_id: m.tool_call_id,
|
|
47705
|
+
content: m.content
|
|
47706
|
+
};
|
|
47707
|
+
}
|
|
47708
|
+
return { role: m.role, content: m.content };
|
|
47709
|
+
}),
|
|
47710
|
+
tools: CANVAS_TOOLS,
|
|
47711
|
+
tool_choice: "auto"
|
|
47712
|
+
};
|
|
47713
|
+
if (webSearchEnabled) {
|
|
47714
|
+
body.plugins = [{ id: "web", max_results: 5 }];
|
|
47715
|
+
}
|
|
47716
|
+
const response = await fetch(
|
|
47717
|
+
"https://openrouter.ai/api/v1/chat/completions",
|
|
47718
|
+
{
|
|
47719
|
+
method: "POST",
|
|
47720
|
+
signal,
|
|
47721
|
+
headers: {
|
|
47722
|
+
Authorization: `Bearer ${apiKey}`,
|
|
47723
|
+
"Content-Type": "application/json"
|
|
47724
|
+
},
|
|
47725
|
+
body: JSON.stringify(body)
|
|
47726
|
+
}
|
|
47727
|
+
);
|
|
47728
|
+
if (!response.ok) {
|
|
47729
|
+
let message = `OpenRouter error ${response.status}`;
|
|
47730
|
+
try {
|
|
47731
|
+
const err = await response.json();
|
|
47732
|
+
if (err?.error?.message) {
|
|
47733
|
+
message = err.error.message;
|
|
47734
|
+
}
|
|
47735
|
+
} catch {
|
|
47736
|
+
}
|
|
47737
|
+
throw new Error(message);
|
|
47738
|
+
}
|
|
47739
|
+
const data = await response.json();
|
|
47740
|
+
const choice = data?.choices?.[0]?.message;
|
|
47741
|
+
if (!choice) {
|
|
47742
|
+
throw new Error("No response returned by the API.");
|
|
47743
|
+
}
|
|
47744
|
+
return {
|
|
47745
|
+
content: choice.content ?? null,
|
|
47746
|
+
tool_calls: choice.tool_calls ?? void 0
|
|
47747
|
+
};
|
|
47748
|
+
}
|
|
47749
|
+
async function runAgentLoop(opts) {
|
|
47750
|
+
const {
|
|
47751
|
+
userMessages,
|
|
47752
|
+
elementContext,
|
|
47753
|
+
frameScreenshot,
|
|
47754
|
+
fileAttachments,
|
|
47755
|
+
webSearchEnabled,
|
|
47756
|
+
apiKey,
|
|
47757
|
+
toolCtx,
|
|
47758
|
+
onUpdate,
|
|
47759
|
+
signal,
|
|
47760
|
+
onBeforeImageGen,
|
|
47761
|
+
onAfterImageGen
|
|
47762
|
+
} = opts;
|
|
47763
|
+
const messages = [{ role: "system", content: SYSTEM_PROMPT }];
|
|
47764
|
+
if (elementContext) {
|
|
47765
|
+
messages.push({
|
|
47766
|
+
role: "system",
|
|
47767
|
+
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:
|
|
47768
|
+
|
|
47769
|
+
${elementContext}`
|
|
47770
|
+
});
|
|
47771
|
+
}
|
|
47772
|
+
for (let i = 0; i < userMessages.length; i++) {
|
|
47773
|
+
const msg = userMessages[i];
|
|
47774
|
+
const isLast = i === userMessages.length - 1;
|
|
47775
|
+
const hasMultimodal = isLast && msg.role === "user" && (frameScreenshot || fileAttachments && fileAttachments.some((a) => a.type === "image"));
|
|
47776
|
+
const hasTextFiles = isLast && msg.role === "user" && fileAttachments && fileAttachments.some((a) => a.type === "text");
|
|
47777
|
+
if (hasMultimodal || hasTextFiles) {
|
|
47778
|
+
const contentParts = [];
|
|
47779
|
+
if (frameScreenshot) {
|
|
47780
|
+
contentParts.push({
|
|
47781
|
+
type: "image_url",
|
|
47782
|
+
image_url: { url: frameScreenshot }
|
|
47783
|
+
});
|
|
47784
|
+
}
|
|
47785
|
+
if (fileAttachments) {
|
|
47786
|
+
for (const att of fileAttachments) {
|
|
47787
|
+
if (att.type === "image" && att.dataUrl) {
|
|
47788
|
+
contentParts.push({
|
|
47789
|
+
type: "image_url",
|
|
47790
|
+
image_url: { url: att.dataUrl }
|
|
47791
|
+
});
|
|
47792
|
+
}
|
|
47793
|
+
}
|
|
47794
|
+
}
|
|
47795
|
+
let text = msg.content;
|
|
47796
|
+
if (frameScreenshot) {
|
|
47797
|
+
text = `[Screenshot of the referenced frame is attached above]
|
|
47798
|
+
|
|
47799
|
+
${text}`;
|
|
47800
|
+
}
|
|
47801
|
+
if (fileAttachments) {
|
|
47802
|
+
const textFiles = fileAttachments.filter((a) => a.type === "text");
|
|
47803
|
+
if (textFiles.length > 0) {
|
|
47804
|
+
text += `
|
|
47805
|
+
|
|
47806
|
+
${textFiles.map((f) => `[File: ${f.name}]
|
|
47807
|
+
${f.textContent}`).join("\n\n")}`;
|
|
47808
|
+
}
|
|
47809
|
+
}
|
|
47810
|
+
contentParts.push({ type: "text", text });
|
|
47811
|
+
messages.push({ role: "user", content: contentParts });
|
|
47812
|
+
} else {
|
|
47813
|
+
messages.push({ role: msg.role, content: msg.content });
|
|
47814
|
+
}
|
|
47815
|
+
}
|
|
47816
|
+
const toolActions = [];
|
|
47817
|
+
let iterations = 0;
|
|
47818
|
+
while (iterations < MAX_ITERATIONS) {
|
|
47819
|
+
iterations++;
|
|
47820
|
+
onUpdate({
|
|
47821
|
+
type: "status",
|
|
47822
|
+
message: iterations === 1 ? "Thinking\u2026" : "Processing tool results\u2026"
|
|
47823
|
+
});
|
|
47824
|
+
const response = await callOpenRouter(
|
|
47825
|
+
messages,
|
|
47826
|
+
apiKey,
|
|
47827
|
+
signal,
|
|
47828
|
+
webSearchEnabled
|
|
47829
|
+
);
|
|
47830
|
+
if (!response.tool_calls || response.tool_calls.length === 0) {
|
|
47831
|
+
const reply = response.content || "Done!";
|
|
47832
|
+
onUpdate({ type: "final", message: reply });
|
|
47833
|
+
return { reply, toolActions };
|
|
47834
|
+
}
|
|
47835
|
+
messages.push({
|
|
47836
|
+
role: "assistant",
|
|
47837
|
+
content: response.content,
|
|
47838
|
+
tool_calls: response.tool_calls
|
|
47839
|
+
});
|
|
47840
|
+
for (const toolCall of response.tool_calls) {
|
|
47841
|
+
const { name, arguments: rawArgs } = toolCall.function;
|
|
47842
|
+
let parsedArgs = {};
|
|
47843
|
+
try {
|
|
47844
|
+
parsedArgs = JSON.parse(rawArgs);
|
|
47845
|
+
} catch {
|
|
47846
|
+
}
|
|
47847
|
+
onUpdate({
|
|
47848
|
+
type: "tool_start",
|
|
47849
|
+
message: formatToolStartMessage(name, parsedArgs)
|
|
47850
|
+
});
|
|
47851
|
+
if (name === "generate_image" && onBeforeImageGen) {
|
|
47852
|
+
const { allowed, error } = await onBeforeImageGen();
|
|
47853
|
+
if (!allowed) {
|
|
47854
|
+
const gatedResult = {
|
|
47855
|
+
success: false,
|
|
47856
|
+
error: error || "Insufficient credits for image generation",
|
|
47857
|
+
statusMessage: error || "Insufficient credits for image generation"
|
|
47858
|
+
};
|
|
47859
|
+
const gatedAction = {
|
|
47860
|
+
toolName: name,
|
|
47861
|
+
args: parsedArgs,
|
|
47862
|
+
result: gatedResult
|
|
47863
|
+
};
|
|
47864
|
+
toolActions.push(gatedAction);
|
|
47865
|
+
onUpdate({
|
|
47866
|
+
type: "tool_done",
|
|
47867
|
+
message: gatedResult.statusMessage,
|
|
47868
|
+
toolAction: gatedAction
|
|
47869
|
+
});
|
|
47870
|
+
messages.push({
|
|
47871
|
+
role: "tool",
|
|
47872
|
+
tool_call_id: toolCall.id,
|
|
47873
|
+
content: JSON.stringify({
|
|
47874
|
+
success: false,
|
|
47875
|
+
error: gatedResult.error
|
|
47876
|
+
})
|
|
47877
|
+
});
|
|
47878
|
+
continue;
|
|
47879
|
+
}
|
|
47880
|
+
}
|
|
47881
|
+
const result = await executeCanvasTool(name, rawArgs, toolCtx);
|
|
47882
|
+
if (name === "generate_image" && result.success && onAfterImageGen) {
|
|
47883
|
+
onAfterImageGen();
|
|
47884
|
+
}
|
|
47885
|
+
const action = {
|
|
47886
|
+
toolName: name,
|
|
47887
|
+
args: parsedArgs,
|
|
47888
|
+
result
|
|
47889
|
+
};
|
|
47890
|
+
toolActions.push(action);
|
|
47891
|
+
onUpdate({
|
|
47892
|
+
type: "tool_done",
|
|
47893
|
+
message: result.statusMessage,
|
|
47894
|
+
toolAction: action
|
|
47895
|
+
});
|
|
47896
|
+
messages.push({
|
|
47897
|
+
role: "tool",
|
|
47898
|
+
tool_call_id: toolCall.id,
|
|
47899
|
+
content: JSON.stringify(
|
|
47900
|
+
result.success ? { success: true, ...result.data } : { success: false, error: result.error }
|
|
47901
|
+
)
|
|
47902
|
+
});
|
|
47903
|
+
}
|
|
47904
|
+
}
|
|
47905
|
+
const fallbackReply = "I've completed the available operations. Let me know if you'd like any adjustments!";
|
|
47906
|
+
onUpdate({ type: "final", message: fallbackReply });
|
|
47907
|
+
return { reply: fallbackReply, toolActions };
|
|
47908
|
+
}
|
|
47909
|
+
function formatToolStartMessage(name, args) {
|
|
47910
|
+
switch (name) {
|
|
47911
|
+
case "create_frame":
|
|
47912
|
+
return `Creating frame (${args.width || 512}\xD7${args.height || 512})\u2026`;
|
|
47913
|
+
case "add_rectangle":
|
|
47914
|
+
return `Adding background rectangle\u2026`;
|
|
47915
|
+
case "add_text":
|
|
47916
|
+
return `Adding text "${String(args.text || "").slice(0, 30)}"\u2026`;
|
|
47917
|
+
case "generate_image":
|
|
47918
|
+
return `Generating image \u2014 this may take a moment\u2026`;
|
|
47919
|
+
case "update_element":
|
|
47920
|
+
return `Updating element\u2026`;
|
|
47921
|
+
case "delete_element":
|
|
47922
|
+
return `Deleting element\u2026`;
|
|
47923
|
+
case "get_frame_elements":
|
|
47924
|
+
return `Inspecting frame contents\u2026`;
|
|
47925
|
+
default:
|
|
47926
|
+
return `Running ${name}\u2026`;
|
|
47927
|
+
}
|
|
47928
|
+
}
|
|
47929
|
+
|
|
47930
|
+
// components/ai-chat/audioUtils.ts
|
|
47931
|
+
async function blobToWavBase64(blob) {
|
|
47932
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
47933
|
+
const audioCtx = new AudioContext();
|
|
47934
|
+
const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
|
|
47935
|
+
await audioCtx.close();
|
|
47936
|
+
const numChannels = audioBuffer.numberOfChannels;
|
|
47937
|
+
const sampleRate = audioBuffer.sampleRate;
|
|
47938
|
+
const numSamples = audioBuffer.length;
|
|
47939
|
+
const bytesPerSample = 2;
|
|
47940
|
+
const dataSize = numSamples * numChannels * bytesPerSample;
|
|
47941
|
+
const bufferSize = 44 + dataSize;
|
|
47942
|
+
const wavBuffer = new ArrayBuffer(bufferSize);
|
|
47943
|
+
const view = new DataView(wavBuffer);
|
|
47944
|
+
const writeStr = (offset2, str) => {
|
|
47945
|
+
for (let i = 0; i < str.length; i++) {
|
|
47946
|
+
view.setUint8(offset2 + i, str.charCodeAt(i));
|
|
47947
|
+
}
|
|
47948
|
+
};
|
|
47949
|
+
writeStr(0, "RIFF");
|
|
47950
|
+
view.setUint32(4, 36 + dataSize, true);
|
|
47951
|
+
writeStr(8, "WAVE");
|
|
47952
|
+
writeStr(12, "fmt ");
|
|
47953
|
+
view.setUint32(16, 16, true);
|
|
47954
|
+
view.setUint16(20, 1, true);
|
|
47955
|
+
view.setUint16(22, numChannels, true);
|
|
47956
|
+
view.setUint32(24, sampleRate, true);
|
|
47957
|
+
view.setUint32(28, sampleRate * numChannels * bytesPerSample, true);
|
|
47958
|
+
view.setUint16(32, numChannels * bytesPerSample, true);
|
|
47959
|
+
view.setUint16(34, 16, true);
|
|
47960
|
+
writeStr(36, "data");
|
|
47961
|
+
view.setUint32(40, dataSize, true);
|
|
47962
|
+
let offset = 44;
|
|
47963
|
+
for (let i = 0; i < numSamples; i++) {
|
|
47964
|
+
for (let ch = 0; ch < numChannels; ch++) {
|
|
47965
|
+
const sample = audioBuffer.getChannelData(ch)[i];
|
|
47966
|
+
const clamped = Math.max(-1, Math.min(1, sample));
|
|
47967
|
+
const int16 = clamped < 0 ? clamped * 32768 : clamped * 32767;
|
|
47968
|
+
view.setInt16(offset, int16, true);
|
|
47969
|
+
offset += 2;
|
|
47970
|
+
}
|
|
47971
|
+
}
|
|
47972
|
+
const uint8 = new Uint8Array(wavBuffer);
|
|
47973
|
+
const CHUNK = 8192;
|
|
47974
|
+
let binary = "";
|
|
47975
|
+
for (let i = 0; i < uint8.length; i += CHUNK) {
|
|
47976
|
+
binary += String.fromCharCode(...uint8.subarray(i, i + CHUNK));
|
|
47977
|
+
}
|
|
47978
|
+
return btoa(binary);
|
|
47979
|
+
}
|
|
47980
|
+
async function transcribeAudio(base64Audio, format, apiKey) {
|
|
47981
|
+
const response = await fetch(
|
|
47982
|
+
"https://openrouter.ai/api/v1/chat/completions",
|
|
47983
|
+
{
|
|
47984
|
+
method: "POST",
|
|
47985
|
+
headers: {
|
|
47986
|
+
Authorization: `Bearer ${apiKey}`,
|
|
47987
|
+
"Content-Type": "application/json"
|
|
47988
|
+
},
|
|
47989
|
+
body: JSON.stringify({
|
|
47990
|
+
model: "mistralai/voxtral-small-24b-2507",
|
|
47991
|
+
messages: [
|
|
47992
|
+
{
|
|
47993
|
+
role: "user",
|
|
47994
|
+
content: [
|
|
47995
|
+
{
|
|
47996
|
+
type: "text",
|
|
47997
|
+
text: "Transcribe this audio. Return only the transcription text, nothing else."
|
|
47998
|
+
},
|
|
47999
|
+
{
|
|
48000
|
+
type: "input_audio",
|
|
48001
|
+
input_audio: { data: base64Audio, format }
|
|
48002
|
+
}
|
|
48003
|
+
]
|
|
48004
|
+
}
|
|
48005
|
+
]
|
|
48006
|
+
})
|
|
48007
|
+
}
|
|
48008
|
+
);
|
|
48009
|
+
if (!response.ok) {
|
|
48010
|
+
let message = `Transcription error ${response.status}`;
|
|
48011
|
+
try {
|
|
48012
|
+
const err = await response.json();
|
|
48013
|
+
if (err?.error?.message) {
|
|
48014
|
+
message = err.error.message;
|
|
48015
|
+
}
|
|
48016
|
+
} catch {
|
|
48017
|
+
}
|
|
48018
|
+
throw new Error(message);
|
|
48019
|
+
}
|
|
48020
|
+
const data = await response.json();
|
|
48021
|
+
return data?.choices?.[0]?.message?.content || "";
|
|
48022
|
+
}
|
|
48023
|
+
|
|
48024
|
+
// components/ui/chat-container.tsx
|
|
48025
|
+
import { StickToBottom } from "use-stick-to-bottom";
|
|
46521
48026
|
import { jsx as jsx177 } from "react/jsx-runtime";
|
|
48027
|
+
function ChatContainerRoot({
|
|
48028
|
+
children,
|
|
48029
|
+
className,
|
|
48030
|
+
...props
|
|
48031
|
+
}) {
|
|
48032
|
+
return /* @__PURE__ */ jsx177(
|
|
48033
|
+
StickToBottom,
|
|
48034
|
+
{
|
|
48035
|
+
className: cn("flex overflow-y-auto", className),
|
|
48036
|
+
resize: "smooth",
|
|
48037
|
+
initial: "instant",
|
|
48038
|
+
role: "log",
|
|
48039
|
+
...props,
|
|
48040
|
+
children
|
|
48041
|
+
}
|
|
48042
|
+
);
|
|
48043
|
+
}
|
|
48044
|
+
function ChatContainerContent({
|
|
48045
|
+
children,
|
|
48046
|
+
className,
|
|
48047
|
+
...props
|
|
48048
|
+
}) {
|
|
48049
|
+
return /* @__PURE__ */ jsx177(
|
|
48050
|
+
StickToBottom.Content,
|
|
48051
|
+
{
|
|
48052
|
+
className: cn("flex w-full flex-col", className),
|
|
48053
|
+
...props,
|
|
48054
|
+
children
|
|
48055
|
+
}
|
|
48056
|
+
);
|
|
48057
|
+
}
|
|
48058
|
+
|
|
48059
|
+
// components/ui/markdown.tsx
|
|
48060
|
+
import { marked } from "marked";
|
|
48061
|
+
import { memo as memo6, useId, useMemo as useMemo13 } from "react";
|
|
48062
|
+
import ReactMarkdown from "react-markdown";
|
|
48063
|
+
import remarkBreaks from "remark-breaks";
|
|
48064
|
+
import remarkGfm from "remark-gfm";
|
|
48065
|
+
|
|
48066
|
+
// components/ui/code-block.tsx
|
|
48067
|
+
import { useEffect as useEffect54, useState as useState48 } from "react";
|
|
48068
|
+
import { codeToHtml } from "shiki";
|
|
48069
|
+
import { jsx as jsx178 } from "react/jsx-runtime";
|
|
48070
|
+
function CodeBlock({ children, className, ...props }) {
|
|
48071
|
+
return /* @__PURE__ */ jsx178(
|
|
48072
|
+
"div",
|
|
48073
|
+
{
|
|
48074
|
+
className: cn(
|
|
48075
|
+
"not-prose flex w-full flex-col overflow-clip border",
|
|
48076
|
+
"border-zinc-200 bg-white text-zinc-950 rounded-xl dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50",
|
|
48077
|
+
className
|
|
48078
|
+
),
|
|
48079
|
+
...props,
|
|
48080
|
+
children
|
|
48081
|
+
}
|
|
48082
|
+
);
|
|
48083
|
+
}
|
|
48084
|
+
function CodeBlockCode({
|
|
48085
|
+
code,
|
|
48086
|
+
language = "tsx",
|
|
48087
|
+
theme = "github-light",
|
|
48088
|
+
className,
|
|
48089
|
+
...props
|
|
48090
|
+
}) {
|
|
48091
|
+
const [highlightedHtml, setHighlightedHtml] = useState48(null);
|
|
48092
|
+
useEffect54(() => {
|
|
48093
|
+
async function highlight() {
|
|
48094
|
+
if (!code) {
|
|
48095
|
+
setHighlightedHtml("<pre><code></code></pre>");
|
|
48096
|
+
return;
|
|
48097
|
+
}
|
|
48098
|
+
const html = await codeToHtml(code, { lang: language, theme });
|
|
48099
|
+
setHighlightedHtml(html);
|
|
48100
|
+
}
|
|
48101
|
+
highlight();
|
|
48102
|
+
}, [code, language, theme]);
|
|
48103
|
+
const classNames = cn(
|
|
48104
|
+
"w-full overflow-x-auto text-[13px] [&>pre]:px-4 [&>pre]:py-4",
|
|
48105
|
+
className
|
|
48106
|
+
);
|
|
48107
|
+
return highlightedHtml ? /* @__PURE__ */ jsx178(
|
|
48108
|
+
"div",
|
|
48109
|
+
{
|
|
48110
|
+
className: classNames,
|
|
48111
|
+
dangerouslySetInnerHTML: { __html: highlightedHtml },
|
|
48112
|
+
...props
|
|
48113
|
+
}
|
|
48114
|
+
) : /* @__PURE__ */ jsx178("div", { className: classNames, ...props, children: /* @__PURE__ */ jsx178("pre", { children: /* @__PURE__ */ jsx178("code", { children: code }) }) });
|
|
48115
|
+
}
|
|
48116
|
+
|
|
48117
|
+
// components/ui/markdown.tsx
|
|
48118
|
+
import { Fragment as Fragment33, jsx as jsx179 } from "react/jsx-runtime";
|
|
48119
|
+
function parseMarkdownIntoBlocks(markdown) {
|
|
48120
|
+
const tokens = marked.lexer(markdown);
|
|
48121
|
+
return tokens.map((token) => token.raw);
|
|
48122
|
+
}
|
|
48123
|
+
function extractLanguage(className) {
|
|
48124
|
+
if (!className) {
|
|
48125
|
+
return "plaintext";
|
|
48126
|
+
}
|
|
48127
|
+
const match = className.match(/language-(\w+)/);
|
|
48128
|
+
return match ? match[1] : "plaintext";
|
|
48129
|
+
}
|
|
48130
|
+
var INITIAL_COMPONENTS = {
|
|
48131
|
+
code: function CodeComponent({ className, children, ...props }) {
|
|
48132
|
+
const isInline = !props.node?.position?.start.line || props.node?.position?.start.line === props.node?.position?.end.line;
|
|
48133
|
+
if (isInline) {
|
|
48134
|
+
return /* @__PURE__ */ jsx179(
|
|
48135
|
+
"span",
|
|
48136
|
+
{
|
|
48137
|
+
className: cn(
|
|
48138
|
+
"bg-zinc-50 rounded-sm px-1 font-mono text-sm dark:bg-zinc-900",
|
|
48139
|
+
className
|
|
48140
|
+
),
|
|
48141
|
+
...props,
|
|
48142
|
+
children
|
|
48143
|
+
}
|
|
48144
|
+
);
|
|
48145
|
+
}
|
|
48146
|
+
const language = extractLanguage(className);
|
|
48147
|
+
return /* @__PURE__ */ jsx179(CodeBlock, { className, children: /* @__PURE__ */ jsx179(CodeBlockCode, { code: children, language }) });
|
|
48148
|
+
},
|
|
48149
|
+
pre: function PreComponent({ children }) {
|
|
48150
|
+
return /* @__PURE__ */ jsx179(Fragment33, { children });
|
|
48151
|
+
}
|
|
48152
|
+
};
|
|
48153
|
+
var MemoizedMarkdownBlock = memo6(
|
|
48154
|
+
({
|
|
48155
|
+
content,
|
|
48156
|
+
components = INITIAL_COMPONENTS
|
|
48157
|
+
}) => {
|
|
48158
|
+
return /* @__PURE__ */ jsx179(
|
|
48159
|
+
ReactMarkdown,
|
|
48160
|
+
{
|
|
48161
|
+
remarkPlugins: [remarkGfm, remarkBreaks],
|
|
48162
|
+
components,
|
|
48163
|
+
children: content
|
|
48164
|
+
}
|
|
48165
|
+
);
|
|
48166
|
+
},
|
|
48167
|
+
(prevProps, nextProps) => {
|
|
48168
|
+
return prevProps.content === nextProps.content;
|
|
48169
|
+
}
|
|
48170
|
+
);
|
|
48171
|
+
MemoizedMarkdownBlock.displayName = "MemoizedMarkdownBlock";
|
|
48172
|
+
function MarkdownComponent({
|
|
48173
|
+
children,
|
|
48174
|
+
id,
|
|
48175
|
+
className,
|
|
48176
|
+
components = INITIAL_COMPONENTS
|
|
48177
|
+
}) {
|
|
48178
|
+
const generatedId = useId();
|
|
48179
|
+
const blockId = id ?? generatedId;
|
|
48180
|
+
const blocks = useMemo13(() => parseMarkdownIntoBlocks(children), [children]);
|
|
48181
|
+
return /* @__PURE__ */ jsx179("div", { className, children: blocks.map((block, index) => /* @__PURE__ */ jsx179(
|
|
48182
|
+
MemoizedMarkdownBlock,
|
|
48183
|
+
{
|
|
48184
|
+
content: block,
|
|
48185
|
+
components
|
|
48186
|
+
},
|
|
48187
|
+
`${blockId}-block-${index}`
|
|
48188
|
+
)) });
|
|
48189
|
+
}
|
|
48190
|
+
var Markdown = memo6(MarkdownComponent);
|
|
48191
|
+
Markdown.displayName = "Markdown";
|
|
48192
|
+
|
|
48193
|
+
// components/ui/avatar.tsx
|
|
48194
|
+
import * as React55 from "react";
|
|
48195
|
+
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
|
48196
|
+
import { jsx as jsx180 } from "react/jsx-runtime";
|
|
48197
|
+
var Avatar2 = React55.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx180(
|
|
48198
|
+
AvatarPrimitive.Root,
|
|
48199
|
+
{
|
|
48200
|
+
ref,
|
|
48201
|
+
className: cn(
|
|
48202
|
+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
|
48203
|
+
className
|
|
48204
|
+
),
|
|
48205
|
+
...props
|
|
48206
|
+
}
|
|
48207
|
+
));
|
|
48208
|
+
Avatar2.displayName = AvatarPrimitive.Root.displayName;
|
|
48209
|
+
var AvatarImage = React55.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx180(
|
|
48210
|
+
AvatarPrimitive.Image,
|
|
48211
|
+
{
|
|
48212
|
+
ref,
|
|
48213
|
+
className: cn("aspect-square h-full w-full", className),
|
|
48214
|
+
...props
|
|
48215
|
+
}
|
|
48216
|
+
));
|
|
48217
|
+
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
|
|
48218
|
+
var AvatarFallback = React55.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx180(
|
|
48219
|
+
AvatarPrimitive.Fallback,
|
|
48220
|
+
{
|
|
48221
|
+
ref,
|
|
48222
|
+
className: cn(
|
|
48223
|
+
"flex h-full w-full items-center justify-center rounded-full bg-zinc-100 dark:bg-zinc-800",
|
|
48224
|
+
className
|
|
48225
|
+
),
|
|
48226
|
+
...props
|
|
48227
|
+
}
|
|
48228
|
+
));
|
|
48229
|
+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
|
48230
|
+
|
|
48231
|
+
// components/ui/message.tsx
|
|
48232
|
+
import { jsx as jsx181, jsxs as jsxs99 } from "react/jsx-runtime";
|
|
48233
|
+
var Message = ({ children, className, ...props }) => /* @__PURE__ */ jsx181("div", { className: cn("flex gap-3", className), ...props, children });
|
|
48234
|
+
var MessageContent = ({
|
|
48235
|
+
children,
|
|
48236
|
+
markdown = false,
|
|
48237
|
+
className,
|
|
48238
|
+
...props
|
|
48239
|
+
}) => {
|
|
48240
|
+
const classNames = cn(
|
|
48241
|
+
"rounded-lg p-2 text-zinc-950 bg-zinc-100 prose break-words whitespace-normal dark:text-zinc-50 dark:bg-zinc-800",
|
|
48242
|
+
className
|
|
48243
|
+
);
|
|
48244
|
+
return markdown ? /* @__PURE__ */ jsx181(Markdown, { className: classNames, ...props, children }) : /* @__PURE__ */ jsx181("div", { className: classNames, ...props, children });
|
|
48245
|
+
};
|
|
48246
|
+
var MessageActions = ({
|
|
48247
|
+
children,
|
|
48248
|
+
className,
|
|
48249
|
+
...props
|
|
48250
|
+
}) => /* @__PURE__ */ jsx181(
|
|
48251
|
+
"div",
|
|
48252
|
+
{
|
|
48253
|
+
className: cn(
|
|
48254
|
+
"text-zinc-500 flex items-center gap-2 dark:text-zinc-400",
|
|
48255
|
+
className
|
|
48256
|
+
),
|
|
48257
|
+
...props,
|
|
48258
|
+
children
|
|
48259
|
+
}
|
|
48260
|
+
);
|
|
48261
|
+
var MessageAction = ({
|
|
48262
|
+
tooltip,
|
|
48263
|
+
children,
|
|
48264
|
+
className,
|
|
48265
|
+
side = "top",
|
|
48266
|
+
...props
|
|
48267
|
+
}) => {
|
|
48268
|
+
return /* @__PURE__ */ jsx181(TooltipProvider, { children: /* @__PURE__ */ jsxs99(Tooltip2, { ...props, children: [
|
|
48269
|
+
/* @__PURE__ */ jsx181(TooltipTrigger, { asChild: true, children }),
|
|
48270
|
+
/* @__PURE__ */ jsx181(TooltipContent, { side, className, children: tooltip })
|
|
48271
|
+
] }) });
|
|
48272
|
+
};
|
|
48273
|
+
|
|
48274
|
+
// components/ui/scroll-button.tsx
|
|
48275
|
+
import { ChevronDown } from "lucide-react";
|
|
48276
|
+
import { useStickToBottomContext } from "use-stick-to-bottom";
|
|
48277
|
+
|
|
48278
|
+
// components/ui/button.tsx
|
|
48279
|
+
import * as React56 from "react";
|
|
48280
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
48281
|
+
import { cva } from "class-variance-authority";
|
|
48282
|
+
import { jsx as jsx182 } from "react/jsx-runtime";
|
|
48283
|
+
var buttonVariants = cva(
|
|
48284
|
+
"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",
|
|
48285
|
+
{
|
|
48286
|
+
variants: {
|
|
48287
|
+
variant: {
|
|
48288
|
+
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",
|
|
48289
|
+
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",
|
|
48290
|
+
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",
|
|
48291
|
+
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",
|
|
48292
|
+
ghost: "hover:bg-zinc-100 hover:text-zinc-900 dark:hover:bg-zinc-800 dark:hover:text-zinc-50",
|
|
48293
|
+
link: "text-zinc-900 underline-offset-4 hover:underline dark:text-zinc-50"
|
|
48294
|
+
},
|
|
48295
|
+
size: {
|
|
48296
|
+
default: "h-9 px-4 py-2",
|
|
48297
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
48298
|
+
lg: "h-10 rounded-md px-8",
|
|
48299
|
+
icon: "h-9 w-9"
|
|
48300
|
+
}
|
|
48301
|
+
},
|
|
48302
|
+
defaultVariants: {
|
|
48303
|
+
variant: "default",
|
|
48304
|
+
size: "default"
|
|
48305
|
+
}
|
|
48306
|
+
}
|
|
48307
|
+
);
|
|
48308
|
+
var Button2 = React56.forwardRef(
|
|
48309
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
48310
|
+
const Comp = asChild ? Slot : "button";
|
|
48311
|
+
return /* @__PURE__ */ jsx182(
|
|
48312
|
+
Comp,
|
|
48313
|
+
{
|
|
48314
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
48315
|
+
ref,
|
|
48316
|
+
...props
|
|
48317
|
+
}
|
|
48318
|
+
);
|
|
48319
|
+
}
|
|
48320
|
+
);
|
|
48321
|
+
Button2.displayName = "Button";
|
|
48322
|
+
|
|
48323
|
+
// components/ui/scroll-button.tsx
|
|
48324
|
+
import { jsx as jsx183 } from "react/jsx-runtime";
|
|
48325
|
+
function ScrollButton({
|
|
48326
|
+
className,
|
|
48327
|
+
variant = "outline",
|
|
48328
|
+
size = "sm",
|
|
48329
|
+
...props
|
|
48330
|
+
}) {
|
|
48331
|
+
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
|
48332
|
+
return /* @__PURE__ */ jsx183(
|
|
48333
|
+
Button2,
|
|
48334
|
+
{
|
|
48335
|
+
variant,
|
|
48336
|
+
size,
|
|
48337
|
+
className: cn(
|
|
48338
|
+
"h-10 w-10 rounded-full transition-all duration-150 ease-out",
|
|
48339
|
+
!isAtBottom ? "translate-y-0 scale-100 opacity-100" : "pointer-events-none translate-y-4 scale-95 opacity-0",
|
|
48340
|
+
className
|
|
48341
|
+
),
|
|
48342
|
+
onClick: () => scrollToBottom(),
|
|
48343
|
+
...props,
|
|
48344
|
+
children: /* @__PURE__ */ jsx183(ChevronDown, { className: "h-5 w-5" })
|
|
48345
|
+
}
|
|
48346
|
+
);
|
|
48347
|
+
}
|
|
48348
|
+
|
|
48349
|
+
// components/AIChatPanel.tsx
|
|
48350
|
+
import { jsx as jsx184, jsxs as jsxs100 } from "react/jsx-runtime";
|
|
48351
|
+
function genId() {
|
|
48352
|
+
return Math.random().toString(36).slice(2, 10);
|
|
48353
|
+
}
|
|
48354
|
+
var MAX_ATTACHED_FILES = 3;
|
|
48355
|
+
var MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
48356
|
+
var SUGGESTIONS = [
|
|
48357
|
+
"Create a pasta ad",
|
|
48358
|
+
"Design a sale banner",
|
|
48359
|
+
"Make a product showcase",
|
|
48360
|
+
"Help me design"
|
|
48361
|
+
];
|
|
48362
|
+
var AIChatPanel = React57.forwardRef(
|
|
48363
|
+
({
|
|
48364
|
+
isOpen,
|
|
48365
|
+
onClose,
|
|
48366
|
+
apiKey,
|
|
48367
|
+
excalidrawAPI,
|
|
48368
|
+
geminiApiKey,
|
|
48369
|
+
initialMessages,
|
|
48370
|
+
initialSessionId,
|
|
48371
|
+
initialSessions,
|
|
48372
|
+
onMessagesChange,
|
|
48373
|
+
onSessionCreate,
|
|
48374
|
+
onSessionSwitch,
|
|
48375
|
+
onBeforeSend,
|
|
48376
|
+
onAfterSend,
|
|
48377
|
+
onBeforeImageGen,
|
|
48378
|
+
onAfterImageGen
|
|
48379
|
+
}, ref) => {
|
|
48380
|
+
const [prompt, setPrompt] = useState49("");
|
|
48381
|
+
const [isLoading, setIsLoading] = useState49(false);
|
|
48382
|
+
const [statusText, setStatusText] = useState49("");
|
|
48383
|
+
const [messages, setMessages] = useState49(
|
|
48384
|
+
initialMessages ?? []
|
|
48385
|
+
);
|
|
48386
|
+
const [sessions, setSessions] = useState49(
|
|
48387
|
+
initialSessions ?? []
|
|
48388
|
+
);
|
|
48389
|
+
const [currentSessionId, setCurrentSessionId] = useState49(
|
|
48390
|
+
initialSessionId ?? genId
|
|
48391
|
+
);
|
|
48392
|
+
const [historyOpen, setHistoryOpen] = useState49(false);
|
|
48393
|
+
const [historySearch, setHistorySearch] = useState49("");
|
|
48394
|
+
const [error, setError] = useState49(null);
|
|
48395
|
+
const [frameRef, setFrameRef] = useState49(null);
|
|
48396
|
+
const [mentionMenuOpen, setMentionMenuOpen] = useState49(false);
|
|
48397
|
+
const [availableFrames, setAvailableFrames] = useState49([]);
|
|
48398
|
+
const [attachedFiles, setAttachedFiles] = useState49([]);
|
|
48399
|
+
const [webSearchEnabled, setWebSearchEnabled] = useState49(false);
|
|
48400
|
+
const [isRecording, setIsRecording] = useState49(false);
|
|
48401
|
+
const [isTranscribing, setIsTranscribing] = useState49(false);
|
|
48402
|
+
const [copiedMsgId, setCopiedMsgId] = useState49(null);
|
|
48403
|
+
const historyRef = useRef49(null);
|
|
48404
|
+
const abortControllerRef = useRef49(null);
|
|
48405
|
+
const mentionMenuRef = useRef49(null);
|
|
48406
|
+
const fileInputRef = useRef49(null);
|
|
48407
|
+
const mediaRecorderRef = useRef49(null);
|
|
48408
|
+
const audioChunksRef = useRef49([]);
|
|
48409
|
+
const prevPromptRef = useRef49("");
|
|
48410
|
+
const voiceUsedRef = useRef49(false);
|
|
48411
|
+
useEffect55(() => {
|
|
48412
|
+
const handler = (e) => {
|
|
48413
|
+
if (historyRef.current && !historyRef.current.contains(e.target)) {
|
|
48414
|
+
setHistoryOpen(false);
|
|
48415
|
+
}
|
|
48416
|
+
if (mentionMenuRef.current && !mentionMenuRef.current.contains(e.target)) {
|
|
48417
|
+
setMentionMenuOpen(false);
|
|
48418
|
+
}
|
|
48419
|
+
};
|
|
48420
|
+
document.addEventListener("mousedown", handler);
|
|
48421
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
48422
|
+
}, []);
|
|
48423
|
+
useEffect55(() => {
|
|
48424
|
+
if (!isOpen) {
|
|
48425
|
+
abortControllerRef.current?.abort();
|
|
48426
|
+
}
|
|
48427
|
+
}, [isOpen]);
|
|
48428
|
+
const currentTitle = messages.length > 0 ? messages[0].content.slice(0, 30) + (messages[0].content.length > 30 ? "\u2026" : "") : "New chat";
|
|
48429
|
+
const saveCurrentSession = useCallback24(() => {
|
|
48430
|
+
if (messages.length === 0) {
|
|
48431
|
+
return;
|
|
48432
|
+
}
|
|
48433
|
+
const title = messages[0].content.slice(0, 40) + (messages[0].content.length > 40 ? "\u2026" : "");
|
|
48434
|
+
setSessions((prev) => {
|
|
48435
|
+
const existing = prev.findIndex((s) => s.id === currentSessionId);
|
|
48436
|
+
const updated = { id: currentSessionId, title, messages };
|
|
48437
|
+
if (existing >= 0) {
|
|
48438
|
+
const next = [...prev];
|
|
48439
|
+
next[existing] = updated;
|
|
48440
|
+
return next;
|
|
48441
|
+
}
|
|
48442
|
+
return [updated, ...prev];
|
|
48443
|
+
});
|
|
48444
|
+
}, [messages, currentSessionId]);
|
|
48445
|
+
const handleNewChat = useCallback24(() => {
|
|
48446
|
+
saveCurrentSession();
|
|
48447
|
+
const newSessionId = genId();
|
|
48448
|
+
setMessages([]);
|
|
48449
|
+
setCurrentSessionId(newSessionId);
|
|
48450
|
+
setHistoryOpen(false);
|
|
48451
|
+
setError(null);
|
|
48452
|
+
setPrompt("");
|
|
48453
|
+
setFrameRef(null);
|
|
48454
|
+
setAttachedFiles([]);
|
|
48455
|
+
setStatusText("");
|
|
48456
|
+
if (onSessionCreate) {
|
|
48457
|
+
onSessionCreate({ id: newSessionId, title: "New chat" });
|
|
48458
|
+
}
|
|
48459
|
+
}, [saveCurrentSession, onSessionCreate]);
|
|
48460
|
+
const handleSwitchSession = useCallback24(
|
|
48461
|
+
(session) => {
|
|
48462
|
+
saveCurrentSession();
|
|
48463
|
+
setMessages(session.messages);
|
|
48464
|
+
setCurrentSessionId(session.id);
|
|
48465
|
+
setHistoryOpen(false);
|
|
48466
|
+
setError(null);
|
|
48467
|
+
if (onSessionSwitch) {
|
|
48468
|
+
onSessionSwitch(session.id);
|
|
48469
|
+
}
|
|
48470
|
+
},
|
|
48471
|
+
[saveCurrentSession, onSessionSwitch]
|
|
48472
|
+
);
|
|
48473
|
+
const handlePromptChange = useCallback24(
|
|
48474
|
+
(value) => {
|
|
48475
|
+
const prevValue = prevPromptRef.current;
|
|
48476
|
+
setPrompt(value);
|
|
48477
|
+
prevPromptRef.current = value;
|
|
48478
|
+
if (excalidrawAPI && value.length > prevValue.length && value.endsWith("@")) {
|
|
48479
|
+
setAvailableFrames(listFrames(excalidrawAPI));
|
|
48480
|
+
setMentionMenuOpen(true);
|
|
48481
|
+
}
|
|
48482
|
+
},
|
|
48483
|
+
[excalidrawAPI]
|
|
48484
|
+
);
|
|
48485
|
+
const handlePickFrame = useCallback24(
|
|
48486
|
+
async (frame) => {
|
|
48487
|
+
if (!excalidrawAPI) {
|
|
48488
|
+
return;
|
|
48489
|
+
}
|
|
48490
|
+
setMentionMenuOpen(false);
|
|
48491
|
+
setPrompt((prev) => {
|
|
48492
|
+
const atIndex2 = prev.lastIndexOf("@");
|
|
48493
|
+
return atIndex2 >= 0 ? prev.slice(0, atIndex2) : prev;
|
|
48494
|
+
});
|
|
48495
|
+
const atIndex = prevPromptRef.current.lastIndexOf("@");
|
|
48496
|
+
prevPromptRef.current = atIndex >= 0 ? prevPromptRef.current.slice(0, atIndex) : prevPromptRef.current;
|
|
48497
|
+
const ctx = getFrameContext(excalidrawAPI, frame.id);
|
|
48498
|
+
if (!ctx) {
|
|
48499
|
+
return;
|
|
48500
|
+
}
|
|
48501
|
+
const screenshot = await captureFrameScreenshot(excalidrawAPI, frame.id);
|
|
48502
|
+
setFrameRef({
|
|
48503
|
+
frameId: frame.id,
|
|
48504
|
+
label: ctx.frameInfo.name,
|
|
48505
|
+
serialized: ctx.serialized,
|
|
48506
|
+
screenshot: screenshot ?? void 0
|
|
48507
|
+
});
|
|
48508
|
+
},
|
|
48509
|
+
[excalidrawAPI]
|
|
48510
|
+
);
|
|
48511
|
+
const handleRemoveRef = useCallback24(() => {
|
|
48512
|
+
setFrameRef(null);
|
|
48513
|
+
}, []);
|
|
48514
|
+
const handleFileUpload = useCallback24(
|
|
48515
|
+
(e) => {
|
|
48516
|
+
const files = e.target.files;
|
|
48517
|
+
if (!files) {
|
|
48518
|
+
return;
|
|
48519
|
+
}
|
|
48520
|
+
const remaining = MAX_ATTACHED_FILES - attachedFiles.length;
|
|
48521
|
+
if (remaining <= 0) {
|
|
48522
|
+
return;
|
|
48523
|
+
}
|
|
48524
|
+
const toAdd = Array.from(files).slice(0, remaining);
|
|
48525
|
+
for (const file2 of toAdd) {
|
|
48526
|
+
if (file2.size > MAX_FILE_SIZE_BYTES) {
|
|
48527
|
+
setError(`"${file2.name}" is too large (max 10 MB).`);
|
|
48528
|
+
continue;
|
|
48529
|
+
}
|
|
48530
|
+
const id = genId();
|
|
48531
|
+
const isImage = file2.type.startsWith("image/");
|
|
48532
|
+
const reader = new FileReader();
|
|
48533
|
+
if (isImage) {
|
|
48534
|
+
reader.onload = () => {
|
|
48535
|
+
setAttachedFiles(
|
|
48536
|
+
(prev) => prev.length < MAX_ATTACHED_FILES ? [
|
|
48537
|
+
...prev,
|
|
48538
|
+
{
|
|
48539
|
+
id,
|
|
48540
|
+
file: file2,
|
|
48541
|
+
name: file2.name,
|
|
48542
|
+
type: "image",
|
|
48543
|
+
dataUrl: reader.result
|
|
48544
|
+
}
|
|
48545
|
+
] : prev
|
|
48546
|
+
);
|
|
48547
|
+
};
|
|
48548
|
+
reader.readAsDataURL(file2);
|
|
48549
|
+
} else {
|
|
48550
|
+
reader.onload = () => {
|
|
48551
|
+
setAttachedFiles(
|
|
48552
|
+
(prev) => prev.length < MAX_ATTACHED_FILES ? [
|
|
48553
|
+
...prev,
|
|
48554
|
+
{
|
|
48555
|
+
id,
|
|
48556
|
+
file: file2,
|
|
48557
|
+
name: file2.name,
|
|
48558
|
+
type: "text",
|
|
48559
|
+
textContent: reader.result
|
|
48560
|
+
}
|
|
48561
|
+
] : prev
|
|
48562
|
+
);
|
|
48563
|
+
};
|
|
48564
|
+
reader.readAsText(file2);
|
|
48565
|
+
}
|
|
48566
|
+
}
|
|
48567
|
+
if (fileInputRef.current) {
|
|
48568
|
+
fileInputRef.current.value = "";
|
|
48569
|
+
}
|
|
48570
|
+
},
|
|
48571
|
+
[attachedFiles.length]
|
|
48572
|
+
);
|
|
48573
|
+
const handleRemoveFile = useCallback24((fileId) => {
|
|
48574
|
+
setAttachedFiles((prev) => prev.filter((f) => f.id !== fileId));
|
|
48575
|
+
}, []);
|
|
48576
|
+
const toggleWebSearch = useCallback24(() => {
|
|
48577
|
+
setWebSearchEnabled((prev) => !prev);
|
|
48578
|
+
}, []);
|
|
48579
|
+
const handleVoiceInput = useCallback24(async () => {
|
|
48580
|
+
if (isRecording) {
|
|
48581
|
+
mediaRecorderRef.current?.stop();
|
|
48582
|
+
setIsRecording(false);
|
|
48583
|
+
voiceUsedRef.current = true;
|
|
48584
|
+
return;
|
|
48585
|
+
}
|
|
48586
|
+
let stream = null;
|
|
48587
|
+
try {
|
|
48588
|
+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
48589
|
+
const mimeType = MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4";
|
|
48590
|
+
const mediaRecorder = new MediaRecorder(stream, { mimeType });
|
|
48591
|
+
mediaRecorderRef.current = mediaRecorder;
|
|
48592
|
+
audioChunksRef.current = [];
|
|
48593
|
+
mediaRecorder.ondataavailable = (e) => {
|
|
48594
|
+
if (e.data.size > 0) {
|
|
48595
|
+
audioChunksRef.current.push(e.data);
|
|
48596
|
+
}
|
|
48597
|
+
};
|
|
48598
|
+
mediaRecorder.onstop = async () => {
|
|
48599
|
+
stream?.getTracks().forEach((t2) => t2.stop());
|
|
48600
|
+
const audioBlob = new Blob(audioChunksRef.current, {
|
|
48601
|
+
type: mediaRecorder.mimeType
|
|
48602
|
+
});
|
|
48603
|
+
const normalizedKey = resolveOpenRouterApiKey(apiKey);
|
|
48604
|
+
if (!normalizedKey) {
|
|
48605
|
+
setError("No API key for voice transcription.");
|
|
48606
|
+
return;
|
|
48607
|
+
}
|
|
48608
|
+
setIsTranscribing(true);
|
|
48609
|
+
setStatusText("Transcribing\u2026");
|
|
48610
|
+
try {
|
|
48611
|
+
const wavBase64 = await blobToWavBase64(audioBlob);
|
|
48612
|
+
const transcription = await transcribeAudio(
|
|
48613
|
+
wavBase64,
|
|
48614
|
+
"wav",
|
|
48615
|
+
normalizedKey
|
|
48616
|
+
);
|
|
48617
|
+
const updated = (prevPromptRef.current ? `${prevPromptRef.current} ` : "") + transcription;
|
|
48618
|
+
prevPromptRef.current = updated;
|
|
48619
|
+
setPrompt(updated);
|
|
48620
|
+
} catch (err) {
|
|
48621
|
+
setError(err instanceof Error ? err.message : "Transcription failed");
|
|
48622
|
+
} finally {
|
|
48623
|
+
setIsTranscribing(false);
|
|
48624
|
+
setStatusText("");
|
|
48625
|
+
}
|
|
48626
|
+
};
|
|
48627
|
+
mediaRecorder.start();
|
|
48628
|
+
setIsRecording(true);
|
|
48629
|
+
} catch {
|
|
48630
|
+
stream?.getTracks().forEach((t2) => t2.stop());
|
|
48631
|
+
setError("Could not access microphone. Check browser permissions.");
|
|
48632
|
+
}
|
|
48633
|
+
}, [isRecording, apiKey]);
|
|
48634
|
+
const handleCopy = useCallback24((msgId, content) => {
|
|
48635
|
+
navigator.clipboard.writeText(content).then(
|
|
48636
|
+
() => {
|
|
48637
|
+
setCopiedMsgId(msgId);
|
|
48638
|
+
setTimeout(() => setCopiedMsgId(null), 2e3);
|
|
48639
|
+
},
|
|
48640
|
+
() => {
|
|
48641
|
+
setError("Copy failed \u2014 please grant clipboard permission.");
|
|
48642
|
+
}
|
|
48643
|
+
);
|
|
48644
|
+
}, []);
|
|
48645
|
+
const handleSend = useCallback24(async (textOverride) => {
|
|
48646
|
+
const text = (textOverride ?? prompt).trim();
|
|
48647
|
+
const normalizedKey = resolveOpenRouterApiKey(apiKey);
|
|
48648
|
+
if (!text || isLoading) {
|
|
48649
|
+
return;
|
|
48650
|
+
}
|
|
48651
|
+
if (!normalizedKey) {
|
|
48652
|
+
setError(
|
|
48653
|
+
"No OpenRouter API key. Set VITE_APP_OPENROUTER_API_KEY in .env or pass the key via the `apiKey` prop."
|
|
48654
|
+
);
|
|
48655
|
+
return;
|
|
48656
|
+
}
|
|
48657
|
+
const hadVoice = voiceUsedRef.current;
|
|
48658
|
+
voiceUsedRef.current = false;
|
|
48659
|
+
if (onBeforeSend) {
|
|
48660
|
+
const { allowed, error: creditError } = await onBeforeSend({
|
|
48661
|
+
hasVoice: hadVoice
|
|
48662
|
+
});
|
|
48663
|
+
if (!allowed) {
|
|
48664
|
+
setError(creditError || "Insufficient credits");
|
|
48665
|
+
return;
|
|
48666
|
+
}
|
|
48667
|
+
}
|
|
48668
|
+
const capturedFiles = [...attachedFiles];
|
|
48669
|
+
const capturedWebSearch = webSearchEnabled;
|
|
48670
|
+
const capturedFrameRef = frameRef;
|
|
48671
|
+
const userMsg = {
|
|
48672
|
+
id: genId(),
|
|
48673
|
+
role: "user",
|
|
48674
|
+
content: text,
|
|
48675
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
48676
|
+
attachments: capturedFiles.length > 0 ? capturedFiles.map((f) => ({
|
|
48677
|
+
name: f.name,
|
|
48678
|
+
type: f.type,
|
|
48679
|
+
dataUrl: f.dataUrl
|
|
48680
|
+
})) : void 0
|
|
48681
|
+
};
|
|
48682
|
+
const nextMessages = [...messages, userMsg];
|
|
48683
|
+
setMessages(nextMessages);
|
|
48684
|
+
setPrompt("");
|
|
48685
|
+
prevPromptRef.current = "";
|
|
48686
|
+
setAttachedFiles([]);
|
|
48687
|
+
setFrameRef(null);
|
|
48688
|
+
const controller = new AbortController();
|
|
48689
|
+
abortControllerRef.current = controller;
|
|
48690
|
+
setIsLoading(true);
|
|
48691
|
+
setError(null);
|
|
48692
|
+
setStatusText("Thinking\u2026");
|
|
48693
|
+
if (!excalidrawAPI) {
|
|
48694
|
+
try {
|
|
48695
|
+
const reply = await callPlainChatAPI(
|
|
48696
|
+
nextMessages,
|
|
48697
|
+
normalizedKey,
|
|
48698
|
+
controller.signal,
|
|
48699
|
+
{
|
|
48700
|
+
webSearchEnabled: capturedWebSearch,
|
|
48701
|
+
attachments: capturedFiles
|
|
48702
|
+
}
|
|
48703
|
+
);
|
|
48704
|
+
const assistantMsg = {
|
|
48705
|
+
id: genId(),
|
|
48706
|
+
role: "assistant",
|
|
48707
|
+
content: reply,
|
|
48708
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
48709
|
+
};
|
|
48710
|
+
const updatedMessages = [...nextMessages, assistantMsg];
|
|
48711
|
+
setMessages(updatedMessages);
|
|
48712
|
+
if (onAfterSend) {
|
|
48713
|
+
onAfterSend({ hasVoice: hadVoice });
|
|
48714
|
+
}
|
|
48715
|
+
if (onMessagesChange) {
|
|
48716
|
+
onMessagesChange(updatedMessages, currentSessionId);
|
|
48717
|
+
}
|
|
48718
|
+
} catch (err) {
|
|
48719
|
+
if (err instanceof Error && err.name !== "AbortError") {
|
|
48720
|
+
setError(err.message);
|
|
48721
|
+
}
|
|
48722
|
+
} finally {
|
|
48723
|
+
abortControllerRef.current = null;
|
|
48724
|
+
setIsLoading(false);
|
|
48725
|
+
setStatusText("");
|
|
48726
|
+
}
|
|
48727
|
+
return;
|
|
48728
|
+
}
|
|
48729
|
+
try {
|
|
48730
|
+
const result = await runAgentLoop({
|
|
48731
|
+
userMessages: nextMessages.map((m) => ({
|
|
48732
|
+
role: m.role,
|
|
48733
|
+
content: m.content
|
|
48734
|
+
})),
|
|
48735
|
+
elementContext: capturedFrameRef?.serialized,
|
|
48736
|
+
frameScreenshot: capturedFrameRef?.screenshot,
|
|
48737
|
+
fileAttachments: capturedFiles.length > 0 ? capturedFiles.map((f) => ({
|
|
48738
|
+
name: f.name,
|
|
48739
|
+
type: f.type,
|
|
48740
|
+
dataUrl: f.dataUrl,
|
|
48741
|
+
textContent: f.textContent
|
|
48742
|
+
})) : void 0,
|
|
48743
|
+
webSearchEnabled: capturedWebSearch,
|
|
48744
|
+
apiKey: normalizedKey,
|
|
48745
|
+
toolCtx: {
|
|
48746
|
+
excalidrawAPI,
|
|
48747
|
+
geminiApiKey: geminiApiKey || "",
|
|
48748
|
+
signal: controller.signal
|
|
48749
|
+
},
|
|
48750
|
+
onUpdate: (update) => {
|
|
48751
|
+
if (update.type !== "final") {
|
|
48752
|
+
setStatusText(update.message);
|
|
48753
|
+
}
|
|
48754
|
+
},
|
|
48755
|
+
signal: controller.signal,
|
|
48756
|
+
onBeforeImageGen,
|
|
48757
|
+
onAfterImageGen
|
|
48758
|
+
});
|
|
48759
|
+
const assistantMsgAgent = {
|
|
48760
|
+
id: genId(),
|
|
48761
|
+
role: "assistant",
|
|
48762
|
+
content: result.reply,
|
|
48763
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
48764
|
+
toolActions: result.toolActions.length > 0 ? result.toolActions : void 0
|
|
48765
|
+
};
|
|
48766
|
+
const updatedMessagesAgent = [...nextMessages, assistantMsgAgent];
|
|
48767
|
+
setMessages(updatedMessagesAgent);
|
|
48768
|
+
if (onAfterSend) {
|
|
48769
|
+
onAfterSend({ hasVoice: hadVoice });
|
|
48770
|
+
}
|
|
48771
|
+
if (onMessagesChange) {
|
|
48772
|
+
onMessagesChange(updatedMessagesAgent, currentSessionId);
|
|
48773
|
+
}
|
|
48774
|
+
} catch (err) {
|
|
48775
|
+
if (err instanceof Error && err.name !== "AbortError") {
|
|
48776
|
+
setError(err.message);
|
|
48777
|
+
}
|
|
48778
|
+
} finally {
|
|
48779
|
+
abortControllerRef.current = null;
|
|
48780
|
+
setIsLoading(false);
|
|
48781
|
+
setStatusText("");
|
|
48782
|
+
}
|
|
48783
|
+
}, [
|
|
48784
|
+
prompt,
|
|
48785
|
+
isLoading,
|
|
48786
|
+
messages,
|
|
48787
|
+
apiKey,
|
|
48788
|
+
excalidrawAPI,
|
|
48789
|
+
geminiApiKey,
|
|
48790
|
+
frameRef,
|
|
48791
|
+
attachedFiles,
|
|
48792
|
+
webSearchEnabled,
|
|
48793
|
+
currentSessionId,
|
|
48794
|
+
onBeforeSend,
|
|
48795
|
+
onAfterSend,
|
|
48796
|
+
onMessagesChange,
|
|
48797
|
+
onBeforeImageGen,
|
|
48798
|
+
onAfterImageGen
|
|
48799
|
+
]);
|
|
48800
|
+
useImperativeHandle4(
|
|
48801
|
+
ref,
|
|
48802
|
+
() => ({
|
|
48803
|
+
setMessages,
|
|
48804
|
+
send: (text) => handleSend(text)
|
|
48805
|
+
}),
|
|
48806
|
+
[setMessages, handleSend]
|
|
48807
|
+
);
|
|
48808
|
+
const handleStop = useCallback24(() => {
|
|
48809
|
+
abortControllerRef.current?.abort();
|
|
48810
|
+
}, []);
|
|
48811
|
+
const handleChip = useCallback24((chip) => {
|
|
48812
|
+
setPrompt(chip);
|
|
48813
|
+
}, []);
|
|
48814
|
+
const filteredSessions = sessions.filter(
|
|
48815
|
+
(s) => historySearch ? s.title.toLowerCase().includes(historySearch.toLowerCase()) : true
|
|
48816
|
+
);
|
|
48817
|
+
if (!isOpen) {
|
|
48818
|
+
return null;
|
|
48819
|
+
}
|
|
48820
|
+
return /* @__PURE__ */ jsx184(
|
|
48821
|
+
"div",
|
|
48822
|
+
{
|
|
48823
|
+
className: "acp",
|
|
48824
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
48825
|
+
onClick: (e) => e.stopPropagation(),
|
|
48826
|
+
children: /* @__PURE__ */ jsxs100("div", { className: "acp-panel", children: [
|
|
48827
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-header", ref: historyRef, children: [
|
|
48828
|
+
/* @__PURE__ */ jsxs100(
|
|
48829
|
+
"button",
|
|
48830
|
+
{
|
|
48831
|
+
className: "acp-title-btn",
|
|
48832
|
+
onClick: () => setHistoryOpen((v) => !v),
|
|
48833
|
+
title: "Chat history",
|
|
48834
|
+
children: [
|
|
48835
|
+
/* @__PURE__ */ jsx184("span", { children: currentTitle }),
|
|
48836
|
+
/* @__PURE__ */ jsx184(ChevronDown2, { size: 13 })
|
|
48837
|
+
]
|
|
48838
|
+
}
|
|
48839
|
+
),
|
|
48840
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-header-right", children: [
|
|
48841
|
+
/* @__PURE__ */ jsx184(
|
|
48842
|
+
"button",
|
|
48843
|
+
{
|
|
48844
|
+
className: "acp-icon-btn",
|
|
48845
|
+
onClick: handleNewChat,
|
|
48846
|
+
title: "New chat",
|
|
48847
|
+
children: /* @__PURE__ */ jsx184(Plus, { size: 15 })
|
|
48848
|
+
}
|
|
48849
|
+
),
|
|
48850
|
+
/* @__PURE__ */ jsx184("button", { className: "acp-icon-btn", onClick: onClose, title: "Close", children: /* @__PURE__ */ jsx184(X2, { size: 15 }) })
|
|
48851
|
+
] }),
|
|
48852
|
+
historyOpen && /* @__PURE__ */ jsxs100("div", { className: "acp-history-dropdown", children: [
|
|
48853
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-history-search", children: [
|
|
48854
|
+
/* @__PURE__ */ jsx184(Search, { size: 13 }),
|
|
48855
|
+
/* @__PURE__ */ jsx184(
|
|
48856
|
+
"input",
|
|
48857
|
+
{
|
|
48858
|
+
type: "search",
|
|
48859
|
+
placeholder: "Search chats\u2026",
|
|
48860
|
+
value: historySearch,
|
|
48861
|
+
onChange: (e) => setHistorySearch(e.target.value),
|
|
48862
|
+
autoFocus: true
|
|
48863
|
+
}
|
|
48864
|
+
)
|
|
48865
|
+
] }),
|
|
48866
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-history-list", children: [
|
|
48867
|
+
/* @__PURE__ */ jsxs100(
|
|
48868
|
+
"button",
|
|
48869
|
+
{
|
|
48870
|
+
className: "acp-history-item acp-history-item--new",
|
|
48871
|
+
onClick: handleNewChat,
|
|
48872
|
+
children: [
|
|
48873
|
+
/* @__PURE__ */ jsx184(Plus, { size: 13 }),
|
|
48874
|
+
/* @__PURE__ */ jsx184("span", { children: "New chat" })
|
|
48875
|
+
]
|
|
48876
|
+
}
|
|
48877
|
+
),
|
|
48878
|
+
filteredSessions.map((session) => /* @__PURE__ */ jsxs100(
|
|
48879
|
+
"button",
|
|
48880
|
+
{
|
|
48881
|
+
className: "acp-history-item",
|
|
48882
|
+
onClick: () => handleSwitchSession(session),
|
|
48883
|
+
children: [
|
|
48884
|
+
/* @__PURE__ */ jsx184(MessageSquare, { size: 13 }),
|
|
48885
|
+
/* @__PURE__ */ jsx184("span", { children: session.title })
|
|
48886
|
+
]
|
|
48887
|
+
},
|
|
48888
|
+
session.id
|
|
48889
|
+
)),
|
|
48890
|
+
filteredSessions.length === 0 && historySearch && /* @__PURE__ */ jsx184("div", { className: "acp-history-empty", children: "No matching chats" })
|
|
48891
|
+
] })
|
|
48892
|
+
] })
|
|
48893
|
+
] }),
|
|
48894
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-messages-wrap", children: messages.length === 0 && !isLoading ? /* @__PURE__ */ jsxs100("div", { className: "acp-empty", children: [
|
|
48895
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-empty-icon", children: /* @__PURE__ */ jsx184(MessageSquare, { size: 22 }) }),
|
|
48896
|
+
/* @__PURE__ */ jsxs100("div", { children: [
|
|
48897
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-empty-title", children: "AI Ad Designer" }),
|
|
48898
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-empty-sub", children: "Describe an ad and I'll create it on the canvas. Type @ to reference frames." })
|
|
48899
|
+
] }),
|
|
48900
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-chips", children: SUGGESTIONS.map((chip) => /* @__PURE__ */ jsx184(
|
|
48901
|
+
"button",
|
|
48902
|
+
{
|
|
48903
|
+
className: "acp-chip",
|
|
48904
|
+
onClick: () => handleChip(chip),
|
|
48905
|
+
children: chip
|
|
48906
|
+
},
|
|
48907
|
+
chip
|
|
48908
|
+
)) })
|
|
48909
|
+
] }) : /* @__PURE__ */ jsxs100(ChatContainerRoot, { className: "acp-chat-root", children: [
|
|
48910
|
+
/* @__PURE__ */ jsxs100(ChatContainerContent, { className: "acp-chat-content", children: [
|
|
48911
|
+
messages.map((msg, index) => {
|
|
48912
|
+
const isAssistant = msg.role === "assistant";
|
|
48913
|
+
const isLast = index === messages.length - 1;
|
|
48914
|
+
return /* @__PURE__ */ jsx184(
|
|
48915
|
+
Message,
|
|
48916
|
+
{
|
|
48917
|
+
className: `acp-msg ${isAssistant ? "acp-msg--assistant" : "acp-msg--user"}`,
|
|
48918
|
+
children: isAssistant ? /* @__PURE__ */ jsxs100("div", { className: "acp-msg-inner", children: [
|
|
48919
|
+
msg.toolActions && msg.toolActions.length > 0 && /* @__PURE__ */ jsx184(ToolActionsDisplay, { actions: msg.toolActions }),
|
|
48920
|
+
/* @__PURE__ */ jsx184(
|
|
48921
|
+
MessageContent,
|
|
48922
|
+
{
|
|
48923
|
+
markdown: true,
|
|
48924
|
+
className: "acp-content-assistant",
|
|
48925
|
+
children: msg.content
|
|
48926
|
+
}
|
|
48927
|
+
),
|
|
48928
|
+
/* @__PURE__ */ jsx184(
|
|
48929
|
+
MessageActions,
|
|
48930
|
+
{
|
|
48931
|
+
className: `acp-msg-actions${isLast ? " acp-msg-actions--visible" : ""}`,
|
|
48932
|
+
children: /* @__PURE__ */ jsx184(MessageAction, { tooltip: "Copy", delayDuration: 100, children: /* @__PURE__ */ jsx184(
|
|
48933
|
+
"button",
|
|
48934
|
+
{
|
|
48935
|
+
className: "acp-action-btn",
|
|
48936
|
+
onClick: () => handleCopy(msg.id, msg.content),
|
|
48937
|
+
children: copiedMsgId === msg.id ? /* @__PURE__ */ jsx184(Check, { size: 14 }) : /* @__PURE__ */ jsx184(Copy, { size: 14 })
|
|
48938
|
+
}
|
|
48939
|
+
) })
|
|
48940
|
+
}
|
|
48941
|
+
)
|
|
48942
|
+
] }) : /* @__PURE__ */ jsxs100("div", { className: "acp-msg-inner", children: [
|
|
48943
|
+
msg.attachments && msg.attachments.length > 0 && /* @__PURE__ */ jsx184("div", { className: "acp-msg-attachments", children: msg.attachments.map(
|
|
48944
|
+
(att, i) => att.type === "image" && att.dataUrl ? /* @__PURE__ */ jsx184(
|
|
48945
|
+
"img",
|
|
48946
|
+
{
|
|
48947
|
+
src: att.dataUrl,
|
|
48948
|
+
alt: att.name,
|
|
48949
|
+
className: "acp-msg-att-thumb",
|
|
48950
|
+
title: att.name
|
|
48951
|
+
},
|
|
48952
|
+
i
|
|
48953
|
+
) : /* @__PURE__ */ jsxs100("span", { className: "acp-msg-att-pill", children: [
|
|
48954
|
+
/* @__PURE__ */ jsx184(Paperclip, { size: 11 }),
|
|
48955
|
+
att.name
|
|
48956
|
+
] }, i)
|
|
48957
|
+
) }),
|
|
48958
|
+
/* @__PURE__ */ jsx184(MessageContent, { className: "acp-content-user", children: msg.content }),
|
|
48959
|
+
/* @__PURE__ */ jsx184(MessageActions, { className: "acp-msg-actions", children: /* @__PURE__ */ jsx184(MessageAction, { tooltip: "Copy", delayDuration: 100, children: /* @__PURE__ */ jsx184(
|
|
48960
|
+
"button",
|
|
48961
|
+
{
|
|
48962
|
+
className: "acp-action-btn",
|
|
48963
|
+
onClick: () => handleCopy(msg.id, msg.content),
|
|
48964
|
+
children: copiedMsgId === msg.id ? /* @__PURE__ */ jsx184(Check, { size: 14 }) : /* @__PURE__ */ jsx184(Copy, { size: 14 })
|
|
48965
|
+
}
|
|
48966
|
+
) }) })
|
|
48967
|
+
] })
|
|
48968
|
+
},
|
|
48969
|
+
msg.id
|
|
48970
|
+
);
|
|
48971
|
+
}),
|
|
48972
|
+
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: [
|
|
48973
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-loading-dots", children: [
|
|
48974
|
+
/* @__PURE__ */ jsx184("span", {}),
|
|
48975
|
+
/* @__PURE__ */ jsx184("span", {}),
|
|
48976
|
+
/* @__PURE__ */ jsx184("span", {})
|
|
48977
|
+
] }),
|
|
48978
|
+
statusText && /* @__PURE__ */ jsx184("span", { className: "acp-status-text", children: statusText })
|
|
48979
|
+
] }) }) })
|
|
48980
|
+
] }),
|
|
48981
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-scroll-anchor", children: /* @__PURE__ */ jsx184(ScrollButton, { className: "acp-scroll-btn" }) })
|
|
48982
|
+
] }) }),
|
|
48983
|
+
error && /* @__PURE__ */ jsx184("div", { className: "acp-error", children: error }),
|
|
48984
|
+
frameRef && /* @__PURE__ */ jsx184("div", { className: "acp-ref-area", children: /* @__PURE__ */ jsxs100("div", { className: "acp-ref-pill", children: [
|
|
48985
|
+
/* @__PURE__ */ jsx184(AtSign, { size: 12 }),
|
|
48986
|
+
/* @__PURE__ */ jsx184("span", { children: frameRef.label }),
|
|
48987
|
+
/* @__PURE__ */ jsx184(
|
|
48988
|
+
"button",
|
|
48989
|
+
{
|
|
48990
|
+
className: "acp-ref-remove",
|
|
48991
|
+
onClick: handleRemoveRef,
|
|
48992
|
+
title: "Remove reference",
|
|
48993
|
+
children: /* @__PURE__ */ jsx184(X2, { size: 10 })
|
|
48994
|
+
}
|
|
48995
|
+
)
|
|
48996
|
+
] }) }),
|
|
48997
|
+
attachedFiles.length > 0 && /* @__PURE__ */ jsx184("div", { className: "acp-attachments-area", children: attachedFiles.map((f) => /* @__PURE__ */ jsxs100("div", { className: "acp-attachment-pill", children: [
|
|
48998
|
+
f.type === "image" && f.dataUrl ? /* @__PURE__ */ jsx184(
|
|
48999
|
+
"img",
|
|
49000
|
+
{
|
|
49001
|
+
src: f.dataUrl,
|
|
49002
|
+
alt: f.name,
|
|
49003
|
+
className: "acp-attachment-thumb"
|
|
49004
|
+
}
|
|
49005
|
+
) : /* @__PURE__ */ jsx184(Paperclip, { size: 12 }),
|
|
49006
|
+
/* @__PURE__ */ jsx184("span", { children: f.name }),
|
|
49007
|
+
/* @__PURE__ */ jsx184(
|
|
49008
|
+
"button",
|
|
49009
|
+
{
|
|
49010
|
+
className: "acp-attachment-remove",
|
|
49011
|
+
onClick: () => handleRemoveFile(f.id),
|
|
49012
|
+
title: "Remove file",
|
|
49013
|
+
children: /* @__PURE__ */ jsx184(X2, { size: 10 })
|
|
49014
|
+
}
|
|
49015
|
+
)
|
|
49016
|
+
] }, f.id)) }),
|
|
49017
|
+
/* @__PURE__ */ jsx184("div", { className: "acp-input-area", children: /* @__PURE__ */ jsxs100("div", { className: "acp-input-wrapper", ref: mentionMenuRef, children: [
|
|
49018
|
+
mentionMenuOpen && /* @__PURE__ */ jsx184("div", { className: "acp-mention-menu", children: availableFrames.length > 0 ? availableFrames.map((f) => /* @__PURE__ */ jsxs100(
|
|
49019
|
+
"button",
|
|
49020
|
+
{
|
|
49021
|
+
className: "acp-mention-item",
|
|
49022
|
+
onClick: () => handlePickFrame(f),
|
|
49023
|
+
children: [
|
|
49024
|
+
/* @__PURE__ */ jsx184(AtSign, { size: 14 }),
|
|
49025
|
+
/* @__PURE__ */ jsxs100("span", { children: [
|
|
49026
|
+
f.name,
|
|
49027
|
+
" ",
|
|
49028
|
+
/* @__PURE__ */ jsxs100("span", { className: "acp-mention-dim", children: [
|
|
49029
|
+
"(",
|
|
49030
|
+
f.width,
|
|
49031
|
+
"x",
|
|
49032
|
+
f.height,
|
|
49033
|
+
", ",
|
|
49034
|
+
f.childCount,
|
|
49035
|
+
" items)"
|
|
49036
|
+
] })
|
|
49037
|
+
] })
|
|
49038
|
+
]
|
|
49039
|
+
},
|
|
49040
|
+
f.id
|
|
49041
|
+
)) : /* @__PURE__ */ jsx184("div", { className: "acp-mention-empty", children: "No frames on the canvas. Create a frame first, then type @ to reference it." }) }),
|
|
49042
|
+
/* @__PURE__ */ jsx184(
|
|
49043
|
+
"input",
|
|
49044
|
+
{
|
|
49045
|
+
ref: fileInputRef,
|
|
49046
|
+
type: "file",
|
|
49047
|
+
multiple: true,
|
|
49048
|
+
accept: "image/*,.txt,.json,.csv,.md,.html,.css,.js,.ts,.tsx,.jsx,.py,.xml",
|
|
49049
|
+
onChange: handleFileUpload,
|
|
49050
|
+
style: { display: "none" }
|
|
49051
|
+
}
|
|
49052
|
+
),
|
|
49053
|
+
/* @__PURE__ */ jsxs100(
|
|
49054
|
+
PromptInput,
|
|
49055
|
+
{
|
|
49056
|
+
isLoading,
|
|
49057
|
+
value: prompt,
|
|
49058
|
+
onValueChange: handlePromptChange,
|
|
49059
|
+
onSubmit: () => handleSend(),
|
|
49060
|
+
className: "acp-prompt-input",
|
|
49061
|
+
children: [
|
|
49062
|
+
/* @__PURE__ */ jsx184(
|
|
49063
|
+
PromptInputTextarea,
|
|
49064
|
+
{
|
|
49065
|
+
placeholder: excalidrawAPI ? "Describe an ad to create\u2026 (@ for frames)" : "Ask anything",
|
|
49066
|
+
className: "acp-textarea"
|
|
49067
|
+
}
|
|
49068
|
+
),
|
|
49069
|
+
/* @__PURE__ */ jsxs100(PromptInputActions, { className: "acp-actions-bar", children: [
|
|
49070
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-actions-left", children: [
|
|
49071
|
+
/* @__PURE__ */ jsx184(PromptInputAction, { tooltip: "Attach files (max 3)", children: /* @__PURE__ */ jsx184(
|
|
49072
|
+
"button",
|
|
49073
|
+
{
|
|
49074
|
+
className: "acp-pill-btn acp-pill-btn--icon",
|
|
49075
|
+
onClick: () => fileInputRef.current?.click(),
|
|
49076
|
+
disabled: attachedFiles.length >= MAX_ATTACHED_FILES,
|
|
49077
|
+
children: /* @__PURE__ */ jsx184(Paperclip, { size: 16 })
|
|
49078
|
+
}
|
|
49079
|
+
) }),
|
|
49080
|
+
/* @__PURE__ */ jsx184(
|
|
49081
|
+
PromptInputAction,
|
|
49082
|
+
{
|
|
49083
|
+
tooltip: webSearchEnabled ? "Web search enabled" : "Enable web search",
|
|
49084
|
+
children: /* @__PURE__ */ jsxs100(
|
|
49085
|
+
"button",
|
|
49086
|
+
{
|
|
49087
|
+
className: `acp-pill-btn${webSearchEnabled ? " acp-pill-btn--active" : ""}`,
|
|
49088
|
+
onClick: toggleWebSearch,
|
|
49089
|
+
children: [
|
|
49090
|
+
/* @__PURE__ */ jsx184(Globe, { size: 15 }),
|
|
49091
|
+
/* @__PURE__ */ jsx184("span", { children: "Search" })
|
|
49092
|
+
]
|
|
49093
|
+
}
|
|
49094
|
+
)
|
|
49095
|
+
}
|
|
49096
|
+
)
|
|
49097
|
+
] }),
|
|
49098
|
+
/* @__PURE__ */ jsxs100("div", { className: "acp-actions-right", children: [
|
|
49099
|
+
/* @__PURE__ */ jsx184(
|
|
49100
|
+
PromptInputAction,
|
|
49101
|
+
{
|
|
49102
|
+
tooltip: isTranscribing ? "Transcribing\u2026" : isRecording ? "Stop recording" : "Voice input",
|
|
49103
|
+
children: /* @__PURE__ */ jsx184(
|
|
49104
|
+
"button",
|
|
49105
|
+
{
|
|
49106
|
+
className: `acp-pill-btn acp-pill-btn--icon${isRecording ? " acp-pill-btn--recording" : ""}${isTranscribing ? " acp-pill-btn--transcribing" : ""}`,
|
|
49107
|
+
onClick: handleVoiceInput,
|
|
49108
|
+
disabled: isTranscribing,
|
|
49109
|
+
children: isTranscribing ? /* @__PURE__ */ jsx184(
|
|
49110
|
+
"svg",
|
|
49111
|
+
{
|
|
49112
|
+
width: "16",
|
|
49113
|
+
height: "16",
|
|
49114
|
+
viewBox: "0 0 24 24",
|
|
49115
|
+
fill: "none",
|
|
49116
|
+
stroke: "currentColor",
|
|
49117
|
+
strokeWidth: "2",
|
|
49118
|
+
strokeLinecap: "round",
|
|
49119
|
+
strokeLinejoin: "round",
|
|
49120
|
+
className: "acp-mic-spinner",
|
|
49121
|
+
children: /* @__PURE__ */ jsx184("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" })
|
|
49122
|
+
}
|
|
49123
|
+
) : isRecording ? /* @__PURE__ */ jsx184(MicOff, { size: 16 }) : /* @__PURE__ */ jsx184(Mic, { size: 16 })
|
|
49124
|
+
}
|
|
49125
|
+
)
|
|
49126
|
+
}
|
|
49127
|
+
),
|
|
49128
|
+
/* @__PURE__ */ jsx184(
|
|
49129
|
+
"button",
|
|
49130
|
+
{
|
|
49131
|
+
className: "acp-send-btn",
|
|
49132
|
+
onClick: isLoading ? handleStop : () => handleSend(),
|
|
49133
|
+
disabled: !isLoading && !prompt.trim(),
|
|
49134
|
+
title: isLoading ? "Stop" : "Send",
|
|
49135
|
+
children: isLoading ? /* @__PURE__ */ jsx184("span", { className: "acp-stop-square" }) : /* @__PURE__ */ jsx184(ArrowUp3, { size: 16 })
|
|
49136
|
+
}
|
|
49137
|
+
)
|
|
49138
|
+
] })
|
|
49139
|
+
] })
|
|
49140
|
+
]
|
|
49141
|
+
}
|
|
49142
|
+
)
|
|
49143
|
+
] }) })
|
|
49144
|
+
] })
|
|
49145
|
+
}
|
|
49146
|
+
);
|
|
49147
|
+
}
|
|
49148
|
+
);
|
|
49149
|
+
AIChatPanel.displayName = "AIChatPanel";
|
|
49150
|
+
function ToolActionsDisplay({ actions: actions2 }) {
|
|
49151
|
+
const [expanded, setExpanded] = useState49(false);
|
|
49152
|
+
return /* @__PURE__ */ jsxs100("div", { className: "acp-tool-actions", children: [
|
|
49153
|
+
/* @__PURE__ */ jsxs100(
|
|
49154
|
+
"button",
|
|
49155
|
+
{
|
|
49156
|
+
className: "acp-tool-actions-toggle",
|
|
49157
|
+
onClick: () => setExpanded((v) => !v),
|
|
49158
|
+
children: [
|
|
49159
|
+
/* @__PURE__ */ jsx184(Wrench, { size: 13 }),
|
|
49160
|
+
/* @__PURE__ */ jsxs100("span", { children: [
|
|
49161
|
+
actions2.length,
|
|
49162
|
+
" action",
|
|
49163
|
+
actions2.length !== 1 ? "s" : "",
|
|
49164
|
+
" performed"
|
|
49165
|
+
] }),
|
|
49166
|
+
/* @__PURE__ */ jsx184(
|
|
49167
|
+
ChevronRight,
|
|
49168
|
+
{
|
|
49169
|
+
size: 12,
|
|
49170
|
+
className: `acp-tool-chevron ${expanded ? "acp-tool-chevron--open" : ""}`
|
|
49171
|
+
}
|
|
49172
|
+
)
|
|
49173
|
+
]
|
|
49174
|
+
}
|
|
49175
|
+
),
|
|
49176
|
+
expanded && /* @__PURE__ */ jsx184("div", { className: "acp-tool-actions-list", children: actions2.map((action, i) => /* @__PURE__ */ jsxs100(
|
|
49177
|
+
"div",
|
|
49178
|
+
{
|
|
49179
|
+
className: `acp-tool-action-item ${action.result.success ? "acp-tool-action-item--ok" : "acp-tool-action-item--err"}`,
|
|
49180
|
+
children: [
|
|
49181
|
+
/* @__PURE__ */ jsx184("span", { className: "acp-tool-action-dot" }),
|
|
49182
|
+
/* @__PURE__ */ jsx184("span", { children: action.result.statusMessage })
|
|
49183
|
+
]
|
|
49184
|
+
},
|
|
49185
|
+
i
|
|
49186
|
+
)) })
|
|
49187
|
+
] });
|
|
49188
|
+
}
|
|
49189
|
+
async function callPlainChatAPI(messages, apiKey, signal, opts) {
|
|
49190
|
+
const model = opts?.webSearchEnabled ? "openai/gpt-4.1-mini:online" : "openai/gpt-4.1-mini";
|
|
49191
|
+
const apiMessages = messages.map((m, i) => {
|
|
49192
|
+
const isLast = i === messages.length - 1;
|
|
49193
|
+
if (isLast && m.role === "user" && opts?.attachments?.length) {
|
|
49194
|
+
const content2 = [];
|
|
49195
|
+
for (const att of opts.attachments) {
|
|
49196
|
+
if (att.type === "image" && att.dataUrl) {
|
|
49197
|
+
content2.push({
|
|
49198
|
+
type: "image_url",
|
|
49199
|
+
image_url: { url: att.dataUrl }
|
|
49200
|
+
});
|
|
49201
|
+
}
|
|
49202
|
+
}
|
|
49203
|
+
let text = m.content;
|
|
49204
|
+
const textFiles = opts.attachments.filter((a) => a.type === "text");
|
|
49205
|
+
if (textFiles.length > 0) {
|
|
49206
|
+
text += `
|
|
49207
|
+
|
|
49208
|
+
${textFiles.map((f) => `[File: ${f.name}]
|
|
49209
|
+
${f.textContent}`).join("\n\n")}`;
|
|
49210
|
+
}
|
|
49211
|
+
content2.push({ type: "text", text });
|
|
49212
|
+
return { role: m.role, content: content2 };
|
|
49213
|
+
}
|
|
49214
|
+
return { role: m.role, content: m.content };
|
|
49215
|
+
});
|
|
49216
|
+
const body = { model, messages: apiMessages };
|
|
49217
|
+
if (opts?.webSearchEnabled) {
|
|
49218
|
+
body.plugins = [{ id: "web", max_results: 5 }];
|
|
49219
|
+
}
|
|
49220
|
+
const response = await fetch(
|
|
49221
|
+
"https://openrouter.ai/api/v1/chat/completions",
|
|
49222
|
+
{
|
|
49223
|
+
method: "POST",
|
|
49224
|
+
signal,
|
|
49225
|
+
headers: {
|
|
49226
|
+
Authorization: `Bearer ${apiKey}`,
|
|
49227
|
+
"Content-Type": "application/json"
|
|
49228
|
+
},
|
|
49229
|
+
body: JSON.stringify(body)
|
|
49230
|
+
}
|
|
49231
|
+
);
|
|
49232
|
+
if (!response.ok) {
|
|
49233
|
+
let message = `OpenRouter error ${response.status}`;
|
|
49234
|
+
try {
|
|
49235
|
+
const err = await response.json();
|
|
49236
|
+
if (err?.error?.message) {
|
|
49237
|
+
message = err.error.message;
|
|
49238
|
+
}
|
|
49239
|
+
} catch {
|
|
49240
|
+
}
|
|
49241
|
+
throw new Error(message);
|
|
49242
|
+
}
|
|
49243
|
+
const data = await response.json();
|
|
49244
|
+
const content = data?.choices?.[0]?.message?.content;
|
|
49245
|
+
if (!content) {
|
|
49246
|
+
throw new Error("No response returned by the API.");
|
|
49247
|
+
}
|
|
49248
|
+
return content;
|
|
49249
|
+
}
|
|
49250
|
+
|
|
49251
|
+
// index.tsx
|
|
49252
|
+
import { jsx as jsx185 } from "react/jsx-runtime";
|
|
46522
49253
|
polyfill_default();
|
|
46523
49254
|
var ExcalidrawBase = (props) => {
|
|
46524
49255
|
const {
|
|
@@ -46555,7 +49286,10 @@ var ExcalidrawBase = (props) => {
|
|
|
46555
49286
|
renderEmbeddable,
|
|
46556
49287
|
aiEnabled,
|
|
46557
49288
|
showDeprecatedFonts,
|
|
46558
|
-
|
|
49289
|
+
geminiApiKey,
|
|
49290
|
+
renderScrollbars,
|
|
49291
|
+
onBeforeImageGen,
|
|
49292
|
+
onAfterImageGen
|
|
46559
49293
|
} = props;
|
|
46560
49294
|
const canvasActions = props.UIOptions?.canvasActions;
|
|
46561
49295
|
const UIOptions = {
|
|
@@ -46574,7 +49308,7 @@ var ExcalidrawBase = (props) => {
|
|
|
46574
49308
|
if (UIOptions.canvasActions.toggleTheme === null && typeof theme === "undefined") {
|
|
46575
49309
|
UIOptions.canvasActions.toggleTheme = true;
|
|
46576
49310
|
}
|
|
46577
|
-
|
|
49311
|
+
useEffect56(() => {
|
|
46578
49312
|
const importPolyfill = async () => {
|
|
46579
49313
|
await import("canvas-roundrect-polyfill");
|
|
46580
49314
|
};
|
|
@@ -46591,7 +49325,7 @@ var ExcalidrawBase = (props) => {
|
|
|
46591
49325
|
document.removeEventListener("touchmove", handleTouchMove);
|
|
46592
49326
|
};
|
|
46593
49327
|
}, []);
|
|
46594
|
-
return /* @__PURE__ */
|
|
49328
|
+
return /* @__PURE__ */ jsx185(EditorJotaiProvider, { store: editorJotaiStore, children: /* @__PURE__ */ jsx185(InitializeApp, { langCode, theme, children: /* @__PURE__ */ jsx185(
|
|
46595
49329
|
App_default,
|
|
46596
49330
|
{
|
|
46597
49331
|
onChange,
|
|
@@ -46627,7 +49361,10 @@ var ExcalidrawBase = (props) => {
|
|
|
46627
49361
|
renderEmbeddable,
|
|
46628
49362
|
aiEnabled: aiEnabled !== false,
|
|
46629
49363
|
showDeprecatedFonts,
|
|
49364
|
+
geminiApiKey,
|
|
46630
49365
|
renderScrollbars,
|
|
49366
|
+
onBeforeImageGen,
|
|
49367
|
+
onAfterImageGen,
|
|
46631
49368
|
children
|
|
46632
49369
|
}
|
|
46633
49370
|
) }) });
|
|
@@ -46670,9 +49407,10 @@ var areEqual5 = (prevProps, nextProps) => {
|
|
|
46670
49407
|
});
|
|
46671
49408
|
return isUIOptionsSame && isShallowEqual10(prev, next);
|
|
46672
49409
|
};
|
|
46673
|
-
var Excalidraw =
|
|
49410
|
+
var Excalidraw = React58.memo(ExcalidrawBase, areEqual5);
|
|
46674
49411
|
Excalidraw.displayName = "Excalidraw";
|
|
46675
49412
|
export {
|
|
49413
|
+
AIChatPanel,
|
|
46676
49414
|
Button,
|
|
46677
49415
|
CaptureUpdateAction39 as CaptureUpdateAction,
|
|
46678
49416
|
CommandPalette,
|
|
@@ -46740,7 +49478,7 @@ export {
|
|
|
46740
49478
|
useHandleLibrary,
|
|
46741
49479
|
useI18n,
|
|
46742
49480
|
useStylesPanelMode,
|
|
46743
|
-
|
|
49481
|
+
viewportCoordsToSceneCoords5 as viewportCoordsToSceneCoords,
|
|
46744
49482
|
zoomToFitBounds
|
|
46745
49483
|
};
|
|
46746
49484
|
//# sourceMappingURL=index.js.map
|