ZENLLC commited on
Commit
3eff5bc
·
verified ·
1 Parent(s): f5cdd36

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -33
app.py CHANGED
@@ -1,6 +1,5 @@
1
  import json
2
  import textwrap
3
- import io
4
  from typing import Dict, Any, List, Tuple
5
 
6
  import gradio as gr
@@ -20,17 +19,23 @@ def call_chat_completion(
20
  system_prompt: str,
21
  user_prompt: str,
22
  temperature: float = 0.3,
23
- max_tokens: int = 1800,
24
  ) -> str:
25
  """
26
  Generic OpenAI-compatible chat completion call using HTTP.
27
- Supports providers that mimic the /v1/chat/completions API.
 
 
 
 
 
 
28
  """
29
  if not api_key:
30
  raise ValueError("API key is required.")
31
 
32
  if not base_url:
33
- base_url = "https://api.openai.com" # default
34
 
35
  url = base_url.rstrip("/") + "/v1/chat/completions"
36
 
@@ -39,17 +44,28 @@ def call_chat_completion(
39
  "Content-Type": "application/json",
40
  }
41
 
42
- payload = {
43
- "model": model,
44
- "temperature": temperature,
45
- "max_tokens": max_tokens,
46
- "messages": [
47
- {"role": "system", "content": system_prompt},
48
- {"role": "user", "content": user_prompt},
49
- ],
50
- }
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
- resp = requests.post(url, headers=headers, json=payload, timeout=60)
53
  if resp.status_code != 200:
54
  raise RuntimeError(
55
  f"LLM API error: {resp.status_code} - {resp.text[:400]}"
@@ -69,14 +85,14 @@ def call_chat_completion(
69
  SOP_SYSTEM_PROMPT = """
70
  You are an expert operations consultant and technical writer.
71
 
72
- You generate **clear, professional, and implementation-ready Standard Operating Procedures (SOPs)**.
73
 
74
  The user will give:
75
  - A title or short description of the process
76
  - Context about the organization or scenario
77
  - Optional industry, tone, and detail level
78
 
79
- You MUST respond **strictly as JSON**, with no extra commentary, using this schema:
80
 
81
  {
82
  "title": "string",
@@ -143,17 +159,18 @@ def parse_sop_json(raw_text: str) -> Dict[str, Any]:
143
  """
144
  text = raw_text.strip()
145
 
146
- # Strip markdown fences if present
147
  if text.startswith("```"):
148
- # Remove leading ```json or ```
149
- text = text.split("```", 2)
150
- if len(text) == 3:
151
- text = text[1] if "{" in text[1] else text[2]
 
152
  else:
153
- text = text[-1]
154
  text = text.strip()
155
 
156
- # Try to find the first '{' and last '}' to extract JSON
157
  first_brace = text.find("{")
158
  last_brace = text.rfind("}")
159
  if first_brace != -1 and last_brace != -1:
@@ -164,6 +181,7 @@ def parse_sop_json(raw_text: str) -> Dict[str, Any]:
164
 
165
  def sop_to_markdown(sop: Dict[str, Any]) -> str:
166
  """Render the SOP JSON into a readable Markdown document."""
 
167
  def bullet_list(items: List[str]) -> str:
168
  if not items:
169
  return "_None specified._"
@@ -260,6 +278,7 @@ def create_sop_steps_figure(sop: Dict[str, Any]) -> Figure:
260
  wrap=True,
261
  )
262
  ax.axis("off")
 
263
  return fig
264
 
265
  n = len(steps)
@@ -274,13 +293,12 @@ def create_sop_steps_figure(sop: Dict[str, Any]) -> Figure:
274
  owner = step.get("owner_role", "")
275
  desc = step.get("description", "")
276
 
277
- # Clamp description length
278
  desc_short = textwrap.shorten(desc, width=120, placeholder="...")
279
 
280
- # Draw a rounded rectangle as a "card"
281
  x0, x1 = 0.05, 0.95
282
  y0, y1 = y - 0.3, y + 0.3
283
 
 
284
  ax.add_patch(
285
  plt.Rectangle(
286
  (x0, y0),
@@ -291,7 +309,8 @@ def create_sop_steps_figure(sop: Dict[str, Any]) -> Figure:
291
  linestyle="-",
292
  )
293
  )
294
- # Number bubble
 
295
  ax.text(
296
  x0 + 0.02,
297
  y,
@@ -302,7 +321,7 @@ def create_sop_steps_figure(sop: Dict[str, Any]) -> Figure:
302
  fontweight="bold",
303
  )
304
 
305
- # Title & owner
306
  ax.text(
307
  x0 + 0.12,
308
  y + 0.15,
@@ -312,6 +331,8 @@ def create_sop_steps_figure(sop: Dict[str, Any]) -> Figure:
312
  fontsize=11,
313
  fontweight="bold",
314
  )
 
 
315
  if owner:
316
  ax.text(
317
  x0 + 0.12,
@@ -427,7 +448,7 @@ def generate_sop_ui(
427
  system_prompt=SOP_SYSTEM_PROMPT,
428
  user_prompt=user_prompt,
429
  temperature=0.25,
430
- max_tokens=1800,
431
  )
432
 
433
  sop_json = parse_sop_json(raw_response)
@@ -454,13 +475,13 @@ with gr.Blocks(title="ZEN Simple SOP Builder") as demo:
454
  # 🧭 ZEN Simple SOP Builder
455
 
456
  Generate clean, professional Standard Operating Procedures (SOPs) from a short description.
457
- Perfect for mid-career professionals who need **clarity, structure, and ownership** — fast.
458
 
459
  1. Configure your API settings
460
  2. Describe the process you want to document
461
  3. Generate a full SOP + visual flow of the steps
462
 
463
- > Your API key is stored only in your session state and **never logged or saved to disk**.
464
  """
465
  )
466
 
@@ -472,7 +493,7 @@ Perfect for mid-career professionals who need **clarity, structure, and ownershi
472
 
473
  api_key_input = gr.Textbox(
474
  label="LLM API Key",
475
- placeholder="Enter your API key (OpenAI, compatible provider, etc.)",
476
  type="password",
477
  )
478
  base_url = gr.Textbox(
@@ -488,8 +509,8 @@ Perfect for mid-career professionals who need **clarity, structure, and ownershi
488
 
489
  gr.Markdown(
490
  """
491
- You can use **any OpenAI-compatible provider**
492
- (just update the Base URL + Model Name accordingly).
493
  """
494
  )
495
 
 
1
  import json
2
  import textwrap
 
3
  from typing import Dict, Any, List, Tuple
4
 
5
  import gradio as gr
 
19
  system_prompt: str,
20
  user_prompt: str,
21
  temperature: float = 0.3,
22
+ max_completion_tokens: int = 1800,
23
  ) -> str:
24
  """
25
  Generic OpenAI-compatible chat completion call using HTTP.
26
+
27
+ Primary path:
28
+ - Uses `max_completion_tokens` (new OpenAI-style param).
29
+
30
+ Fallback:
31
+ - If the provider complains that `max_completion_tokens` is unsupported,
32
+ retry once using legacy `max_tokens` instead.
33
  """
34
  if not api_key:
35
  raise ValueError("API key is required.")
36
 
37
  if not base_url:
38
+ base_url = "https://api.openai.com"
39
 
40
  url = base_url.rstrip("/") + "/v1/chat/completions"
41
 
 
44
  "Content-Type": "application/json",
45
  }
46
 
47
+ def _payload(use_new_param: bool) -> Dict[str, Any]:
48
+ base = {
49
+ "model": model,
50
+ "temperature": temperature,
51
+ "messages": [
52
+ {"role": "system", "content": system_prompt},
53
+ {"role": "user", "content": user_prompt},
54
+ ],
55
+ }
56
+ if use_new_param:
57
+ base["max_completion_tokens"] = max_completion_tokens
58
+ else:
59
+ base["max_tokens"] = max_completion_tokens
60
+ return base
61
+
62
+ # First attempt: new param
63
+ resp = requests.post(url, headers=headers, json=_payload(True), timeout=60)
64
+
65
+ # If provider doesn't support max_completion_tokens, fallback to max_tokens
66
+ if resp.status_code == 400 and "max_completion_tokens" in resp.text:
67
+ resp = requests.post(url, headers=headers, json=_payload(False), timeout=60)
68
 
 
69
  if resp.status_code != 200:
70
  raise RuntimeError(
71
  f"LLM API error: {resp.status_code} - {resp.text[:400]}"
 
85
  SOP_SYSTEM_PROMPT = """
86
  You are an expert operations consultant and technical writer.
87
 
88
+ You generate clear, professional, implementation-ready Standard Operating Procedures (SOPs).
89
 
90
  The user will give:
91
  - A title or short description of the process
92
  - Context about the organization or scenario
93
  - Optional industry, tone, and detail level
94
 
95
+ You MUST respond strictly as JSON, with no extra commentary, using this schema:
96
 
97
  {
98
  "title": "string",
 
159
  """
160
  text = raw_text.strip()
161
 
162
+ # Strip markdown code fences if present
163
  if text.startswith("```"):
164
+ parts = text.split("```")
165
+ # Try to find part containing '{'
166
+ candidates = [p for p in parts if "{" in p]
167
+ if candidates:
168
+ text = candidates[0]
169
  else:
170
+ text = parts[-1]
171
  text = text.strip()
172
 
173
+ # Extract JSON between first '{' and last '}'
174
  first_brace = text.find("{")
175
  last_brace = text.rfind("}")
176
  if first_brace != -1 and last_brace != -1:
 
181
 
182
  def sop_to_markdown(sop: Dict[str, Any]) -> str:
183
  """Render the SOP JSON into a readable Markdown document."""
184
+
185
  def bullet_list(items: List[str]) -> str:
186
  if not items:
187
  return "_None specified._"
 
278
  wrap=True,
279
  )
280
  ax.axis("off")
281
+ fig.tight_layout()
282
  return fig
283
 
284
  n = len(steps)
 
293
  owner = step.get("owner_role", "")
294
  desc = step.get("description", "")
295
 
 
296
  desc_short = textwrap.shorten(desc, width=120, placeholder="...")
297
 
 
298
  x0, x1 = 0.05, 0.95
299
  y0, y1 = y - 0.3, y + 0.3
300
 
301
+ # Card rectangle (using default styles)
302
  ax.add_patch(
303
  plt.Rectangle(
304
  (x0, y0),
 
309
  linestyle="-",
310
  )
311
  )
312
+
313
+ # Step number
314
  ax.text(
315
  x0 + 0.02,
316
  y,
 
321
  fontweight="bold",
322
  )
323
 
324
+ # Title
325
  ax.text(
326
  x0 + 0.12,
327
  y + 0.15,
 
331
  fontsize=11,
332
  fontweight="bold",
333
  )
334
+
335
+ # Owner
336
  if owner:
337
  ax.text(
338
  x0 + 0.12,
 
448
  system_prompt=SOP_SYSTEM_PROMPT,
449
  user_prompt=user_prompt,
450
  temperature=0.25,
451
+ max_completion_tokens=1800,
452
  )
453
 
454
  sop_json = parse_sop_json(raw_response)
 
475
  # 🧭 ZEN Simple SOP Builder
476
 
477
  Generate clean, professional Standard Operating Procedures (SOPs) from a short description.
478
+ Perfect for mid-career professionals who need clarity, structure, and ownership — fast.
479
 
480
  1. Configure your API settings
481
  2. Describe the process you want to document
482
  3. Generate a full SOP + visual flow of the steps
483
 
484
+ > Your API key is stored only in your session state and is not logged to disk.
485
  """
486
  )
487
 
 
493
 
494
  api_key_input = gr.Textbox(
495
  label="LLM API Key",
496
+ placeholder="Enter your API key (OpenAI or compatible provider)",
497
  type="password",
498
  )
499
  base_url = gr.Textbox(
 
509
 
510
  gr.Markdown(
511
  """
512
+ You can use any **OpenAI-compatible** provider
513
+ by adjusting the Base URL and Model Name.
514
  """
515
  )
516