awilliams88 commited on
Commit
8a039f8
ยท
verified ยท
1 Parent(s): 2786d6b

Upload folder using huggingface_hub

Browse files
.ruff_cache/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Automatically created by ruff.
2
+ *
.ruff_cache/0.15.15/12558576469893395475 ADDED
Binary file (187 Bytes). View file
 
.ruff_cache/CACHEDIR.TAG ADDED
@@ -0,0 +1 @@
 
 
1
+ Signature: 8a477f597d28d172789f06886806bc55
README.md CHANGED
@@ -1,15 +1,84 @@
1
  ---
2
  title: Vessel Studio
3
- emoji: ๐Ÿข
4
- colorFrom: green
5
  colorTo: gray
6
  sdk: gradio
7
- sdk_version: 6.16.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
11
  license: mit
12
- short_description: A local-first Creative Studio and Agentic Assistant for arti
13
  ---
14
 
15
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Vessel Studio
3
+ emoji: ๐Ÿบ
4
+ colorFrom: blue
5
  colorTo: gray
6
  sdk: gradio
7
+ sdk_version: 4.44.0
 
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
+ short_description: Local-first agentic studio for artisans using Gemma 4 12B.
12
  ---
13
 
14
+ # Vessel: A Local-First Agentic Studio for Craft Creators
15
+
16
+ `Vessel` is a local-first creative workspace designed specifically for local artisans and craft creators (e.g., Etsy sellers, local market vendors, hobbyist potters) to manage and automate their creative businesses. By combining local multimodal image analysis, brand-voice copy generation, and a code-executing local agent, Vessel turns raw product photos and simple text instructions into professional product listings, customized social marketing campaigns, invoice databases, and customer emails.
17
+
18
+ This project is built for **Track 1: Backyard AI**, specifically targeting a local artisan ("Clara," a pottery maker) who spends too much time on business admin and copywriting.
19
+
20
+ ---
21
+
22
+ ## ๐Ÿš€ Key Highlights & Merit Badges
23
+
24
+ * **Off the Grid:** Runs entirely offline inside the Space (using local GPU/MPS inference). No external paid APIs are called.
25
+ * **Off-Brand:** Custom glassmorphic dark-mode dashboard using custom CSS/JS.
26
+ * **Sharing is Caring:** Employs Hugging Faceโ€™s `smolagents` library, pushing detailed agent execution traces directly to the Hub.
27
+ * **Field Notes:** A detailed Hugging Face blog post documenting our experience being among the first to deploy and build an agentic, multimodal workspace with the new Gemma 4 12B model.
28
+
29
+ ---
30
+
31
+ ## ๐Ÿ› ๏ธ Tech Stack & Model Configuration
32
+
33
+ * **LLM Engine:** `google/gemma-4-12B-it` (quantized to Q4_K_M GGUF locally, loaded in full precision on Space ZeroGPU).
34
+ * **Frameworks:** Hugging Face `transformers` and `smolagents` (code-writing agent).
35
+ * **Database:** SQLite (`vessel_studio.db`) representing inventory, orders, and customer lists.
36
+ * **Aesthetics:** Custom vanilla CSS/JS injected directly into Gradio 6.0 layout.
37
+
38
+ ---
39
+
40
+ ## ๐Ÿ“ฆ Project Layout
41
+
42
+ ```
43
+ .
44
+ โ”œโ”€โ”€ app.py # Main Gradio application interface
45
+ โ”œโ”€โ”€ agent.py # smolagents CodeAgent and tool definitions
46
+ โ”œโ”€โ”€ models.py # Gemma 4 model loader and inference helper
47
+ โ”œโ”€โ”€ database.py # SQLite connection and seed data
48
+ โ”œโ”€โ”€ style.css # Glassmorphic dark-mode CSS override rules
49
+ โ”œโ”€โ”€ script.js # Javascript helper for clipboard and notifications
50
+ โ”œโ”€โ”€ verify_code.py # Code quality validation script ( Ruff + Pyright )
51
+ โ”œโ”€โ”€ pyrightconfig.json # Exclusion rules for type checker
52
+ โ”œโ”€โ”€ requirements.txt # Dependency lists
53
+ โ””โ”€โ”€ run.sh # Executable shell script with venv auto-activation
54
+ ```
55
+
56
+ ---
57
+
58
+ ## ๐Ÿ”ง Local Installation & Execution
59
+
60
+ ### 1. Prerequisite: Gemma 4 gated model approval
61
+ Visit [https://huggingface.co/google/gemma-4-12B-it](https://huggingface.co/google/gemma-4-12B-it) and accept the license terms.
62
+
63
+ ### 2. Set Up Environment Variables
64
+ Create a `.env` file in the root directory:
65
+ ```env
66
+ HF_TOKEN=your_huggingface_write_token_here
67
+ SPACE_ID=your_username/your_space_name # Required to share agent traces to the Hub
68
+ ```
69
+
70
+ ### 3. Run using the auto-activating wrapper script:
71
+ The `./run.sh` script automatically detects, activates, and configures the python virtual environment:
72
+
73
+ * **To initialize the SQLite Database:**
74
+ ```bash
75
+ ./run.sh db
76
+ ```
77
+ * **To launch the local web server:**
78
+ ```bash
79
+ ./run.sh
80
+ ```
81
+ * **To run formatting, lints, and static type checking:**
82
+ ```bash
83
+ ./run.sh verify
84
+ ```
agent.py ADDED
@@ -0,0 +1,400 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Import os paths, serialization tools, and datetime metadata
2
+ import json
3
+ import os
4
+ from datetime import datetime
5
+
6
+ # Import deep learning acceleration libraries
7
+ import torch
8
+
9
+ # Import Hugging Face API connections and agent frameworks
10
+ from huggingface_hub import HfApi
11
+ from smolagents import CodeAgent, Model, tool
12
+
13
+ # Import query functions from database module
14
+ from database import db_query, db_execute
15
+
16
+
17
+ # Import model loaders and device mapping tools
18
+ from models import load_gemma_model, get_device
19
+
20
+
21
+ # Custom Response Wrapper class matching smolagents Model return contract
22
+ class ChatResponse:
23
+ # Initialize response object with generated text content
24
+ def __init__(self, content):
25
+ self.content = content
26
+
27
+
28
+ # Custom Model Subclass to reuse our warm, preloaded Gemma 4 model instance
29
+ class GemmaLocalModel(Model):
30
+ # Initialize the custom model wrapper
31
+ def __init__(self, model_id="google/gemma-4-12B-it", **kwargs):
32
+ super().__init__(model_id=model_id, **kwargs)
33
+
34
+ # Implement generate to run inference inside agent execution loops
35
+ def generate(self, messages, stop_sequences=None, **kwargs):
36
+ # Retrieve the preloaded model and processor from cache
37
+ model, processor = load_gemma_model()
38
+ # Resolve target device mapping
39
+ device = get_device()
40
+
41
+ # Format prompt messages into the model's native chat syntax
42
+ text_prompt = processor.apply_chat_template(
43
+ messages, add_generation_prompt=True
44
+ )
45
+ # Process and transfer text inputs to the hardware device
46
+ inputs = processor(text=text_prompt, return_tensors="pt").to(device)
47
+
48
+ # Ensure correct datatypes are mapped for visual/multimodal layers
49
+ if "pixel_values" in inputs:
50
+ inputs["pixel_values"] = inputs["pixel_values"].to(model.dtype)
51
+
52
+ # Run text generation under no-gradient constraints
53
+ with torch.no_grad():
54
+ generated_ids = model.generate(
55
+ **inputs, max_new_tokens=1024, temperature=0.2, do_sample=True
56
+ )
57
+
58
+ # Isolate the input token count
59
+ input_len = inputs["input_ids"].shape[1]
60
+ # Decode the output token IDs, skipping prompt tokens
61
+ response_ids = generated_ids[0][input_len:]
62
+ # Convert token IDs back to a string
63
+ response_text = processor.decode(response_ids, skip_special_tokens=True).strip()
64
+
65
+ # Truncate response text if the model outputs any defined stop sequence
66
+ if stop_sequences:
67
+ for stop_seq in stop_sequences:
68
+ if stop_seq in response_text:
69
+ response_text = response_text.split(stop_seq)[0]
70
+
71
+ # Return response wrapped in our ChatResponse object
72
+ return ChatResponse(content=response_text)
73
+
74
+
75
+ # --- Define Agent Tools ---
76
+
77
+
78
+ @tool
79
+ def query_inventory_database(sql_query: str) -> str:
80
+ """
81
+ Executes a read-only SQL SELECT query against the local database and returns results.
82
+ Use this to look up inventory items, orders, or customers.
83
+
84
+ Args:
85
+ sql_query: A valid SQL SELECT query string.
86
+ """
87
+ # Enforce SELECT constraint to block malicious write commands in this tool
88
+ if not sql_query.strip().lower().startswith("select"):
89
+ return "Error: This tool only allows SELECT queries. Use execute_database_command for database modifications."
90
+ # Run the read query against SQLite
91
+ results = db_query(sql_query)
92
+ # Format database rows into a readable JSON string
93
+ return json.dumps(results, indent=2)
94
+
95
+
96
+ @tool
97
+ def execute_database_command(sql_command: str) -> str:
98
+ """
99
+ Executes an INSERT, UPDATE, or DELETE SQL statement against the local database to modify records.
100
+ Use this to record new orders, update stock levels, or update customer shipping addresses.
101
+
102
+ Args:
103
+ sql_command: A valid SQL write command string.
104
+ """
105
+ # Block SELECT statements to ensure clean usage mapping
106
+ if sql_command.strip().lower().startswith("select"):
107
+ return "Error: Use query_inventory_database for SELECT queries."
108
+ # Run database write modification command
109
+ result = db_execute(sql_command)
110
+ return result
111
+
112
+
113
+ @tool
114
+ def generate_invoice_pdf(order_id: int) -> str:
115
+ """
116
+ Generates a professional PDF invoice for a given order ID and saves it locally.
117
+ Returns the file path of the generated PDF.
118
+
119
+ Args:
120
+ order_id: The ID of the order to generate an invoice for.
121
+ """
122
+ # Import ReportLab page sizing utilities
123
+ from reportlab.lib.pagesizes import letter
124
+
125
+ # Import flowable element classes for layout building
126
+ from reportlab.platypus import (
127
+ SimpleDocTemplate,
128
+ Paragraph,
129
+ Spacer,
130
+ Table,
131
+ TableStyle,
132
+ )
133
+
134
+ # Import text style registries
135
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
136
+
137
+ # Import color palettes
138
+ from reportlab.lib import colors
139
+
140
+ # Query order details from database
141
+ order_rows = db_query("SELECT * FROM orders WHERE id = ?", (order_id,))
142
+ # Validate order existence
143
+ if not order_rows or "error" in order_rows[0]:
144
+ return f"Error: Order ID {order_id} not found."
145
+ # Reference target order dictionary
146
+ order = order_rows[0]
147
+
148
+ # Query matching customer details
149
+ customer_rows = db_query(
150
+ "SELECT * FROM customers WHERE id = ?", (order["customer_id"],)
151
+ )
152
+ # Map customer dict, fallback to placeholders if missing
153
+ customer = (
154
+ customer_rows[0]
155
+ if customer_rows
156
+ else {"name": "Unknown", "email": "N/A", "shipping_address": "N/A"}
157
+ )
158
+
159
+ # Query all item rows related to this order
160
+ items = db_query(
161
+ "SELECT order_items.*, inventory.name AS item_name FROM order_items JOIN inventory ON order_items.product_id = inventory.id WHERE order_id = ?",
162
+ (order_id,),
163
+ )
164
+
165
+ # Compile the target PDF filename
166
+ pdf_filename = f"invoice_order_{order_id}.pdf"
167
+ # Resolve absolute path for PDF generation
168
+ pdf_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), pdf_filename)
169
+
170
+ try:
171
+ # Instantiate ReportLab page layout with standard letter sizing
172
+ doc = SimpleDocTemplate(pdf_path, pagesize=letter)
173
+ # Initialize empty document story flow list
174
+ story = []
175
+ # Load sample style dictionary
176
+ styles = getSampleStyleSheet()
177
+
178
+ # Design custom brand-aligned header style
179
+ title_style = ParagraphStyle(
180
+ "InvoiceTitle",
181
+ parent=styles["Heading1"],
182
+ fontName="Helvetica-Bold",
183
+ fontSize=24,
184
+ leading=28,
185
+ textColor=colors.HexColor("#0d9488"), # Teal branding color
186
+ )
187
+
188
+ # Reference normal text style
189
+ normal_style = styles["Normal"]
190
+
191
+ # Append Title to document flow
192
+ story.append(Paragraph("INVOICE", title_style))
193
+ # Append spacing under Title
194
+ story.append(Spacer(1, 15))
195
+
196
+ # Format customer billing info block
197
+ details_text = f"""
198
+ <b>Order ID:</b> {order_id}<br/>
199
+ <b>Date:</b> {order["order_date"]}<br/>
200
+ <b>Status:</b> {order["status"]}<br/>
201
+ <br/>
202
+ <b>Billed To:</b><br/>
203
+ {customer["name"]}<br/>
204
+ Email: {customer["email"]}<br/>
205
+ Address: {customer["shipping_address"]}<br/>
206
+ """
207
+ # Append customer billing details to document flow
208
+ story.append(Paragraph(details_text, normal_style))
209
+ # Append spacing under details
210
+ story.append(Spacer(1, 20))
211
+
212
+ # Setup items list header
213
+ data = [["Item", "Quantity", "Price", "Total"]]
214
+ # Format line items into the grid list
215
+ for item in items:
216
+ qty = item["quantity"]
217
+ price = item["price"]
218
+ total = qty * price
219
+ data.append([item["item_name"], str(qty), f"${price:.2f}", f"${total:.2f}"])
220
+
221
+ # Append final sum indicator row
222
+ data.append(["", "", "Total Due:", f"${order['total_price']:.2f}"])
223
+
224
+ # Create table mapping columns widths
225
+ table = Table(data, colWidths=[250, 70, 90, 90])
226
+ # Apply custom table grids, colors, padding, and font stylings
227
+ table.setStyle(
228
+ TableStyle(
229
+ [
230
+ ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#f3f4f6")),
231
+ ("TEXTCOLOR", (0, 0), (-1, 0), colors.HexColor("#111827")),
232
+ ("ALIGN", (0, 0), (-1, -1), "LEFT"),
233
+ ("BOTTOMPADDING", (0, 0), (-1, 0), 8),
234
+ ("GRID", (0, 0), (-1, -2), 0.5, colors.HexColor("#e5e7eb")),
235
+ ("LINEBELOW", (0, -1), (-1, -1), 1.5, colors.HexColor("#0d9488")),
236
+ ("TOPPADDING", (0, -1), (-1, -1), 8),
237
+ ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
238
+ ]
239
+ )
240
+ )
241
+
242
+ # Append table grid to document flow
243
+ story.append(table)
244
+ # Build the final PDF document on disk
245
+ doc.build(story)
246
+ return f"Success: Invoice PDF generated successfully at {pdf_path}"
247
+ except Exception as e:
248
+ # Catch and report any formatting errors during generation
249
+ return f"Error building PDF: {str(e)}"
250
+
251
+
252
+ @tool
253
+ def draft_customer_email(to_email: str, subject: str, body_content: str) -> str:
254
+ """
255
+ Drafts an email to a customer and saves it locally in a structured text file.
256
+ Returns the file path of the draft email.
257
+
258
+ Args:
259
+ to_email: The customer's email address.
260
+ subject: The subject line of the email.
261
+ body_content: The formatted body content of the email.
262
+ """
263
+ # Create the target email filename
264
+ draft_filename = f"email_draft_{to_email.replace('@', '_at_')}.txt"
265
+ # Resolve absolute path for email generation
266
+ draft_path = os.path.join(
267
+ os.path.dirname(os.path.abspath(__file__)), draft_filename
268
+ )
269
+
270
+ try:
271
+ # Write email text blocks to draft file
272
+ with open(draft_path, "w", encoding="utf-8") as f:
273
+ f.write(f"To: {to_email}\n")
274
+ f.write(f"Subject: {subject}\n")
275
+ f.write(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
276
+ f.write("=" * 40 + "\n\n")
277
+ f.write(body_content)
278
+ f.write("\n\n" + "=" * 40 + "\n")
279
+ f.write("Draft auto-generated by Vessel Studio Client.\n")
280
+
281
+ return f"Success: Email draft saved successfully at {draft_path}"
282
+ except Exception as e:
283
+ # Return error if file write fails
284
+ return f"Error writing email file: {str(e)}"
285
+
286
+
287
+ # --- Trace Pushing Mechanism ---
288
+
289
+
290
+ def share_trace_on_hub(task_id: str, steps: list) -> bool:
291
+ """Serializes the agent execution steps to JSON and pushes it to the Hugging Face Space."""
292
+ # Load Hugging Face authorization credentials from environment
293
+ token = os.getenv("HF_TOKEN")
294
+ # Load Space repository identifier from environment
295
+ repo_id = os.getenv("SPACE_ID")
296
+
297
+ # Resolve trace logging directory path
298
+ trace_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "agent_traces")
299
+ # Create directory if it is absent
300
+ os.makedirs(trace_dir, exist_ok=True)
301
+
302
+ # Format trace log dictionary
303
+ trace_data = {
304
+ "task_id": task_id,
305
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
306
+ "steps": [str(step) for step in steps],
307
+ }
308
+
309
+ # Format trace filename
310
+ filename = f"trace_{task_id}.json"
311
+ # Resolve target file path
312
+ local_path = os.path.join(trace_dir, filename)
313
+
314
+ # Save structured trace log into a JSON file locally
315
+ with open(local_path, "w", encoding="utf-8") as f:
316
+ json.dump(trace_data, f, indent=2)
317
+
318
+ # Push file to Space Hub if credentials and Space target are configured
319
+ if token and token != "hf_YOUR_WRITE_TOKEN_HERE" and repo_id:
320
+ try:
321
+ # Instantiate Hugging Face API client
322
+ api = HfApi(token=token)
323
+ # Upload file directly to the Space repository
324
+ api.upload_file(
325
+ path_or_fileobj=local_path,
326
+ path_in_repo=f"agent_traces/{filename}",
327
+ repo_id=repo_id,
328
+ repo_type="space",
329
+ )
330
+ print(f"Trace pushed to Space repo: {repo_id}/agent_traces/{filename}")
331
+ return True
332
+ except Exception as e:
333
+ # Log failure if upload is blocked
334
+ print(f"Failed to push trace to Hub: {e}")
335
+ return False
336
+
337
+ # Log local fallback
338
+ print("Trace saved locally (SPACE_ID or HF_TOKEN config not found).")
339
+ return False
340
+
341
+
342
+ # --- Core Run Agent Loop ---
343
+
344
+
345
+ def run_agent(task_prompt: str, task_id: str | None = None) -> dict:
346
+ """Instantiates a CodeAgent with our Gemma 4 model and runs the task."""
347
+ # Fallback to dynamic timestamp-based ID if none is supplied
348
+ if task_id is None:
349
+ task_id = f"task_{int(datetime.now().timestamp())}"
350
+
351
+ # Initialize our custom model wrapper
352
+ model_wrapper = GemmaLocalModel()
353
+
354
+ # Define tools list for agent capability mapping
355
+ tools = [
356
+ query_inventory_database,
357
+ execute_database_command,
358
+ generate_invoice_pdf,
359
+ draft_customer_email,
360
+ ]
361
+
362
+ # Instantiate CodeAgent
363
+ agent = CodeAgent(tools=tools, model=model_wrapper, max_steps=5)
364
+
365
+ # Execute target task instruction
366
+ result = agent.run(task_prompt)
367
+
368
+ # Fetch intermediate execution steps from agent memory
369
+ steps = agent.memory.get_full_steps() if hasattr(agent, "memory") else []
370
+
371
+ # Upload logs file to the Hub (Sharing is Caring badge)
372
+ shared = share_trace_on_hub(task_id, steps)
373
+
374
+ return {
375
+ "task_id": task_id,
376
+ "result": result,
377
+ "trace_shared": shared,
378
+ "local_trace_path": os.path.join("agent_traces", f"trace_{task_id}.json"),
379
+ }
380
+
381
+
382
+ # Execute test runs on direct execution
383
+ if __name__ == "__main__":
384
+ # Import database initializer
385
+ import database
386
+
387
+ # Set tables schemas and seed data
388
+ database.initialize_database()
389
+
390
+ # Test query instruction
391
+ test_prompt = "Query the inventory to find all items that have 'rustic' style, and count them."
392
+ try:
393
+ print("Running agent test task...")
394
+ # Run test execution
395
+ agent_out = run_agent(test_prompt, "test_run_01")
396
+ print("\nAgent Output:", agent_out["result"])
397
+ print("Trace Path:", agent_out["local_trace_path"])
398
+ except Exception as e:
399
+ # Log failure
400
+ print("Agent failed to run:", e)
app.py ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Import directory and path utilities
2
+ import os
3
+ import sys
4
+
5
+ # Import Gradio web components
6
+ import gradio as gr
7
+
8
+ # Ensure workspace paths are resolved correctly for package imports
9
+ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
10
+
11
+ # Import database, model, and agent modules
12
+ import agent
13
+ import database
14
+ import models
15
+
16
+ # Initialize SQLite schemas and seed baseline mock records
17
+ database.initialize_database()
18
+
19
+ # Resolve absolute paths to CSS and JS static assets
20
+ CSS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "style.css")
21
+ JS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "script.js")
22
+
23
+ # Read CSS override styles
24
+ with open(CSS_PATH, "r", encoding="utf-8") as f:
25
+ custom_css = f.read()
26
+
27
+ # Read Javascript clipboard hooks
28
+ with open(JS_PATH, "r", encoding="utf-8") as f:
29
+ custom_js = f.read()
30
+
31
+ # --- Backend Ingestion Endpoints ---
32
+
33
+
34
+ def analyze_and_draft_copy(image, custom_notes):
35
+ """Processes product image + custom notes via Gemma 4 to output copy blocks."""
36
+ # Ensure image has been uploaded
37
+ if image is None:
38
+ return ("Please upload an image of the craft item first.", "", "", "", "")
39
+
40
+ # Define system instructions prompting Gemma 4 for structured outputs
41
+ prompt = f"""
42
+ You are the creative coordinator for Clara's artisan design studio.
43
+ Analyze the uploaded image of this hand-made craft product.
44
+
45
+ Custom artisan notes about the item:
46
+ "{custom_notes if custom_notes else "No additional notes provided."}"
47
+
48
+ Based on the image details and notes, generate the following content in a single structured response.
49
+ Format each section exactly as labeled with headings:
50
+
51
+ ---SECTION 1: STRUCTURED ATTRIBUTES---
52
+ Category: [Extracted category, e.g., Mug, Vase, Planter]
53
+ Materials: [List materials, e.g., stoneware clay, blue glaze]
54
+ Colors: [Dominant colors]
55
+ Style: [Esthetic style, e.g., rustic, modern, whimsical]
56
+
57
+ ---SECTION 2: SEO LISTING COPY---
58
+ Title: [A SEO-friendly title under 140 characters]
59
+ Description: [A warm, narrative, story-driven product description detailing the handmade feel. Do NOT use buzzwords like "unmatched", "perfect addition", or "elevate your space".]
60
+ Tags: [13 keywords separated by commas]
61
+
62
+ ---SECTION 3: SOCIAL MARKETING---
63
+ Instagram: [Engaging Instagram caption with calls to action and relevant craft hashtags]
64
+ Pinterest: [SEO-optimized Pinterest description detailing the visuals]
65
+ """
66
+
67
+ try:
68
+ # Load Gemma 4 from cache and run multimodal inference
69
+ response = models.generate_response(prompt, image=image, max_new_tokens=1536)
70
+
71
+ # Initialize fallback values for parsed segments
72
+ attributes = "Not extracted"
73
+ listing_copy = "Not extracted"
74
+ social_copy = "Not extracted"
75
+
76
+ # Split output text blocks using separators
77
+ sections = response.split("---")
78
+ # Extract individual section strings
79
+ for section in sections:
80
+ if "SECTION 1" in section:
81
+ attributes = section.replace(
82
+ "SECTION 1: STRUCTURED ATTRIBUTES---", ""
83
+ ).strip()
84
+ elif "SECTION 2" in section:
85
+ listing_copy = section.replace(
86
+ "SECTION 2: SEO LISTING COPY---", ""
87
+ ).strip()
88
+ elif "SECTION 3" in section:
89
+ social_copy = section.replace(
90
+ "SECTION 3: SOCIAL MARKETING---", ""
91
+ ).strip()
92
+
93
+ return "Success!", attributes, listing_copy, social_copy, response
94
+ except Exception as e:
95
+ # Catch and return error output
96
+ return f"Error during model analysis: {str(e)}", "", "", "", ""
97
+
98
+
99
+ def run_agent_task(prompt):
100
+ """Traces and executes administrative business tasks using smolagents."""
101
+ # Verify input exists
102
+ if not prompt.strip():
103
+ return "Please input a query or task instruction.", "", None, "Trace idle."
104
+
105
+ try:
106
+ # Trigger smolagents CodeAgent execution loop
107
+ res = agent.run_agent(prompt)
108
+
109
+ # Initialize export files collector list
110
+ exported_files = []
111
+ # Get active directory path
112
+ workspace_dir = os.path.dirname(os.path.abspath(__file__))
113
+ # Scan workspace for generated PDF invoices or email text drafts
114
+ for file in os.listdir(workspace_dir):
115
+ if (file.startswith("invoice_order_") and file.endswith(".pdf")) or (
116
+ file.startswith("email_draft_") and file.endswith(".txt")
117
+ ):
118
+ exported_files.append(os.path.join(workspace_dir, file))
119
+
120
+ # Format output strings
121
+ result_text = str(res["result"])
122
+ # Format Hub trace sharing confirmation message
123
+ trace_status = (
124
+ "Trace successfully shared on Hugging Face Hub!"
125
+ if res["trace_shared"]
126
+ else "Trace saved locally in agent_traces/"
127
+ )
128
+
129
+ # Read contents from generated local trace JSON file
130
+ trace_path = os.path.join(workspace_dir, res["local_trace_path"])
131
+ trace_log = ""
132
+ if os.path.exists(trace_path):
133
+ with open(trace_path, "r", encoding="utf-8") as f:
134
+ trace_log = f.read()
135
+
136
+ return (
137
+ result_text,
138
+ trace_log,
139
+ exported_files if exported_files else None,
140
+ trace_status,
141
+ )
142
+ except Exception as e:
143
+ # Return error output if agent crashes
144
+ return f"Error executing agent task: {str(e)}", "", None, "Trace failed."
145
+
146
+
147
+ # --- Gradio UI Layout Building ---
148
+
149
+ # Initialize Blocks with custom browser title
150
+ with gr.Blocks(title="Vessel: Creative Studio Client") as demo:
151
+ # Render main header styling panel
152
+ gr.HTML("""
153
+ <div class="header-row" style="text-align: center; margin-bottom: 2rem;">
154
+ <h1 style="color: #2dd4bf; font-weight: 900; font-size: 2.8rem; margin-bottom: 0.2rem; letter-spacing: 0.05em;">VESSEL</h1>
155
+ <p style="color: #e5e7eb; font-size: 1.15rem; font-weight: 500; max-width: 800px; margin: 0.5rem auto 1rem auto; line-height: 1.6;">
156
+ A Local-First Creative Studio Client and Agentic Assistant for Artisans, automating content curation and studio administration.
157
+ </p>
158
+ <!-- Horizontal highlight pills mapping model specifications -->
159
+ <div class="badge-container">
160
+ <span class="vessel-badge">๐Ÿบ google/gemma-4-12b-it</span>
161
+ <span class="vessel-badge">๐Ÿค– smolagents framework</span>
162
+ <span class="vessel-badge">๐Ÿ’พ local sqlite db</span>
163
+ <span class="vessel-badge">๐ŸŽจ off-brand theme</span>
164
+ <span class="vessel-badge vessel-badge-orange">๐ŸŒ track 1: backyard ai</span>
165
+ <span class="vessel-badge vessel-badge-orange">๐Ÿ”’ off the grid</span>
166
+ </div>
167
+ </div>
168
+ """)
169
+
170
+ # Render collapsible testing instructions accordion
171
+ with gr.Accordion("๐Ÿ“– Quick Start & Testing Instructions", open=False):
172
+ gr.Markdown("""
173
+ ### How to test Vessel Studio:
174
+
175
+ #### 1. Ingest & Copywriting Tab
176
+ * **Objective**: Automatically analyze a raw product photo to generate attributes, descriptions, and social posts.
177
+ * **Action**: Drag and drop a product image (e.g. ceramic mug or plate) into the **Product Canvas** panel, optionally add notes, and click **Ingest & Generate Copy**.
178
+ * **Result**: Gemma 4 extracts materials/styles and writes SEO-optimized listings and captions inside copy-to-clipboard cards.
179
+
180
+ #### 2. Assistant Terminal Tab
181
+ * **Objective**: Query and modify the local database, generate PDF invoices, and draft client emails.
182
+ * **Action**: Try typing one of these mock instructions into the **Task Command** input field and click **Execute Studio Task**:
183
+ * *Type*: `Query the customer list to find John Doe's email and shipping address.`
184
+ * *Type*: `Create a PDF invoice for order #1 and save it.`
185
+ * *Type*: `Draft a thank you email for order #2 to the customer's email address.`
186
+ * *Type*: `Update the status of order #1 in the database to 'Shipped'.`
187
+ * **Result**: The agent writes and runs local Python commands on-the-fly, updates SQLite records, generates files for download, and logs its trace!
188
+ """)
189
+
190
+ # Render primary layout tabs
191
+ with gr.Tabs():
192
+ # Setup Studio Ingest Tab
193
+ with gr.Tab("Studio Ingest"):
194
+ with gr.Row():
195
+ # Setup Ingest canvas column (Left Column)
196
+ with gr.Column(scale=1):
197
+ gr.HTML(
198
+ "<h3 style='color: #2dd4bf; margin-bottom: 1rem;'>Product Canvas</h3>"
199
+ )
200
+ # Upload image container
201
+ product_image = gr.Image(
202
+ type="pil",
203
+ label="Upload Product Photo",
204
+ sources=["upload", "webcam"],
205
+ )
206
+ # Input notes text field
207
+ artisan_notes = gr.Textbox(
208
+ label="Artisan Notes (Optional)",
209
+ placeholder="Add notes on custom dimensions, inspiration, or custom customer stories...",
210
+ lines=3,
211
+ )
212
+ # Submit button
213
+ ingest_btn = gr.Button("Ingest & Generate Copy", variant="primary")
214
+ # Processing feedback textbox
215
+ status_indicator = gr.Textbox(
216
+ label="Ingest Status", interactive=False
217
+ )
218
+
219
+ # Setup Output Canvas column (Right Column)
220
+ with gr.Column(scale=2):
221
+ gr.HTML(
222
+ "<h3 style='color: #2dd4bf; margin-bottom: 1rem;'>Generated Studio Deliverables</h3>"
223
+ )
224
+
225
+ with gr.Tabs():
226
+ # Structured attributes metadata tab
227
+ with gr.Tab("Structured Attributes"):
228
+ attr_output = gr.Textbox(
229
+ label="Visual Metadata", lines=6, interactive=False
230
+ )
231
+ copy_attr_btn = gr.Button(
232
+ "Copy Attributes to Clipboard", variant="secondary"
233
+ )
234
+ # Register JS click event
235
+ copy_attr_btn.click(
236
+ fn=None,
237
+ inputs=[attr_output],
238
+ js="(text) => copyToClipboard(text)",
239
+ )
240
+
241
+ # SEO listings copywriting tab
242
+ with gr.Tab("SEO Listings"):
243
+ listing_output = gr.Textbox(
244
+ label="Shopify / Etsy Listing Draft",
245
+ lines=12,
246
+ interactive=False,
247
+ )
248
+ copy_listing_btn = gr.Button(
249
+ "Copy Listing to Clipboard", variant="secondary"
250
+ )
251
+ # Register JS click event
252
+ copy_listing_btn.click(
253
+ fn=None,
254
+ inputs=[listing_output],
255
+ js="(text) => copyToClipboard(text)",
256
+ )
257
+
258
+ # Social marketing copywriting tab
259
+ with gr.Tab("Social Marketing"):
260
+ social_output = gr.Textbox(
261
+ label="Pinterest & Instagram Drafts",
262
+ lines=10,
263
+ interactive=False,
264
+ )
265
+ copy_social_btn = gr.Button(
266
+ "Copy Marketing Content to Clipboard",
267
+ variant="secondary",
268
+ )
269
+ # Register JS click event
270
+ copy_social_btn.click(
271
+ fn=None,
272
+ inputs=[social_output],
273
+ js="(text) => copyToClipboard(text)",
274
+ )
275
+
276
+ # Raw completion response debugging tab
277
+ with gr.Tab("Raw Response"):
278
+ raw_output = gr.Textbox(
279
+ label="Gemma 4 Raw Output", lines=15, interactive=False
280
+ )
281
+
282
+ # Map the Ingestion trigger to backend analysis logic
283
+ ingest_btn.click(
284
+ fn=analyze_and_draft_copy,
285
+ inputs=[product_image, artisan_notes],
286
+ outputs=[
287
+ status_indicator,
288
+ attr_output,
289
+ listing_output,
290
+ social_output,
291
+ raw_output,
292
+ ],
293
+ )
294
+
295
+ # Setup Assistant Terminal Tab
296
+ with gr.Tab("Assistant Terminal"):
297
+ with gr.Row():
298
+ # Setup Assistant Control Column (Left Column)
299
+ with gr.Column(scale=1):
300
+ gr.HTML(
301
+ "<h3 style='color: #2dd4bf; margin-bottom: 1rem;'>Assistant Control</h3>"
302
+ )
303
+ # Text prompt query input
304
+ agent_prompt = gr.Textbox(
305
+ label="Task Command",
306
+ placeholder="e.g., Query the customer database, find Sarah's email, and compile a PDF invoice for order #1.",
307
+ lines=4,
308
+ )
309
+ # Action execution trigger button
310
+ run_agent_btn = gr.Button("Execute Studio Task", variant="primary")
311
+
312
+ gr.HTML(
313
+ "<h4 style='color: #e5e7eb; margin-top: 2rem; margin-bottom: 0.5rem;'>Exported Documents</h4>"
314
+ )
315
+ # Output file downloader container
316
+ output_files = gr.File(
317
+ label="Download Generated Invoices / Email Text Files",
318
+ file_count="multiple",
319
+ interactive=False,
320
+ )
321
+
322
+ # Setup Agent Workspace Trace Column (Right Column)
323
+ with gr.Column(scale=2):
324
+ gr.HTML(
325
+ "<h3 style='color: #2dd4bf; margin-bottom: 1rem;'>Agent Workspace Trace</h3>"
326
+ )
327
+
328
+ # Wrap terminal text area in a custom window component
329
+ with gr.Column(elem_classes=["terminal-window"]):
330
+ gr.HTML("""
331
+ <div class="terminal-header">
332
+ <span class="dot dot-red"></span>
333
+ <span class="dot dot-yellow"></span>
334
+ <span class="dot dot-green"></span>
335
+ <span class="terminal-title">smolagents@vessel-studio-terminal ~ bash</span>
336
+ </div>
337
+ """)
338
+ # Execution logs console view
339
+ agent_trace_view = gr.Textbox(
340
+ label="Execution Log",
341
+ lines=18,
342
+ interactive=False,
343
+ )
344
+
345
+ # Task execution summary textbox
346
+ agent_result_view = gr.Textbox(
347
+ label="Task Result Summary", lines=3, interactive=False
348
+ )
349
+ # Hub trace upload status feedback label
350
+ trace_upload_status = gr.Label(label="Trace Hub Sharing Status")
351
+
352
+ # Map the Assistant execution trigger to backend agent loop
353
+ run_agent_btn.click(
354
+ fn=run_agent_task,
355
+ inputs=[agent_prompt],
356
+ outputs=[
357
+ agent_result_view,
358
+ agent_trace_view,
359
+ output_files,
360
+ trace_upload_status,
361
+ ],
362
+ )
363
+
364
+ # Run web app server with custom styling layers
365
+ if __name__ == "__main__":
366
+ demo.launch(
367
+ server_name="0.0.0.0",
368
+ server_port=7860,
369
+ css=custom_css,
370
+ js=custom_js,
371
+ )
database.py ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Import native sqlite engine and file path system utilities
2
+ import os
3
+ import sqlite3
4
+ from datetime import datetime
5
+
6
+ # Resolve the absolute path to the local database file inside the project directory
7
+ DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "vessel_studio.db")
8
+
9
+
10
+ def get_connection():
11
+ """Returns a connection to the SQLite database with row factory enabled."""
12
+ # Establish a connection to the SQLite database file
13
+ conn = sqlite3.connect(DB_PATH)
14
+ # Enable dict-like row formatting to access columns by their name keys
15
+ conn.row_factory = sqlite3.Row
16
+ return conn
17
+
18
+
19
+ def db_query(sql_query: str, params: tuple = ()) -> list:
20
+ """Executes a SELECT query and returns the results as a list of dicts."""
21
+ # Retrieve a fresh database connection instance
22
+ conn = get_connection()
23
+ try:
24
+ # Initialize the cursor to execute SQL commands
25
+ cursor = conn.cursor()
26
+ # Execute the SELECT statement with the provided parameter tuple
27
+ cursor.execute(sql_query, params)
28
+ # Fetch all matching database records
29
+ rows = cursor.fetchall()
30
+ # Convert row objects to standard Python dictionaries and return the list
31
+ return [dict(row) for row in rows]
32
+ except Exception as e:
33
+ # Return the error message inside a list to prevent application crashes
34
+ return [{"error": str(e)}]
35
+ finally:
36
+ # Guarantee database connection closure in all execution paths
37
+ conn.close()
38
+
39
+
40
+ def db_execute(sql_command: str, params: tuple = ()) -> str:
41
+ """Executes an INSERT, UPDATE, or DELETE command and commits changes."""
42
+ # Retrieve a fresh database connection instance
43
+ conn = get_connection()
44
+ try:
45
+ # Initialize the cursor to execute SQL commands
46
+ cursor = conn.cursor()
47
+ # Execute the database modification statement
48
+ cursor.execute(sql_command, params)
49
+ # Commit the modification transaction to write changes to disk
50
+ conn.commit()
51
+ # Capture the primary key of the last inserted row
52
+ last_row_id = cursor.lastrowid
53
+ # Calculate the total number of rows modified by the command
54
+ changes = conn.total_changes
55
+ return f"Success. Row ID affected/inserted: {last_row_id}. Rows changed: {changes}."
56
+ except Exception as e:
57
+ # Return a descriptive error message if execution fails
58
+ return f"Error executing command: {str(e)}"
59
+ finally:
60
+ # Guarantee database connection closure in all execution paths
61
+ conn.close()
62
+
63
+
64
+ def initialize_database():
65
+ """Creates the SQLite database tables and populates them with initial mock data if empty."""
66
+ # Open connection and get cursor
67
+ conn = get_connection()
68
+ cursor = conn.cursor()
69
+
70
+ # Create the customer profile registry table
71
+ cursor.execute("""
72
+ CREATE TABLE IF NOT EXISTS customers (
73
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
74
+ name TEXT NOT NULL,
75
+ email TEXT UNIQUE NOT NULL,
76
+ shipping_address TEXT
77
+ )
78
+ """)
79
+
80
+ # Create the inventory and product stock details table
81
+ cursor.execute("""
82
+ CREATE TABLE IF NOT EXISTS inventory (
83
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
84
+ name TEXT NOT NULL,
85
+ description TEXT,
86
+ price REAL NOT NULL,
87
+ stock INTEGER NOT NULL DEFAULT 0,
88
+ materials TEXT,
89
+ style TEXT
90
+ )
91
+ """)
92
+
93
+ # Create the customer orders transaction header table
94
+ cursor.execute("""
95
+ CREATE TABLE IF NOT EXISTS orders (
96
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
97
+ customer_id INTEGER NOT NULL,
98
+ order_date TEXT NOT NULL,
99
+ status TEXT NOT NULL DEFAULT 'Pending',
100
+ total_price REAL,
101
+ FOREIGN KEY (customer_id) REFERENCES customers (id)
102
+ )
103
+ """)
104
+
105
+ # Create the line item association details table for orders
106
+ cursor.execute("""
107
+ CREATE TABLE IF NOT EXISTS order_items (
108
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
109
+ order_id INTEGER NOT NULL,
110
+ product_id INTEGER NOT NULL,
111
+ quantity INTEGER NOT NULL,
112
+ price REAL NOT NULL,
113
+ FOREIGN KEY (order_id) REFERENCES orders (id),
114
+ FOREIGN KEY (product_id) REFERENCES inventory (id)
115
+ )
116
+ """)
117
+
118
+ # Commit structural tables creation
119
+ conn.commit()
120
+
121
+ # Query if customers already exist to skip seeding if data is present
122
+ cursor.execute("SELECT COUNT(*) FROM customers")
123
+ if cursor.fetchone()[0] == 0:
124
+ # Seed customer database rows
125
+ customers_data = [
126
+ (
127
+ "Sarah Jenkins",
128
+ "sarah.j@example.com",
129
+ "123 Maple Street, Portland, OR 97201",
130
+ ),
131
+ ("John Doe", "johndoe@example.com", "456 Oak Avenue, Seattle, WA 98101"),
132
+ (
133
+ "Emma Watson",
134
+ "emma@example.com",
135
+ "789 Pine Lane, San Francisco, CA 94103",
136
+ ),
137
+ ]
138
+ # Insert customer profiles in bulk
139
+ cursor.executemany(
140
+ "INSERT INTO customers (name, email, shipping_address) VALUES (?, ?, ?)",
141
+ customers_data,
142
+ )
143
+
144
+ # Seed inventory database items
145
+ inventory_data = [
146
+ (
147
+ "Rustic Teal Stoneware Mug",
148
+ "Hand-thrown ceramic mug with a beautiful teal drip glaze. Holds 12oz.",
149
+ 24.00,
150
+ 15,
151
+ "stoneware clay, teal glaze",
152
+ "rustic",
153
+ ),
154
+ (
155
+ "Minimalist White Vase",
156
+ "Stoneware bud vase with a matte white finish. Ideal for single stems.",
157
+ 38.00,
158
+ 8,
159
+ "ceramic clay, matte white glaze",
160
+ "minimalist",
161
+ ),
162
+ (
163
+ "Whimsical Forest Spoon Rest",
164
+ "Playful ceramic spoon rest stamped with leaf impressions and finished in olive green.",
165
+ 18.00,
166
+ 12,
167
+ "earth clay, olive green glaze",
168
+ "whimsical",
169
+ ),
170
+ (
171
+ "Terracotta Planter",
172
+ "Raw terracotta plant pot with matching saucer. Breathable clay base.",
173
+ 28.00,
174
+ 20,
175
+ "terracotta clay",
176
+ "rustic",
177
+ ),
178
+ ]
179
+ # Insert product specifications in bulk
180
+ cursor.executemany(
181
+ "INSERT INTO inventory (name, description, price, stock, materials, style) VALUES (?, ?, ?, ?, ?, ?)",
182
+ inventory_data,
183
+ )
184
+
185
+ # Create initial pending order transaction for customer Sarah Jenkins
186
+ cursor.execute(
187
+ "INSERT INTO orders (customer_id, order_date, status, total_price) VALUES (1, ?, 'Pending', 24.00)",
188
+ (datetime.now().strftime("%Y-%m-%d"),),
189
+ )
190
+ # Capture the ID of the newly generated order
191
+ order_id = cursor.lastrowid
192
+ # Add order line item mapping order to the Teal Stoneware Mug
193
+ cursor.execute(
194
+ "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, 1, 1, 24.00)",
195
+ (order_id,),
196
+ )
197
+
198
+ # Create second completed order transaction for customer Emma Watson
199
+ cursor.execute(
200
+ "INSERT INTO orders (customer_id, order_date, status, total_price) VALUES (3, ?, 'Shipped', 56.00)",
201
+ (datetime.now().strftime("%Y-%m-%d"),),
202
+ )
203
+ # Capture the ID of the second order
204
+ order_id = cursor.lastrowid
205
+ # Add multiple order line items in bulk
206
+ cursor.executemany(
207
+ "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)",
208
+ [(order_id, 2, 1, 38.00), (order_id, 3, 1, 18.00)],
209
+ )
210
+
211
+ # Save all seeded records transaction to disk
212
+ conn.commit()
213
+
214
+ # Close connection
215
+ conn.close()
216
+ print("Database initialized successfully.")
217
+
218
+
219
+ # Initialize SQLite tables on direct script execution
220
+ if __name__ == "__main__":
221
+ initialize_database()
models.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Import environment loaders and deep learning frameworks
2
+ import os
3
+ from dotenv import load_dotenv
4
+ from PIL import Image
5
+ import torch
6
+ from transformers import AutoModelForMultimodalLM, AutoProcessor
7
+
8
+ # Load token variables from local environment file
9
+ load_dotenv()
10
+
11
+ # Specify the Hugging Face hub repository target for Gemma 4
12
+ MODEL_ID = "google/gemma-4-12B-it"
13
+
14
+ # Initialize global cache elements to keep load states persistent
15
+ _model = None
16
+ _processor = None
17
+ _device = None
18
+
19
+
20
+ def get_device():
21
+ """Determines and returns the best available device."""
22
+ global _device
23
+ # Return device if already determined and cached
24
+ if _device is not None:
25
+ return _device
26
+
27
+ # Use NVIDIA GPU if CUDA framework is active
28
+ if torch.cuda.is_available():
29
+ _device = "cuda"
30
+ # Use Metal Performance Shaders if running on Apple Silicon
31
+ elif torch.backends.mps.is_available():
32
+ _device = "mps"
33
+ # Fall back to standard CPU processing if no accelerators are present
34
+ else:
35
+ _device = "cpu"
36
+ return _device
37
+
38
+
39
+ def load_gemma_model():
40
+ """Loads and caches the Gemma 4 12B-it model and processor."""
41
+ global _model, _processor
42
+ # Avoid reload if model and processor are already warm in memory
43
+ if _model is not None and _processor is not None:
44
+ return _model, _processor
45
+
46
+ # Retrieve Hugging Face authentication token from environment
47
+ token = os.getenv("HF_TOKEN")
48
+ # Verify the token is present and not set to default template value
49
+ if not token or token == "hf_YOUR_WRITE_TOKEN_HERE":
50
+ raise ValueError(
51
+ "HF_TOKEN not set or invalid in the .env file. Please add your Hugging Face write token."
52
+ )
53
+
54
+ # Detect the active target execution hardware
55
+ device = get_device()
56
+ print(f"Loading {MODEL_ID} on device: {device}...")
57
+
58
+ # Download and instantiate the native multimodal processor config
59
+ _processor = AutoProcessor.from_pretrained(MODEL_ID, token=token)
60
+
61
+ # Initialize model options depending on device classification
62
+ if device == "cuda":
63
+ # Load in half-precision bfloat16 on server CUDA hardware
64
+ _model = AutoModelForMultimodalLM.from_pretrained(
65
+ MODEL_ID, dtype=torch.bfloat16, device_map="auto", token=token
66
+ )
67
+ elif device == "mps":
68
+ # Load in half-precision bfloat16 using CPU low memory options on local Mac
69
+ _model = AutoModelForMultimodalLM.from_pretrained(
70
+ MODEL_ID,
71
+ dtype=torch.bfloat16,
72
+ low_cpu_mem_usage=True,
73
+ device_map="auto",
74
+ token=token,
75
+ )
76
+ else:
77
+ # Load in standard 32-bit floating point precision on standard CPU
78
+ _model = AutoModelForMultimodalLM.from_pretrained(
79
+ MODEL_ID,
80
+ dtype=torch.float32,
81
+ low_cpu_mem_usage=True,
82
+ device_map="cpu",
83
+ token=token,
84
+ )
85
+
86
+ # Print success log when model initialization is finished
87
+ print("Model loaded successfully.")
88
+ return _model, _processor
89
+
90
+
91
+ def generate_response(
92
+ prompt: str,
93
+ image: Image.Image | None = None,
94
+ max_new_tokens: int = 1024,
95
+ temperature: float = 0.4,
96
+ ) -> str:
97
+ """Generates a text response from Gemma 4 given a prompt and optional image."""
98
+ # Retrieve the model and processor from cache
99
+ model, processor = load_gemma_model()
100
+ # Resolve the active hardware device
101
+ device = get_device()
102
+
103
+ # Initialize message list
104
+ content = []
105
+ # Append the image item if it is provided
106
+ if image is not None:
107
+ content.append({"type": "image", "image": image})
108
+ # Append the text prompt item
109
+ content.append({"type": "text", "text": prompt})
110
+
111
+ # Wrap the contents inside a user role dict structure
112
+ messages = [{"role": "user", "content": content}]
113
+
114
+ # Format user inputs into the model's native chat syntax
115
+ text_prompt = processor.apply_chat_template(messages, add_generation_prompt=True)
116
+
117
+ # Run tokenization with the image if present
118
+ if image is not None:
119
+ inputs = processor(text=text_prompt, images=image, return_tensors="pt")
120
+ # Run tokenization with text-only parameters if image is absent
121
+ else:
122
+ inputs = processor(text=text_prompt, return_tensors="pt")
123
+
124
+ # Shift all input tensors to the target hardware device
125
+ inputs = {k: v.to(device) for k, v in inputs.items()}
126
+ # Convert visual features to match the exact data type of the model weights
127
+ if "pixel_values" in inputs:
128
+ inputs["pixel_values"] = inputs["pixel_values"].to(model.dtype)
129
+ # Convert audio features to match the model weights dtype
130
+ if "audio_values" in inputs:
131
+ inputs["audio_values"] = inputs["audio_values"].to(model.dtype)
132
+
133
+ # Generate text completions using no-gradient evaluation
134
+ with torch.no_grad():
135
+ generated_ids = model.generate(
136
+ **inputs,
137
+ max_new_tokens=max_new_tokens,
138
+ temperature=temperature,
139
+ do_sample=True if temperature > 0.0 else False,
140
+ )
141
+
142
+ # Extract the length of the input sequence to isolate the output completion
143
+ input_len = inputs["input_ids"].shape[1]
144
+ # Decode the newly generated token IDs, discarding the prompt tokens
145
+ response_ids = generated_ids[0][input_len:]
146
+ # Convert token IDs back to a readable text string
147
+ response_text = processor.decode(response_ids, skip_special_tokens=True)
148
+ return response_text.strip()
149
+
150
+
151
+ # Test loading mechanism when running the script directly
152
+ if __name__ == "__main__":
153
+ try:
154
+ # Load the model and print the resolved hardware configuration
155
+ model, processor = load_gemma_model()
156
+ print(
157
+ "Device map:",
158
+ model.hf_device_map if hasattr(model, "hf_device_map") else "CPU",
159
+ )
160
+ except Exception as e:
161
+ # Print failure trace
162
+ print("Error during test loading:", e)
pyrightconfig.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "exclude": [
3
+ "**/node_modules",
4
+ "**/__pycache__",
5
+ ".venv"
6
+ ],
7
+ "reportMissingImports": true,
8
+ "typeCheckingMode": "basic"
9
+ }
run.sh ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Vessel Auto-activating Runner Script
3
+
4
+ # Resolve directory of this script
5
+ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6
+ cd "$DIR"
7
+
8
+ # Automatically activate virtual environment if it exists
9
+ if [ -d ".venv" ]; then
10
+ source .venv/bin/activate
11
+ else
12
+ echo "โš ๏ธ Warning: Local python virtual environment (.venv) not found."
13
+ echo "Please run: python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt"
14
+ fi
15
+
16
+ # Determine script target (default to app.py)
17
+ TARGET="${1:-app.py}"
18
+
19
+ if [ "$TARGET" == "verify" ]; then
20
+ python3 verify_code.py "${@:2}"
21
+ elif [ "$TARGET" == "db" ]; then
22
+ python3 database.py "${@:2}"
23
+ elif [ -f "$TARGET" ]; then
24
+ python3 "$TARGET" "${@:2}"
25
+ else
26
+ echo "โŒ Error: Target script '$TARGET' not found."
27
+ echo "Usage:"
28
+ echo " ./run.sh # Launch the main Gradio application"
29
+ echo " ./run.sh verify # Run code format, lint, and type checks"
30
+ echo " ./run.sh db # Initialize/seed SQLite database"
31
+ fi
script.js ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Vessel Studio Frontend Javascript Utilities
2
+
3
+ /**
4
+ * Copies the provided string content to the system clipboard and displays a visual toast.
5
+ * @param {string} text - Content to copy.
6
+ */
7
+ function copyToClipboard(text) {
8
+ if (!text) return;
9
+ navigator.clipboard.writeText(text).then(function() {
10
+ showVesselToast("Copied to clipboard!");
11
+ }, function(err) {
12
+ console.error('Could not copy text: ', err);
13
+ });
14
+ }
15
+
16
+ /**
17
+ * Creates and displays a premium animated toast notification.
18
+ * @param {string} message - Content message to display.
19
+ */
20
+ function showVesselToast(message) {
21
+ // Delete existing toast if active to prevent overlapping
22
+ let existingToast = document.querySelector(".vessel-toast");
23
+ if (existingToast) {
24
+ document.body.removeChild(existingToast);
25
+ }
26
+
27
+ let toast = document.createElement("div");
28
+ toast.className = "vessel-toast";
29
+ toast.innerText = message;
30
+
31
+ // Apply styling parameters
32
+ Object.assign(toast.style, {
33
+ position: "fixed",
34
+ bottom: "30px",
35
+ right: "30px",
36
+ backgroundColor: "#0d9488",
37
+ color: "#ffffff",
38
+ padding: "14px 24px",
39
+ borderRadius: "8px",
40
+ boxShadow: "0 10px 25px -5px rgba(13, 148, 136, 0.4)",
41
+ zIndex: "99999",
42
+ opacity: "0",
43
+ transition: "opacity 0.25s ease, transform 0.25s ease",
44
+ transform: "translateY(15px)",
45
+ fontFamily: "'Plus Jakarta Sans', system-ui, sans-serif",
46
+ fontSize: "0.9rem",
47
+ fontWeight: "600",
48
+ letterSpacing: "0.02em"
49
+ });
50
+
51
+ document.body.appendChild(toast);
52
+
53
+ // Trigger entrance animation
54
+ setTimeout(() => {
55
+ toast.style.opacity = "1";
56
+ toast.style.transform = "translateY(0)";
57
+ }, 30);
58
+
59
+ // Trigger dismissal animation
60
+ setTimeout(() => {
61
+ toast.style.opacity = "0";
62
+ toast.style.transform = "translateY(15px)";
63
+ setTimeout(() => {
64
+ if (toast.parentNode) {
65
+ document.body.removeChild(toast);
66
+ }
67
+ }, 250);
68
+ }, 2800);
69
+ }
70
+
71
+ // Initialize custom event integrations when Gradio loads
72
+ document.addEventListener("DOMContentLoaded", () => {
73
+ console.log("Vessel Studio frontend initialized successfully.");
74
+ });
style.css ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Vessel Studio Premium Glassmorphic Theme */
2
+
3
+ @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap');
4
+
5
+ /* Global Reset & Font Mapping */
6
+ body, .gradio-container {
7
+ font-family: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif !important;
8
+ background: radial-gradient(circle at top left, #0d1e2d 0%, #090d16 100%) !important;
9
+ color: #f3f4f6 !important;
10
+ margin: 0;
11
+ padding: 0;
12
+ min-height: 100vh;
13
+ display: flex !important;
14
+ justify-content: center !important;
15
+ }
16
+
17
+ /* Custom Scrollbar styling */
18
+ ::-webkit-scrollbar {
19
+ width: 8px;
20
+ height: 8px;
21
+ }
22
+ ::-webkit-scrollbar-track {
23
+ background: rgba(17, 24, 39, 0.3);
24
+ }
25
+ ::-webkit-scrollbar-thumb {
26
+ background: rgba(13, 148, 136, 0.4);
27
+ border-radius: 4px;
28
+ }
29
+ ::-webkit-scrollbar-thumb:hover {
30
+ background: rgba(13, 148, 136, 0.6);
31
+ }
32
+
33
+ /* Glassmorphic Container Overrides */
34
+ .gradio-container {
35
+ width: 100% !important;
36
+ padding: 2rem !important;
37
+ }
38
+
39
+ /* Header customization */
40
+ header, .header-row {
41
+ margin-bottom: 2rem;
42
+ text-align: center;
43
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
44
+ padding-bottom: 1.5rem;
45
+ }
46
+
47
+ /* Card and Column Styling */
48
+ .gr-box, .gr-padded, .gr-panel, .block {
49
+ background: rgba(17, 24, 39, 0.65) !important;
50
+ backdrop-filter: blur(16px) !important;
51
+ -webkit-backdrop-filter: blur(16px) !important;
52
+ border: 1px solid rgba(255, 255, 255, 0.08) !important;
53
+ border-radius: 16px !important;
54
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3) !important;
55
+ padding: 1.5rem !important;
56
+ margin-bottom: 1rem;
57
+ transition: border-color 0.3s, box-shadow 0.3s;
58
+ }
59
+
60
+ .block:hover {
61
+ border-color: rgba(13, 148, 136, 0.3) !important;
62
+ box-shadow: 0 8px 32px 0 rgba(13, 148, 136, 0.05) !important;
63
+ }
64
+
65
+ /* Inputs and Forms */
66
+ input, textarea, select {
67
+ background-color: rgba(31, 41, 55, 0.5) !important;
68
+ border: 1px solid rgba(255, 255, 255, 0.08) !important;
69
+ color: #f9fafb !important;
70
+ border-radius: 8px !important;
71
+ padding: 10px 14px !important;
72
+ font-size: 0.95rem !important;
73
+ transition: all 0.2s ease-in-out !important;
74
+ }
75
+
76
+ input:focus, textarea:focus, select:focus {
77
+ border-color: #0d9488 !important;
78
+ box-shadow: 0 0 0 2px rgba(13, 148, 136, 0.25) !important;
79
+ background-color: rgba(31, 41, 55, 0.8) !important;
80
+ outline: none !important;
81
+ }
82
+
83
+ /* Form labels */
84
+ .block span, label {
85
+ color: #9ca3af !important;
86
+ font-weight: 500 !important;
87
+ font-size: 0.85rem !important;
88
+ text-transform: uppercase;
89
+ letter-spacing: 0.05em;
90
+ margin-bottom: 4px;
91
+ }
92
+
93
+ /* Tab Headers */
94
+ .tabs {
95
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08) !important;
96
+ margin-bottom: 1.5rem !important;
97
+ }
98
+
99
+ .tab-nav button {
100
+ color: #9ca3af !important;
101
+ border-bottom: 2px solid transparent !important;
102
+ font-weight: 600 !important;
103
+ padding: 12px 20px !important;
104
+ transition: all 0.3s !important;
105
+ }
106
+
107
+ .tab-nav button.selected {
108
+ color: #2dd4bf !important;
109
+ border-bottom: 2px solid #0d9488 !important;
110
+ background: transparent !important;
111
+ }
112
+
113
+ .tab-nav button:hover:not(.selected) {
114
+ color: #f3f4f6 !important;
115
+ border-bottom: 2px solid rgba(13, 148, 136, 0.3) !important;
116
+ }
117
+
118
+ /* Buttons Styling */
119
+ button.primary {
120
+ background: linear-gradient(135deg, #0d9488 0%, #0f766e 100%) !important;
121
+ color: #ffffff !important;
122
+ border: none !important;
123
+ font-weight: 600 !important;
124
+ border-radius: 8px !important;
125
+ padding: 12px 24px !important;
126
+ cursor: pointer !important;
127
+ box-shadow: 0 4px 14px 0 rgba(13, 148, 136, 0.3) !important;
128
+ transition: all 0.2s ease-in-out !important;
129
+ }
130
+
131
+ button.primary:hover {
132
+ transform: translateY(-1px) !important;
133
+ box-shadow: 0 6px 20px 0 rgba(13, 148, 136, 0.5) !important;
134
+ background: linear-gradient(135deg, #0f766e 0%, #115e59 100%) !important;
135
+ }
136
+
137
+ button.primary:active {
138
+ transform: translateY(1px) !important;
139
+ }
140
+
141
+ button.secondary, button:not(.primary) {
142
+ background: rgba(31, 41, 55, 0.5) !important;
143
+ color: #e5e7eb !important;
144
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
145
+ font-weight: 500 !important;
146
+ border-radius: 8px !important;
147
+ padding: 12px 24px !important;
148
+ transition: all 0.2s !important;
149
+ }
150
+
151
+ button:not(.primary):hover {
152
+ background: rgba(55, 65, 81, 0.7) !important;
153
+ border-color: rgba(255, 255, 255, 0.2) !important;
154
+ }
155
+
156
+ /* Visual Studio Client Terminal (Agent panel) */
157
+ .terminal-window {
158
+ background: #05070c !important;
159
+ border: 1px solid rgba(13, 148, 136, 0.25) !important;
160
+ border-radius: 12px !important;
161
+ padding: 1rem !important;
162
+ font-family: 'Fira Code', 'Courier New', Courier, monospace !important;
163
+ box-shadow: inset 0 0 10px rgba(0,0,0,0.8), 0 4px 20px rgba(0,0,0,0.5) !important;
164
+ }
165
+
166
+ .terminal-header {
167
+ display: flex;
168
+ align-items: center;
169
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
170
+ padding-bottom: 8px;
171
+ margin-bottom: 10px;
172
+ }
173
+
174
+ .dot {
175
+ width: 10px;
176
+ height: 10px;
177
+ border-radius: 50%;
178
+ margin-right: 6px;
179
+ display: inline-block;
180
+ }
181
+
182
+ .dot-red { background-color: #ef4444; }
183
+ .dot-yellow { background-color: #f59e0b; }
184
+ .dot-green { background-color: #10b981; }
185
+
186
+ .terminal-title {
187
+ color: #6b7280;
188
+ font-size: 0.75rem;
189
+ font-weight: 600;
190
+ margin-left: 8px;
191
+ letter-spacing: 0.05em;
192
+ }
193
+
194
+ /* Copy Card Widget styling */
195
+ .copy-card {
196
+ border-left: 4px solid #0d9488 !important;
197
+ background: rgba(13, 148, 136, 0.05) !important;
198
+ }
199
+
200
+ /* Hide standard Gradio footer for a clean product feel */
201
+ footer {
202
+ display: none !important;
203
+ }
204
+
205
+ /* Progress indicator override */
206
+ .generating {
207
+ border-color: #0d9488 !important;
208
+ animation: pulse 1.5s infinite;
209
+ }
210
+
211
+ @keyframes pulse {
212
+ 0% { opacity: 0.6; }
213
+ 50% { opacity: 1; }
214
+ 100% { opacity: 0.6; }
215
+ }
216
+
217
+ /* Custom UI Badges & Pills styling */
218
+ .badge-container {
219
+ display: flex;
220
+ flex-wrap: wrap;
221
+ justify-content: center;
222
+ gap: 8px;
223
+ margin-top: 14px;
224
+ margin-bottom: 8px;
225
+ }
226
+
227
+ .vessel-badge {
228
+ background: rgba(13, 148, 136, 0.15) !important;
229
+ border: 1px solid rgba(13, 148, 136, 0.3) !important;
230
+ color: #2dd4bf !important;
231
+ padding: 5px 14px !important;
232
+ border-radius: 9999px !important;
233
+ font-size: 0.75rem !important;
234
+ font-weight: 600 !important;
235
+ letter-spacing: 0.05em !important;
236
+ text-transform: uppercase;
237
+ }
238
+
239
+ .vessel-badge-orange {
240
+ background: rgba(249, 115, 22, 0.12) !important;
241
+ border: 1px solid rgba(249, 115, 22, 0.25) !important;
242
+ color: #fb923c !important;
243
+ }
244
+
245
+ /* Accordion instruction block overrides */
246
+ .instruction-box {
247
+ margin-top: 1rem !important;
248
+ border: 1px solid rgba(255, 255, 255, 0.06) !important;
249
+ border-radius: 12px !important;
250
+ background: rgba(17, 24, 39, 0.4) !important;
251
+ }
252
+
verify_code.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Import standard OS path features
2
+ import os
3
+
4
+ # Import sys to handle runtime exits
5
+ import sys
6
+
7
+ # Import command-line argument parser utilities
8
+ import argparse
9
+
10
+ # Import subprocess to execute linter binaries
11
+ import subprocess
12
+
13
+
14
+ def run_command(command: list, description: str) -> bool:
15
+ """Runs a shell command and displays output with color markers."""
16
+ # Display the active task title
17
+ print(f"\n=== Running {description} ===")
18
+ # Print the exact command list parsed into a string
19
+ print(f"Command: {' '.join(command)}")
20
+
21
+ try:
22
+ # Run binary in subprocess, capturing output text blocks
23
+ result = subprocess.run(command, capture_output=True, text=True)
24
+ # Check if the execution returned exit code zero (success)
25
+ if result.returncode == 0:
26
+ print("โœ… SUCCESS")
27
+ # Print standard output if text is present
28
+ if result.stdout.strip():
29
+ print(result.stdout.strip())
30
+ return True
31
+ # Handle execution failure (non-zero exit code)
32
+ else:
33
+ print("โŒ FAILED")
34
+ # Print output logs to support troubleshooting
35
+ if result.stdout.strip():
36
+ print("\nStdout:")
37
+ print(result.stdout.strip())
38
+ # Print error logs if present
39
+ if result.stderr.strip():
40
+ print("\nStderr:")
41
+ print(result.stderr.strip())
42
+ return False
43
+ except FileNotFoundError:
44
+ # Log failure if linter binary cannot be found in virtual env
45
+ print(
46
+ f"โŒ ERROR: Command '{command[0]}' not found. Make sure dependencies are installed in your virtual environment."
47
+ )
48
+ return False
49
+
50
+
51
+ def main():
52
+ # Setup CLI argument parser
53
+ parser = argparse.ArgumentParser(
54
+ description="Code hygiene and quality verification pipeline."
55
+ )
56
+ # Register the auto-fix parameter flag
57
+ parser.add_argument(
58
+ "--fix",
59
+ "-f",
60
+ action="store_true",
61
+ help="Attempt to auto-format files and auto-fix linter issues.",
62
+ )
63
+ # Parse incoming CLI command arguments
64
+ args = parser.parse_args()
65
+
66
+ # Resolve the absolute workspace directory of this checker script
67
+ workspace_dir = os.path.dirname(os.path.abspath(__file__))
68
+ # Shift current working directory to workspace base
69
+ os.chdir(workspace_dir)
70
+
71
+ # Resolve virtual environment bin executable directory path
72
+ venv_bin = os.path.join(workspace_dir, ".venv", "bin")
73
+ # Prepend virtual environment bin path to PATH to locate local ruff/pyright binaries
74
+ if os.path.exists(venv_bin):
75
+ os.environ["PATH"] = venv_bin + os.pathsep + os.environ.get("PATH", "")
76
+
77
+ # Execute auto-fixing parameters if flag is parsed
78
+ if args.fix:
79
+ print("๐Ÿ”ง Attempting to auto-correct issues...")
80
+ # Execute Ruff formatting in write mode
81
+ fmt_passed = run_command(
82
+ ["ruff", "format", "."], "Ruff Code Formatting (Auto-fixing)"
83
+ )
84
+ # Execute Ruff linter in auto-fix write mode
85
+ lint_passed = run_command(
86
+ ["ruff", "check", "--fix", "."], "Ruff Linter (Auto-fixing)"
87
+ )
88
+ # Execute default validation parameters (dry run)
89
+ else:
90
+ # Execute Ruff formatting in validation check-only mode
91
+ fmt_passed = run_command(
92
+ ["ruff", "format", "--check", "."], "Ruff Code Formatting Validation"
93
+ )
94
+ # Execute Ruff linter in check-only validation mode
95
+ lint_passed = run_command(["ruff", "check", "."], "Ruff Linter Validation")
96
+
97
+ # Run Pyright static type checker against the codebase
98
+ type_passed = run_command(["pyright", "."], "Pyright Static Type Validation")
99
+
100
+ # Evaluate if all checkers completed successfully
101
+ if fmt_passed and lint_passed and type_passed:
102
+ print(
103
+ "\n๐ŸŽ‰ CODEBASE IS PRISTINE: Formatting is correct, lint checks passed, and type signatures are valid!"
104
+ )
105
+ # Exit with success code
106
+ sys.exit(0)
107
+ else:
108
+ # Report failure if any validation check fails
109
+ print(
110
+ "\nโš ๏ธ CODE BASE ISSUES FOUND: Please resolve the formatting, lint, or type signatures issues listed above."
111
+ )
112
+ # Suggest auto-fix parameter tip if running in dry-run mode
113
+ if not args.fix:
114
+ print(
115
+ "๐Ÿ’ก TIP: Run 'python verify_code.py --fix' to automatically resolve formatting and simple linter warnings."
116
+ )
117
+ # Exit with failure code
118
+ sys.exit(1)
119
+
120
+
121
+ # Execute checks when running checker script directly
122
+ if __name__ == "__main__":
123
+ main()