LehongWu commited on
Commit
06e36cd
·
verified ·
1 Parent(s): dc9d288

Upload folder using huggingface_hub

Browse files
.gitignore CHANGED
@@ -1,3 +1,12 @@
1
  examples
2
  output
3
- .venv
 
 
 
 
 
 
 
 
 
 
1
  examples
2
  output
3
+ .venv
4
+ venv
5
+ .env
6
+ .env.*
7
+ !.env.example
8
+ __pycache__/
9
+ *.py[cod]
10
+ *.egg-info/
11
+ .pytest_cache/
12
+ .DS_Store
docs/WEB_DEV_GUIDE.md CHANGED
@@ -31,6 +31,7 @@ Do not commit secrets; use a `.env` with your process manager if you like.
31
  | `WEB_UI_PASSWORD` | Login password. |
32
  | `SESSION_SECRET` | Session signing, e.g. `openssl rand -hex 32`. |
33
  | `GENERATION_OPTIONS_PATH` | Optional. Overrides default `web/config/generation_options.json`. |
 
34
 
35
  ```bash
36
  export GEMINI_API_KEY="your_key"
 
31
  | `WEB_UI_PASSWORD` | Login password. |
32
  | `SESSION_SECRET` | Session signing, e.g. `openssl rand -hex 32`. |
33
  | `GENERATION_OPTIONS_PATH` | Optional. Overrides default `web/config/generation_options.json`. |
34
+ | `SESSION_COOKIE_SECURE` | Optional. Set to `1` / `true` / `yes` to send session cookies with the **Secure** flag (use on real HTTPS, e.g. Hugging Face). Leave unset for `http://127.0.0.1` local dev. Requires proxy to set `X-Forwarded-Proto` (see §10). |
35
 
36
  ```bash
37
  export GEMINI_API_KEY="your_key"
web/backend/main.py CHANGED
@@ -1,5 +1,6 @@
1
  """FastAPI application: session auth, Gemini generation API, static SPA."""
2
 
 
3
  from contextlib import asynccontextmanager
4
  from pathlib import Path
5
 
@@ -22,12 +23,21 @@ async def lifespan(app: FastAPI):
22
 
23
  app = FastAPI(title="Gemini Studio Web", lifespan=lifespan)
24
 
 
 
 
 
 
 
 
 
 
25
  app.add_middleware(
26
  SessionMiddleware,
27
  secret_key=get_session_secret(),
28
  max_age=14 * 24 * 3600,
29
  same_site="lax",
30
- https_only=False,
31
  )
32
 
33
  app.include_router(auth.router)
 
1
  """FastAPI application: session auth, Gemini generation API, static SPA."""
2
 
3
+ import os
4
  from contextlib import asynccontextmanager
5
  from pathlib import Path
6
 
 
23
 
24
  app = FastAPI(title="Gemini Studio Web", lifespan=lifespan)
25
 
26
+ # Session cookie: default allows http://127.0.0.1 local dev. For HTTPS-only deployments
27
+ # where the proxy sets X-Forwarded-Proto (see uvicorn --forwarded-allow-ips), set
28
+ # SESSION_COOKIE_SECURE=1 so browsers get the Secure flag (recommended on HF Spaces).
29
+ _session_secure = os.environ.get("SESSION_COOKIE_SECURE", "").strip().lower() in (
30
+ "1",
31
+ "true",
32
+ "yes",
33
+ )
34
+
35
  app.add_middleware(
36
  SessionMiddleware,
37
  secret_key=get_session_secret(),
38
  max_age=14 * 24 * 3600,
39
  same_site="lax",
40
+ https_only=_session_secure,
41
  )
42
 
43
  app.include_router(auth.router)
web/backend/static/assets/index-Cxt2XIBW.js ADDED
The diff for this file is too large to render. See raw diff
 
web/backend/static/index.html CHANGED
@@ -43,7 +43,7 @@
43
  }
44
  })();
45
  </script>
46
- <script type="module" crossorigin src="/assets/index-Ctfz00qX.js"></script>
47
  <link rel="stylesheet" crossorigin href="/assets/index-BIkuBov8.css">
48
  </head>
49
  <body>
 
43
  }
44
  })();
45
  </script>
46
+ <script type="module" crossorigin src="/assets/index-Cxt2XIBW.js"></script>
47
  <link rel="stylesheet" crossorigin href="/assets/index-BIkuBov8.css">
48
  </head>
49
  <body>
web/frontend/src/context/GenerationOptionsContext.tsx CHANGED
@@ -48,6 +48,10 @@ export function GenerationOptionsProvider({ children }: { children: ReactNode })
48
  let cancelled = false;
49
  fetch("/api/config/generation-options", { credentials: "include" })
50
  .then(async (r) => {
 
 
 
 
51
  const j = await r.json().catch(() => ({}));
52
  if (!r.ok) {
53
  const detail = (j as { detail?: unknown }).detail;
@@ -62,7 +66,8 @@ export function GenerationOptionsProvider({ children }: { children: ReactNode })
62
  return j as GenerationOptions;
63
  })
64
  .then((opts) => {
65
- if (!cancelled) setData(opts);
 
66
  })
67
  .catch((e: unknown) => {
68
  if (!cancelled)
 
48
  let cancelled = false;
49
  fetch("/api/config/generation-options", { credentials: "include" })
50
  .then(async (r) => {
51
+ if (r.status === 401) {
52
+ window.location.assign("/login");
53
+ return null;
54
+ }
55
  const j = await r.json().catch(() => ({}));
56
  if (!r.ok) {
57
  const detail = (j as { detail?: unknown }).detail;
 
66
  return j as GenerationOptions;
67
  })
68
  .then((opts) => {
69
+ if (cancelled || opts === null) return;
70
+ setData(opts);
71
  })
72
  .catch((e: unknown) => {
73
  if (!cancelled)
web/frontend/src/pages/Login.tsx CHANGED
@@ -1,13 +1,23 @@
1
- import { FormEvent, useState } from "react";
2
  import { useNavigate } from "react-router-dom";
3
- import { login } from "../api";
4
 
5
  export function Login() {
6
  const [password, setPassword] = useState("");
7
  const [err, setErr] = useState<string | null>(null);
8
  const [loading, setLoading] = useState(false);
 
9
  const nav = useNavigate();
10
 
 
 
 
 
 
 
 
 
 
11
  async function onSubmit(e: FormEvent) {
12
  e.preventDefault();
13
  setErr(null);
@@ -23,6 +33,14 @@ export function Login() {
23
  }
24
  }
25
 
 
 
 
 
 
 
 
 
26
  return (
27
  <div className="min-h-screen flex items-center justify-center p-6 sm:p-10 bg-gradient-to-br from-paper via-indigo-50/35 to-violet-100/45">
28
  <div className="w-full max-w-md rounded-2xl border border-line/80 bg-white/85 backdrop-blur-sm shadow-xl shadow-indigo-950/10 p-10 sm:p-12">
 
1
+ import { FormEvent, useEffect, useState } from "react";
2
  import { useNavigate } from "react-router-dom";
3
+ import { fetchMe, login } from "../api";
4
 
5
  export function Login() {
6
  const [password, setPassword] = useState("");
7
  const [err, setErr] = useState<string | null>(null);
8
  const [loading, setLoading] = useState(false);
9
+ const [checkingSession, setCheckingSession] = useState(true);
10
  const nav = useNavigate();
11
 
12
+ useEffect(() => {
13
+ fetchMe()
14
+ .then((r) => {
15
+ if (r.authenticated) nav("/image", { replace: true });
16
+ })
17
+ .catch(() => {})
18
+ .finally(() => setCheckingSession(false));
19
+ }, [nav]);
20
+
21
  async function onSubmit(e: FormEvent) {
22
  e.preventDefault();
23
  setErr(null);
 
33
  }
34
  }
35
 
36
+ if (checkingSession) {
37
+ return (
38
+ <div className="min-h-screen flex items-center justify-center text-mist text-sm">
39
+ 请稍候…
40
+ </div>
41
+ );
42
+ }
43
+
44
  return (
45
  <div className="min-h-screen flex items-center justify-center p-6 sm:p-10 bg-gradient-to-br from-paper via-indigo-50/35 to-violet-100/45">
46
  <div className="w-full max-w-md rounded-2xl border border-line/80 bg-white/85 backdrop-blur-sm shadow-xl shadow-indigo-950/10 p-10 sm:p-12">