@forgecharts/sdk 1.3.6 → 1.3.8
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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -12
- package/dist/index.js.map +1 -1
- package/dist/internal.js +45 -12
- package/dist/internal.js.map +1 -1
- package/dist/licensing/ChartRuntimeResolver.d.ts.map +1 -1
- package/dist/licensing/LicenseManager.d.ts +23 -1
- package/dist/licensing/LicenseManager.d.ts.map +1 -1
- package/dist/licensing/__tests__/lockdownPolicy.test.d.ts +2 -0
- package/dist/licensing/__tests__/lockdownPolicy.test.d.ts.map +1 -0
- package/dist/react/index.js +1943 -261
- package/dist/react/index.js.map +1 -1
- package/dist/react/internal.js +4005 -3956
- package/dist/react/internal.js.map +1 -1
- package/dist/react/shell/ManagedAppShell.d.ts.map +1 -1
- package/dist/react/workspace/ChartWorkspace.d.ts +20 -3
- package/dist/react/workspace/ChartWorkspace.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React11, { forwardRef, useRef, useState, useImperativeHandle, useEffect, useCallback, createContext, useMemo, useContext } from 'react';
|
|
2
2
|
import { TextStyle, Application, Container, Graphics, Text, FillGradient } from 'pixi.js';
|
|
3
3
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import ReactDOM from 'react-dom';
|
|
@@ -10732,6 +10732,7 @@ var DEFAULT_FEATURES = {};
|
|
|
10732
10732
|
var LicenseManager = class _LicenseManager {
|
|
10733
10733
|
static instance = null;
|
|
10734
10734
|
payload = null;
|
|
10735
|
+
status = "idle";
|
|
10735
10736
|
subscribers = /* @__PURE__ */ new Set();
|
|
10736
10737
|
constructor() {
|
|
10737
10738
|
}
|
|
@@ -10778,17 +10779,27 @@ var LicenseManager = class _LicenseManager {
|
|
|
10778
10779
|
if (!verifyUrl) {
|
|
10779
10780
|
throw new Error("LicenseManager: verifyUrl is required");
|
|
10780
10781
|
}
|
|
10781
|
-
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
|
|
10782
|
+
this.status = "pending";
|
|
10783
|
+
this.notify();
|
|
10784
|
+
let data;
|
|
10785
|
+
try {
|
|
10786
|
+
const res = await fetch(verifyUrl, {
|
|
10787
|
+
method: "POST",
|
|
10788
|
+
headers: { "Content-Type": "application/json" },
|
|
10789
|
+
body: JSON.stringify({ licenseKey: key.trim().toUpperCase() })
|
|
10790
|
+
});
|
|
10791
|
+
if (!res.ok) {
|
|
10792
|
+
throw new Error(`License server error: ${res.status} ${res.statusText}`);
|
|
10793
|
+
}
|
|
10794
|
+
data = await res.json();
|
|
10795
|
+
if (!data.valid) {
|
|
10796
|
+
throw new Error(data.reason ?? "License invalid");
|
|
10797
|
+
}
|
|
10798
|
+
} catch (err) {
|
|
10799
|
+
this.payload = null;
|
|
10800
|
+
this.status = "failed";
|
|
10801
|
+
this.notify();
|
|
10802
|
+
throw err;
|
|
10792
10803
|
}
|
|
10793
10804
|
const raw = {
|
|
10794
10805
|
licenseKey: data.licenseKey ?? key.trim().toUpperCase(),
|
|
@@ -10803,14 +10814,29 @@ var LicenseManager = class _LicenseManager {
|
|
|
10803
10814
|
};
|
|
10804
10815
|
const payload = this.#applyPolicyPipeline(raw);
|
|
10805
10816
|
this.payload = payload;
|
|
10817
|
+
this.status = "valid";
|
|
10806
10818
|
this.notify();
|
|
10807
10819
|
return payload;
|
|
10808
10820
|
}
|
|
10809
10821
|
/** Load a pre-validated payload (e.g. from cache / localStorage). Runs through the same migration + guardrail pipeline as `validateLicense`. */
|
|
10810
10822
|
loadLicense(payload) {
|
|
10811
10823
|
this.payload = this.#applyPolicyPipeline(payload);
|
|
10824
|
+
this.status = "valid";
|
|
10812
10825
|
this.notify();
|
|
10813
10826
|
}
|
|
10827
|
+
/** Where the license is in its validation lifecycle. */
|
|
10828
|
+
getValidationStatus() {
|
|
10829
|
+
return this.status;
|
|
10830
|
+
}
|
|
10831
|
+
/**
|
|
10832
|
+
* True when a validation attempt is pending or has failed — every
|
|
10833
|
+
* license-gated feature must be denied. 'idle' (validation never
|
|
10834
|
+
* attempted) is NOT locked down: dev/test usage keeps the permissive
|
|
10835
|
+
* "omitted = allowed" defaults.
|
|
10836
|
+
*/
|
|
10837
|
+
isLockedDown() {
|
|
10838
|
+
return this.status === "pending" || this.status === "failed";
|
|
10839
|
+
}
|
|
10814
10840
|
/**
|
|
10815
10841
|
* Runs a raw/legacy/partial payload through `migrateLegacyPayload` (fills
|
|
10816
10842
|
* missing productTier/capabilities/schemaVersion, deterministically, with
|
|
@@ -10865,9 +10891,10 @@ var LicenseManager = class _LicenseManager {
|
|
|
10865
10891
|
hasCapability(name) {
|
|
10866
10892
|
return this.getCapabilities()[name] === true;
|
|
10867
10893
|
}
|
|
10868
|
-
/** Clear the current license state (returns to safe unmanaged defaults). */
|
|
10894
|
+
/** Clear the current license state (returns to safe unmanaged defaults and 'idle' status). */
|
|
10869
10895
|
clear() {
|
|
10870
10896
|
this.payload = null;
|
|
10897
|
+
this.status = "idle";
|
|
10871
10898
|
this.notify();
|
|
10872
10899
|
}
|
|
10873
10900
|
};
|
|
@@ -11234,8 +11261,14 @@ var ChartRuntimeResolver = class _ChartRuntimeResolver {
|
|
|
11234
11261
|
* If the active license has an explicit feature flag, use it.
|
|
11235
11262
|
* Otherwise fall back to `defaultValue` (allows capability even without an
|
|
11236
11263
|
* explicit flag, matching the "omitted = allowed" convention).
|
|
11264
|
+
*
|
|
11265
|
+
* Lockdown override: while a validation attempt is pending or has failed,
|
|
11266
|
+
* every gated feature is denied — a failed verify must never yield more
|
|
11267
|
+
* than a valid one. 'idle' (validation never attempted) keeps the
|
|
11268
|
+
* permissive defaults for dev/test usage.
|
|
11237
11269
|
*/
|
|
11238
11270
|
#featureOrDefault(name, defaultValue) {
|
|
11271
|
+
if (this.lm.isLockedDown()) return false;
|
|
11239
11272
|
const features = this.lm.getFeatures();
|
|
11240
11273
|
const flag = features[name];
|
|
11241
11274
|
return flag === void 0 ? defaultValue : flag === true;
|
|
@@ -28018,249 +28051,1889 @@ function OrderTicket({
|
|
|
28018
28051
|
}
|
|
28019
28052
|
);
|
|
28020
28053
|
}
|
|
28021
|
-
|
|
28022
|
-
|
|
28023
|
-
|
|
28024
|
-
|
|
28025
|
-
|
|
28026
|
-
|
|
28027
|
-
|
|
28028
|
-
|
|
28029
|
-
|
|
28030
|
-
|
|
28031
|
-
|
|
28032
|
-
|
|
28033
|
-
|
|
28034
|
-
|
|
28035
|
-
|
|
28036
|
-
|
|
28037
|
-
|
|
28038
|
-
|
|
28039
|
-
|
|
28040
|
-
|
|
28041
|
-
|
|
28042
|
-
|
|
28043
|
-
|
|
28044
|
-
|
|
28045
|
-
|
|
28046
|
-
|
|
28047
|
-
|
|
28048
|
-
|
|
28049
|
-
|
|
28050
|
-
|
|
28051
|
-
|
|
28052
|
-
|
|
28053
|
-
|
|
28054
|
-
|
|
28055
|
-
|
|
28056
|
-
|
|
28057
|
-
|
|
28058
|
-
|
|
28059
|
-
|
|
28060
|
-
|
|
28061
|
-
|
|
28062
|
-
|
|
28063
|
-
|
|
28064
|
-
|
|
28065
|
-
|
|
28066
|
-
|
|
28067
|
-
|
|
28068
|
-
|
|
28069
|
-
|
|
28070
|
-
|
|
28071
|
-
|
|
28072
|
-
|
|
28073
|
-
|
|
28074
|
-
|
|
28075
|
-
|
|
28076
|
-
|
|
28077
|
-
|
|
28078
|
-
|
|
28079
|
-
|
|
28080
|
-
|
|
28081
|
-
|
|
28082
|
-
|
|
28083
|
-
|
|
28084
|
-
|
|
28085
|
-
|
|
28086
|
-
|
|
28087
|
-
|
|
28088
|
-
|
|
28089
|
-
|
|
28090
|
-
|
|
28091
|
-
|
|
28092
|
-
|
|
28093
|
-
|
|
28094
|
-
|
|
28095
|
-
|
|
28096
|
-
|
|
28097
|
-
|
|
28098
|
-
|
|
28099
|
-
|
|
28100
|
-
|
|
28101
|
-
|
|
28102
|
-
|
|
28103
|
-
|
|
28104
|
-
|
|
28105
|
-
|
|
28106
|
-
|
|
28107
|
-
|
|
28108
|
-
|
|
28109
|
-
|
|
28110
|
-
|
|
28111
|
-
|
|
28112
|
-
|
|
28113
|
-
|
|
28114
|
-
|
|
28115
|
-
|
|
28116
|
-
|
|
28117
|
-
|
|
28118
|
-
|
|
28119
|
-
|
|
28120
|
-
|
|
28121
|
-
|
|
28122
|
-
|
|
28123
|
-
|
|
28124
|
-
|
|
28125
|
-
|
|
28126
|
-
|
|
28127
|
-
|
|
28128
|
-
|
|
28129
|
-
|
|
28130
|
-
|
|
28131
|
-
|
|
28132
|
-
|
|
28133
|
-
|
|
28134
|
-
|
|
28135
|
-
|
|
28136
|
-
|
|
28137
|
-
|
|
28138
|
-
|
|
28139
|
-
|
|
28140
|
-
|
|
28141
|
-
|
|
28142
|
-
|
|
28143
|
-
|
|
28144
|
-
|
|
28145
|
-
|
|
28146
|
-
|
|
28147
|
-
|
|
28148
|
-
|
|
28149
|
-
|
|
28150
|
-
|
|
28151
|
-
|
|
28152
|
-
|
|
28153
|
-
|
|
28154
|
-
|
|
28155
|
-
|
|
28156
|
-
|
|
28157
|
-
|
|
28158
|
-
|
|
28159
|
-
|
|
28160
|
-
|
|
28161
|
-
|
|
28162
|
-
|
|
28163
|
-
|
|
28164
|
-
|
|
28165
|
-
|
|
28166
|
-
|
|
28167
|
-
|
|
28168
|
-
|
|
28169
|
-
|
|
28170
|
-
|
|
28171
|
-
|
|
28172
|
-
|
|
28173
|
-
|
|
28174
|
-
|
|
28175
|
-
|
|
28176
|
-
|
|
28177
|
-
|
|
28178
|
-
|
|
28179
|
-
|
|
28180
|
-
|
|
28181
|
-
|
|
28182
|
-
|
|
28183
|
-
|
|
28184
|
-
|
|
28185
|
-
|
|
28186
|
-
|
|
28187
|
-
|
|
28188
|
-
|
|
28189
|
-
|
|
28190
|
-
|
|
28191
|
-
|
|
28192
|
-
|
|
28193
|
-
|
|
28194
|
-
|
|
28195
|
-
|
|
28196
|
-
|
|
28197
|
-
|
|
28198
|
-
|
|
28199
|
-
|
|
28200
|
-
|
|
28201
|
-
|
|
28202
|
-
|
|
28203
|
-
|
|
28204
|
-
|
|
28205
|
-
|
|
28206
|
-
|
|
28207
|
-
|
|
28208
|
-
|
|
28209
|
-
|
|
28210
|
-
|
|
28211
|
-
|
|
28212
|
-
|
|
28213
|
-
|
|
28214
|
-
|
|
28215
|
-
|
|
28216
|
-
|
|
28217
|
-
|
|
28218
|
-
|
|
28219
|
-
|
|
28220
|
-
|
|
28221
|
-
|
|
28222
|
-
|
|
28223
|
-
|
|
28224
|
-
|
|
28225
|
-
|
|
28226
|
-
|
|
28227
|
-
|
|
28228
|
-
|
|
28229
|
-
|
|
28230
|
-
|
|
28231
|
-
|
|
28232
|
-
|
|
28233
|
-
|
|
28234
|
-
|
|
28235
|
-
|
|
28236
|
-
|
|
28237
|
-
|
|
28238
|
-
|
|
28239
|
-
|
|
28240
|
-
|
|
28241
|
-
|
|
28242
|
-
|
|
28243
|
-
|
|
28244
|
-
|
|
28245
|
-
|
|
28246
|
-
|
|
28247
|
-
|
|
28248
|
-
|
|
28249
|
-
|
|
28250
|
-
|
|
28251
|
-
|
|
28252
|
-
|
|
28253
|
-
|
|
28254
|
-
|
|
28255
|
-
|
|
28256
|
-
|
|
28257
|
-
|
|
28258
|
-
|
|
28259
|
-
|
|
28260
|
-
|
|
28261
|
-
|
|
28262
|
-
|
|
28263
|
-
|
|
28054
|
+
var COLUMN_TYPES = [
|
|
28055
|
+
{ value: "number", label: "Number", hint: "Float (prices, scores, ratios)" },
|
|
28056
|
+
{ value: "integer", label: "Integer", hint: "Whole number (volume, count)" },
|
|
28057
|
+
{ value: "text", label: "Text", hint: "String (labels, symbols)" },
|
|
28058
|
+
{ value: "boolean", label: "Boolean", hint: "True / False flag" },
|
|
28059
|
+
{ value: "date", label: "Date", hint: "Calendar date (no time)" },
|
|
28060
|
+
{ value: "timestamp", label: "Timestamp", hint: "Full datetime with timezone" },
|
|
28061
|
+
{ value: "percentage", label: "Percentage", hint: "Float stored as 0\u2013100" },
|
|
28062
|
+
{ value: "price", label: "Price", hint: "Float (currency value)" }
|
|
28063
|
+
];
|
|
28064
|
+
function DatasetManagerDrawer({ scriptId, isOwner, apiBase, authToken: staticToken, getAuthToken }) {
|
|
28065
|
+
const resolveToken = React11.useCallback(async () => {
|
|
28066
|
+
if (staticToken) return staticToken;
|
|
28067
|
+
if (getAuthToken) {
|
|
28068
|
+
try {
|
|
28069
|
+
return await getAuthToken();
|
|
28070
|
+
} catch {
|
|
28071
|
+
return "";
|
|
28072
|
+
}
|
|
28073
|
+
}
|
|
28074
|
+
return "";
|
|
28075
|
+
}, [staticToken, getAuthToken]);
|
|
28076
|
+
const [view, setView] = useState("list");
|
|
28077
|
+
const [datasets, setDatasets] = useState([]);
|
|
28078
|
+
const [activeDataset, setActiveDataset] = useState(null);
|
|
28079
|
+
const [loading, setLoading] = useState(false);
|
|
28080
|
+
const [error, setError] = useState(null);
|
|
28081
|
+
const fetchDatasets = useCallback(async () => {
|
|
28082
|
+
if (!scriptId) return;
|
|
28083
|
+
setLoading(true);
|
|
28084
|
+
setError(null);
|
|
28085
|
+
try {
|
|
28086
|
+
const token = await resolveToken();
|
|
28087
|
+
const res = await fetch(`${apiBase}/api/scripts/${scriptId}/datasets`, {
|
|
28088
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
28089
|
+
});
|
|
28090
|
+
if (!res.ok) throw new Error(await res.text());
|
|
28091
|
+
setDatasets(await res.json());
|
|
28092
|
+
} catch (e) {
|
|
28093
|
+
setError(String(e));
|
|
28094
|
+
} finally {
|
|
28095
|
+
setLoading(false);
|
|
28096
|
+
}
|
|
28097
|
+
}, [scriptId, apiBase, resolveToken]);
|
|
28098
|
+
useEffect(() => {
|
|
28099
|
+
void fetchDatasets();
|
|
28100
|
+
}, [fetchDatasets]);
|
|
28101
|
+
useEffect(() => {
|
|
28102
|
+
const processing = datasets.some((d) => d.ingest_status === "pending" || d.ingest_status === "processing");
|
|
28103
|
+
if (!processing) return;
|
|
28104
|
+
const timer = setInterval(() => {
|
|
28105
|
+
void fetchDatasets();
|
|
28106
|
+
}, 3e3);
|
|
28107
|
+
return () => clearInterval(timer);
|
|
28108
|
+
}, [datasets, fetchDatasets]);
|
|
28109
|
+
const handleDelete = async (dsId) => {
|
|
28110
|
+
if (!scriptId || !confirm("Delete this dataset and all its rows?")) return;
|
|
28111
|
+
const token = await resolveToken();
|
|
28112
|
+
await fetch(`${apiBase}/api/scripts/${scriptId}/datasets/${dsId}`, {
|
|
28113
|
+
method: "DELETE",
|
|
28114
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
28115
|
+
});
|
|
28116
|
+
void fetchDatasets();
|
|
28117
|
+
};
|
|
28118
|
+
const handleViewUsage = (ds) => {
|
|
28119
|
+
setActiveDataset(ds);
|
|
28120
|
+
setView("usage");
|
|
28121
|
+
};
|
|
28122
|
+
const handleEdit = (ds) => {
|
|
28123
|
+
setActiveDataset(ds);
|
|
28124
|
+
setView("design");
|
|
28125
|
+
};
|
|
28126
|
+
if (!scriptId) {
|
|
28127
|
+
return /* @__PURE__ */ jsx("div", { className: "dsm-empty", children: /* @__PURE__ */ jsx("p", { children: "Save your script first to manage datasets." }) });
|
|
28128
|
+
}
|
|
28129
|
+
return /* @__PURE__ */ jsxs("div", { className: "dsm-root", children: [
|
|
28130
|
+
/* @__PURE__ */ jsxs("div", { className: "dsm-header", children: [
|
|
28131
|
+
view !== "list" && /* @__PURE__ */ jsx("button", { className: "dsm-back-btn", onClick: () => {
|
|
28132
|
+
setView("list");
|
|
28133
|
+
setActiveDataset(null);
|
|
28134
|
+
}, children: "\u2190 Back" }),
|
|
28135
|
+
/* @__PURE__ */ jsx("span", { className: "dsm-title", children: view === "list" ? "Datasets" : view === "design" ? activeDataset ? `Edit: ${activeDataset.name}` : "New Dataset" : view === "import" ? `Import: ${activeDataset?.name}` : `Usage: ${activeDataset?.name}` }),
|
|
28136
|
+
view === "list" && isOwner && /* @__PURE__ */ jsx("button", { className: "dsm-new-btn", onClick: () => {
|
|
28137
|
+
setActiveDataset(null);
|
|
28138
|
+
setView("design");
|
|
28139
|
+
}, children: "+ New Dataset" })
|
|
28140
|
+
] }),
|
|
28141
|
+
error && /* @__PURE__ */ jsx("div", { className: "dsm-error", children: error }),
|
|
28142
|
+
view === "list" && /* @__PURE__ */ jsx(
|
|
28143
|
+
DatasetList,
|
|
28144
|
+
{
|
|
28145
|
+
datasets,
|
|
28146
|
+
loading,
|
|
28147
|
+
isOwner,
|
|
28148
|
+
onDelete: handleDelete,
|
|
28149
|
+
onViewUsage: handleViewUsage,
|
|
28150
|
+
onEdit: handleEdit,
|
|
28151
|
+
onImport: (ds) => {
|
|
28152
|
+
setActiveDataset(ds);
|
|
28153
|
+
setView("import");
|
|
28154
|
+
}
|
|
28155
|
+
}
|
|
28156
|
+
),
|
|
28157
|
+
view === "design" && /* @__PURE__ */ jsx(
|
|
28158
|
+
DatasetDesigner,
|
|
28159
|
+
{
|
|
28160
|
+
scriptId,
|
|
28161
|
+
existing: activeDataset,
|
|
28162
|
+
apiBase,
|
|
28163
|
+
resolveToken,
|
|
28164
|
+
onSchemaSaved: (ds) => {
|
|
28165
|
+
setDatasets((prev) => {
|
|
28166
|
+
const idx = prev.findIndex((d) => d.id === ds.id);
|
|
28167
|
+
return idx >= 0 ? prev.map((d, i) => i === idx ? ds : d) : [...prev, ds];
|
|
28168
|
+
});
|
|
28169
|
+
setActiveDataset(ds);
|
|
28170
|
+
setView("import");
|
|
28171
|
+
},
|
|
28172
|
+
onError: setError
|
|
28173
|
+
}
|
|
28174
|
+
),
|
|
28175
|
+
view === "import" && activeDataset && /* @__PURE__ */ jsx(
|
|
28176
|
+
DatasetImporter,
|
|
28177
|
+
{
|
|
28178
|
+
scriptId,
|
|
28179
|
+
dataset: activeDataset,
|
|
28180
|
+
apiBase,
|
|
28181
|
+
resolveToken,
|
|
28182
|
+
onImported: (ds) => {
|
|
28183
|
+
setDatasets((prev) => prev.map((d) => d.id === ds.id ? ds : d));
|
|
28184
|
+
setActiveDataset(ds);
|
|
28185
|
+
setView("usage");
|
|
28186
|
+
},
|
|
28187
|
+
onSkip: () => setView("usage"),
|
|
28188
|
+
onError: setError
|
|
28189
|
+
}
|
|
28190
|
+
),
|
|
28191
|
+
view === "usage" && activeDataset && /* @__PURE__ */ jsx(DatasetUsage, { dataset: activeDataset })
|
|
28192
|
+
] });
|
|
28193
|
+
}
|
|
28194
|
+
function DatasetList({ datasets, loading, isOwner, onDelete, onViewUsage, onEdit, onImport }) {
|
|
28195
|
+
if (loading && datasets.length === 0) {
|
|
28196
|
+
return /* @__PURE__ */ jsx("div", { className: "dsm-loading", children: "Loading datasets\u2026" });
|
|
28197
|
+
}
|
|
28198
|
+
if (datasets.length === 0) {
|
|
28199
|
+
return /* @__PURE__ */ jsxs("div", { className: "dsm-empty", children: [
|
|
28200
|
+
/* @__PURE__ */ jsx("p", { children: "No datasets yet." }),
|
|
28201
|
+
isOwner && /* @__PURE__ */ jsxs("p", { children: [
|
|
28202
|
+
"Click ",
|
|
28203
|
+
/* @__PURE__ */ jsx("strong", { children: "+ New Dataset" }),
|
|
28204
|
+
" to import your first dataset."
|
|
28205
|
+
] })
|
|
28206
|
+
] });
|
|
28207
|
+
}
|
|
28208
|
+
return /* @__PURE__ */ jsx("div", { className: "dsm-list", children: datasets.map((ds) => /* @__PURE__ */ jsxs("div", { className: "dsm-list-item", children: [
|
|
28209
|
+
/* @__PURE__ */ jsxs("div", { className: "dsm-list-item-header", children: [
|
|
28210
|
+
/* @__PURE__ */ jsx("span", { className: "dsm-list-item-name", children: ds.name }),
|
|
28211
|
+
/* @__PURE__ */ jsx("span", { className: `dsm-status dsm-status--${ds.ingest_status}`, children: ds.ingest_status === "ready" ? `${ds.row_count.toLocaleString()} rows` : ds.ingest_status })
|
|
28212
|
+
] }),
|
|
28213
|
+
/* @__PURE__ */ jsxs("div", { className: "dsm-list-item-cols", children: [
|
|
28214
|
+
/* @__PURE__ */ jsx("span", { className: "dsm-col-tag dsm-col-tag--time", children: "time" }),
|
|
28215
|
+
ds.columns.map((c) => /* @__PURE__ */ jsx("span", { className: `dsm-col-tag dsm-col-tag--${c.type}`, title: c.type, children: c.name }, c.name))
|
|
28216
|
+
] }),
|
|
28217
|
+
ds.ingest_error && /* @__PURE__ */ jsxs("div", { className: "dsm-ingest-error", children: [
|
|
28218
|
+
"\u26A0 ",
|
|
28219
|
+
ds.ingest_error
|
|
28220
|
+
] }),
|
|
28221
|
+
/* @__PURE__ */ jsxs("div", { className: "dsm-list-item-actions", children: [
|
|
28222
|
+
ds.ingest_status === "ready" && /* @__PURE__ */ jsx("button", { className: "dsm-action-btn", onClick: () => onViewUsage(ds), children: "Usage" }),
|
|
28223
|
+
isOwner && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
28224
|
+
/* @__PURE__ */ jsx("button", { className: "dsm-action-btn dsm-action-btn--primary", onClick: () => onImport(ds), children: ds.ingest_status === "ready" ? "Re-import" : "Import Data" }),
|
|
28225
|
+
/* @__PURE__ */ jsx("button", { className: "dsm-action-btn", onClick: () => onEdit(ds), children: "Edit Schema" }),
|
|
28226
|
+
/* @__PURE__ */ jsx("button", { className: "dsm-action-btn dsm-action-btn--danger", onClick: () => onDelete(ds.id), children: "Delete" })
|
|
28227
|
+
] })
|
|
28228
|
+
] })
|
|
28229
|
+
] }, ds.id)) });
|
|
28230
|
+
}
|
|
28231
|
+
function DatasetDesigner({ scriptId, existing, apiBase, resolveToken, onSchemaSaved, onError }) {
|
|
28232
|
+
const [name, setName] = useState(existing?.name ?? "");
|
|
28233
|
+
const [columns, setColumns] = useState(
|
|
28234
|
+
existing?.columns ?? [{ name: "", type: "number", nullable: true }]
|
|
28235
|
+
);
|
|
28236
|
+
const [saving, setSaving] = useState(false);
|
|
28237
|
+
const addColumn = () => setColumns((prev) => [...prev, { name: "", type: "number", nullable: true }]);
|
|
28238
|
+
const removeColumn = (i) => setColumns((prev) => prev.filter((_, idx) => idx !== i));
|
|
28239
|
+
const updateColumn = (i, field, value) => setColumns((prev) => prev.map((c, idx) => idx === i ? { ...c, [field]: value } : c));
|
|
28240
|
+
const handleSaveSchema = async () => {
|
|
28241
|
+
if (!name.trim()) {
|
|
28242
|
+
onError("Dataset name is required.");
|
|
28243
|
+
return;
|
|
28244
|
+
}
|
|
28245
|
+
if (columns.some((c) => !c.name.trim())) {
|
|
28246
|
+
onError("All columns must have a name.");
|
|
28247
|
+
return;
|
|
28248
|
+
}
|
|
28249
|
+
setSaving(true);
|
|
28250
|
+
try {
|
|
28251
|
+
const body = {
|
|
28252
|
+
name: name.trim(),
|
|
28253
|
+
columns: columns.map((c) => ({ ...c, name: c.name.trim() }))
|
|
28254
|
+
};
|
|
28255
|
+
const token = await resolveToken();
|
|
28256
|
+
const url = existing ? `${apiBase}/api/scripts/${scriptId}/datasets/${existing.id}` : `${apiBase}/api/scripts/${scriptId}/datasets`;
|
|
28257
|
+
const method = existing ? "PATCH" : "POST";
|
|
28258
|
+
const res = await fetch(url, {
|
|
28259
|
+
method,
|
|
28260
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
28261
|
+
body: JSON.stringify(body)
|
|
28262
|
+
});
|
|
28263
|
+
if (!res.ok) {
|
|
28264
|
+
const text = await res.text();
|
|
28265
|
+
let msg;
|
|
28266
|
+
try {
|
|
28267
|
+
msg = JSON.stringify(JSON.parse(text).error);
|
|
28268
|
+
} catch {
|
|
28269
|
+
msg = `HTTP ${res.status}`;
|
|
28270
|
+
}
|
|
28271
|
+
throw new Error(msg);
|
|
28272
|
+
}
|
|
28273
|
+
onSchemaSaved(await res.json());
|
|
28274
|
+
} catch (e) {
|
|
28275
|
+
onError(String(e));
|
|
28276
|
+
} finally {
|
|
28277
|
+
setSaving(false);
|
|
28278
|
+
}
|
|
28279
|
+
};
|
|
28280
|
+
return /* @__PURE__ */ jsxs("div", { className: "dsm-designer", children: [
|
|
28281
|
+
!existing && /* @__PURE__ */ jsxs("div", { className: "dsm-steps", children: [
|
|
28282
|
+
/* @__PURE__ */ jsx("span", { className: "dsm-step dsm-step--active", children: "1 \xB7 Define schema" }),
|
|
28283
|
+
/* @__PURE__ */ jsx("span", { className: "dsm-step-sep", children: "\u203A" }),
|
|
28284
|
+
/* @__PURE__ */ jsx("span", { className: "dsm-step dsm-step--upcoming", children: "2 \xB7 Import data" })
|
|
28285
|
+
] }),
|
|
28286
|
+
/* @__PURE__ */ jsx("label", { className: "dsm-label", children: "Dataset Name" }),
|
|
28287
|
+
/* @__PURE__ */ jsx(
|
|
28288
|
+
"input",
|
|
28289
|
+
{
|
|
28290
|
+
className: "dsm-input",
|
|
28291
|
+
value: name,
|
|
28292
|
+
onChange: (e) => setName(e.target.value),
|
|
28293
|
+
placeholder: "e.g. sentiment",
|
|
28294
|
+
disabled: !!existing
|
|
28295
|
+
}
|
|
28296
|
+
),
|
|
28297
|
+
/* @__PURE__ */ jsx("p", { className: "dsm-hint", children: "Lowercase letters, numbers, and underscores only." }),
|
|
28298
|
+
/* @__PURE__ */ jsx("label", { className: "dsm-label", children: "Columns" }),
|
|
28299
|
+
/* @__PURE__ */ jsxs("div", { className: "dsm-col-locked", children: [
|
|
28300
|
+
/* @__PURE__ */ jsx("span", { className: "dsm-col-tag dsm-col-tag--time", children: "time" }),
|
|
28301
|
+
/* @__PURE__ */ jsx("span", { className: "dsm-hint", children: "Required \u2014 always the first column" })
|
|
28302
|
+
] }),
|
|
28303
|
+
columns.map((col, i) => /* @__PURE__ */ jsxs("div", { className: "dsm-col-row", children: [
|
|
28304
|
+
/* @__PURE__ */ jsx(
|
|
28305
|
+
"input",
|
|
28306
|
+
{
|
|
28307
|
+
className: "dsm-input dsm-col-name",
|
|
28308
|
+
value: col.name,
|
|
28309
|
+
onChange: (e) => updateColumn(i, "name", e.target.value),
|
|
28310
|
+
placeholder: "column_name"
|
|
28311
|
+
}
|
|
28312
|
+
),
|
|
28313
|
+
/* @__PURE__ */ jsx(
|
|
28314
|
+
"select",
|
|
28315
|
+
{
|
|
28316
|
+
className: "dsm-select",
|
|
28317
|
+
value: col.type,
|
|
28318
|
+
onChange: (e) => updateColumn(i, "type", e.target.value),
|
|
28319
|
+
children: COLUMN_TYPES.map((t) => /* @__PURE__ */ jsx("option", { value: t.value, title: t.hint, children: t.label }, t.value))
|
|
28320
|
+
}
|
|
28321
|
+
),
|
|
28322
|
+
/* @__PURE__ */ jsxs("label", { className: "dsm-nullable-label", children: [
|
|
28323
|
+
/* @__PURE__ */ jsx(
|
|
28324
|
+
"input",
|
|
28325
|
+
{
|
|
28326
|
+
type: "checkbox",
|
|
28327
|
+
checked: col.nullable,
|
|
28328
|
+
onChange: (e) => updateColumn(i, "nullable", e.target.checked)
|
|
28329
|
+
}
|
|
28330
|
+
),
|
|
28331
|
+
"Nullable"
|
|
28332
|
+
] }),
|
|
28333
|
+
/* @__PURE__ */ jsx("button", { className: "dsm-remove-col-btn", onClick: () => removeColumn(i), children: "\u2715" })
|
|
28334
|
+
] }, i)),
|
|
28335
|
+
/* @__PURE__ */ jsx("button", { className: "dsm-add-col-btn", onClick: addColumn, children: "+ Add Column" }),
|
|
28336
|
+
/* @__PURE__ */ jsx("button", { className: "dsm-save-btn", onClick: handleSaveSchema, disabled: saving, children: saving ? "Saving\u2026" : existing ? "Save Schema \u2192" : "Create Dataset \u2192" })
|
|
28337
|
+
] });
|
|
28338
|
+
}
|
|
28339
|
+
function buildCsvTemplate(dataset) {
|
|
28340
|
+
const headers = ["time", ...dataset.columns.map((c) => c.name)].join(",");
|
|
28341
|
+
const exampleRow = [
|
|
28342
|
+
"2024-01-01",
|
|
28343
|
+
...dataset.columns.map((c) => {
|
|
28344
|
+
switch (c.type) {
|
|
28345
|
+
case "number":
|
|
28346
|
+
case "price":
|
|
28347
|
+
case "percentage":
|
|
28348
|
+
return "0.00";
|
|
28349
|
+
case "integer":
|
|
28350
|
+
return "0";
|
|
28351
|
+
case "boolean":
|
|
28352
|
+
return "false";
|
|
28353
|
+
case "date":
|
|
28354
|
+
return "2024-01-01";
|
|
28355
|
+
case "timestamp":
|
|
28356
|
+
return "2024-01-01T00:00:00Z";
|
|
28357
|
+
case "text":
|
|
28358
|
+
return "value";
|
|
28359
|
+
default:
|
|
28360
|
+
return "";
|
|
28361
|
+
}
|
|
28362
|
+
})
|
|
28363
|
+
].join(",");
|
|
28364
|
+
return `${headers}
|
|
28365
|
+
${exampleRow}
|
|
28366
|
+
`;
|
|
28367
|
+
}
|
|
28368
|
+
function DatasetImporter({ scriptId, dataset, apiBase, resolveToken, onImported, onSkip, onError }) {
|
|
28369
|
+
const [fileContent, setFileContent] = useState("");
|
|
28370
|
+
const [fileType, setFileType] = useState("csv");
|
|
28371
|
+
const [preview, setPreview] = useState([]);
|
|
28372
|
+
const [importing, setImporting] = useState(false);
|
|
28373
|
+
const [fileName, setFileName] = useState("");
|
|
28374
|
+
const fileRef = useRef(null);
|
|
28375
|
+
const allHeaders = ["time", ...dataset.columns.map((c) => c.name)];
|
|
28376
|
+
const downloadTemplate = () => {
|
|
28377
|
+
const csv = buildCsvTemplate(dataset);
|
|
28378
|
+
const blob = new Blob([csv], { type: "text/csv" });
|
|
28379
|
+
const url = URL.createObjectURL(blob);
|
|
28380
|
+
const a = document.createElement("a");
|
|
28381
|
+
a.href = url;
|
|
28382
|
+
a.download = `${dataset.name}_template.csv`;
|
|
28383
|
+
a.click();
|
|
28384
|
+
URL.revokeObjectURL(url);
|
|
28385
|
+
};
|
|
28386
|
+
const handleFile = (e) => {
|
|
28387
|
+
const file = e.target.files?.[0];
|
|
28388
|
+
if (!file) return;
|
|
28389
|
+
setFileName(file.name);
|
|
28390
|
+
const ft = file.name.endsWith(".json") ? "json" : "csv";
|
|
28391
|
+
setFileType(ft);
|
|
28392
|
+
const reader = new FileReader();
|
|
28393
|
+
reader.onload = (ev) => {
|
|
28394
|
+
const content = ev.target?.result;
|
|
28395
|
+
setFileContent(content);
|
|
28396
|
+
if (ft === "csv") {
|
|
28397
|
+
const lines = content.split(/\r?\n/).filter((l) => l.trim()).slice(0, 6);
|
|
28398
|
+
setPreview(lines.map((l) => l.split(",")));
|
|
28399
|
+
} else {
|
|
28400
|
+
try {
|
|
28401
|
+
const arr = JSON.parse(content);
|
|
28402
|
+
const first = arr.slice(0, 5);
|
|
28403
|
+
const keys = Object.keys(first[0] ?? {});
|
|
28404
|
+
setPreview([keys, ...first.map((r) => keys.map((k) => String(r[k] ?? "")))]);
|
|
28405
|
+
} catch {
|
|
28406
|
+
setPreview([]);
|
|
28407
|
+
}
|
|
28408
|
+
}
|
|
28409
|
+
};
|
|
28410
|
+
reader.readAsText(file);
|
|
28411
|
+
};
|
|
28412
|
+
const handleImport = async () => {
|
|
28413
|
+
if (!fileContent) {
|
|
28414
|
+
onError("Please choose a file to import.");
|
|
28415
|
+
return;
|
|
28416
|
+
}
|
|
28417
|
+
setImporting(true);
|
|
28418
|
+
try {
|
|
28419
|
+
const token = await resolveToken();
|
|
28420
|
+
const res = await fetch(`${apiBase}/api/scripts/${scriptId}/datasets/${dataset.id}`, {
|
|
28421
|
+
method: "PATCH",
|
|
28422
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
28423
|
+
body: JSON.stringify({ file_content: fileContent, file_type: fileType })
|
|
28424
|
+
});
|
|
28425
|
+
if (!res.ok) {
|
|
28426
|
+
const text = await res.text();
|
|
28427
|
+
let msg;
|
|
28428
|
+
try {
|
|
28429
|
+
msg = JSON.stringify(JSON.parse(text).error);
|
|
28430
|
+
} catch {
|
|
28431
|
+
msg = `HTTP ${res.status}`;
|
|
28432
|
+
}
|
|
28433
|
+
throw new Error(msg);
|
|
28434
|
+
}
|
|
28435
|
+
onImported(await res.json());
|
|
28436
|
+
} catch (e) {
|
|
28437
|
+
onError(String(e));
|
|
28438
|
+
} finally {
|
|
28439
|
+
setImporting(false);
|
|
28440
|
+
}
|
|
28441
|
+
};
|
|
28442
|
+
return /* @__PURE__ */ jsxs("div", { className: "dsm-importer", children: [
|
|
28443
|
+
/* @__PURE__ */ jsxs("div", { className: "dsm-steps", children: [
|
|
28444
|
+
/* @__PURE__ */ jsx("span", { className: "dsm-step dsm-step--done", children: "1 \xB7 Define schema \u2713" }),
|
|
28445
|
+
/* @__PURE__ */ jsx("span", { className: "dsm-step-sep", children: "\u203A" }),
|
|
28446
|
+
/* @__PURE__ */ jsx("span", { className: "dsm-step dsm-step--active", children: "2 \xB7 Import data" })
|
|
28447
|
+
] }),
|
|
28448
|
+
/* @__PURE__ */ jsxs("div", { className: "dsm-schema-summary", children: [
|
|
28449
|
+
/* @__PURE__ */ jsx("span", { className: "dsm-schema-summary-label", children: "Expected columns:" }),
|
|
28450
|
+
allHeaders.map((h) => /* @__PURE__ */ jsx("span", { className: `dsm-col-tag dsm-col-tag--${h === "time" ? "time" : dataset.columns.find((c) => c.name === h)?.type ?? "number"}`, children: h }, h))
|
|
28451
|
+
] }),
|
|
28452
|
+
/* @__PURE__ */ jsxs("div", { className: "dsm-template-section", children: [
|
|
28453
|
+
/* @__PURE__ */ jsxs("div", { className: "dsm-template-desc", children: [
|
|
28454
|
+
"Download a pre-filled CSV template with the correct column headers for ",
|
|
28455
|
+
/* @__PURE__ */ jsx("strong", { children: dataset.name }),
|
|
28456
|
+
". Fill it with your data, then upload it below."
|
|
28457
|
+
] }),
|
|
28458
|
+
/* @__PURE__ */ jsx("button", { className: "dsm-template-btn", onClick: downloadTemplate, children: "\u2193 Download CSV Template" })
|
|
28459
|
+
] }),
|
|
28460
|
+
/* @__PURE__ */ jsx("label", { className: "dsm-label", children: "Upload Data (CSV or JSON)" }),
|
|
28461
|
+
/* @__PURE__ */ jsx("input", { ref: fileRef, type: "file", accept: ".csv,.json", className: "dsm-file-input", onChange: handleFile }),
|
|
28462
|
+
/* @__PURE__ */ jsx("button", { className: "dsm-upload-btn", onClick: () => fileRef.current?.click(), children: fileName ? `\u2713 ${fileName} \u2014 click to change` : "Choose File" }),
|
|
28463
|
+
preview.length > 0 && /* @__PURE__ */ jsxs("div", { className: "dsm-preview", children: [
|
|
28464
|
+
/* @__PURE__ */ jsx("div", { className: "dsm-preview-label", children: "Preview (first 5 rows)" }),
|
|
28465
|
+
/* @__PURE__ */ jsx("div", { className: "dsm-preview-table-wrap", children: /* @__PURE__ */ jsxs("table", { className: "dsm-preview-table", children: [
|
|
28466
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: preview[0].map((h, i) => /* @__PURE__ */ jsx("th", { className: allHeaders.includes(h.trim()) ? "" : "dsm-col-unknown", children: h }, i)) }) }),
|
|
28467
|
+
/* @__PURE__ */ jsx("tbody", { children: preview.slice(1).map((row, i) => /* @__PURE__ */ jsx("tr", { children: row.map((cell, j) => /* @__PURE__ */ jsx("td", { children: cell }, j)) }, i)) })
|
|
28468
|
+
] }) })
|
|
28469
|
+
] }),
|
|
28470
|
+
/* @__PURE__ */ jsxs("div", { className: "dsm-import-actions", children: [
|
|
28471
|
+
/* @__PURE__ */ jsx("button", { className: "dsm-save-btn", onClick: handleImport, disabled: importing || !fileContent, children: importing ? "Importing\u2026" : "Import Data" }),
|
|
28472
|
+
/* @__PURE__ */ jsx("button", { className: "dsm-skip-btn", onClick: onSkip, children: "Skip for now" })
|
|
28473
|
+
] })
|
|
28474
|
+
] });
|
|
28475
|
+
}
|
|
28476
|
+
function DatasetUsage({ dataset }) {
|
|
28477
|
+
const [copied, setCopied] = useState(false);
|
|
28478
|
+
const snippet = [
|
|
28479
|
+
`// Dataset: ${dataset.name} (${dataset.row_count.toLocaleString()} rows)`,
|
|
28480
|
+
...dataset.columns.map((c) => `${c.name} = request.data("${dataset.name}", "${c.name}")`)
|
|
28481
|
+
].join("\n");
|
|
28482
|
+
const copy = () => {
|
|
28483
|
+
void navigator.clipboard.writeText(snippet);
|
|
28484
|
+
setCopied(true);
|
|
28485
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
28486
|
+
};
|
|
28487
|
+
return /* @__PURE__ */ jsxs("div", { className: "dsm-usage", children: [
|
|
28488
|
+
/* @__PURE__ */ jsxs("p", { className: "dsm-usage-intro", children: [
|
|
28489
|
+
"Copy this snippet into your ForgeScript indicator to access the ",
|
|
28490
|
+
/* @__PURE__ */ jsx("strong", { children: dataset.name }),
|
|
28491
|
+
" dataset."
|
|
28492
|
+
] }),
|
|
28493
|
+
/* @__PURE__ */ jsxs("div", { className: "dsm-usage-code-wrap", children: [
|
|
28494
|
+
/* @__PURE__ */ jsx("pre", { className: "dsm-usage-code", children: snippet }),
|
|
28495
|
+
/* @__PURE__ */ jsx("button", { className: "dsm-copy-btn", onClick: copy, children: copied ? "\u2713 Copied" : "Copy" })
|
|
28496
|
+
] }),
|
|
28497
|
+
/* @__PURE__ */ jsxs("div", { className: "dsm-usage-cols", children: [
|
|
28498
|
+
/* @__PURE__ */ jsx("div", { className: "dsm-usage-col-label", children: "Available columns:" }),
|
|
28499
|
+
/* @__PURE__ */ jsx("div", { className: "dsm-col-tag dsm-col-tag--time", children: "time" }),
|
|
28500
|
+
dataset.columns.map((c) => /* @__PURE__ */ jsxs("div", { className: `dsm-col-tag dsm-col-tag--${c.type}`, title: c.type, children: [
|
|
28501
|
+
c.name,
|
|
28502
|
+
" ",
|
|
28503
|
+
/* @__PURE__ */ jsxs("span", { className: "dsm-col-type", children: [
|
|
28504
|
+
"(",
|
|
28505
|
+
c.type,
|
|
28506
|
+
")"
|
|
28507
|
+
] })
|
|
28508
|
+
] }, c.name))
|
|
28509
|
+
] })
|
|
28510
|
+
] });
|
|
28511
|
+
}
|
|
28512
|
+
var TEMPLATE = `## Overview
|
|
28513
|
+
|
|
28514
|
+
Describe what this indicator measures and why it is useful.
|
|
28515
|
+
|
|
28516
|
+
## How It Works
|
|
28517
|
+
|
|
28518
|
+
Explain the calculation logic in plain language.
|
|
28519
|
+
|
|
28520
|
+
## Inputs & Parameters
|
|
28521
|
+
|
|
28522
|
+
List each \`input.*\` variable the user can configure.
|
|
28523
|
+
|
|
28524
|
+
## Interpreting Signals
|
|
28525
|
+
|
|
28526
|
+
Explain how to read the plots, shapes, or alerts this indicator produces.
|
|
28527
|
+
|
|
28528
|
+
## Datasets Required
|
|
28529
|
+
|
|
28530
|
+
If this indicator uses \`request.data()\`, describe the bundled datasets and their columns.
|
|
28531
|
+
|
|
28532
|
+
## Limitations & Caveats
|
|
28533
|
+
|
|
28534
|
+
Note any known edge cases, asset classes it does not work well on, or timeframe restrictions.
|
|
28535
|
+
|
|
28536
|
+
## Changelog
|
|
28537
|
+
|
|
28538
|
+
- **v1** \u2014 Initial release
|
|
28539
|
+
`;
|
|
28540
|
+
function DocsEditor({ scriptId, isOwner, apiBase, authToken: staticToken, getAuthToken }) {
|
|
28541
|
+
const resolveToken = useCallback(async () => {
|
|
28542
|
+
if (getAuthToken) {
|
|
28543
|
+
try {
|
|
28544
|
+
return await getAuthToken();
|
|
28545
|
+
} catch {
|
|
28546
|
+
return "";
|
|
28547
|
+
}
|
|
28548
|
+
}
|
|
28549
|
+
return staticToken ?? "";
|
|
28550
|
+
}, [getAuthToken, staticToken]);
|
|
28551
|
+
const [content, setContent] = useState("");
|
|
28552
|
+
const [original, setOriginal] = useState("");
|
|
28553
|
+
const [loading, setLoading] = useState(false);
|
|
28554
|
+
const [saving, setSaving] = useState(false);
|
|
28555
|
+
const [saved, setSaved] = useState(false);
|
|
28556
|
+
const [error, setError] = useState(null);
|
|
28557
|
+
const [preview, setPreview] = useState(false);
|
|
28558
|
+
const textareaRef = useRef(null);
|
|
28559
|
+
const fetchDocs = useCallback(async () => {
|
|
28560
|
+
if (!scriptId) return;
|
|
28561
|
+
setLoading(true);
|
|
28562
|
+
setError(null);
|
|
28563
|
+
try {
|
|
28564
|
+
const tok = await resolveToken();
|
|
28565
|
+
const res = await fetch(`${apiBase}/api/scripts/${scriptId}/docs`, {
|
|
28566
|
+
headers: { Authorization: `Bearer ${tok}` }
|
|
28567
|
+
});
|
|
28568
|
+
if (!res.ok) throw new Error(await res.text());
|
|
28569
|
+
const data = await res.json();
|
|
28570
|
+
setContent(data.content || "");
|
|
28571
|
+
setOriginal(data.content || "");
|
|
28572
|
+
} catch (e) {
|
|
28573
|
+
setError(String(e));
|
|
28574
|
+
} finally {
|
|
28575
|
+
setLoading(false);
|
|
28576
|
+
}
|
|
28577
|
+
}, [scriptId, apiBase, resolveToken]);
|
|
28578
|
+
useEffect(() => {
|
|
28579
|
+
void fetchDocs();
|
|
28580
|
+
}, [fetchDocs]);
|
|
28581
|
+
const handleSave = async () => {
|
|
28582
|
+
if (!scriptId) return;
|
|
28583
|
+
setSaving(true);
|
|
28584
|
+
setError(null);
|
|
28585
|
+
try {
|
|
28586
|
+
const tok = await resolveToken();
|
|
28587
|
+
const res = await fetch(`${apiBase}/api/scripts/${scriptId}/docs`, {
|
|
28588
|
+
method: "PUT",
|
|
28589
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${tok}` },
|
|
28590
|
+
body: JSON.stringify({ content })
|
|
28591
|
+
});
|
|
28592
|
+
if (!res.ok) throw new Error(await res.text());
|
|
28593
|
+
setOriginal(content);
|
|
28594
|
+
setSaved(true);
|
|
28595
|
+
setTimeout(() => setSaved(false), 2500);
|
|
28596
|
+
} catch (e) {
|
|
28597
|
+
setError(String(e));
|
|
28598
|
+
} finally {
|
|
28599
|
+
setSaving(false);
|
|
28600
|
+
}
|
|
28601
|
+
};
|
|
28602
|
+
const isDirty = content !== original;
|
|
28603
|
+
if (!scriptId) {
|
|
28604
|
+
return /* @__PURE__ */ jsx("div", { className: "docs-empty", children: "Save your script first to add documentation." });
|
|
28605
|
+
}
|
|
28606
|
+
if (loading) {
|
|
28607
|
+
return /* @__PURE__ */ jsx("div", { className: "docs-loading", children: "Loading documentation\u2026" });
|
|
28608
|
+
}
|
|
28609
|
+
if (!isOwner) {
|
|
28610
|
+
return /* @__PURE__ */ jsx("div", { className: "docs-viewer", children: content ? /* @__PURE__ */ jsx("div", { className: "docs-rendered", dangerouslySetInnerHTML: { __html: renderMarkdown(content) } }) : /* @__PURE__ */ jsx("div", { className: "docs-empty", children: "The author has not added documentation yet." }) });
|
|
28611
|
+
}
|
|
28612
|
+
return /* @__PURE__ */ jsxs("div", { className: "docs-editor-root", children: [
|
|
28613
|
+
/* @__PURE__ */ jsxs("div", { className: "docs-toolbar", children: [
|
|
28614
|
+
/* @__PURE__ */ jsx(
|
|
28615
|
+
"button",
|
|
28616
|
+
{
|
|
28617
|
+
className: `docs-tab-btn${!preview ? " active" : ""}`,
|
|
28618
|
+
onClick: () => setPreview(false),
|
|
28619
|
+
children: "Edit"
|
|
28620
|
+
}
|
|
28621
|
+
),
|
|
28622
|
+
/* @__PURE__ */ jsx(
|
|
28623
|
+
"button",
|
|
28624
|
+
{
|
|
28625
|
+
className: `docs-tab-btn${preview ? " active" : ""}`,
|
|
28626
|
+
onClick: () => setPreview(true),
|
|
28627
|
+
children: "Preview"
|
|
28628
|
+
}
|
|
28629
|
+
),
|
|
28630
|
+
!content && /* @__PURE__ */ jsx("button", { className: "docs-template-btn", onClick: () => setContent(TEMPLATE), children: "Use Template" }),
|
|
28631
|
+
/* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
|
|
28632
|
+
error && /* @__PURE__ */ jsx("span", { className: "docs-error", children: error }),
|
|
28633
|
+
saved && /* @__PURE__ */ jsx("span", { className: "docs-saved", children: "\u2713 Saved" }),
|
|
28634
|
+
/* @__PURE__ */ jsx(
|
|
28635
|
+
"button",
|
|
28636
|
+
{
|
|
28637
|
+
className: "docs-save-btn",
|
|
28638
|
+
onClick: handleSave,
|
|
28639
|
+
disabled: saving || !isDirty,
|
|
28640
|
+
children: saving ? "Saving\u2026" : "Save Docs"
|
|
28641
|
+
}
|
|
28642
|
+
)
|
|
28643
|
+
] }),
|
|
28644
|
+
/* @__PURE__ */ jsx("div", { className: "docs-panes", children: !preview ? /* @__PURE__ */ jsx(
|
|
28645
|
+
"textarea",
|
|
28646
|
+
{
|
|
28647
|
+
ref: textareaRef,
|
|
28648
|
+
className: "docs-textarea",
|
|
28649
|
+
value: content,
|
|
28650
|
+
onChange: (e) => setContent(e.target.value),
|
|
28651
|
+
placeholder: "Write your indicator documentation in Markdown\u2026",
|
|
28652
|
+
spellCheck: true
|
|
28653
|
+
}
|
|
28654
|
+
) : /* @__PURE__ */ jsx("div", { className: "docs-rendered", dangerouslySetInnerHTML: { __html: renderMarkdown(content) } }) })
|
|
28655
|
+
] });
|
|
28656
|
+
}
|
|
28657
|
+
function renderMarkdown(md) {
|
|
28658
|
+
let html = escapeHtml(md);
|
|
28659
|
+
html = html.replace(
|
|
28660
|
+
/```[\w]*\n([\s\S]*?)```/g,
|
|
28661
|
+
(_m, code) => `<pre class="docs-code-block"><code>${code.trimEnd()}</code></pre>`
|
|
28662
|
+
);
|
|
28663
|
+
html = html.replace(/`([^`]+)`/g, '<code class="docs-inline-code">$1</code>');
|
|
28664
|
+
html = html.replace(/^######\s+(.+)$/gm, "<h6>$1</h6>");
|
|
28665
|
+
html = html.replace(/^#####\s+(.+)$/gm, "<h5>$1</h5>");
|
|
28666
|
+
html = html.replace(/^####\s+(.+)$/gm, "<h4>$1</h4>");
|
|
28667
|
+
html = html.replace(/^###\s+(.+)$/gm, "<h3>$1</h3>");
|
|
28668
|
+
html = html.replace(/^##\s+(.+)$/gm, "<h2>$1</h2>");
|
|
28669
|
+
html = html.replace(/^#\s+(.+)$/gm, "<h1>$1</h1>");
|
|
28670
|
+
html = html.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>");
|
|
28671
|
+
html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
|
28672
|
+
html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
|
|
28673
|
+
html = html.replace(/((?:^[-*]\s.+\n?)+)/gm, (block) => {
|
|
28674
|
+
const items = block.trim().split("\n").map((l) => `<li>${l.replace(/^[-*]\s/, "")}</li>`);
|
|
28675
|
+
return `<ul>${items.join("")}</ul>`;
|
|
28676
|
+
});
|
|
28677
|
+
html = html.replace(/^---$/gm, "<hr>");
|
|
28678
|
+
html = html.replace(/^(?!<[hupoli]|<pre|<hr)(.+)$/gm, "<p>$1</p>");
|
|
28679
|
+
html = html.replace(/\n{2,}/g, "\n");
|
|
28680
|
+
return html;
|
|
28681
|
+
}
|
|
28682
|
+
function escapeHtml(s) {
|
|
28683
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
28684
|
+
}
|
|
28685
|
+
var VISIBILITY_OPTIONS = [
|
|
28686
|
+
{
|
|
28687
|
+
value: "private",
|
|
28688
|
+
label: "Private",
|
|
28689
|
+
desc: "Only you can see and run this indicator."
|
|
28690
|
+
},
|
|
28691
|
+
{
|
|
28692
|
+
value: "public",
|
|
28693
|
+
label: "Public",
|
|
28694
|
+
desc: "Any user can view and run this indicator for free."
|
|
28695
|
+
},
|
|
28696
|
+
{
|
|
28697
|
+
value: "for_sale",
|
|
28698
|
+
label: "For Sale",
|
|
28699
|
+
desc: "Any user can see a preview. Only purchasers can run it."
|
|
28700
|
+
}
|
|
28701
|
+
];
|
|
28702
|
+
function ScriptSettings({ scriptId, isOwner, apiBase, authToken: staticToken, getAuthToken, onUpdated }) {
|
|
28703
|
+
const resolveToken = useCallback(async () => {
|
|
28704
|
+
if (getAuthToken) {
|
|
28705
|
+
try {
|
|
28706
|
+
return await getAuthToken();
|
|
28707
|
+
} catch {
|
|
28708
|
+
return "";
|
|
28709
|
+
}
|
|
28710
|
+
}
|
|
28711
|
+
return staticToken ?? "";
|
|
28712
|
+
}, [getAuthToken, staticToken]);
|
|
28713
|
+
const [meta, setMeta] = useState(null);
|
|
28714
|
+
const [name, setName] = useState("");
|
|
28715
|
+
const [visibility, setVis] = useState("private");
|
|
28716
|
+
const [salePrice, setSalePrice] = useState("");
|
|
28717
|
+
const [currency, setCurrency] = useState("USD");
|
|
28718
|
+
const [previewPct, setPreviewPct] = useState(0);
|
|
28719
|
+
const [loading, setLoading] = useState(false);
|
|
28720
|
+
const [saving, setSaving] = useState(false);
|
|
28721
|
+
const [saved, setSaved] = useState(false);
|
|
28722
|
+
const [error, setError] = useState(null);
|
|
28723
|
+
useEffect(() => {
|
|
28724
|
+
if (!scriptId) return;
|
|
28725
|
+
setLoading(true);
|
|
28726
|
+
resolveToken().then(
|
|
28727
|
+
(tok) => fetch(`${apiBase}/api/scripts/${scriptId}`, {
|
|
28728
|
+
headers: { Authorization: `Bearer ${tok}` }
|
|
28729
|
+
})
|
|
28730
|
+
).then((r) => r.json()).then((data) => {
|
|
28731
|
+
setMeta(data);
|
|
28732
|
+
setName(data.name);
|
|
28733
|
+
setVis(data.visibility);
|
|
28734
|
+
setSalePrice(data.sale_price ?? "");
|
|
28735
|
+
setCurrency(data.sale_currency);
|
|
28736
|
+
setPreviewPct(data.preview_pct);
|
|
28737
|
+
}).catch((e) => setError(String(e))).finally(() => setLoading(false));
|
|
28738
|
+
}, [scriptId, apiBase]);
|
|
28739
|
+
const handleSave = async () => {
|
|
28740
|
+
if (!scriptId) return;
|
|
28741
|
+
if (visibility === "for_sale" && (!salePrice || Number(salePrice) < 1)) {
|
|
28742
|
+
setError("Sale price must be at least $1.00 when set to For Sale.");
|
|
28743
|
+
return;
|
|
28744
|
+
}
|
|
28745
|
+
setSaving(true);
|
|
28746
|
+
setError(null);
|
|
28747
|
+
try {
|
|
28748
|
+
const tok = await resolveToken();
|
|
28749
|
+
const body = {
|
|
28750
|
+
name: name.trim() || meta?.name,
|
|
28751
|
+
visibility,
|
|
28752
|
+
sale_currency: currency,
|
|
28753
|
+
preview_pct: previewPct
|
|
28754
|
+
};
|
|
28755
|
+
if (visibility === "for_sale") {
|
|
28756
|
+
body["sale_price"] = Number(salePrice);
|
|
28757
|
+
}
|
|
28758
|
+
const res = await fetch(`${apiBase}/api/scripts/${scriptId}`, {
|
|
28759
|
+
method: "PATCH",
|
|
28760
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${tok}` },
|
|
28761
|
+
body: JSON.stringify(body)
|
|
28762
|
+
});
|
|
28763
|
+
if (!res.ok) throw new Error(await res.text());
|
|
28764
|
+
const updated = await res.json();
|
|
28765
|
+
setMeta(updated);
|
|
28766
|
+
setSaved(true);
|
|
28767
|
+
setTimeout(() => setSaved(false), 2500);
|
|
28768
|
+
onUpdated?.(updated);
|
|
28769
|
+
} catch (e) {
|
|
28770
|
+
setError(String(e));
|
|
28771
|
+
} finally {
|
|
28772
|
+
setSaving(false);
|
|
28773
|
+
}
|
|
28774
|
+
};
|
|
28775
|
+
if (!scriptId) {
|
|
28776
|
+
return /* @__PURE__ */ jsx("div", { className: "ss-empty", children: "Save your script first to configure settings." });
|
|
28777
|
+
}
|
|
28778
|
+
if (loading) {
|
|
28779
|
+
return /* @__PURE__ */ jsx("div", { className: "ss-loading", children: "Loading settings\u2026" });
|
|
28780
|
+
}
|
|
28781
|
+
if (!isOwner) {
|
|
28782
|
+
return /* @__PURE__ */ jsxs("div", { className: "ss-viewer", children: [
|
|
28783
|
+
/* @__PURE__ */ jsxs("div", { className: "ss-row", children: [
|
|
28784
|
+
/* @__PURE__ */ jsx("span", { className: "ss-label", children: "Visibility" }),
|
|
28785
|
+
/* @__PURE__ */ jsx("span", { className: `ss-badge ss-badge--${meta?.visibility}`, children: meta?.visibility?.replace("_", " ") })
|
|
28786
|
+
] }),
|
|
28787
|
+
meta?.visibility === "for_sale" && /* @__PURE__ */ jsxs("div", { className: "ss-row", children: [
|
|
28788
|
+
/* @__PURE__ */ jsx("span", { className: "ss-label", children: "Price" }),
|
|
28789
|
+
/* @__PURE__ */ jsxs("span", { className: "ss-value", children: [
|
|
28790
|
+
meta.sale_currency,
|
|
28791
|
+
" ",
|
|
28792
|
+
meta.sale_price
|
|
28793
|
+
] })
|
|
28794
|
+
] })
|
|
28795
|
+
] });
|
|
28796
|
+
}
|
|
28797
|
+
return /* @__PURE__ */ jsxs("div", { className: "ss-root", children: [
|
|
28798
|
+
/* @__PURE__ */ jsxs("div", { className: "ss-section", children: [
|
|
28799
|
+
/* @__PURE__ */ jsx("label", { className: "ss-section-label", children: "Indicator Name" }),
|
|
28800
|
+
/* @__PURE__ */ jsx(
|
|
28801
|
+
"input",
|
|
28802
|
+
{
|
|
28803
|
+
className: "ss-input",
|
|
28804
|
+
value: name,
|
|
28805
|
+
onChange: (e) => setName(e.target.value),
|
|
28806
|
+
placeholder: "My Indicator"
|
|
28807
|
+
}
|
|
28808
|
+
)
|
|
28809
|
+
] }),
|
|
28810
|
+
/* @__PURE__ */ jsxs("div", { className: "ss-section", children: [
|
|
28811
|
+
/* @__PURE__ */ jsx("label", { className: "ss-section-label", children: "Visibility" }),
|
|
28812
|
+
/* @__PURE__ */ jsx("div", { className: "ss-vis-group", children: VISIBILITY_OPTIONS.map((opt) => /* @__PURE__ */ jsx(
|
|
28813
|
+
"button",
|
|
28814
|
+
{
|
|
28815
|
+
className: `ss-vis-btn${visibility === opt.value ? " active" : ""}`,
|
|
28816
|
+
onClick: () => setVis(opt.value),
|
|
28817
|
+
children: opt.label
|
|
28818
|
+
},
|
|
28819
|
+
opt.value
|
|
28820
|
+
)) }),
|
|
28821
|
+
/* @__PURE__ */ jsx("p", { className: "ss-vis-desc", children: VISIBILITY_OPTIONS.find((o) => o.value === visibility)?.desc })
|
|
28822
|
+
] }),
|
|
28823
|
+
visibility === "for_sale" && /* @__PURE__ */ jsxs("div", { className: "ss-section", children: [
|
|
28824
|
+
/* @__PURE__ */ jsx("label", { className: "ss-section-label", children: "Sale Price" }),
|
|
28825
|
+
/* @__PURE__ */ jsxs("div", { className: "ss-price-row", children: [
|
|
28826
|
+
/* @__PURE__ */ jsxs(
|
|
28827
|
+
"select",
|
|
28828
|
+
{
|
|
28829
|
+
className: "ss-currency-select",
|
|
28830
|
+
value: currency,
|
|
28831
|
+
onChange: (e) => setCurrency(e.target.value),
|
|
28832
|
+
children: [
|
|
28833
|
+
/* @__PURE__ */ jsx("option", { value: "USD", children: "USD" }),
|
|
28834
|
+
/* @__PURE__ */ jsx("option", { value: "EUR", children: "EUR" }),
|
|
28835
|
+
/* @__PURE__ */ jsx("option", { value: "GBP", children: "GBP" })
|
|
28836
|
+
]
|
|
28837
|
+
}
|
|
28838
|
+
),
|
|
28839
|
+
/* @__PURE__ */ jsx(
|
|
28840
|
+
"input",
|
|
28841
|
+
{
|
|
28842
|
+
className: "ss-price-input",
|
|
28843
|
+
type: "number",
|
|
28844
|
+
min: "1",
|
|
28845
|
+
step: "0.01",
|
|
28846
|
+
value: salePrice,
|
|
28847
|
+
onChange: (e) => setSalePrice(e.target.value),
|
|
28848
|
+
placeholder: "9.99"
|
|
28849
|
+
}
|
|
28850
|
+
)
|
|
28851
|
+
] }),
|
|
28852
|
+
/* @__PURE__ */ jsx("p", { className: "ss-hint", children: "Minimum price: $1.00. Purchasers retain access even if you change visibility later." }),
|
|
28853
|
+
/* @__PURE__ */ jsxs("label", { className: "ss-section-label", style: { marginTop: "1rem" }, children: [
|
|
28854
|
+
"Source Preview for Non-Purchasers: ",
|
|
28855
|
+
/* @__PURE__ */ jsxs("strong", { children: [
|
|
28856
|
+
previewPct,
|
|
28857
|
+
"%"
|
|
28858
|
+
] })
|
|
28859
|
+
] }),
|
|
28860
|
+
/* @__PURE__ */ jsx(
|
|
28861
|
+
"input",
|
|
28862
|
+
{
|
|
28863
|
+
className: "ss-slider",
|
|
28864
|
+
type: "range",
|
|
28865
|
+
min: "0",
|
|
28866
|
+
max: "100",
|
|
28867
|
+
step: "5",
|
|
28868
|
+
value: previewPct,
|
|
28869
|
+
onChange: (e) => setPreviewPct(Number(e.target.value))
|
|
28870
|
+
}
|
|
28871
|
+
),
|
|
28872
|
+
/* @__PURE__ */ jsx("p", { className: "ss-hint", children: previewPct === 0 ? "Non-purchasers see no source code." : previewPct === 100 ? "Non-purchasers see the full source code." : `Non-purchasers see the first ${previewPct}% of the source code.` })
|
|
28873
|
+
] }),
|
|
28874
|
+
/* @__PURE__ */ jsxs("div", { className: "ss-footer", children: [
|
|
28875
|
+
error && /* @__PURE__ */ jsx("span", { className: "ss-error", children: error }),
|
|
28876
|
+
saved && /* @__PURE__ */ jsx("span", { className: "ss-saved", children: "\u2713 Settings saved" }),
|
|
28877
|
+
/* @__PURE__ */ jsx("button", { className: "ss-save-btn", onClick: handleSave, disabled: saving, children: saving ? "Saving\u2026" : "Save Settings" })
|
|
28878
|
+
] })
|
|
28879
|
+
] });
|
|
28880
|
+
}
|
|
28881
|
+
var TA_FUNCTIONS = [
|
|
28882
|
+
// Moving averages
|
|
28883
|
+
{ name: "sma(src, len)", desc: "Simple moving average" },
|
|
28884
|
+
{ name: "ema(src, len)", desc: "Exponential moving average" },
|
|
28885
|
+
{ name: "wma(src, len)", desc: "Weighted moving average" },
|
|
28886
|
+
{ name: "rma(src, len)", desc: "Wilder (RMA) moving average" },
|
|
28887
|
+
{ name: "swma(src)", desc: "Symmetrically weighted MA (period 4)" },
|
|
28888
|
+
{ name: "vwma(src, len)", desc: "Volume-weighted moving average" },
|
|
28889
|
+
// Oscillators & indicators
|
|
28890
|
+
{ name: "rsi(src, len)", desc: "Relative Strength Index [0\u2013100]" },
|
|
28891
|
+
{ name: "macd(src, fast, slow, sig)", desc: "MACD line (stores __macd_signal, __macd_hist)" },
|
|
28892
|
+
{ name: "stoch(src, high, low, len)", desc: "Stochastic %K" },
|
|
28893
|
+
{ name: "obv()", desc: "On-Balance Volume" },
|
|
28894
|
+
// Bands & volatility
|
|
28895
|
+
{ name: "bb(src, len, mult)", desc: "Bollinger middle band (stores __bb_upper, __bb_lower)" },
|
|
28896
|
+
{ name: "atr(len)", desc: "Average true range" },
|
|
28897
|
+
{ name: "tr()", desc: "True range (single bar)" },
|
|
28898
|
+
{ name: "stdev(src, len)", desc: "Standard deviation" },
|
|
28899
|
+
{ name: "dev(src, len)", desc: "Mean absolute deviation" },
|
|
28900
|
+
{ name: "variance(src, len)", desc: "Rolling variance" },
|
|
28901
|
+
// Crossover & direction
|
|
28902
|
+
{ name: "crossover(a, b)", desc: "True when a crosses above b" },
|
|
28903
|
+
{ name: "crossunder(a, b)", desc: "True when a crosses below b" },
|
|
28904
|
+
{ name: "cross(a, b)", desc: "True on any cross (either direction)" },
|
|
28905
|
+
{ name: "rising(src, len)", desc: "True if source rose for len bars" },
|
|
28906
|
+
{ name: "falling(src, len)", desc: "True if source fell for len bars" },
|
|
28907
|
+
// Rolling aggregates
|
|
28908
|
+
{ name: "highest(src, len)", desc: "Highest value over N bars" },
|
|
28909
|
+
{ name: "lowest(src, len)", desc: "Lowest value over N bars" },
|
|
28910
|
+
{ name: "sum(src, len)", desc: "Rolling sum over N bars" },
|
|
28911
|
+
{ name: "cum(src)", desc: "Cumulative sum from bar 0" },
|
|
28912
|
+
// Change & momentum
|
|
28913
|
+
{ name: "change(src, len?)", desc: "Difference from len bars ago (default 1)" },
|
|
28914
|
+
{ name: "mom(src, len)", desc: "Momentum (alias for change)" },
|
|
28915
|
+
// Lookback
|
|
28916
|
+
{ name: "barssince(cond)", desc: "Bars since condition was true" },
|
|
28917
|
+
{ name: "valuewhen(cond, src, occ?)", desc: "Value at Nth true condition" },
|
|
28918
|
+
{ name: "pivothigh(src, l, r)", desc: "Pivot high detection" },
|
|
28919
|
+
{ name: "pivotlow(src, l, r)", desc: "Pivot low detection" },
|
|
28920
|
+
// Statistical
|
|
28921
|
+
{ name: "correlation(s1, s2, len)", desc: "Pearson correlation coefficient" }
|
|
28922
|
+
];
|
|
28923
|
+
var BUILT_IN_SERIES = [
|
|
28924
|
+
"open",
|
|
28925
|
+
"high",
|
|
28926
|
+
"low",
|
|
28927
|
+
"close",
|
|
28928
|
+
"volume",
|
|
28929
|
+
"hl2",
|
|
28930
|
+
"hlc3",
|
|
28931
|
+
"ohlc4",
|
|
28932
|
+
"bar_index"
|
|
28933
|
+
];
|
|
28934
|
+
var BARSTATE_PROPERTIES = [
|
|
28935
|
+
{ name: "barstate.islast", desc: "True on the last bar" },
|
|
28936
|
+
{ name: "barstate.isfirst", desc: "True on the first bar" },
|
|
28937
|
+
{ name: "barstate.isconfirmed", desc: "True when the bar is confirmed (always true for historical)" },
|
|
28938
|
+
{ name: "barstate.isnew", desc: "True on the first tick of a new bar" },
|
|
28939
|
+
{ name: "barstate.ishistory", desc: "True when processing historical data" },
|
|
28940
|
+
{ name: "barstate.isrealtime", desc: "True when processing realtime data (always false)" }
|
|
28941
|
+
];
|
|
28942
|
+
var OUTPUT_FUNCTIONS = [
|
|
28943
|
+
{ name: "plot(value, ...)", desc: "Plot a line on the chart" },
|
|
28944
|
+
{ name: "hline(price, ...)", desc: "Horizontal reference line" },
|
|
28945
|
+
{ name: "fill(id1, id2, ...)", desc: "Fill area between two references" },
|
|
28946
|
+
{ name: "bgcolor(color)", desc: "Set per-bar background color" },
|
|
28947
|
+
{ name: "barcolor(color)", desc: "Override candle color per bar" },
|
|
28948
|
+
{ name: "plotshape(cond, ...)", desc: "Draw a marker shape" },
|
|
28949
|
+
{ name: "plotchar(cond, ...)", desc: "Draw a character marker" },
|
|
28950
|
+
{ name: "plotarrow(value, ...)", desc: "Draw up/down arrow" }
|
|
28951
|
+
];
|
|
28952
|
+
var UTILITY_FUNCTIONS = [
|
|
28953
|
+
{ name: 'indicator("Title")', desc: "Declare the indicator name" },
|
|
28954
|
+
{ name: "input(default)", desc: "Create a numeric user parameter" },
|
|
28955
|
+
{ name: "input.string(default)", desc: "Create a string user parameter" },
|
|
28956
|
+
{ name: "input.text_area(default)", desc: "Create a multiline text parameter" },
|
|
28957
|
+
{ name: "nz(val, rep?)", desc: "Replace NaN with rep (default 0)" },
|
|
28958
|
+
{ name: "na(val)", desc: "Returns true if value is NaN" }
|
|
28959
|
+
];
|
|
28960
|
+
var MATH_FUNCTIONS = [
|
|
28961
|
+
"abs",
|
|
28962
|
+
"max",
|
|
28963
|
+
"min",
|
|
28964
|
+
"round",
|
|
28965
|
+
"floor",
|
|
28966
|
+
"ceil",
|
|
28967
|
+
"sqrt",
|
|
28968
|
+
"log",
|
|
28969
|
+
"log10",
|
|
28970
|
+
"exp",
|
|
28971
|
+
"pow",
|
|
28972
|
+
"sign",
|
|
28973
|
+
"sin",
|
|
28974
|
+
"cos",
|
|
28975
|
+
"tan",
|
|
28976
|
+
"asin",
|
|
28977
|
+
"acos",
|
|
28978
|
+
"atan",
|
|
28979
|
+
"avg",
|
|
28980
|
+
"random"
|
|
28981
|
+
];
|
|
28982
|
+
var COLOR_CONSTANTS = [
|
|
28983
|
+
"red",
|
|
28984
|
+
"green",
|
|
28985
|
+
"blue",
|
|
28986
|
+
"white",
|
|
28987
|
+
"black",
|
|
28988
|
+
"yellow",
|
|
28989
|
+
"orange",
|
|
28990
|
+
"purple",
|
|
28991
|
+
"aqua",
|
|
28992
|
+
"lime",
|
|
28993
|
+
"teal",
|
|
28994
|
+
"fuchsia",
|
|
28995
|
+
"silver",
|
|
28996
|
+
"gray",
|
|
28997
|
+
"olive",
|
|
28998
|
+
"maroon",
|
|
28999
|
+
"navy"
|
|
29000
|
+
];
|
|
29001
|
+
var COLOR_FUNCTIONS = [
|
|
29002
|
+
{ name: "color.new(base, transp)", desc: "Apply transparency (0\u2013100) to a color" },
|
|
29003
|
+
{ name: "color.rgb(r, g, b, t?)", desc: "Create color from RGBA components" }
|
|
29004
|
+
];
|
|
29005
|
+
var STRING_FUNCTIONS = [
|
|
29006
|
+
{ name: "str.tostring(val)", desc: "Convert value to string" },
|
|
29007
|
+
{ name: "str.tonumber(val)", desc: "Convert string to number" },
|
|
29008
|
+
{ name: "str.format(tpl, ...)", desc: "Format string with {0}, {1}, \u2026 placeholders" },
|
|
29009
|
+
{ name: "str.length(s)", desc: "String length" },
|
|
29010
|
+
{ name: "str.trim(s)", desc: "Remove leading/trailing whitespace" },
|
|
29011
|
+
{ name: "str.contains(s, sub)", desc: "True if s contains sub" },
|
|
29012
|
+
{ name: "str.substring(s, st, en)", desc: "Extract substring" },
|
|
29013
|
+
{ name: "str.replace_all(s, t, r)", desc: "Replace all occurrences of t with r" },
|
|
29014
|
+
{ name: "str.upper(s)", desc: "Uppercase" },
|
|
29015
|
+
{ name: "str.lower(s)", desc: "Lowercase" },
|
|
29016
|
+
{ name: "str.split(s, sep)", desc: "Split string into array by separator" }
|
|
29017
|
+
];
|
|
29018
|
+
var ARRAY_FUNCTIONS = [
|
|
29019
|
+
{ name: "array.new_float(sz, val)", desc: "Create float array of size with initial value" },
|
|
29020
|
+
{ name: "array.from(v1, v2, ...)", desc: "Create array from values" },
|
|
29021
|
+
{ name: "array.size(arr)", desc: "Number of elements" },
|
|
29022
|
+
{ name: "array.get(arr, i)", desc: "Get element at index" },
|
|
29023
|
+
{ name: "array.set(arr, i, v)", desc: "Set element at index" },
|
|
29024
|
+
{ name: "array.push(arr, v)", desc: "Append element" },
|
|
29025
|
+
{ name: "array.pop(arr)", desc: "Remove and return last element" },
|
|
29026
|
+
{ name: "array.remove(arr, i)", desc: "Remove element at index" },
|
|
29027
|
+
{ name: "array.clear(arr)", desc: "Remove all elements" },
|
|
29028
|
+
{ name: "array.includes(arr, v)", desc: "True if array contains value" },
|
|
29029
|
+
{ name: "array.indexof(arr, v)", desc: "Index of first occurrence (-1 if none)" },
|
|
29030
|
+
{ name: "array.slice(arr, s, e)", desc: "Sub-array from start to end" },
|
|
29031
|
+
{ name: "array.join(arr, sep)", desc: "Join elements into string" },
|
|
29032
|
+
{ name: "array.sort(arr)", desc: "Sort array in place (numeric)" },
|
|
29033
|
+
{ name: "array.reverse(arr)", desc: "Reverse array in place" },
|
|
29034
|
+
{ name: "array.avg(arr)", desc: "Average of elements" },
|
|
29035
|
+
{ name: "array.sum(arr)", desc: "Sum of elements" },
|
|
29036
|
+
{ name: "array.min(arr)", desc: "Minimum element" },
|
|
29037
|
+
{ name: "array.max(arr)", desc: "Maximum element" }
|
|
29038
|
+
];
|
|
29039
|
+
var TABLE_FUNCTIONS = [
|
|
29040
|
+
{ name: "table.new(pos, cols, rows)", desc: "Create table (+ bgcolor=, border_color=, border_width=, frame_color=, frame_width=)" },
|
|
29041
|
+
{ name: "table.cell(id, c, r, text)", desc: "Set cell (+ text_color=, bgcolor=, text_size=, text_halign=, text_valign=)" },
|
|
29042
|
+
{ name: "table.clear(id, c, r)", desc: "Clear a single cell" },
|
|
29043
|
+
{ name: "table.delete(id)", desc: "Delete the entire table" },
|
|
29044
|
+
{ name: "text.align_left/center/right", desc: "Horizontal alignment constants" },
|
|
29045
|
+
{ name: "text.align_top/center/bottom", desc: "Vertical alignment constants" }
|
|
29046
|
+
];
|
|
29047
|
+
var TSCRIPT_TEMPLATE = `indicator("My Indicator")
|
|
29048
|
+
|
|
29049
|
+
// Parameters \u2014 shown in the UI
|
|
29050
|
+
length = input(14)
|
|
29051
|
+
fast = input(9)
|
|
29052
|
+
slow = input(21)
|
|
29053
|
+
|
|
29054
|
+
// Calculations using built-in TA functions
|
|
29055
|
+
fast_ma = ema(close, fast)
|
|
29056
|
+
slow_ma = ema(close, slow)
|
|
29057
|
+
signal = crossover(fast_ma, slow_ma)
|
|
29058
|
+
|
|
29059
|
+
// Plot results (overlay: true draws on price pane)
|
|
29060
|
+
plot(fast_ma)
|
|
29061
|
+
plot(slow_ma)
|
|
29062
|
+
`;
|
|
29063
|
+
function ScriptDrawer({ onClose, onAddIndicator, apiUrl, getAuthToken }) {
|
|
29064
|
+
const [activeTab, setActiveTab] = useState("code");
|
|
29065
|
+
const [isOwner, setIsOwner] = useState(true);
|
|
29066
|
+
const [code, setCode] = useState(TSCRIPT_TEMPLATE);
|
|
29067
|
+
const [overlay, setOverlay] = useState(false);
|
|
29068
|
+
useEffect(() => {
|
|
29069
|
+
const match = code.match(/indicator\s*\([^)]*overlay\s*=\s*(true|false)/i);
|
|
29070
|
+
if (match) {
|
|
29071
|
+
setOverlay(match[1].toLowerCase() === "true");
|
|
29072
|
+
}
|
|
29073
|
+
}, [code]);
|
|
29074
|
+
const [errors, setErrors] = useState([]);
|
|
29075
|
+
const [refOpen, setRefOpen] = useState(false);
|
|
29076
|
+
const [drawerWidth, setDrawerWidth] = useState(380);
|
|
29077
|
+
const [savedScripts, setSavedScripts] = useState([]);
|
|
29078
|
+
const [scriptsMenuOpen, setScriptsMenuOpen] = useState(false);
|
|
29079
|
+
const [saving, setSaving] = useState(false);
|
|
29080
|
+
const [activeScriptId, setActiveScriptId] = useState(null);
|
|
29081
|
+
const [saveNameInput, setSaveNameInput] = useState("");
|
|
29082
|
+
const [showSaveNamePrompt, setShowSaveNamePrompt] = useState(false);
|
|
29083
|
+
const scriptsMenuRef = useRef(null);
|
|
29084
|
+
const dragState = useRef(null);
|
|
29085
|
+
const [agentTyping, setAgentTyping] = useState(false);
|
|
29086
|
+
const agentCodeRef = useRef("");
|
|
29087
|
+
useAgentUIEvent("script:clear", () => {
|
|
29088
|
+
agentCodeRef.current = "";
|
|
29089
|
+
setCode("");
|
|
29090
|
+
setErrors([]);
|
|
29091
|
+
setAgentTyping(true);
|
|
29092
|
+
setActiveTab("code");
|
|
29093
|
+
});
|
|
29094
|
+
useAgentUIEvent("script:type", ({ chunk, done, scriptName }) => {
|
|
29095
|
+
agentCodeRef.current += chunk;
|
|
29096
|
+
setCode(agentCodeRef.current);
|
|
29097
|
+
if (scriptName) setSaveNameInput(scriptName);
|
|
29098
|
+
if (done) setAgentTyping(false);
|
|
29099
|
+
});
|
|
29100
|
+
useAgentUIEvent("script:reload", () => {
|
|
29101
|
+
fetchSavedScripts();
|
|
29102
|
+
});
|
|
29103
|
+
useAgentUIEvent("script:activate", ({ scriptId }) => {
|
|
29104
|
+
setActiveScriptId(scriptId);
|
|
29105
|
+
fetchSavedScripts();
|
|
29106
|
+
});
|
|
29107
|
+
const handleAddToChartRef = useRef(() => {
|
|
29108
|
+
});
|
|
29109
|
+
useAgentUIEvent("script:attach", () => {
|
|
29110
|
+
handleAddToChartRef.current();
|
|
29111
|
+
});
|
|
29112
|
+
useAgentUIEvent("script:ready", () => {
|
|
29113
|
+
setAgentTyping(false);
|
|
29114
|
+
setErrors([]);
|
|
29115
|
+
});
|
|
29116
|
+
const onResizeMouseDown = (e) => {
|
|
29117
|
+
e.preventDefault();
|
|
29118
|
+
dragState.current = { startX: e.clientX, startW: drawerWidth };
|
|
29119
|
+
const onMove = (ev) => {
|
|
29120
|
+
if (!dragState.current) return;
|
|
29121
|
+
const delta = dragState.current.startX - ev.clientX;
|
|
29122
|
+
setDrawerWidth(Math.min(Math.round(window.innerWidth * 0.7), Math.max(280, dragState.current.startW + delta)));
|
|
29123
|
+
};
|
|
29124
|
+
const onUp = () => {
|
|
29125
|
+
dragState.current = null;
|
|
29126
|
+
window.removeEventListener("mousemove", onMove);
|
|
29127
|
+
window.removeEventListener("mouseup", onUp);
|
|
29128
|
+
};
|
|
29129
|
+
window.addEventListener("mousemove", onMove);
|
|
29130
|
+
window.addEventListener("mouseup", onUp);
|
|
29131
|
+
};
|
|
29132
|
+
const _authHeaders = useCallback(async () => {
|
|
29133
|
+
if (!getAuthToken) return {};
|
|
29134
|
+
try {
|
|
29135
|
+
const tok = await getAuthToken();
|
|
29136
|
+
return tok ? { Authorization: `Bearer ${tok}` } : {};
|
|
29137
|
+
} catch {
|
|
29138
|
+
return {};
|
|
29139
|
+
}
|
|
29140
|
+
}, [getAuthToken]);
|
|
29141
|
+
const extractScriptName = useCallback((src) => {
|
|
29142
|
+
const m = src.match(/indicator\s*\(\s*["']([^"']+)["']/);
|
|
29143
|
+
return m?.[1] ?? "Untitled Script";
|
|
29144
|
+
}, []);
|
|
29145
|
+
const fetchSavedScripts = useCallback(async () => {
|
|
29146
|
+
const base = apiUrl ?? "";
|
|
29147
|
+
try {
|
|
29148
|
+
const auth = await _authHeaders();
|
|
29149
|
+
const res = await fetch(`${base}/api/scripts?mine=true`, { headers: { ...auth } });
|
|
29150
|
+
if (res.ok) {
|
|
29151
|
+
const data = await res.json();
|
|
29152
|
+
setSavedScripts(data);
|
|
29153
|
+
}
|
|
29154
|
+
} catch {
|
|
29155
|
+
}
|
|
29156
|
+
}, [apiUrl, _authHeaders]);
|
|
29157
|
+
useEffect(() => {
|
|
29158
|
+
fetchSavedScripts();
|
|
29159
|
+
}, [fetchSavedScripts]);
|
|
29160
|
+
useEffect(() => {
|
|
29161
|
+
if (!scriptsMenuOpen) return;
|
|
29162
|
+
const handler = (e) => {
|
|
29163
|
+
if (scriptsMenuRef.current && !scriptsMenuRef.current.contains(e.target)) {
|
|
29164
|
+
setScriptsMenuOpen(false);
|
|
29165
|
+
setShowSaveNamePrompt(false);
|
|
29166
|
+
}
|
|
29167
|
+
};
|
|
29168
|
+
document.addEventListener("mousedown", handler);
|
|
29169
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
29170
|
+
}, [scriptsMenuOpen]);
|
|
29171
|
+
const handleSaveScript = useCallback(async (name) => {
|
|
29172
|
+
setSaving(true);
|
|
29173
|
+
const base = apiUrl ?? "";
|
|
29174
|
+
const auth = await _authHeaders();
|
|
29175
|
+
const scriptSource = code;
|
|
29176
|
+
const scriptLang = "forgescript";
|
|
29177
|
+
const scriptName = name ?? extractScriptName(scriptSource);
|
|
29178
|
+
try {
|
|
29179
|
+
if (activeScriptId) {
|
|
29180
|
+
await fetch(`${base}/api/scripts/${encodeURIComponent(activeScriptId)}/versions`, {
|
|
29181
|
+
method: "POST",
|
|
29182
|
+
headers: { "Content-Type": "application/json", ...auth },
|
|
29183
|
+
body: JSON.stringify({ source: scriptSource })
|
|
29184
|
+
});
|
|
29185
|
+
} else {
|
|
29186
|
+
const res = await fetch(`${base}/api/scripts`, {
|
|
29187
|
+
method: "POST",
|
|
29188
|
+
headers: { "Content-Type": "application/json", ...auth },
|
|
29189
|
+
body: JSON.stringify({ name: scriptName, language: scriptLang, source: scriptSource })
|
|
29190
|
+
});
|
|
29191
|
+
if (res.ok) {
|
|
29192
|
+
const created = await res.json();
|
|
29193
|
+
setActiveScriptId(created.id);
|
|
29194
|
+
}
|
|
29195
|
+
}
|
|
29196
|
+
await fetchSavedScripts();
|
|
29197
|
+
} catch {
|
|
29198
|
+
}
|
|
29199
|
+
setSaving(false);
|
|
29200
|
+
setShowSaveNamePrompt(false);
|
|
29201
|
+
}, [apiUrl, _authHeaders, code, activeScriptId, extractScriptName, fetchSavedScripts]);
|
|
29202
|
+
const handleSaveClick = useCallback(() => {
|
|
29203
|
+
if (activeScriptId) {
|
|
29204
|
+
handleSaveScript();
|
|
29205
|
+
} else {
|
|
29206
|
+
const autoName = extractScriptName(code);
|
|
29207
|
+
setSaveNameInput(autoName);
|
|
29208
|
+
setShowSaveNamePrompt(true);
|
|
29209
|
+
setScriptsMenuOpen(true);
|
|
29210
|
+
}
|
|
29211
|
+
}, [activeScriptId, handleSaveScript, code, extractScriptName]);
|
|
29212
|
+
const handleLoadScript = useCallback(async (script) => {
|
|
29213
|
+
const base = apiUrl ?? "";
|
|
29214
|
+
const auth = await _authHeaders();
|
|
29215
|
+
try {
|
|
29216
|
+
const res = await fetch(`${base}/api/scripts/${encodeURIComponent(script.id)}/versions`, {
|
|
29217
|
+
headers: { ...auth }
|
|
29218
|
+
});
|
|
29219
|
+
if (!res.ok) return;
|
|
29220
|
+
const versions = await res.json();
|
|
29221
|
+
if (versions.length === 0) return;
|
|
29222
|
+
const latest = versions[0];
|
|
29223
|
+
setActiveScriptId(script.id);
|
|
29224
|
+
setCode(latest.source);
|
|
29225
|
+
setErrors([]);
|
|
29226
|
+
} catch {
|
|
29227
|
+
}
|
|
29228
|
+
setScriptsMenuOpen(false);
|
|
29229
|
+
}, [apiUrl, _authHeaders]);
|
|
29230
|
+
const handleDeleteScript = useCallback(async (id) => {
|
|
29231
|
+
const base = apiUrl ?? "";
|
|
29232
|
+
const auth = await _authHeaders();
|
|
29233
|
+
try {
|
|
29234
|
+
await fetch(`${base}/api/scripts/${encodeURIComponent(id)}`, {
|
|
29235
|
+
method: "DELETE",
|
|
29236
|
+
headers: { ...auth }
|
|
29237
|
+
});
|
|
29238
|
+
if (activeScriptId === id) setActiveScriptId(null);
|
|
29239
|
+
await fetchSavedScripts();
|
|
29240
|
+
} catch {
|
|
29241
|
+
}
|
|
29242
|
+
}, [apiUrl, _authHeaders, activeScriptId, fetchSavedScripts]);
|
|
29243
|
+
const handleAddToChart = () => {
|
|
29244
|
+
setErrors([]);
|
|
29245
|
+
onAddIndicator({ type: "script", script: code, overlay });
|
|
29246
|
+
};
|
|
29247
|
+
handleAddToChartRef.current = handleAddToChart;
|
|
29248
|
+
return /* @__PURE__ */ jsxs("div", { className: "script-drawer", style: { width: drawerWidth }, children: [
|
|
29249
|
+
/* @__PURE__ */ jsx("div", { className: "script-drawer-resize", onMouseDown: onResizeMouseDown }),
|
|
29250
|
+
/* @__PURE__ */ jsxs("div", { className: "script-drawer-header", children: [
|
|
29251
|
+
/* @__PURE__ */ jsxs("span", { className: "script-drawer-title", children: [
|
|
29252
|
+
/* @__PURE__ */ jsxs(
|
|
29253
|
+
"svg",
|
|
29254
|
+
{
|
|
29255
|
+
viewBox: "0 0 16 16",
|
|
29256
|
+
width: "14",
|
|
29257
|
+
height: "14",
|
|
29258
|
+
fill: "none",
|
|
29259
|
+
stroke: "currentColor",
|
|
29260
|
+
strokeWidth: "1.6",
|
|
29261
|
+
strokeLinecap: "round",
|
|
29262
|
+
style: { flexShrink: 0 },
|
|
29263
|
+
children: [
|
|
29264
|
+
/* @__PURE__ */ jsx("polyline", { points: "4,6 2,8 4,10" }),
|
|
29265
|
+
/* @__PURE__ */ jsx("polyline", { points: "12,6 14,8 12,10" }),
|
|
29266
|
+
/* @__PURE__ */ jsx("line", { x1: "9", y1: "3", x2: "7", y2: "13" })
|
|
29267
|
+
]
|
|
29268
|
+
}
|
|
29269
|
+
),
|
|
29270
|
+
"Script Engine"
|
|
29271
|
+
] }),
|
|
29272
|
+
/* @__PURE__ */ jsxs("div", { className: "script-header-actions", children: [
|
|
29273
|
+
/* @__PURE__ */ jsxs(
|
|
29274
|
+
"button",
|
|
29275
|
+
{
|
|
29276
|
+
className: "script-header-btn",
|
|
29277
|
+
onClick: handleSaveClick,
|
|
29278
|
+
disabled: saving,
|
|
29279
|
+
title: activeScriptId ? "Save changes" : "Save Script",
|
|
29280
|
+
children: [
|
|
29281
|
+
/* @__PURE__ */ jsxs(
|
|
29282
|
+
"svg",
|
|
29283
|
+
{
|
|
29284
|
+
viewBox: "0 0 14 14",
|
|
29285
|
+
width: "13",
|
|
29286
|
+
height: "13",
|
|
29287
|
+
fill: "none",
|
|
29288
|
+
stroke: "currentColor",
|
|
29289
|
+
strokeWidth: "1.5",
|
|
29290
|
+
strokeLinecap: "round",
|
|
29291
|
+
strokeLinejoin: "round",
|
|
29292
|
+
children: [
|
|
29293
|
+
/* @__PURE__ */ jsx("path", { d: "M11.5 13H2.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h7l3 3v8a1 1 0 0 1-1 1z" }),
|
|
29294
|
+
/* @__PURE__ */ jsx("path", { d: "M9.5 13V8h-5v5" }),
|
|
29295
|
+
/* @__PURE__ */ jsx("path", { d: "M4.5 1v3h4" })
|
|
29296
|
+
]
|
|
29297
|
+
}
|
|
29298
|
+
),
|
|
29299
|
+
saving ? "\u2026" : "Save"
|
|
29300
|
+
]
|
|
29301
|
+
}
|
|
29302
|
+
),
|
|
29303
|
+
/* @__PURE__ */ jsxs("div", { className: "script-my-scripts-wrap", ref: scriptsMenuRef, children: [
|
|
29304
|
+
/* @__PURE__ */ jsxs(
|
|
29305
|
+
"button",
|
|
29306
|
+
{
|
|
29307
|
+
className: "script-header-btn",
|
|
29308
|
+
onClick: () => {
|
|
29309
|
+
setScriptsMenuOpen((o) => !o);
|
|
29310
|
+
setShowSaveNamePrompt(false);
|
|
29311
|
+
},
|
|
29312
|
+
title: "My Scripts",
|
|
29313
|
+
children: [
|
|
29314
|
+
/* @__PURE__ */ jsx(
|
|
29315
|
+
"svg",
|
|
29316
|
+
{
|
|
29317
|
+
viewBox: "0 0 14 14",
|
|
29318
|
+
width: "13",
|
|
29319
|
+
height: "13",
|
|
29320
|
+
fill: "none",
|
|
29321
|
+
stroke: "currentColor",
|
|
29322
|
+
strokeWidth: "1.5",
|
|
29323
|
+
strokeLinecap: "round",
|
|
29324
|
+
strokeLinejoin: "round",
|
|
29325
|
+
children: /* @__PURE__ */ jsx("path", { d: "M1.5 3.5h11M1.5 7h11M1.5 10.5h11" })
|
|
29326
|
+
}
|
|
29327
|
+
),
|
|
29328
|
+
"My Scripts"
|
|
29329
|
+
]
|
|
29330
|
+
}
|
|
29331
|
+
),
|
|
29332
|
+
scriptsMenuOpen && /* @__PURE__ */ jsxs("div", { className: "script-my-scripts-dropdown", children: [
|
|
29333
|
+
showSaveNamePrompt && /* @__PURE__ */ jsxs("div", { className: "script-save-name-prompt", children: [
|
|
29334
|
+
/* @__PURE__ */ jsx(
|
|
29335
|
+
"input",
|
|
29336
|
+
{
|
|
29337
|
+
className: "script-save-name-input",
|
|
29338
|
+
type: "text",
|
|
29339
|
+
placeholder: "Script name",
|
|
29340
|
+
value: saveNameInput,
|
|
29341
|
+
onChange: (e) => setSaveNameInput(e.target.value),
|
|
29342
|
+
onKeyDown: (e) => {
|
|
29343
|
+
if (e.key === "Enter" && saveNameInput.trim()) {
|
|
29344
|
+
handleSaveScript(saveNameInput.trim());
|
|
29345
|
+
}
|
|
29346
|
+
},
|
|
29347
|
+
autoFocus: true
|
|
29348
|
+
}
|
|
29349
|
+
),
|
|
29350
|
+
/* @__PURE__ */ jsx(
|
|
29351
|
+
"button",
|
|
29352
|
+
{
|
|
29353
|
+
className: "script-save-name-confirm",
|
|
29354
|
+
onClick: () => saveNameInput.trim() && handleSaveScript(saveNameInput.trim()),
|
|
29355
|
+
disabled: !saveNameInput.trim() || saving,
|
|
29356
|
+
children: saving ? "\u2026" : "Save"
|
|
29357
|
+
}
|
|
29358
|
+
)
|
|
29359
|
+
] }),
|
|
29360
|
+
savedScripts.length === 0 && !showSaveNamePrompt && /* @__PURE__ */ jsx("div", { className: "script-my-scripts-empty", children: "No saved scripts yet" }),
|
|
29361
|
+
savedScripts.map((s) => /* @__PURE__ */ jsxs("div", { className: "script-my-scripts-item", children: [
|
|
29362
|
+
/* @__PURE__ */ jsxs(
|
|
29363
|
+
"button",
|
|
29364
|
+
{
|
|
29365
|
+
className: "script-my-scripts-load",
|
|
29366
|
+
onClick: () => handleLoadScript(s),
|
|
29367
|
+
title: `Load "${s.name}"`,
|
|
29368
|
+
children: [
|
|
29369
|
+
/* @__PURE__ */ jsx("span", { className: "script-lang-badge forge", children: "ForgeScript" }),
|
|
29370
|
+
/* @__PURE__ */ jsx("span", { className: "script-my-scripts-name", children: s.name })
|
|
29371
|
+
]
|
|
29372
|
+
}
|
|
29373
|
+
),
|
|
29374
|
+
/* @__PURE__ */ jsx(
|
|
29375
|
+
"button",
|
|
29376
|
+
{
|
|
29377
|
+
className: "script-my-scripts-delete",
|
|
29378
|
+
onClick: (e) => {
|
|
29379
|
+
e.stopPropagation();
|
|
29380
|
+
handleDeleteScript(s.id);
|
|
29381
|
+
},
|
|
29382
|
+
title: "Delete",
|
|
29383
|
+
children: /* @__PURE__ */ jsxs(
|
|
29384
|
+
"svg",
|
|
29385
|
+
{
|
|
29386
|
+
viewBox: "0 0 10 10",
|
|
29387
|
+
width: "10",
|
|
29388
|
+
height: "10",
|
|
29389
|
+
stroke: "currentColor",
|
|
29390
|
+
strokeWidth: "1.5",
|
|
29391
|
+
strokeLinecap: "round",
|
|
29392
|
+
children: [
|
|
29393
|
+
/* @__PURE__ */ jsx("line", { x1: "2", y1: "2", x2: "8", y2: "8" }),
|
|
29394
|
+
/* @__PURE__ */ jsx("line", { x1: "8", y1: "2", x2: "2", y2: "8" })
|
|
29395
|
+
]
|
|
29396
|
+
}
|
|
29397
|
+
)
|
|
29398
|
+
}
|
|
29399
|
+
)
|
|
29400
|
+
] }, s.id))
|
|
29401
|
+
] })
|
|
29402
|
+
] }),
|
|
29403
|
+
/* @__PURE__ */ jsx("button", { className: "script-close-btn", onClick: onClose, title: "Close", children: /* @__PURE__ */ jsxs(
|
|
29404
|
+
"svg",
|
|
29405
|
+
{
|
|
29406
|
+
viewBox: "0 0 12 12",
|
|
29407
|
+
width: "12",
|
|
29408
|
+
height: "12",
|
|
29409
|
+
stroke: "currentColor",
|
|
29410
|
+
strokeWidth: "1.8",
|
|
29411
|
+
strokeLinecap: "round",
|
|
29412
|
+
children: [
|
|
29413
|
+
/* @__PURE__ */ jsx("line", { x1: "1", y1: "1", x2: "11", y2: "11" }),
|
|
29414
|
+
/* @__PURE__ */ jsx("line", { x1: "11", y1: "1", x2: "1", y2: "11" })
|
|
29415
|
+
]
|
|
29416
|
+
}
|
|
29417
|
+
) })
|
|
29418
|
+
] })
|
|
29419
|
+
] }),
|
|
29420
|
+
/* @__PURE__ */ jsxs("div", { className: "script-main-tabs", children: [
|
|
29421
|
+
/* @__PURE__ */ jsx(
|
|
29422
|
+
"button",
|
|
29423
|
+
{
|
|
29424
|
+
className: `script-main-tab${activeTab === "code" ? " active" : ""}`,
|
|
29425
|
+
onClick: () => setActiveTab("code"),
|
|
29426
|
+
children: "Code"
|
|
29427
|
+
}
|
|
29428
|
+
),
|
|
29429
|
+
/* @__PURE__ */ jsx(
|
|
29430
|
+
"button",
|
|
29431
|
+
{
|
|
29432
|
+
className: `script-main-tab${activeTab === "datasets" ? " active" : ""}`,
|
|
29433
|
+
onClick: () => setActiveTab("datasets"),
|
|
29434
|
+
children: "Datasets"
|
|
29435
|
+
}
|
|
29436
|
+
),
|
|
29437
|
+
/* @__PURE__ */ jsx(
|
|
29438
|
+
"button",
|
|
29439
|
+
{
|
|
29440
|
+
className: `script-main-tab${activeTab === "docs" ? " active" : ""}`,
|
|
29441
|
+
onClick: () => setActiveTab("docs"),
|
|
29442
|
+
children: "Docs"
|
|
29443
|
+
}
|
|
29444
|
+
),
|
|
29445
|
+
/* @__PURE__ */ jsx(
|
|
29446
|
+
"button",
|
|
29447
|
+
{
|
|
29448
|
+
className: `script-main-tab${activeTab === "settings" ? " active" : ""}`,
|
|
29449
|
+
onClick: () => setActiveTab("settings"),
|
|
29450
|
+
children: "Settings"
|
|
29451
|
+
}
|
|
29452
|
+
)
|
|
29453
|
+
] }),
|
|
29454
|
+
activeTab === "datasets" && /* @__PURE__ */ jsx(
|
|
29455
|
+
DatasetManagerDrawer,
|
|
29456
|
+
{
|
|
29457
|
+
scriptId: activeScriptId,
|
|
29458
|
+
isOwner,
|
|
29459
|
+
apiBase: apiUrl ?? "",
|
|
29460
|
+
...getAuthToken ? { getAuthToken } : {}
|
|
29461
|
+
}
|
|
29462
|
+
),
|
|
29463
|
+
activeTab === "docs" && /* @__PURE__ */ jsx(
|
|
29464
|
+
DocsEditor,
|
|
29465
|
+
{
|
|
29466
|
+
scriptId: activeScriptId,
|
|
29467
|
+
isOwner,
|
|
29468
|
+
apiBase: apiUrl ?? "",
|
|
29469
|
+
...getAuthToken ? { getAuthToken } : {}
|
|
29470
|
+
}
|
|
29471
|
+
),
|
|
29472
|
+
activeTab === "settings" && /* @__PURE__ */ jsx(
|
|
29473
|
+
ScriptSettings,
|
|
29474
|
+
{
|
|
29475
|
+
scriptId: activeScriptId,
|
|
29476
|
+
isOwner,
|
|
29477
|
+
apiBase: apiUrl ?? "",
|
|
29478
|
+
...getAuthToken ? { getAuthToken } : {},
|
|
29479
|
+
onUpdated: () => fetchSavedScripts()
|
|
29480
|
+
}
|
|
29481
|
+
),
|
|
29482
|
+
activeTab !== "code" ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
29483
|
+
/* @__PURE__ */ jsxs("div", { className: "script-lang-tabs", children: [
|
|
29484
|
+
/* @__PURE__ */ jsx("button", { className: "script-lang-tab active", children: "ForgeScript" }),
|
|
29485
|
+
/* @__PURE__ */ jsxs(
|
|
29486
|
+
"button",
|
|
29487
|
+
{
|
|
29488
|
+
className: "script-lang-tab-help",
|
|
29489
|
+
title: "ForgeScript Manual",
|
|
29490
|
+
onClick: () => window.open("/forgescript-manual.html", "_blank", "noopener,noreferrer"),
|
|
29491
|
+
"aria-label": "Open ForgeScript Manual",
|
|
29492
|
+
children: [
|
|
29493
|
+
/* @__PURE__ */ jsxs(
|
|
29494
|
+
"svg",
|
|
29495
|
+
{
|
|
29496
|
+
viewBox: "0 0 16 16",
|
|
29497
|
+
width: "13",
|
|
29498
|
+
height: "13",
|
|
29499
|
+
fill: "none",
|
|
29500
|
+
stroke: "currentColor",
|
|
29501
|
+
strokeWidth: "1.5",
|
|
29502
|
+
strokeLinecap: "round",
|
|
29503
|
+
strokeLinejoin: "round",
|
|
29504
|
+
children: [
|
|
29505
|
+
/* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "7" }),
|
|
29506
|
+
/* @__PURE__ */ jsx("path", { d: "M6 6c0-1.1.9-2 2-2s2 .9 2 2c0 1-.7 1.6-1.5 2-.4.2-.5.5-.5.8" }),
|
|
29507
|
+
/* @__PURE__ */ jsx("circle", { cx: "8", cy: "12", r: "0.6", fill: "currentColor", stroke: "none" })
|
|
29508
|
+
]
|
|
29509
|
+
}
|
|
29510
|
+
),
|
|
29511
|
+
"Help"
|
|
29512
|
+
]
|
|
29513
|
+
}
|
|
29514
|
+
)
|
|
29515
|
+
] }),
|
|
29516
|
+
/* @__PURE__ */ jsxs("div", { className: "script-ref-section", children: [
|
|
29517
|
+
/* @__PURE__ */ jsxs(
|
|
29518
|
+
"button",
|
|
29519
|
+
{
|
|
29520
|
+
className: "script-ref-toggle",
|
|
29521
|
+
onClick: () => setRefOpen((o) => !o),
|
|
29522
|
+
children: [
|
|
29523
|
+
/* @__PURE__ */ jsx(
|
|
29524
|
+
"svg",
|
|
29525
|
+
{
|
|
29526
|
+
viewBox: "0 0 10 6",
|
|
29527
|
+
width: "8",
|
|
29528
|
+
height: "6",
|
|
29529
|
+
fill: "currentColor",
|
|
29530
|
+
style: { transform: refOpen ? "rotate(180deg)" : "none", transition: "transform 0.15s" },
|
|
29531
|
+
children: /* @__PURE__ */ jsx("path", { d: "M0 0l5 6 5-6z" })
|
|
29532
|
+
}
|
|
29533
|
+
),
|
|
29534
|
+
"Built-in Reference"
|
|
29535
|
+
]
|
|
29536
|
+
}
|
|
29537
|
+
),
|
|
29538
|
+
refOpen && /* @__PURE__ */ jsxs("div", { className: "script-ref-body", children: [
|
|
29539
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "Series" }),
|
|
29540
|
+
/* @__PURE__ */ jsx("div", { className: "script-feature-list", children: BUILT_IN_SERIES.map((s) => /* @__PURE__ */ jsx("span", { className: "script-feature-pill series", children: s }, s)) }),
|
|
29541
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "Bar State" }),
|
|
29542
|
+
/* @__PURE__ */ jsx("div", { className: "script-feature-list", children: BARSTATE_PROPERTIES.map((f) => /* @__PURE__ */ jsx("span", { className: "script-feature-pill barstate", title: f.desc, children: f.name }, f.name)) }),
|
|
29543
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "TA Functions" }),
|
|
29544
|
+
/* @__PURE__ */ jsx("div", { className: "script-feature-list", children: TA_FUNCTIONS.map((f) => /* @__PURE__ */ jsxs("span", { className: "script-feature-pill ta", title: f.desc, children: [
|
|
29545
|
+
f.name.split("(")[0],
|
|
29546
|
+
"()"
|
|
29547
|
+
] }, f.name)) }),
|
|
29548
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "Output" }),
|
|
29549
|
+
/* @__PURE__ */ jsx("div", { className: "script-feature-list", children: OUTPUT_FUNCTIONS.map((f) => /* @__PURE__ */ jsxs("span", { className: "script-feature-pill output", title: f.desc, children: [
|
|
29550
|
+
f.name.split("(")[0],
|
|
29551
|
+
"()"
|
|
29552
|
+
] }, f.name)) }),
|
|
29553
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "Math" }),
|
|
29554
|
+
/* @__PURE__ */ jsx("div", { className: "script-feature-list", children: MATH_FUNCTIONS.map((f) => /* @__PURE__ */ jsxs("span", { className: "script-feature-pill math", children: [
|
|
29555
|
+
f,
|
|
29556
|
+
"()"
|
|
29557
|
+
] }, f)) }),
|
|
29558
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "Color" }),
|
|
29559
|
+
/* @__PURE__ */ jsxs("div", { className: "script-feature-list", children: [
|
|
29560
|
+
COLOR_CONSTANTS.map((c) => /* @__PURE__ */ jsxs("span", { className: "script-feature-pill color", title: `color.${c}`, children: [
|
|
29561
|
+
"color.",
|
|
29562
|
+
c
|
|
29563
|
+
] }, c)),
|
|
29564
|
+
COLOR_FUNCTIONS.map((f) => /* @__PURE__ */ jsxs("span", { className: "script-feature-pill color", title: f.desc, children: [
|
|
29565
|
+
f.name.split("(")[0],
|
|
29566
|
+
"()"
|
|
29567
|
+
] }, f.name))
|
|
29568
|
+
] }),
|
|
29569
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "String" }),
|
|
29570
|
+
/* @__PURE__ */ jsx("div", { className: "script-feature-list", children: STRING_FUNCTIONS.map((f) => /* @__PURE__ */ jsxs("span", { className: "script-feature-pill str", title: f.desc, children: [
|
|
29571
|
+
f.name.split("(")[0],
|
|
29572
|
+
"()"
|
|
29573
|
+
] }, f.name)) }),
|
|
29574
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "Array" }),
|
|
29575
|
+
/* @__PURE__ */ jsx("div", { className: "script-feature-list", children: ARRAY_FUNCTIONS.map((f) => /* @__PURE__ */ jsxs("span", { className: "script-feature-pill array", title: f.desc, children: [
|
|
29576
|
+
f.name.split("(")[0],
|
|
29577
|
+
"()"
|
|
29578
|
+
] }, f.name)) }),
|
|
29579
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "Table" }),
|
|
29580
|
+
/* @__PURE__ */ jsx("div", { className: "script-feature-list", children: TABLE_FUNCTIONS.map((f) => /* @__PURE__ */ jsxs("span", { className: "script-feature-pill table", title: f.desc, children: [
|
|
29581
|
+
f.name.split("(")[0],
|
|
29582
|
+
"()"
|
|
29583
|
+
] }, f.name)) }),
|
|
29584
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "Utility" }),
|
|
29585
|
+
/* @__PURE__ */ jsx("div", { className: "script-feature-list", children: UTILITY_FUNCTIONS.map((f) => /* @__PURE__ */ jsxs("span", { className: "script-feature-pill util", title: f.desc, children: [
|
|
29586
|
+
f.name.split("(")[0],
|
|
29587
|
+
"()"
|
|
29588
|
+
] }, f.name)) }),
|
|
29589
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-detail", children: [...BARSTATE_PROPERTIES, ...TA_FUNCTIONS, ...OUTPUT_FUNCTIONS, ...COLOR_FUNCTIONS, ...STRING_FUNCTIONS, ...ARRAY_FUNCTIONS, ...TABLE_FUNCTIONS, ...UTILITY_FUNCTIONS].map((f) => /* @__PURE__ */ jsxs("div", { className: "script-ref-row", children: [
|
|
29590
|
+
/* @__PURE__ */ jsx("code", { className: "script-ref-fn", children: f.name }),
|
|
29591
|
+
/* @__PURE__ */ jsx("span", { className: "script-ref-desc", children: f.desc })
|
|
29592
|
+
] }, f.name)) })
|
|
29593
|
+
] })
|
|
29594
|
+
] }),
|
|
29595
|
+
/* @__PURE__ */ jsxs("div", { className: "script-editor-wrap", style: { position: "relative" }, children: [
|
|
29596
|
+
agentTyping && /* @__PURE__ */ jsxs("div", { style: {
|
|
29597
|
+
position: "absolute",
|
|
29598
|
+
top: 6,
|
|
29599
|
+
right: 10,
|
|
29600
|
+
zIndex: 10,
|
|
29601
|
+
display: "flex",
|
|
29602
|
+
alignItems: "center",
|
|
29603
|
+
gap: 5,
|
|
29604
|
+
fontSize: 11,
|
|
29605
|
+
color: "var(--primary, #8ab4f8)",
|
|
29606
|
+
background: "var(--surface, #1e1e1e)",
|
|
29607
|
+
padding: "2px 8px",
|
|
29608
|
+
borderRadius: 10,
|
|
29609
|
+
border: "1px solid rgba(138,180,248,0.3)",
|
|
29610
|
+
pointerEvents: "none"
|
|
29611
|
+
}, children: [
|
|
29612
|
+
/* @__PURE__ */ jsx("span", { style: {
|
|
29613
|
+
width: 6,
|
|
29614
|
+
height: 6,
|
|
29615
|
+
borderRadius: "50%",
|
|
29616
|
+
background: "var(--primary, #8ab4f8)",
|
|
29617
|
+
animation: "agent-pulse 1s ease-in-out infinite"
|
|
29618
|
+
} }),
|
|
29619
|
+
"Agent typing\u2026"
|
|
29620
|
+
] }),
|
|
29621
|
+
/* @__PURE__ */ jsx(
|
|
29622
|
+
"textarea",
|
|
29623
|
+
{
|
|
29624
|
+
className: "script-editor",
|
|
29625
|
+
spellCheck: false,
|
|
29626
|
+
value: code,
|
|
29627
|
+
readOnly: agentTyping,
|
|
29628
|
+
onChange: (e) => {
|
|
29629
|
+
if (!agentTyping) {
|
|
29630
|
+
setCode(e.target.value);
|
|
29631
|
+
setErrors([]);
|
|
29632
|
+
}
|
|
29633
|
+
}
|
|
29634
|
+
}
|
|
29635
|
+
)
|
|
29636
|
+
] }),
|
|
29637
|
+
errors.length > 0 && /* @__PURE__ */ jsx("div", { className: "script-errors", children: errors.map((msg, i) => /* @__PURE__ */ jsxs("div", { className: "script-error-line", children: [
|
|
29638
|
+
/* @__PURE__ */ jsxs(
|
|
29639
|
+
"svg",
|
|
29640
|
+
{
|
|
29641
|
+
viewBox: "0 0 12 12",
|
|
29642
|
+
width: "11",
|
|
29643
|
+
height: "11",
|
|
29644
|
+
fill: "none",
|
|
29645
|
+
stroke: "currentColor",
|
|
29646
|
+
strokeWidth: "1.6",
|
|
29647
|
+
strokeLinecap: "round",
|
|
29648
|
+
style: { flexShrink: 0, color: "var(--down, #ef5350)" },
|
|
29649
|
+
children: [
|
|
29650
|
+
/* @__PURE__ */ jsx("circle", { cx: "6", cy: "6", r: "5" }),
|
|
29651
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "3.5", x2: "6", y2: "6.5" }),
|
|
29652
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "8", x2: "6", y2: "8.5" })
|
|
29653
|
+
]
|
|
29654
|
+
}
|
|
29655
|
+
),
|
|
29656
|
+
msg
|
|
29657
|
+
] }, i)) })
|
|
29658
|
+
] }),
|
|
29659
|
+
activeTab === "code" && /* @__PURE__ */ jsxs("div", { className: "script-footer", children: [
|
|
29660
|
+
/* @__PURE__ */ jsxs("label", { className: "script-overlay-label", children: [
|
|
29661
|
+
/* @__PURE__ */ jsx(
|
|
29662
|
+
"input",
|
|
29663
|
+
{
|
|
29664
|
+
type: "checkbox",
|
|
29665
|
+
checked: overlay,
|
|
29666
|
+
onChange: (e) => setOverlay(e.target.checked)
|
|
29667
|
+
}
|
|
29668
|
+
),
|
|
29669
|
+
"Overlay on price"
|
|
29670
|
+
] }),
|
|
29671
|
+
/* @__PURE__ */ jsxs("button", { className: "script-run-btn", onClick: handleAddToChart, children: [
|
|
29672
|
+
/* @__PURE__ */ jsx(
|
|
29673
|
+
"svg",
|
|
29674
|
+
{
|
|
29675
|
+
viewBox: "0 0 12 12",
|
|
29676
|
+
width: "11",
|
|
29677
|
+
height: "11",
|
|
29678
|
+
fill: "currentColor",
|
|
29679
|
+
style: { flexShrink: 0 },
|
|
29680
|
+
children: /* @__PURE__ */ jsx("polygon", { points: "2,1 11,6 2,11" })
|
|
29681
|
+
}
|
|
29682
|
+
),
|
|
29683
|
+
"Add to Chart"
|
|
29684
|
+
] })
|
|
29685
|
+
] })
|
|
29686
|
+
] });
|
|
29687
|
+
}
|
|
29688
|
+
function ChartWorkspace({
|
|
29689
|
+
// TabBar
|
|
29690
|
+
tabs,
|
|
29691
|
+
activeTabId,
|
|
29692
|
+
onSelectTab,
|
|
29693
|
+
onAddTab,
|
|
29694
|
+
onCloseTab,
|
|
29695
|
+
onRenameTab,
|
|
29696
|
+
// TopToolbar
|
|
29697
|
+
symbol,
|
|
29698
|
+
timeframe,
|
|
29699
|
+
theme,
|
|
29700
|
+
customTimeframes,
|
|
29701
|
+
favoriteTfs,
|
|
29702
|
+
onSymbolChange,
|
|
29703
|
+
onTimeframeChange,
|
|
29704
|
+
onAddCustomTimeframe,
|
|
29705
|
+
onRemoveCustomTimeframe,
|
|
29706
|
+
onFavoriteTfsChange,
|
|
29707
|
+
onAddIndicator,
|
|
29708
|
+
maxIndicators,
|
|
29709
|
+
indicatorsOpen,
|
|
29710
|
+
onToggleIndicators,
|
|
29711
|
+
onToggleTheme,
|
|
29712
|
+
onCopyScreenshot,
|
|
29713
|
+
onDownloadScreenshot,
|
|
29714
|
+
onFullscreen,
|
|
29715
|
+
isFullscreen,
|
|
29716
|
+
currentLayoutName,
|
|
29717
|
+
currentLayoutId,
|
|
29718
|
+
autoSave,
|
|
29719
|
+
onFetchLayouts,
|
|
29720
|
+
onSaveLayout,
|
|
29721
|
+
onLoadLayout,
|
|
29722
|
+
onRenameLayout,
|
|
29723
|
+
onCopyLayout,
|
|
29724
|
+
onToggleAutoSave,
|
|
29725
|
+
onDeleteLayout,
|
|
29726
|
+
onOpenLayoutInNewTab,
|
|
29727
|
+
showTradeButton,
|
|
29728
|
+
tradeDrawerOpen,
|
|
29729
|
+
onToggleTradeDrawer,
|
|
29730
|
+
symbolResolver,
|
|
29731
|
+
rollRule,
|
|
29732
|
+
onRollRuleChange,
|
|
29733
|
+
extraTfGroups,
|
|
29734
|
+
// RightToolbar
|
|
29735
|
+
watchlistOpen,
|
|
29736
|
+
onToggleWatchlist,
|
|
29737
|
+
orderEntryOpen,
|
|
29738
|
+
onToggleOrderEntry,
|
|
29739
|
+
showOrderEntry,
|
|
29740
|
+
showAgent,
|
|
29741
|
+
agentOpen,
|
|
29742
|
+
onToggleAgent,
|
|
29743
|
+
domOpen,
|
|
29744
|
+
onToggleDOM,
|
|
29745
|
+
// Platform feature capability flags
|
|
29746
|
+
showDrawingTools,
|
|
29747
|
+
showIndicators,
|
|
29748
|
+
showForgeScript,
|
|
29749
|
+
showWatchlist,
|
|
29750
|
+
showSavedLayouts,
|
|
29751
|
+
showCustomTimeframes,
|
|
29752
|
+
showDataExport,
|
|
29753
|
+
showMultiChartLayout,
|
|
29754
|
+
showMultipleTabs,
|
|
29755
|
+
// BottomToolbar
|
|
29756
|
+
activeSymbol,
|
|
29757
|
+
timezone,
|
|
29758
|
+
onTimezoneChange,
|
|
29759
|
+
session,
|
|
29760
|
+
onSessionChange,
|
|
29761
|
+
scriptDrawerOpen,
|
|
29762
|
+
onToggleScriptDrawer,
|
|
29763
|
+
builtinScriptDrawer,
|
|
29764
|
+
scriptApiUrl,
|
|
29765
|
+
// Trading panel
|
|
29766
|
+
tradingPanel,
|
|
29767
|
+
tradingPanelOpen,
|
|
29768
|
+
onToggleTradingPanel,
|
|
29769
|
+
tradingPanelRefreshTick,
|
|
29770
|
+
rithmicConnected,
|
|
29771
|
+
// Grid layout
|
|
29772
|
+
gridTemplate,
|
|
29773
|
+
gridSyncOptions,
|
|
29774
|
+
onGridTemplateChange,
|
|
29775
|
+
onGridSyncChange,
|
|
29776
|
+
onExitGridMode,
|
|
29777
|
+
// Chart + state
|
|
29778
|
+
chartSlots,
|
|
29779
|
+
isLicensed,
|
|
29780
|
+
wsLoaded,
|
|
29781
|
+
leftDrawers,
|
|
29782
|
+
drawers,
|
|
29783
|
+
getAuthToken,
|
|
29784
|
+
tradingBridge,
|
|
29785
|
+
tradingOverlayStore
|
|
29786
|
+
}) {
|
|
29787
|
+
const capabilities = useChartCapabilities();
|
|
29788
|
+
const autoTrading = tradingBridge !== void 0;
|
|
29789
|
+
const [autoOrderEntryOpen, setAutoOrderEntryOpen] = React11.useState(false);
|
|
29790
|
+
const [autoTradingPanelOpen, setAutoTradingPanelOpen] = React11.useState(false);
|
|
29791
|
+
const effOnToggleOrderEntry = onToggleOrderEntry ?? (autoTrading ? () => setAutoOrderEntryOpen((o) => !o) : void 0);
|
|
29792
|
+
const effOrderEntryOpen = onToggleOrderEntry ? orderEntryOpen ?? false : autoOrderEntryOpen;
|
|
29793
|
+
const effOnToggleTradeDrawer = onToggleTradeDrawer ?? (autoTrading ? () => setAutoOrderEntryOpen((o) => !o) : void 0);
|
|
29794
|
+
const effTradeDrawerOpen = onToggleTradeDrawer ? tradeDrawerOpen : autoOrderEntryOpen;
|
|
29795
|
+
const autoJournal = autoTrading && !tradingPanel && capabilities.tradingPanel;
|
|
29796
|
+
const [autoScriptOpen, setAutoScriptOpen] = React11.useState(false);
|
|
29797
|
+
const effOnToggleScriptDrawer = onToggleScriptDrawer ?? (() => setAutoScriptOpen((o) => !o));
|
|
29798
|
+
const effScriptDrawerOpen = onToggleScriptDrawer ? scriptDrawerOpen ?? false : autoScriptOpen;
|
|
29799
|
+
const closeScriptDrawer = onToggleScriptDrawer ?? (() => setAutoScriptOpen(false));
|
|
29800
|
+
const gate = (hostProp, licensed) => hostProp !== false && licensed;
|
|
29801
|
+
const effShowTradeButton = gate(showTradeButton, capabilities.orderEntry) && effOnToggleTradeDrawer !== void 0;
|
|
29802
|
+
const effShowOrderEntry = gate(showOrderEntry, capabilities.orderEntry) && effOnToggleOrderEntry !== void 0;
|
|
29803
|
+
const effShowAgent = gate(showAgent, capabilities.aiAgent) && onToggleAgent !== void 0;
|
|
29804
|
+
const effShowIndicators = gate(showIndicators, capabilities.indicators);
|
|
29805
|
+
const effShowForgeScript = gate(showForgeScript, capabilities.forgeScript);
|
|
29806
|
+
const effShowWatchlist = gate(showWatchlist, capabilities.watchlists);
|
|
29807
|
+
const effShowSavedLayouts = gate(showSavedLayouts, capabilities.savedLayouts);
|
|
29808
|
+
const effShowCustomTimeframes = gate(showCustomTimeframes, capabilities.customTimeframes);
|
|
29809
|
+
const effShowDataExport = gate(showDataExport, capabilities.dataExport);
|
|
29810
|
+
const effShowMultiChartLayout = gate(showMultiChartLayout, capabilities.multiChartLayout);
|
|
29811
|
+
const effShowMultipleTabs = gate(showMultipleTabs, capabilities.multipleWorkspaces);
|
|
29812
|
+
const tabItems = tabs.map((t) => ({
|
|
29813
|
+
id: t.id,
|
|
29814
|
+
label: t.label,
|
|
29815
|
+
...t.isSaved !== void 0 ? { isSaved: t.isSaved } : {}
|
|
29816
|
+
}));
|
|
29817
|
+
return /* @__PURE__ */ jsxs(
|
|
29818
|
+
"div",
|
|
29819
|
+
{
|
|
29820
|
+
style: {
|
|
29821
|
+
display: "flex",
|
|
29822
|
+
flexDirection: "column",
|
|
29823
|
+
height: "100%",
|
|
29824
|
+
background: "var(--shelf-bg)",
|
|
29825
|
+
padding: 6,
|
|
29826
|
+
gap: 4
|
|
29827
|
+
},
|
|
29828
|
+
children: [
|
|
29829
|
+
!wsLoaded && /* @__PURE__ */ jsx("div", { style: {
|
|
29830
|
+
position: "absolute",
|
|
29831
|
+
inset: 0,
|
|
29832
|
+
display: "flex",
|
|
29833
|
+
alignItems: "center",
|
|
29834
|
+
justifyContent: "center",
|
|
29835
|
+
background: "var(--bg)",
|
|
29836
|
+
zIndex: 1e3
|
|
29837
|
+
}, children: /* @__PURE__ */ jsxs("svg", { width: "56", height: "112", viewBox: "0 0 28 56", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
|
|
29838
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
29839
|
+
@keyframes fc-ws-wick-grow {
|
|
29840
|
+
0% { transform: scaleY(0); opacity: 0; }
|
|
29841
|
+
20% { transform: scaleY(1); opacity: 1; }
|
|
29842
|
+
80% { transform: scaleY(1); opacity: 1; }
|
|
29843
|
+
100% { transform: scaleY(0); opacity: 0; }
|
|
29844
|
+
}
|
|
29845
|
+
@keyframes fc-ws-body-grow {
|
|
29846
|
+
0% { transform: scaleY(0); opacity: 0; }
|
|
29847
|
+
15% { transform: scaleY(0); opacity: 0; }
|
|
29848
|
+
55% { transform: scaleY(1); opacity: 1; }
|
|
29849
|
+
80% { transform: scaleY(1); opacity: 1; }
|
|
29850
|
+
100% { transform: scaleY(0); opacity: 0; }
|
|
29851
|
+
}
|
|
29852
|
+
.fc-ws-wick {
|
|
29853
|
+
transform-box: fill-box;
|
|
29854
|
+
transform-origin: bottom center;
|
|
29855
|
+
animation: fc-ws-wick-grow 1.6s cubic-bezier(0.4,0,0.2,1) infinite;
|
|
29856
|
+
}
|
|
29857
|
+
.fc-ws-body {
|
|
29858
|
+
transform-box: fill-box;
|
|
29859
|
+
transform-origin: bottom center;
|
|
29860
|
+
animation: fc-ws-body-grow 1.6s cubic-bezier(0.4,0,0.2,1) infinite;
|
|
29861
|
+
}
|
|
29862
|
+
` }),
|
|
29863
|
+
/* @__PURE__ */ jsx("rect", { className: "fc-ws-wick", x: "13", y: "44", width: "2", height: "10", rx: "1", fill: "#26a69a" }),
|
|
29864
|
+
/* @__PURE__ */ jsx("rect", { className: "fc-ws-body", x: "6", y: "18", width: "16", height: "28", rx: "2", fill: "#26a69a" }),
|
|
29865
|
+
/* @__PURE__ */ jsx("rect", { className: "fc-ws-wick", x: "13", y: "4", width: "2", height: "14", rx: "1", fill: "#26a69a" })
|
|
29866
|
+
] }) }),
|
|
29867
|
+
/* @__PURE__ */ jsx(
|
|
29868
|
+
TabBar,
|
|
29869
|
+
{
|
|
29870
|
+
tabs: tabItems,
|
|
29871
|
+
activeId: activeTabId,
|
|
29872
|
+
onSelect: onSelectTab,
|
|
29873
|
+
onAdd: onAddTab,
|
|
29874
|
+
onClose: onCloseTab,
|
|
29875
|
+
onRename: onRenameTab,
|
|
29876
|
+
showAddTab: effShowMultipleTabs
|
|
29877
|
+
}
|
|
29878
|
+
),
|
|
29879
|
+
/* @__PURE__ */ jsx(
|
|
29880
|
+
TopToolbar,
|
|
29881
|
+
{
|
|
29882
|
+
symbol,
|
|
29883
|
+
timeframe,
|
|
29884
|
+
theme,
|
|
29885
|
+
customTimeframes,
|
|
29886
|
+
favorites: favoriteTfs,
|
|
29887
|
+
onSymbolChange,
|
|
29888
|
+
onTimeframeChange,
|
|
29889
|
+
onAddCustomTimeframe,
|
|
29890
|
+
onRemoveCustomTimeframe,
|
|
29891
|
+
onFavoritesChange: onFavoriteTfsChange,
|
|
29892
|
+
onAddIndicator,
|
|
29893
|
+
maxIndicators,
|
|
29894
|
+
...indicatorsOpen !== void 0 ? { indicatorsOpen } : {},
|
|
29895
|
+
...onToggleIndicators !== void 0 ? { onToggleIndicators } : {},
|
|
29896
|
+
onToggleTheme,
|
|
29897
|
+
onCopyScreenshot,
|
|
29898
|
+
onDownloadScreenshot,
|
|
29899
|
+
onFullscreen,
|
|
29900
|
+
isFullscreen,
|
|
29901
|
+
currentLayoutName,
|
|
29902
|
+
currentLayoutId,
|
|
29903
|
+
autoSave,
|
|
29904
|
+
onFetchLayouts,
|
|
29905
|
+
onSaveLayout,
|
|
29906
|
+
onLoadLayout,
|
|
29907
|
+
onRenameLayout,
|
|
29908
|
+
onCopyLayout,
|
|
29909
|
+
onToggleAutoSave,
|
|
29910
|
+
onDeleteLayout,
|
|
29911
|
+
onOpenLayoutInNewTab,
|
|
29912
|
+
symbolResolver,
|
|
29913
|
+
...rollRule !== void 0 && capabilities.continuousContracts ? { rollRule } : {},
|
|
29914
|
+
...onRollRuleChange !== void 0 && capabilities.continuousContracts ? { onRollRuleChange } : {},
|
|
29915
|
+
showTradeButton: effShowTradeButton,
|
|
29916
|
+
...effTradeDrawerOpen !== void 0 ? { tradeDrawerOpen: effTradeDrawerOpen } : {},
|
|
29917
|
+
...effOnToggleTradeDrawer !== void 0 ? { onToggleTradeDrawer: effOnToggleTradeDrawer } : {},
|
|
29918
|
+
...extraTfGroups !== void 0 ? { extraTfGroups } : {},
|
|
29919
|
+
...getAuthToken !== void 0 ? { getAuthToken } : {},
|
|
29920
|
+
...gridTemplate !== void 0 ? { gridTemplate } : {},
|
|
29921
|
+
...gridSyncOptions !== void 0 ? { gridSyncOptions } : {},
|
|
29922
|
+
...onGridTemplateChange !== void 0 ? { onGridTemplateChange } : {},
|
|
29923
|
+
...onGridSyncChange !== void 0 ? { onGridSyncChange } : {},
|
|
29924
|
+
...onExitGridMode !== void 0 ? { onExitGridMode } : {},
|
|
29925
|
+
showIndicators: effShowIndicators,
|
|
29926
|
+
showSavedLayouts: effShowSavedLayouts,
|
|
29927
|
+
showCustomTimeframes: effShowCustomTimeframes,
|
|
29928
|
+
showDataExport: effShowDataExport,
|
|
29929
|
+
showMultiChartLayout: effShowMultiChartLayout
|
|
29930
|
+
}
|
|
29931
|
+
),
|
|
29932
|
+
/* @__PURE__ */ jsx("div", { style: { flex: 1, display: "flex", gap: 4, minHeight: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsxs("div", { style: { position: "relative", flex: 1, display: "flex", gap: 4, minWidth: 0, minHeight: 0 }, children: [
|
|
29933
|
+
!isLicensed && /* @__PURE__ */ jsxs("div", { style: {
|
|
29934
|
+
position: "absolute",
|
|
29935
|
+
inset: 0,
|
|
29936
|
+
zIndex: 100,
|
|
28264
29937
|
background: "rgba(0,0,0,0.6)",
|
|
28265
29938
|
backdropFilter: "blur(4px)",
|
|
28266
29939
|
display: "flex",
|
|
@@ -28287,6 +29960,15 @@ function ChartWorkspace({
|
|
|
28287
29960
|
onSymbolChange
|
|
28288
29961
|
}
|
|
28289
29962
|
),
|
|
29963
|
+
builtinScriptDrawer !== false && effScriptDrawerOpen && capabilities.forgeScript && /* @__PURE__ */ jsx(
|
|
29964
|
+
ScriptDrawer,
|
|
29965
|
+
{
|
|
29966
|
+
onClose: closeScriptDrawer,
|
|
29967
|
+
onAddIndicator,
|
|
29968
|
+
...scriptApiUrl !== void 0 ? { apiUrl: scriptApiUrl } : {},
|
|
29969
|
+
...getAuthToken !== void 0 ? { getAuthToken } : {}
|
|
29970
|
+
}
|
|
29971
|
+
),
|
|
28290
29972
|
leftDrawers,
|
|
28291
29973
|
drawers,
|
|
28292
29974
|
/* @__PURE__ */ jsx(
|
|
@@ -28340,8 +30022,8 @@ function ChartWorkspace({
|
|
|
28340
30022
|
session,
|
|
28341
30023
|
onSessionChange,
|
|
28342
30024
|
showSessionSelector: capabilities.sessionHours,
|
|
28343
|
-
onToggleScriptDrawer,
|
|
28344
|
-
scriptDrawerOpen,
|
|
30025
|
+
onToggleScriptDrawer: effOnToggleScriptDrawer,
|
|
30026
|
+
scriptDrawerOpen: effScriptDrawerOpen,
|
|
28345
30027
|
showScriptButton: effShowForgeScript,
|
|
28346
30028
|
...tradingPanelOpen !== void 0 ? { tradingPanelOpen } : {},
|
|
28347
30029
|
...onToggleTradingPanel !== void 0 && capabilities.tradingPanel ? { onToggleTradingPanel } : autoJournal ? { tradingPanelOpen: autoTradingPanelOpen, onToggleTradingPanel: () => setAutoTradingPanelOpen((o) => !o) } : {},
|
|
@@ -29373,7 +31055,7 @@ function fileToTextAttachment(file) {
|
|
|
29373
31055
|
reader.readAsText(file);
|
|
29374
31056
|
});
|
|
29375
31057
|
}
|
|
29376
|
-
function
|
|
31058
|
+
function renderMarkdown2(text) {
|
|
29377
31059
|
if (!text) return null;
|
|
29378
31060
|
const parts = [];
|
|
29379
31061
|
const codeBlockRegex = /```([\s\S]*?)```/g;
|
|
@@ -29597,7 +31279,7 @@ function AssistantPanel({ onClose, chartContext }) {
|
|
|
29597
31279
|
/* @__PURE__ */ jsx("div", { children: "Ask the AI assistant anything" }),
|
|
29598
31280
|
/* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "var(--text-muted, #777)", marginTop: 4 }, children: "About your chart, indicators, or trading ideas." })
|
|
29599
31281
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
29600
|
-
agent.messages.map((msg, idx) => /* @__PURE__ */ jsxs(
|
|
31282
|
+
agent.messages.map((msg, idx) => /* @__PURE__ */ jsxs(React11.Fragment, { children: [
|
|
29601
31283
|
idx === agent.sessionStart && agent.sessionStart > 0 && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "8px 0", margin: "4px 0" }, children: [
|
|
29602
31284
|
/* @__PURE__ */ jsx("div", { style: { flex: 1, height: 1, background: "var(--border, rgba(0,0,0,0.12))" } }),
|
|
29603
31285
|
/* @__PURE__ */ jsx("span", { style: { fontSize: 10, color: "var(--text-muted, #888)", whiteSpace: "nowrap", textTransform: "uppercase", letterSpacing: "0.06em" }, children: "New session" }),
|
|
@@ -29620,7 +31302,7 @@ function AssistantPanel({ onClose, chartContext }) {
|
|
|
29620
31302
|
},
|
|
29621
31303
|
imgIdx
|
|
29622
31304
|
)) }),
|
|
29623
|
-
msg.role === "assistant" ?
|
|
31305
|
+
msg.role === "assistant" ? renderMarkdown2(msg.content) : msg.content,
|
|
29624
31306
|
msg.role === "assistant" && agent.isStreaming && msg === agent.messages[agent.messages.length - 1] && /* @__PURE__ */ jsx("span", { style: { marginLeft: 4, animation: "agent-blink 1s infinite" }, children: "\u258C" })
|
|
29625
31307
|
] }) }),
|
|
29626
31308
|
msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx("div", { style: { marginTop: 4 }, children: msg.toolCalls.map((tool) => /* @__PURE__ */ jsxs("div", { children: [
|