kratadata commited on
Commit
e8ee71c
·
verified ·
1 Parent(s): 0f8b3a0

Upload folder via script

Browse files
Dockerfile CHANGED
@@ -13,10 +13,6 @@ RUN sh /uv-installer.sh && rm /uv-installer.sh
13
  # Ensure the installed binary is on the PATH
14
  ENV PATH="/root/.local/bin:${PATH}"
15
 
16
- # Reproducible venv inside the image (no host .venv symlinks; see .dockerignore)
17
- ENV UV_COMPILE_BYTECODE=1
18
- ENV UV_LINK_MODE=copy
19
-
20
  # Set the work directory inside the container
21
  WORKDIR /app
22
 
@@ -35,5 +31,5 @@ ENV VELAI_PORT=7860
35
 
36
  EXPOSE 7860
37
 
38
- # uv run uses the .venv from `uv sync` above (.dockerignore keeps host .venv out)
39
- CMD ["uv", "run", "--frozen", "--no-dev", "python", "main.py"]
 
13
  # Ensure the installed binary is on the PATH
14
  ENV PATH="/root/.local/bin:${PATH}"
15
 
 
 
 
 
16
  # Set the work directory inside the container
17
  WORKDIR /app
18
 
 
31
 
32
  EXPOSE 7860
33
 
34
+ # Default command to start your app
35
+ CMD ["uv", "run", "--no-sync", "--no-dev", "python", "main.py"]
README.md CHANGED
@@ -37,8 +37,8 @@ VELAI is designed for educators, facilitators, and creators who want a lightweig
37
  - Open the browser at `http://127.0.0.1:7860`. If a password is configured, enter it to access the canvas.
38
 
39
  **Run with Docker**
40
- - Build the image: `docker build -t VELAI .`.
41
- - Start a container: `docker run -p 7860:7860 -e VELAI_APP_PASSWORD=yourpassword -e VELAI_STORAGE_SECRET=changeme -v $(pwd)/.storage:/app/.storage VELAI`.
42
  - Visit `http://127.0.0.1:7860` to begin exploring.
43
 
44
  ## First session walkthrough
 
37
  - Open the browser at `http://127.0.0.1:7860`. If a password is configured, enter it to access the canvas.
38
 
39
  **Run with Docker**
40
+ - Build the image: `docker build -t velai .`.
41
+ - Start a container: `docker run -p 7860:7860 -e VELAI_APP_PASSWORD=yourpassword -e VELAI_STORAGE_SECRET=changeme -e VELAI_ADMIN_PASSWORD=youradminpassword -e FAL_KEY=falapikey -v $(pwd)/.storage:/app/.storage velai`.
42
  - Visit `http://127.0.0.1:7860` to begin exploring.
43
 
44
  ## First session walkthrough
velai/app_context.py CHANGED
@@ -77,10 +77,7 @@ async def get_or_create_app_context(client: Client) -> AppContext:
77
  # init storage
78
  app_storage = DocumentStorage(NiceGuiKeyValueStorage[dict[str, Any]](NiceGuiStorageType.General))
79
  user_storage = DocumentStorage(NiceGuiKeyValueStorage[dict[str, Any]](NiceGuiStorageType.User))
80
- blob_storage: BlobStorage = FileSystemBlobStorage(
81
- user_id=session_id, root_dir=environment.VELAI_BLOB_STORAGE_PATH, document_storage=user_storage
82
- )
83
- async_blob_storage = AsyncBlobStorageAdapter(blob_storage)
84
 
85
  # create and load app config
86
  config = _create_config(app_storage)
@@ -115,10 +112,15 @@ async def current_app_context() -> AppContext:
115
  raise AppContextNotInitializedError("AppContext not initialized") from exc
116
 
117
 
118
- async def app_context_from_request(request: Request) -> AppContext:
119
  session_id = request.session.get("id")
120
  if not session_id:
121
  raise AppContextNotInitializedError("missing session")
 
 
 
 
 
122
 
123
  try:
124
  wrapper = await _app_contexts.get(session_id)
@@ -127,6 +129,33 @@ async def app_context_from_request(request: Request) -> AppContext:
127
  raise AppContextNotInitializedError("AppContext not initialized") from exc
128
 
129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  def register_app_context_cleanup(client: Client) -> None:
131
  session_id = _browser_session_id()
132
 
 
77
  # init storage
78
  app_storage = DocumentStorage(NiceGuiKeyValueStorage[dict[str, Any]](NiceGuiStorageType.General))
79
  user_storage = DocumentStorage(NiceGuiKeyValueStorage[dict[str, Any]](NiceGuiStorageType.User))
80
+ async_blob_storage = create_blob_storage_for_session(session_id)
 
 
 
81
 
82
  # create and load app config
83
  config = _create_config(app_storage)
 
112
  raise AppContextNotInitializedError("AppContext not initialized") from exc
113
 
114
 
115
+ def _session_id_from_request(request: Request) -> str:
116
  session_id = request.session.get("id")
117
  if not session_id:
118
  raise AppContextNotInitializedError("missing session")
119
+ return session_id
120
+
121
+
122
+ async def app_context_from_request(request: Request) -> AppContext:
123
+ session_id = _session_id_from_request(request)
124
 
125
  try:
126
  wrapper = await _app_contexts.get(session_id)
 
129
  raise AppContextNotInitializedError("AppContext not initialized") from exc
130
 
131
 
132
+ def create_blob_storage_for_session(session_id: str) -> AsyncBlobStorageAdapter:
133
+ """Filesystem-backed blob storage for a browser session (no in-memory AppContext required)."""
134
+ user_storage = DocumentStorage(NiceGuiKeyValueStorage[dict[str, Any]](NiceGuiStorageType.User))
135
+ blob_storage: BlobStorage = FileSystemBlobStorage(
136
+ user_id=session_id,
137
+ root_dir=environment.VELAI_BLOB_STORAGE_PATH,
138
+ document_storage=user_storage,
139
+ )
140
+ return AsyncBlobStorageAdapter(blob_storage)
141
+
142
+
143
+ async def blob_storage_from_request(request: Request) -> AsyncBlobStorageAdapter:
144
+ """
145
+ Resolve blob storage for HTTP requests (e.g. <img src="/storage/...">).
146
+
147
+ Image loads are separate HTTP requests and may arrive while the WebSocket is
148
+ reconnecting or on another worker; they must not depend on in-memory AppContext.
149
+ """
150
+ session_id = _session_id_from_request(request)
151
+
152
+ if await _app_contexts.has(session_id):
153
+ wrapper = await _app_contexts.get(session_id)
154
+ return wrapper.context.blob_storage
155
+
156
+ return create_blob_storage_for_session(session_id)
157
+
158
+
159
  def register_app_context_cleanup(client: Client) -> None:
160
  session_id = _browser_session_id()
161
 
velai/session.py CHANGED
@@ -152,9 +152,12 @@ class GraphSession:
152
  return await self.restore_from_server_storage()
153
 
154
  async def save_to_server_storage(self) -> None:
155
- data = self.to_json()
 
 
 
156
 
157
- ctx = await app_context.current_app_context()
158
  ctx.user_storage[GRAPH_STORAGE_KEY] = data
159
 
160
  async def restore_from_server_storage(self) -> bool:
 
152
  return await self.restore_from_server_storage()
153
 
154
  async def save_to_server_storage(self) -> None:
155
+ try:
156
+ ctx = await app_context.current_app_context()
157
+ except app_context.AppContextNotInitializedError:
158
+ return
159
 
160
+ data = self.to_json()
161
  ctx.user_storage[GRAPH_STORAGE_KEY] = data
162
 
163
  async def restore_from_server_storage(self) -> bool:
velai/storage/storage_endpoint.py CHANGED
@@ -65,10 +65,10 @@ def register():
65
 
66
  @app.get(storage_endpoint)
67
  async def download_blob(request: Request, blob_id: str, original_name: str) -> StreamingResponse:
68
- ctx = await app_context.app_context_from_request(request)
69
 
70
  try:
71
- blob = await ctx.blob_storage.require(blob_id)
72
  except KeyError as exc:
73
  raise HTTPException(status_code=404, detail="blob not found") from exc
74
 
 
65
 
66
  @app.get(storage_endpoint)
67
  async def download_blob(request: Request, blob_id: str, original_name: str) -> StreamingResponse:
68
+ blob_storage = await app_context.blob_storage_from_request(request)
69
 
70
  try:
71
+ blob = await blob_storage.require(blob_id)
72
  except KeyError as exc:
73
  raise HTTPException(status_code=404, detail="blob not found") from exc
74