@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.
@@ -1,4 +1,4 @@
1
- import React7, { forwardRef, useRef, useState, useImperativeHandle, useEffect, useCallback, createContext, useMemo, useContext } from 'react';
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
- const res = await fetch(verifyUrl, {
10782
- method: "POST",
10783
- headers: { "Content-Type": "application/json" },
10784
- body: JSON.stringify({ licenseKey: key.trim().toUpperCase() })
10785
- });
10786
- if (!res.ok) {
10787
- throw new Error(`License server error: ${res.status} ${res.statusText}`);
10788
- }
10789
- const data = await res.json();
10790
- if (!data.valid) {
10791
- throw new Error(data.reason ?? "License invalid");
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
- function ChartWorkspace({
28022
- // TabBar
28023
- tabs,
28024
- activeTabId,
28025
- onSelectTab,
28026
- onAddTab,
28027
- onCloseTab,
28028
- onRenameTab,
28029
- // TopToolbar
28030
- symbol,
28031
- timeframe,
28032
- theme,
28033
- customTimeframes,
28034
- favoriteTfs,
28035
- onSymbolChange,
28036
- onTimeframeChange,
28037
- onAddCustomTimeframe,
28038
- onRemoveCustomTimeframe,
28039
- onFavoriteTfsChange,
28040
- onAddIndicator,
28041
- maxIndicators,
28042
- indicatorsOpen,
28043
- onToggleIndicators,
28044
- onToggleTheme,
28045
- onCopyScreenshot,
28046
- onDownloadScreenshot,
28047
- onFullscreen,
28048
- isFullscreen,
28049
- currentLayoutName,
28050
- currentLayoutId,
28051
- autoSave,
28052
- onFetchLayouts,
28053
- onSaveLayout,
28054
- onLoadLayout,
28055
- onRenameLayout,
28056
- onCopyLayout,
28057
- onToggleAutoSave,
28058
- onDeleteLayout,
28059
- onOpenLayoutInNewTab,
28060
- showTradeButton,
28061
- tradeDrawerOpen,
28062
- onToggleTradeDrawer,
28063
- symbolResolver,
28064
- rollRule,
28065
- onRollRuleChange,
28066
- extraTfGroups,
28067
- // RightToolbar
28068
- watchlistOpen,
28069
- onToggleWatchlist,
28070
- orderEntryOpen,
28071
- onToggleOrderEntry,
28072
- showOrderEntry,
28073
- showAgent,
28074
- agentOpen,
28075
- onToggleAgent,
28076
- domOpen,
28077
- onToggleDOM,
28078
- // Platform feature capability flags
28079
- showDrawingTools,
28080
- showIndicators,
28081
- showForgeScript,
28082
- showWatchlist,
28083
- showSavedLayouts,
28084
- showCustomTimeframes,
28085
- showDataExport,
28086
- showMultiChartLayout,
28087
- showMultipleTabs,
28088
- // BottomToolbar
28089
- activeSymbol,
28090
- timezone,
28091
- onTimezoneChange,
28092
- session,
28093
- onSessionChange,
28094
- scriptDrawerOpen,
28095
- onToggleScriptDrawer,
28096
- // Trading panel
28097
- tradingPanel,
28098
- tradingPanelOpen,
28099
- onToggleTradingPanel,
28100
- tradingPanelRefreshTick,
28101
- rithmicConnected,
28102
- // Grid layout
28103
- gridTemplate,
28104
- gridSyncOptions,
28105
- onGridTemplateChange,
28106
- onGridSyncChange,
28107
- onExitGridMode,
28108
- // Chart + state
28109
- chartSlots,
28110
- isLicensed,
28111
- wsLoaded,
28112
- leftDrawers,
28113
- drawers,
28114
- getAuthToken,
28115
- tradingBridge,
28116
- tradingOverlayStore
28117
- }) {
28118
- const capabilities = useChartCapabilities();
28119
- const autoTrading = tradingBridge !== void 0;
28120
- const [autoOrderEntryOpen, setAutoOrderEntryOpen] = React7.useState(false);
28121
- const [autoTradingPanelOpen, setAutoTradingPanelOpen] = React7.useState(false);
28122
- const effOnToggleOrderEntry = onToggleOrderEntry ?? (autoTrading ? () => setAutoOrderEntryOpen((o) => !o) : void 0);
28123
- const effOrderEntryOpen = onToggleOrderEntry ? orderEntryOpen ?? false : autoOrderEntryOpen;
28124
- const effOnToggleTradeDrawer = onToggleTradeDrawer ?? (autoTrading ? () => setAutoOrderEntryOpen((o) => !o) : void 0);
28125
- const effTradeDrawerOpen = onToggleTradeDrawer ? tradeDrawerOpen : autoOrderEntryOpen;
28126
- const autoJournal = autoTrading && !tradingPanel && capabilities.tradingPanel;
28127
- const gate = (hostProp, licensed) => hostProp !== false && licensed;
28128
- const effShowTradeButton = gate(showTradeButton, capabilities.orderEntry) && effOnToggleTradeDrawer !== void 0;
28129
- const effShowOrderEntry = gate(showOrderEntry, capabilities.orderEntry) && effOnToggleOrderEntry !== void 0;
28130
- const effShowAgent = gate(showAgent, capabilities.aiAgent) && onToggleAgent !== void 0;
28131
- const effShowIndicators = gate(showIndicators, capabilities.indicators);
28132
- const effShowForgeScript = gate(showForgeScript, capabilities.forgeScript);
28133
- const effShowWatchlist = gate(showWatchlist, capabilities.watchlists);
28134
- const effShowSavedLayouts = gate(showSavedLayouts, capabilities.savedLayouts);
28135
- const effShowCustomTimeframes = gate(showCustomTimeframes, capabilities.customTimeframes);
28136
- const effShowDataExport = gate(showDataExport, capabilities.dataExport);
28137
- const effShowMultiChartLayout = gate(showMultiChartLayout, capabilities.multiChartLayout);
28138
- const effShowMultipleTabs = gate(showMultipleTabs, capabilities.multipleWorkspaces);
28139
- const tabItems = tabs.map((t) => ({
28140
- id: t.id,
28141
- label: t.label,
28142
- ...t.isSaved !== void 0 ? { isSaved: t.isSaved } : {}
28143
- }));
28144
- return /* @__PURE__ */ jsxs(
28145
- "div",
28146
- {
28147
- style: {
28148
- display: "flex",
28149
- flexDirection: "column",
28150
- height: "100%",
28151
- background: "var(--shelf-bg)",
28152
- padding: 6,
28153
- gap: 4
28154
- },
28155
- children: [
28156
- !wsLoaded && /* @__PURE__ */ jsx("div", { style: {
28157
- position: "absolute",
28158
- inset: 0,
28159
- display: "flex",
28160
- alignItems: "center",
28161
- justifyContent: "center",
28162
- background: "var(--bg)",
28163
- zIndex: 1e3
28164
- }, children: /* @__PURE__ */ jsxs("svg", { width: "56", height: "112", viewBox: "0 0 28 56", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
28165
- /* @__PURE__ */ jsx("style", { children: `
28166
- @keyframes fc-ws-wick-grow {
28167
- 0% { transform: scaleY(0); opacity: 0; }
28168
- 20% { transform: scaleY(1); opacity: 1; }
28169
- 80% { transform: scaleY(1); opacity: 1; }
28170
- 100% { transform: scaleY(0); opacity: 0; }
28171
- }
28172
- @keyframes fc-ws-body-grow {
28173
- 0% { transform: scaleY(0); opacity: 0; }
28174
- 15% { transform: scaleY(0); opacity: 0; }
28175
- 55% { transform: scaleY(1); opacity: 1; }
28176
- 80% { transform: scaleY(1); opacity: 1; }
28177
- 100% { transform: scaleY(0); opacity: 0; }
28178
- }
28179
- .fc-ws-wick {
28180
- transform-box: fill-box;
28181
- transform-origin: bottom center;
28182
- animation: fc-ws-wick-grow 1.6s cubic-bezier(0.4,0,0.2,1) infinite;
28183
- }
28184
- .fc-ws-body {
28185
- transform-box: fill-box;
28186
- transform-origin: bottom center;
28187
- animation: fc-ws-body-grow 1.6s cubic-bezier(0.4,0,0.2,1) infinite;
28188
- }
28189
- ` }),
28190
- /* @__PURE__ */ jsx("rect", { className: "fc-ws-wick", x: "13", y: "44", width: "2", height: "10", rx: "1", fill: "#26a69a" }),
28191
- /* @__PURE__ */ jsx("rect", { className: "fc-ws-body", x: "6", y: "18", width: "16", height: "28", rx: "2", fill: "#26a69a" }),
28192
- /* @__PURE__ */ jsx("rect", { className: "fc-ws-wick", x: "13", y: "4", width: "2", height: "14", rx: "1", fill: "#26a69a" })
28193
- ] }) }),
28194
- /* @__PURE__ */ jsx(
28195
- TabBar,
28196
- {
28197
- tabs: tabItems,
28198
- activeId: activeTabId,
28199
- onSelect: onSelectTab,
28200
- onAdd: onAddTab,
28201
- onClose: onCloseTab,
28202
- onRename: onRenameTab,
28203
- showAddTab: effShowMultipleTabs
28204
- }
28205
- ),
28206
- /* @__PURE__ */ jsx(
28207
- TopToolbar,
28208
- {
28209
- symbol,
28210
- timeframe,
28211
- theme,
28212
- customTimeframes,
28213
- favorites: favoriteTfs,
28214
- onSymbolChange,
28215
- onTimeframeChange,
28216
- onAddCustomTimeframe,
28217
- onRemoveCustomTimeframe,
28218
- onFavoritesChange: onFavoriteTfsChange,
28219
- onAddIndicator,
28220
- maxIndicators,
28221
- ...indicatorsOpen !== void 0 ? { indicatorsOpen } : {},
28222
- ...onToggleIndicators !== void 0 ? { onToggleIndicators } : {},
28223
- onToggleTheme,
28224
- onCopyScreenshot,
28225
- onDownloadScreenshot,
28226
- onFullscreen,
28227
- isFullscreen,
28228
- currentLayoutName,
28229
- currentLayoutId,
28230
- autoSave,
28231
- onFetchLayouts,
28232
- onSaveLayout,
28233
- onLoadLayout,
28234
- onRenameLayout,
28235
- onCopyLayout,
28236
- onToggleAutoSave,
28237
- onDeleteLayout,
28238
- onOpenLayoutInNewTab,
28239
- symbolResolver,
28240
- ...rollRule !== void 0 && capabilities.continuousContracts ? { rollRule } : {},
28241
- ...onRollRuleChange !== void 0 && capabilities.continuousContracts ? { onRollRuleChange } : {},
28242
- showTradeButton: effShowTradeButton,
28243
- ...effTradeDrawerOpen !== void 0 ? { tradeDrawerOpen: effTradeDrawerOpen } : {},
28244
- ...effOnToggleTradeDrawer !== void 0 ? { onToggleTradeDrawer: effOnToggleTradeDrawer } : {},
28245
- ...extraTfGroups !== void 0 ? { extraTfGroups } : {},
28246
- ...getAuthToken !== void 0 ? { getAuthToken } : {},
28247
- ...gridTemplate !== void 0 ? { gridTemplate } : {},
28248
- ...gridSyncOptions !== void 0 ? { gridSyncOptions } : {},
28249
- ...onGridTemplateChange !== void 0 ? { onGridTemplateChange } : {},
28250
- ...onGridSyncChange !== void 0 ? { onGridSyncChange } : {},
28251
- ...onExitGridMode !== void 0 ? { onExitGridMode } : {},
28252
- showIndicators: effShowIndicators,
28253
- showSavedLayouts: effShowSavedLayouts,
28254
- showCustomTimeframes: effShowCustomTimeframes,
28255
- showDataExport: effShowDataExport,
28256
- showMultiChartLayout: effShowMultiChartLayout
28257
- }
28258
- ),
28259
- /* @__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: [
28260
- !isLicensed && /* @__PURE__ */ jsxs("div", { style: {
28261
- position: "absolute",
28262
- inset: 0,
28263
- zIndex: 100,
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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 renderMarkdown(text) {
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(React7.Fragment, { children: [
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" ? renderMarkdown(msg.content) : msg.content,
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: [