@arronqzy/react-view 1.0.11 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/styles.css CHANGED
@@ -1090,6 +1090,10 @@ video {
1090
1090
  width: 2.25rem;
1091
1091
  }
1092
1092
 
1093
+ .w-\[108px\] {
1094
+ width: 108px;
1095
+ }
1096
+
1093
1097
  .w-\[220px\] {
1094
1098
  width: 220px;
1095
1099
  }
@@ -1098,10 +1102,6 @@ video {
1098
1102
  width: 280px;
1099
1103
  }
1100
1104
 
1101
- .w-\[96px\] {
1102
- width: 96px;
1103
- }
1104
-
1105
1105
  .w-\[min\(92vw\2c 720px\)\] {
1106
1106
  width: min(92vw, 720px);
1107
1107
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arronqzy/react-view",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "files": [
5
5
  "src",
6
6
  "dist",
@@ -30,13 +30,13 @@
30
30
  "@codemirror/lang-json": "^6.0.1",
31
31
  "@codemirror/theme-one-dark": "^6.1.2",
32
32
  "@codemirror/view": "^6.36.2",
33
- "@arronqzy/blueprint-dsl": "1.0.5",
34
- "@arronqzy/i18n": "0.1.3",
35
33
  "@arronqzy/ui": "1.0.5",
36
- "@arronqzy/react-blueprint": "1.0.6",
34
+ "@arronqzy/react-blueprint": "1.0.7",
35
+ "@arronqzy/view-table": "0.1.3",
37
36
  "@arronqzy/rx-store": "1.0.4",
38
- "@arronqzy/webllm-assistant": "0.1.5",
39
- "@arronqzy/view-table": "0.1.3"
37
+ "@arronqzy/webllm-assistant": "0.1.7",
38
+ "@arronqzy/blueprint-dsl": "1.0.6",
39
+ "@arronqzy/i18n": "0.1.5"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/react": "^19.0.1",
@@ -35,6 +35,7 @@ import {
35
35
  parsePanelLayers,
36
36
  resolvePreviewLayerElements,
37
37
  } from "./utils/panelStateIO";
38
+ import { applyPreviewSceneFill, readOutputScale } from "./utils/outputScale";
38
39
 
39
40
  const PREVIEW_BOOT_PHASES: PageLifecyclePhase[] = ["mounted"];
40
41
 
@@ -76,6 +77,8 @@ export function ReactViewOnlinePreview({
76
77
  >([]);
77
78
  const [layoutRevision, setLayoutRevision] = useState(0);
78
79
  const [layoutReady, setLayoutReady] = useState(false);
80
+ const [outputScale, setOutputScale] = useState(() => readOutputScale());
81
+ const [fillScale, setFillScale] = useState({ scaleX: 1, scaleY: 1 });
79
82
 
80
83
  const sceneRef = useRef<HTMLDivElement | null>(null);
81
84
 
@@ -179,25 +182,38 @@ export function ReactViewOnlinePreview({
179
182
 
180
183
  const displayElements = useMemo(
181
184
  () =>
182
- scopedLayerElements.map((el) => ({
183
- ...el,
184
- x: el.x - sceneBounds.minX,
185
- y: el.y - sceneBounds.minY,
186
- })),
187
- [sceneBounds.minX, sceneBounds.minY, scopedLayerElements]
185
+ scopedLayerElements.map((el) => {
186
+ const x = el.x - sceneBounds.minX;
187
+ const y = el.y - sceneBounds.minY;
188
+ if (outputScale) return { ...el, x, y };
189
+ return {
190
+ ...el,
191
+ x: x * fillScale.scaleX,
192
+ y: y * fillScale.scaleY,
193
+ width: el.width * fillScale.scaleX,
194
+ height: el.height * fillScale.scaleY,
195
+ };
196
+ }),
197
+ [
198
+ fillScale.scaleX,
199
+ fillScale.scaleY,
200
+ outputScale,
201
+ sceneBounds.minX,
202
+ sceneBounds.minY,
203
+ scopedLayerElements,
204
+ ]
188
205
  );
189
206
 
190
207
  const applySceneFit = useCallback(() => {
191
- const scene = sceneRef.current;
192
- if (!scene) return;
193
- const vw = window.innerWidth || 1;
194
- const vh = window.innerHeight || 1;
195
- const sw = Math.max(1, sceneBounds.width);
196
- const sh = Math.max(1, sceneBounds.height);
197
- const scaleX = vw / sw;
198
- const scaleY = vh / sh;
199
- if (!Number.isFinite(scaleX) || !Number.isFinite(scaleY)) return;
200
- scene.style.transform = `scale(${scaleX}, ${scaleY})`;
208
+ const enabled = readOutputScale();
209
+ setOutputScale(enabled);
210
+ const fill = applyPreviewSceneFill(
211
+ sceneRef.current,
212
+ sceneBounds.width,
213
+ sceneBounds.height,
214
+ enabled
215
+ );
216
+ setFillScale({ scaleX: fill.scaleX, scaleY: fill.scaleY });
201
217
  setLayoutRevision((v) => v + 1);
202
218
  notifyPreviewLayoutChanged();
203
219
  }, [sceneBounds.height, sceneBounds.width]);
@@ -304,9 +320,16 @@ export function ReactViewOnlinePreview({
304
320
  id="preview-scene"
305
321
  className="relative shrink-0 origin-top-left"
306
322
  style={{
307
- width: sceneBounds.width,
308
- height: sceneBounds.height,
323
+ width: outputScale
324
+ ? sceneBounds.width
325
+ : sceneBounds.width * fillScale.scaleX,
326
+ height: outputScale
327
+ ? sceneBounds.height
328
+ : sceneBounds.height * fillScale.scaleY,
309
329
  transformOrigin: "left top",
330
+ transform: outputScale
331
+ ? `scale(${fillScale.scaleX}, ${fillScale.scaleY})`
332
+ : "none",
310
333
  }}
311
334
  >
312
335
  <ElementsLayer
@@ -14,6 +14,7 @@ import { ElementsLayer } from "./components/ElementsLayer";
14
14
  import { SelectLayer } from "./components/SelectLayer";
15
15
  import { MoveableLayer } from "./components/MoveableLayer";
16
16
  import { buildChartOption, CHART_TYPES } from "./utils/chartOptionBuilder";
17
+ import { readOutputScale, writeOutputScale } from "./utils/outputScale";
17
18
  import { MaterialSidebar } from "./components/MaterialSidebar";
18
19
  import { AssistantChatPanel } from "./ai/AssistantChatPanel";
19
20
  import {
@@ -94,6 +95,7 @@ import {
94
95
  EmptyTitle,
95
96
  Input,
96
97
  Menubar,
98
+ MenubarCheckboxItem,
97
99
  MenubarContent,
98
100
  MenubarItem,
99
101
  MenubarMenu,
@@ -460,6 +462,7 @@ export function ReactViewPanel({
460
462
  const [titleIconZoom, setTitleIconZoom] = useState(1.6);
461
463
  const [pendingPreviewLayerId, setPendingPreviewLayerId] = useState<string | null>(null);
462
464
  const [panelFontSize, setPanelFontSize] = useState<"sm" | "md" | "lg">("md");
465
+ const [outputScale, setOutputScale] = useState(() => readOutputScale());
463
466
  const [blueprintOpen, setBlueprintOpen] = useState(false);
464
467
  const [assistantOpen, setAssistantOpen] = useState(false);
465
468
  const [blueprintGraph, setBlueprintGraph] = useState(() => BlueprintGraph.empty());
@@ -570,6 +573,10 @@ export function ReactViewPanel({
570
573
  }
571
574
  }, [panelFontSize]);
572
575
 
576
+ useEffect(() => {
577
+ writeOutputScale(outputScale);
578
+ }, [outputScale]);
579
+
573
580
  useEffect(() => {
574
581
  try {
575
582
  const stored = localStorage.getItem(TITLE_ICON_STORAGE_KEY) ?? "";
@@ -1784,16 +1791,49 @@ export function ReactViewPanel({
1784
1791
  });
1785
1792
  }
1786
1793
  var scene = document.getElementById("preview-scene");
1794
+ var root = document.getElementById("preview-root");
1795
+ var outputScale = ${outputScale ? "true" : "false"};
1796
+ function rememberBox(node) {
1797
+ if (node.dataset.origLeft != null) return;
1798
+ node.dataset.origLeft = String(parseFloat(node.style.left) || 0);
1799
+ node.dataset.origTop = String(parseFloat(node.style.top) || 0);
1800
+ node.dataset.origWidth = String(parseFloat(node.style.width) || node.offsetWidth || 0);
1801
+ node.dataset.origHeight = String(parseFloat(node.style.height) || node.offsetHeight || 0);
1802
+ }
1787
1803
  function fitScene() {
1788
1804
  if (!scene) return;
1789
1805
  var vw = window.innerWidth || 1;
1790
1806
  var vh = window.innerHeight || 1;
1791
1807
  var sw = ${sceneWidth} || 1;
1792
1808
  var sh = ${sceneHeight} || 1;
1793
- // 铺满预览窗口:宽高分别缩放,保证内容贴住四边
1794
1809
  var scaleX = vw / sw;
1795
1810
  var scaleY = vh / sh;
1796
- scene.style.transform = "translate(0px,0px) scale(" + scaleX + "," + scaleY + ")";
1811
+ if (root) root.style.overflow = "hidden";
1812
+ var nodes = scene.querySelectorAll("[data-element-id]");
1813
+ if (outputScale) {
1814
+ scene.style.width = sw + "px";
1815
+ scene.style.height = sh + "px";
1816
+ scene.style.transform = "translate(0px,0px) scale(" + scaleX + "," + scaleY + ")";
1817
+ nodes.forEach(function (node) {
1818
+ rememberBox(node);
1819
+ node.style.left = node.dataset.origLeft + "px";
1820
+ node.style.top = node.dataset.origTop + "px";
1821
+ node.style.width = node.dataset.origWidth + "px";
1822
+ node.style.height = node.dataset.origHeight + "px";
1823
+ });
1824
+ return;
1825
+ }
1826
+ // 块仍按视口撑满,但不对整棵内容做 transform,字体保持原尺寸
1827
+ scene.style.width = vw + "px";
1828
+ scene.style.height = vh + "px";
1829
+ scene.style.transform = "none";
1830
+ nodes.forEach(function (node) {
1831
+ rememberBox(node);
1832
+ node.style.left = (Number(node.dataset.origLeft) * scaleX) + "px";
1833
+ node.style.top = (Number(node.dataset.origTop) * scaleY) + "px";
1834
+ node.style.width = (Number(node.dataset.origWidth) * scaleX) + "px";
1835
+ node.style.height = (Number(node.dataset.origHeight) * scaleY) + "px";
1836
+ });
1797
1837
  }
1798
1838
  fitScene();
1799
1839
  window.addEventListener("resize", fitScene);
@@ -2013,6 +2053,14 @@ export function ReactViewPanel({
2013
2053
  <MenubarRadioItem value="zh-CN">{t("panel.theme.zhCN")}</MenubarRadioItem>
2014
2054
  <MenubarRadioItem value="en-US">{t("panel.theme.enUS")}</MenubarRadioItem>
2015
2055
  </MenubarRadioGroup>
2056
+ <MenubarSeparator />
2057
+ <MenubarCheckboxItem
2058
+ checked={outputScale}
2059
+ onCheckedChange={(checked) => setOutputScale(checked === true)}
2060
+ title={t("panel.menubar.outputScaleHint")}
2061
+ >
2062
+ {t("panel.menubar.outputScale")}
2063
+ </MenubarCheckboxItem>
2016
2064
  </MenubarContent>
2017
2065
  </MenubarMenu>
2018
2066
  </Menubar>
@@ -3176,6 +3224,8 @@ export function ReactViewPanel({
3176
3224
  configFocus={sidebarConfigFocus}
3177
3225
  executionLog={executionLogView}
3178
3226
  selectedBlueprintNode={selectedBlueprintNode}
3227
+ blueprintGraphNodes={blueprintGraph.document.nodes}
3228
+ blueprintGraphEdges={blueprintGraph.document.edges}
3179
3229
  allowFalseSignalPropagation={
3180
3230
  blueprintGraph.document.allowFalseSignalPropagation ?? false
3181
3231
  }
@@ -3,18 +3,17 @@ import {
3
3
  ASSISTANT_MODEL_OPTIONS,
4
4
  DEFAULT_MODEL_ID,
5
5
  WebLLMAssistantRuntime,
6
- buildSystemPrompt,
7
6
  isWebGPUAvailable,
8
7
  looksLikePureQuestion,
9
- parseAssistantAction,
10
8
  inferPanelAddFromUserText,
11
9
  runAgentLoop,
12
10
  buildEditorContext,
13
11
  tryLocalFaqReply,
12
+ buildChatSystemPrompt,
14
13
  buildForceReplyPrompt,
14
+ unwrapChatReply,
15
15
  buildPriorChatMessages,
16
16
  buildRecentDialogSummary,
17
- looksLikeUnclearIntent,
18
17
  UNCLEAR_INTENT_REPLY,
19
18
  inferLocalEditorPlan,
20
19
  type ChatMessage,
@@ -345,15 +344,6 @@ export function AssistantChatPanel({
345
344
  return;
346
345
  }
347
346
 
348
- // Vague greetings / short junk — ask instead of mutating.
349
- if (looksLikeUnclearIntent(trimmed)) {
350
- pushMessage({
351
- role: "assistant",
352
- content: t("panel.ai.unclearIntent") || UNCLEAR_INTENT_REPLY,
353
- });
354
- return;
355
- }
356
-
357
347
  // High-confidence local tools (add / resize / delete / save …) — skip model.
358
348
  const selectedIds = depsRef.current.getSelectedIds() ?? [];
359
349
  const localPlan = inferLocalEditorPlan(trimmed, { selectedIds });
@@ -402,41 +392,20 @@ export function AssistantChatPanel({
402
392
  const runtime = runtimeRef.current!;
403
393
 
404
394
  if (looksLikePureQuestion(trimmed)) {
405
- const ctx = buildEditorContext({
406
- ...buildObservation(),
407
- userGoal: trimmed,
408
- recentDialog,
409
- });
410
395
  const history: ChatMessage[] = [
411
- { role: "system", content: buildSystemPrompt(ctx) },
396
+ { role: "system", content: buildChatSystemPrompt() },
412
397
  ...priorChat,
413
398
  { role: "user", content: trimmed },
414
399
  ];
415
- let parsed = parseAssistantAction(await runtime.chat(history));
416
- if (parsed.ok && parsed.action.type !== "reply") {
417
- // Small models often mutate the page on Q&A — force a reply-only turn.
418
- history.push({
419
- role: "assistant",
420
- content: JSON.stringify(parsed.action),
421
- });
400
+ let message = unwrapChatReply(await runtime.chat(history));
401
+ if (!message) {
422
402
  history.push({ role: "user", content: buildForceReplyPrompt() });
423
- parsed = parseAssistantAction(await runtime.chat(history));
403
+ message = unwrapChatReply(await runtime.chat(history));
424
404
  }
425
- if (!parsed.ok) {
426
- pushMessage({
427
- role: "assistant",
428
- content: `${t("panel.ai.parseFailed")}\n\n\`\`\`\n${parsed.rawText.slice(0, 800)}\n\`\`\``,
429
- });
430
- return;
431
- }
432
- if (parsed.action.type === "reply") {
433
- pushMessage({ role: "assistant", content: parsed.action.message });
434
- return;
435
- }
436
- // Still not a reply — refuse to mutate; ask clearly instead.
437
405
  pushMessage({
438
406
  role: "assistant",
439
- content: t("panel.ai.unclearIntent") || UNCLEAR_INTENT_REPLY,
407
+ content:
408
+ message || t("panel.ai.unclearIntent") || UNCLEAR_INTENT_REPLY,
440
409
  });
441
410
  return;
442
411
  }
@@ -625,12 +594,17 @@ export function AssistantChatPanel({
625
594
  onValueChange={setModelId}
626
595
  disabled={busy || status === "loading"}
627
596
  >
628
- <SelectTrigger className="h-8 w-[96px] rounded-md border-border bg-background text-[12px] shadow-none">
597
+ <SelectTrigger className="h-8 w-[108px] rounded-md border-border bg-background text-[12px] shadow-none">
629
598
  <SelectValue />
630
599
  </SelectTrigger>
631
600
  <SelectContent position="popper" sideOffset={6}>
632
601
  {ASSISTANT_MODEL_OPTIONS.map((opt) => (
633
- <SelectItem key={opt.id} value={opt.id} className="text-[12px]">
602
+ <SelectItem
603
+ key={opt.id}
604
+ value={opt.id}
605
+ className="text-[12px]"
606
+ title={t(opt.hintKey)}
607
+ >
634
608
  {t(opt.labelKey)}
635
609
  </SelectItem>
636
610
  ))}