@dickpy/dsh-imagegen 1.0.4 → 1.0.6

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/lib/index.js CHANGED
@@ -14,7 +14,7 @@ import { spawn } from "node:child_process";
14
14
  /** Settings namespace this plugin owns (host settings seam + bridge). */
15
15
  const IMAGEGEN_SETTINGS_NAMESPACE = "dsh-imagegen";
16
16
  /** Published package version shared by the host updater and the client UI. */
17
- const PLUGIN_VERSION = "1.0.4";
17
+ const PLUGIN_VERSION = "1.0.6";
18
18
  /** Same-origin route family (loopback-only, mirroring the dsh-ssh fence). */
19
19
  const SETTINGS_API = {
20
20
  describe: "/api/dsh-imagegen/settings/describe",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dickpy/dsh-imagegen",
3
3
  "description": "AI 生图 (image generation) plugin for the dsh web GUI: text-to-image and image-to-image through a configurable OpenAI-compatible endpoint (gpt-image-2 / gpt-image-1 / dall-e-3), with a settings card for api_url / api_key and a sidebar entry opening a split-pane generation studio.",
4
- "version": "1.0.4",
4
+ "version": "1.0.6",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -147,6 +147,7 @@ export function ImageGenPanel(props: {
147
147
  const [updateMessage, setUpdateMessage] = useState<string | null>(null)
148
148
  const [updateResult, setUpdateResult] = useState<'success' | 'failed' | null>(null)
149
149
  const fileInput = useRef<HTMLInputElement>(null)
150
+ const previewStage = useRef<HTMLDivElement>(null)
150
151
  const elapsed = useElapsed(generating, startedAt)
151
152
 
152
153
  // Load the host-persisted history once on mount (it lives in ~/.dsh on the
@@ -287,6 +288,19 @@ export function ImageGenPanel(props: {
287
288
  return () => window.removeEventListener('keydown', onKey)
288
289
  }, [preview])
289
290
 
291
+ // A scaled image owns real scrollable space, rather than being visually
292
+ // transformed and clipped. Recenter the viewport after every zoom or slide.
293
+ useEffect(() => {
294
+ if (preview === null) return
295
+ const frame = window.requestAnimationFrame(() => {
296
+ const stage = previewStage.current
297
+ if (stage === null) return
298
+ stage.scrollLeft = Math.max(0, (stage.scrollWidth - stage.clientWidth) / 2)
299
+ stage.scrollTop = Math.max(0, (stage.scrollHeight - stage.clientHeight) / 2)
300
+ })
301
+ return () => window.cancelAnimationFrame(frame)
302
+ }, [preview, previewScale])
303
+
290
304
  /** Load a past generation's images into the canvas. */
291
305
  const viewHistoryEntry = async (entry: HistoryEntry): Promise<void> => {
292
306
  try {
@@ -343,6 +357,8 @@ export function ImageGenPanel(props: {
343
357
  const generateDisabled = generating || !enabled || !configured
344
358
  const viewingEntry = viewingHistoryId === null ? null : history.find(entry => entry.id === viewingHistoryId) ?? null
345
359
  const previewImage = preview === null ? null : preview.images[preview.index] ?? null
360
+ const previewFrameScale = Math.max(1, previewScale)
361
+ const previewImageScale = previewScale / previewFrameScale
346
362
 
347
363
  const copyPreviewPrompt = async (text: string): Promise<void> => {
348
364
  try {
@@ -750,18 +766,24 @@ export function ImageGenPanel(props: {
750
766
  ) : null}
751
767
  <figure className={css.lightboxFigure} onClick={(event) => { event.stopPropagation() }}>
752
768
  <div
769
+ ref={previewStage}
753
770
  className={css.lightboxStage}
754
771
  onWheel={(event) => {
755
772
  event.preventDefault()
756
773
  setPreviewScale(current => clampPreviewScale(current + (event.deltaY < 0 ? PREVIEW_SCALE_STEP : -PREVIEW_SCALE_STEP)))
757
774
  }}
758
775
  >
759
- <img
760
- className={css.lightboxImage}
761
- style={{ transform: `scale(${previewScale})` }}
762
- src={srcOf(previewImage)}
763
- alt={previewImage.revisedPrompt ?? tt('preview.title')}
764
- />
776
+ <div
777
+ className={css.lightboxScaleFrame}
778
+ style={{ width: `${previewFrameScale * 100}%`, height: `${previewFrameScale * 100}%` }}
779
+ >
780
+ <img
781
+ className={css.lightboxImage}
782
+ style={{ width: `${previewImageScale * 100}%`, height: `${previewImageScale * 100}%` }}
783
+ src={srcOf(previewImage)}
784
+ alt={previewImage.revisedPrompt ?? tt('preview.title')}
785
+ />
786
+ </div>
765
787
  </div>
766
788
  <div className={css.lightboxTools} role="group" aria-label={tt('preview.zoomControls')}>
767
789
  <button type="button" className={css.lightboxTool} aria-label={tt('preview.zoomOut')} title={tt('preview.zoomOut')} onClick={() => { setPreviewScale(current => clampPreviewScale(current - PREVIEW_SCALE_STEP)) }}>
@@ -801,26 +801,12 @@ html[data-dsh-imagegen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ss
801
801
  flex: 1;
802
802
  min-height: 0;
803
803
  gap: 14px;
804
- }
805
-
806
- .grid[data-count='1'] {
807
- grid-template-columns: minmax(0, 1fr);
808
- grid-template-rows: minmax(0, 1fr);
809
- }
810
-
811
- .grid[data-count='2'] {
812
804
  grid-template-columns: repeat(2, minmax(0, 1fr));
813
- grid-template-rows: minmax(0, 1fr);
814
- }
815
-
816
- .grid[data-count='3'] {
817
- grid-template-columns: repeat(3, minmax(0, 1fr));
818
- grid-template-rows: minmax(0, 1fr);
805
+ grid-template-rows: repeat(2, minmax(0, 1fr));
819
806
  }
820
807
 
821
- .grid[data-count='4'] {
822
- grid-template-columns: repeat(2, minmax(0, 1fr));
823
- grid-template-rows: repeat(2, minmax(0, 1fr));
808
+ .grid[data-count='1'] .imageCard {
809
+ grid-area: 1 / 1 / 3 / 3;
824
810
  }
825
811
 
826
812
  .imageCard {
@@ -846,7 +832,7 @@ html[data-dsh-imagegen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ss
846
832
  flex: 1;
847
833
  min-height: 0;
848
834
  width: 100%;
849
- object-fit: contain;
835
+ object-fit: cover;
850
836
  background: var(--dsw-alias-bg-base);
851
837
  }
852
838
 
@@ -1014,20 +1000,27 @@ html[data-dsh-imagegen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ss
1014
1000
  .lightboxStage {
1015
1001
  flex: 1;
1016
1002
  min-height: 0;
1017
- display: grid;
1018
- place-items: center;
1003
+ position: relative;
1019
1004
  overflow: auto;
1020
1005
  border-radius: 10px;
1021
1006
  background: rgba(255, 255, 255, 0.04);
1022
1007
  }
1023
1008
 
1009
+ .lightboxScaleFrame {
1010
+ display: flex;
1011
+ align-items: center;
1012
+ justify-content: center;
1013
+ min-width: 100%;
1014
+ min-height: 100%;
1015
+ }
1016
+
1024
1017
  .lightboxImage {
1025
- max-width: calc(100% - 4px);
1026
- max-height: calc(100% - 4px);
1018
+ display: block;
1019
+ max-width: 100%;
1020
+ max-height: 100%;
1027
1021
  object-fit: contain;
1028
1022
  border-radius: 10px;
1029
1023
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);
1030
- transition: transform 120ms ease;
1031
1024
  }
1032
1025
 
1033
1026
  .lightboxTools {
package/src/protocol.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  export const IMAGEGEN_SETTINGS_NAMESPACE = 'dsh-imagegen'
9
9
 
10
10
  /** Published package version shared by the host updater and the client UI. */
11
- export const PLUGIN_VERSION = '1.0.4'
11
+ export const PLUGIN_VERSION = '1.0.6'
12
12
 
13
13
  /** Same-origin route family (loopback-only, mirroring the dsh-ssh fence). */
14
14
  export const SETTINGS_API = {