gpt-image-playground deploy commited on
Commit
e5a9556
·
1 Parent(s): da92fb7

Deploy 7db5c07 to Docker Space

Browse files
README.md CHANGED
@@ -12,7 +12,7 @@ app_port: 4783
12
  本地 AI 图片创作工作台,面向中文内容运营、设计草图和自动化生图流程。支持 `gpt-image-2`、OpenAI 兼容图片接口、文生图、图生图、遮罩编辑、批量任务、历史复用、费用追踪和 Agent API。
13
 
14
  <p align="center">
15
- <img src="https://raw.githubusercontent.com/MisonL/gpt-image-playground-customer/5d089e0e43da0a0478aa6c7f29ffd8e5dc181543/readme-images/interface.jpg?v=20260608-07b596b" alt="GPT Image Playground 界面" width="900"/>
16
  </p>
17
 
18
  ## 快速开始
@@ -83,13 +83,13 @@ start-windows.bat
83
  遮罩编辑示例:
84
 
85
  <p align="center">
86
- <img src="https://raw.githubusercontent.com/MisonL/gpt-image-playground-customer/5d089e0e43da0a0478aa6c7f29ffd8e5dc181543/readme-images/mask-creation.jpg?v=20260608-07b596b" alt="遮罩创建" width="900"/>
87
  </p>
88
 
89
  历史与费用示例:
90
 
91
  <p align="center">
92
- <img src="https://raw.githubusercontent.com/MisonL/gpt-image-playground-customer/5d089e0e43da0a0478aa6c7f29ffd8e5dc181543/readme-images/history.jpg?v=20260608-07b596b" alt="历史面板" width="900"/>
93
  </p>
94
 
95
  ## 配置
 
12
  本地 AI 图片创作工作台,面向中文内容运营、设计草图和自动化生图流程。支持 `gpt-image-2`、OpenAI 兼容图片接口、文生图、图生图、遮罩编辑、批量任务、历史复用、费用追踪和 Agent API。
13
 
14
  <p align="center">
15
+ <img src="https://raw.githubusercontent.com/MisonL/gpt-image-playground-customer/7db5c07cd25ba69e829f2561814fec0a1a73e7e8/readme-images/interface.jpg?v=20260608-07b596b" alt="GPT Image Playground 界面" width="900"/>
16
  </p>
17
 
18
  ## 快速开始
 
83
  遮罩编辑示例:
84
 
85
  <p align="center">
86
+ <img src="https://raw.githubusercontent.com/MisonL/gpt-image-playground-customer/7db5c07cd25ba69e829f2561814fec0a1a73e7e8/readme-images/mask-creation.jpg?v=20260608-07b596b" alt="遮罩创建" width="900"/>
87
  </p>
88
 
89
  历史与费用示例:
90
 
91
  <p align="center">
92
+ <img src="https://raw.githubusercontent.com/MisonL/gpt-image-playground-customer/7db5c07cd25ba69e829f2561814fec0a1a73e7e8/readme-images/history.jpg?v=20260608-07b596b" alt="历史面板" width="900"/>
93
  </p>
94
 
95
  ## 配置
public/hf-space-deploy-marker.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "schema_version": 1,
3
- "local_sha": "5d089e0e43da0a0478aa6c7f29ffd8e5dc181543",
4
- "created_at": "2026-07-23T09:45:05.334Z",
5
- "deploy_id": "a598c41b-0709-4241-9127-e1aa7744e48b"
6
  }
 
1
  {
2
  "schema_version": 1,
3
+ "local_sha": "7db5c07cd25ba69e829f2561814fec0a1a73e7e8",
4
+ "created_at": "2026-07-23T14:28:33.665Z",
5
+ "deploy_id": "e28ac5a2-35c9-4be8-a553-2f3632c02d2e"
6
  }
src/app/api/deploy-marker/route.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { NextResponse } from 'next/server';
2
 
3
- const deployMarker = {"schema_version":1,"local_sha":"5d089e0e43da0a0478aa6c7f29ffd8e5dc181543","created_at":"2026-07-23T09:45:05.334Z","deploy_id":"a598c41b-0709-4241-9127-e1aa7744e48b"} as const;
4
 
5
  export const dynamic = 'force-dynamic';
6
 
 
1
  import { NextResponse } from 'next/server';
2
 
3
+ const deployMarker = {"schema_version":1,"local_sha":"7db5c07cd25ba69e829f2561814fec0a1a73e7e8","created_at":"2026-07-23T14:28:33.665Z","deploy_id":"e28ac5a2-35c9-4be8-a553-2f3632c02d2e"} as const;
4
 
5
  export const dynamic = 'force-dynamic';
6
 
src/components/password-dialog.test.tsx ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { I18nProvider } from '@/lib/i18n';
2
+ import { renderInClientDom } from '@/test-utils/react-dom';
3
+ import assert from 'node:assert/strict';
4
+ import { after, before, describe, it } from 'node:test';
5
+
6
+ type PasswordDialogComponent = typeof import('./password-dialog').PasswordDialog;
7
+
8
+ let PasswordDialog: PasswordDialogComponent;
9
+ let sharedView: Awaited<ReturnType<typeof renderInClientDom>>;
10
+ let ownerDocument: Document;
11
+
12
+ before(async () => {
13
+ sharedView = await renderInClientDom(<div />);
14
+ ownerDocument = sharedView.container.ownerDocument;
15
+ ({ PasswordDialog } = await import('./password-dialog'));
16
+ });
17
+
18
+ after(async () => {
19
+ await sharedView?.cleanup();
20
+ });
21
+
22
+ describe('PasswordDialog', { concurrency: false }, () => {
23
+ it('associates the access-code input and save action with a native form', async () => {
24
+ await sharedView.render(
25
+ <I18nProvider>
26
+ <PasswordDialog
27
+ isOpen
28
+ onOpenChange={() => {}}
29
+ onSave={() => {}}
30
+ description='Configure the access code for this test.'
31
+ />
32
+ </I18nProvider>
33
+ );
34
+
35
+ const input = ownerDocument.querySelector<HTMLInputElement>('#password-input');
36
+ const form = ownerDocument.querySelector<HTMLFormElement>('form');
37
+ const saveButton = [...ownerDocument.querySelectorAll<HTMLButtonElement>('button')].find(
38
+ (button) => button.textContent === '保存'
39
+ );
40
+
41
+ assert.ok(input, 'missing access code input');
42
+ assert.ok(form, 'missing access code form');
43
+ assert.ok(saveButton, 'missing save button');
44
+ assert.equal(input.form, form);
45
+ assert.equal(saveButton.type, 'submit');
46
+ });
47
+ });
src/components/password-dialog.tsx CHANGED
@@ -40,6 +40,12 @@ export function PasswordDialog({
40
  onOpenChange(false);
41
  };
42
 
 
 
 
 
 
 
43
  const handleDialogClose = (open: boolean) => {
44
  if (!open) {
45
  setCurrentPassword('');
@@ -54,35 +60,31 @@ export function PasswordDialog({
54
  <DialogTitle>{title}</DialogTitle>
55
  {description && <DialogDescription>{description}</DialogDescription>}
56
  </DialogHeader>
57
- <div className='grid gap-4 py-4'>
58
- <div className='grid grid-cols-1 items-center gap-4'>
59
- <Label htmlFor='password-input' className='sr-only'>
60
- {t('password.placeholder')}
61
- </Label>
62
- <Input
63
- ref={inputRef}
64
- id='password-input'
65
- name='password'
66
- type='password'
67
- autoComplete='current-password'
68
- placeholder={t('password.placeholder')}
69
- value={currentPassword}
70
- onChange={(e) => setCurrentPassword(e.target.value)}
71
- className='col-span-1'
72
- onKeyDown={(e) => {
73
- if (e.key === 'Enter' && currentPassword.trim()) {
74
- e.preventDefault();
75
- handleSave();
76
- }
77
- }}
78
- />
79
  </div>
80
- </div>
81
- <DialogFooter>
82
- <Button type='button' onClick={handleSave} disabled={!currentPassword.trim()} className='px-6'>
83
- {t('common.save')}
84
- </Button>
85
- </DialogFooter>
86
  </DialogContent>
87
  </Dialog>
88
  );
 
40
  onOpenChange(false);
41
  };
42
 
43
+ const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
44
+ event.preventDefault();
45
+ if (!currentPassword.trim()) return;
46
+ handleSave();
47
+ };
48
+
49
  const handleDialogClose = (open: boolean) => {
50
  if (!open) {
51
  setCurrentPassword('');
 
60
  <DialogTitle>{title}</DialogTitle>
61
  {description && <DialogDescription>{description}</DialogDescription>}
62
  </DialogHeader>
63
+ <form onSubmit={handleSubmit} className='grid gap-4'>
64
+ <div className='grid gap-4 py-4'>
65
+ <div className='grid grid-cols-1 items-center gap-4'>
66
+ <Label htmlFor='password-input' className='sr-only'>
67
+ {t('password.placeholder')}
68
+ </Label>
69
+ <Input
70
+ ref={inputRef}
71
+ id='password-input'
72
+ name='password'
73
+ type='password'
74
+ autoComplete='current-password'
75
+ placeholder={t('password.placeholder')}
76
+ value={currentPassword}
77
+ onChange={(e) => setCurrentPassword(e.target.value)}
78
+ className='col-span-1'
79
+ />
80
+ </div>
 
 
 
 
81
  </div>
82
+ <DialogFooter>
83
+ <Button type='submit' disabled={!currentPassword.trim()} className='px-6'>
84
+ {t('common.save')}
85
+ </Button>
86
+ </DialogFooter>
87
+ </form>
88
  </DialogContent>
89
  </Dialog>
90
  );