empathy-framework 4.6.3__py3-none-any.whl → 4.6.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/METADATA +53 -11
  2. {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/RECORD +32 -57
  3. empathy_llm_toolkit/agent_factory/crews/health_check.py +7 -4
  4. empathy_llm_toolkit/agent_factory/decorators.py +3 -2
  5. empathy_llm_toolkit/agent_factory/memory_integration.py +6 -2
  6. empathy_llm_toolkit/contextual_patterns.py +5 -2
  7. empathy_llm_toolkit/git_pattern_extractor.py +8 -4
  8. empathy_llm_toolkit/providers.py +4 -3
  9. empathy_os/__init__.py +1 -1
  10. empathy_os/cli/__init__.py +306 -0
  11. empathy_os/cli/__main__.py +26 -0
  12. empathy_os/cli/commands/__init__.py +8 -0
  13. empathy_os/cli/commands/inspection.py +48 -0
  14. empathy_os/cli/commands/memory.py +56 -0
  15. empathy_os/cli/commands/provider.py +86 -0
  16. empathy_os/cli/commands/utilities.py +94 -0
  17. empathy_os/cli/core.py +32 -0
  18. empathy_os/cli.py +18 -6
  19. empathy_os/cli_unified.py +19 -3
  20. empathy_os/memory/short_term.py +12 -2
  21. empathy_os/project_index/scanner.py +151 -49
  22. empathy_os/socratic/visual_editor.py +9 -4
  23. empathy_os/workflows/bug_predict.py +70 -1
  24. empathy_os/workflows/pr_review.py +6 -0
  25. empathy_os/workflows/security_audit.py +13 -0
  26. empathy_os/workflows/tier_tracking.py +50 -2
  27. wizards/discharge_summary_wizard.py +4 -2
  28. wizards/incident_report_wizard.py +4 -2
  29. empathy_os/meta_workflows/agent_creator 2.py +0 -254
  30. empathy_os/meta_workflows/builtin_templates 2.py +0 -567
  31. empathy_os/meta_workflows/cli_meta_workflows 2.py +0 -1551
  32. empathy_os/meta_workflows/form_engine 2.py +0 -304
  33. empathy_os/meta_workflows/intent_detector 2.py +0 -298
  34. empathy_os/meta_workflows/pattern_learner 2.py +0 -754
  35. empathy_os/meta_workflows/session_context 2.py +0 -398
  36. empathy_os/meta_workflows/template_registry 2.py +0 -229
  37. empathy_os/meta_workflows/workflow 2.py +0 -980
  38. empathy_os/orchestration/pattern_learner 2.py +0 -699
  39. empathy_os/orchestration/real_tools 2.py +0 -938
  40. empathy_os/socratic/__init__ 2.py +0 -273
  41. empathy_os/socratic/ab_testing 2.py +0 -969
  42. empathy_os/socratic/blueprint 2.py +0 -532
  43. empathy_os/socratic/cli 2.py +0 -689
  44. empathy_os/socratic/collaboration 2.py +0 -1112
  45. empathy_os/socratic/domain_templates 2.py +0 -916
  46. empathy_os/socratic/embeddings 2.py +0 -734
  47. empathy_os/socratic/engine 2.py +0 -729
  48. empathy_os/socratic/explainer 2.py +0 -663
  49. empathy_os/socratic/feedback 2.py +0 -767
  50. empathy_os/socratic/forms 2.py +0 -624
  51. empathy_os/socratic/generator 2.py +0 -716
  52. empathy_os/socratic/llm_analyzer 2.py +0 -635
  53. empathy_os/socratic/mcp_server 2.py +0 -751
  54. empathy_os/socratic/session 2.py +0 -306
  55. empathy_os/socratic/storage 2.py +0 -635
  56. empathy_os/socratic/success 2.py +0 -719
  57. empathy_os/socratic/visual_editor 2.py +0 -812
  58. empathy_os/socratic/web_ui 2.py +0 -925
  59. empathy_os/workflows/batch_processing 2.py +0 -310
  60. empathy_os/workflows/release_prep_crew 2.py +0 -968
  61. empathy_os/workflows/test_coverage_boost_crew 2.py +0 -848
  62. {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/WHEEL +0 -0
  63. {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/entry_points.txt +0 -0
  64. {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/licenses/LICENSE +0 -0
  65. {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/top_level.txt +0 -0
@@ -1,925 +0,0 @@
1
- """Web UI Components for Socratic Workflow Builder
2
-
3
- Provides components for rendering Socratic forms in web interfaces:
4
- - React component schemas
5
- - HTML template rendering
6
- - API endpoint helpers
7
- - WebSocket support for real-time sessions
8
-
9
- Copyright 2026 Smart-AI-Memory
10
- Licensed under Fair Source License 0.9
11
- """
12
-
13
- from __future__ import annotations
14
-
15
- import json
16
- from dataclasses import asdict, dataclass
17
- from typing import Any
18
-
19
- from .blueprint import WorkflowBlueprint
20
- from .forms import FieldType, Form, FormField
21
- from .session import SocraticSession
22
-
23
- # =============================================================================
24
- # REACT COMPONENT SCHEMAS
25
- # =============================================================================
26
-
27
-
28
- @dataclass
29
- class ReactFormSchema:
30
- """Schema for rendering forms in React.
31
-
32
- Can be directly consumed by a React frontend to render
33
- the Socratic form with appropriate components.
34
- """
35
-
36
- form_id: str
37
- title: str
38
- description: str
39
- progress: float
40
- round_number: int
41
- is_final: bool
42
- fields: list[dict[str, Any]]
43
- categories: list[str]
44
-
45
- @classmethod
46
- def from_form(cls, form: Form) -> ReactFormSchema:
47
- """Create schema from a Form object."""
48
- fields = []
49
-
50
- for f in form.fields:
51
- field_schema = {
52
- "id": f.id,
53
- "type": _field_type_to_component(f.field_type),
54
- "label": f.label,
55
- "helpText": f.help_text,
56
- "placeholder": f.placeholder,
57
- "default": f.default,
58
- "category": f.category,
59
- "required": f.validation.required,
60
- "validation": {
61
- "minLength": f.validation.min_length,
62
- "maxLength": f.validation.max_length,
63
- "minValue": f.validation.min_value,
64
- "maxValue": f.validation.max_value,
65
- "pattern": f.validation.pattern,
66
- },
67
- "showWhen": f.show_when,
68
- "options": [
69
- {
70
- "value": o.value,
71
- "label": o.label,
72
- "description": o.description,
73
- "icon": o.icon,
74
- "recommended": o.recommended,
75
- }
76
- for o in f.options
77
- ] if f.options else None,
78
- }
79
- fields.append(field_schema)
80
-
81
- return cls(
82
- form_id=form.id,
83
- title=form.title,
84
- description=form.description,
85
- progress=form.progress,
86
- round_number=form.round_number,
87
- is_final=form.is_final,
88
- fields=fields,
89
- categories=form.categories or list(dict.fromkeys(f.category for f in form.fields)),
90
- )
91
-
92
- def to_json(self) -> str:
93
- """Serialize to JSON for API response."""
94
- return json.dumps(asdict(self), indent=2)
95
-
96
-
97
- def _field_type_to_component(field_type: FieldType) -> str:
98
- """Map FieldType to React component name."""
99
- mapping = {
100
- FieldType.SINGLE_SELECT: "RadioGroup",
101
- FieldType.MULTI_SELECT: "CheckboxGroup",
102
- FieldType.TEXT: "TextInput",
103
- FieldType.TEXT_AREA: "TextArea",
104
- FieldType.SLIDER: "Slider",
105
- FieldType.BOOLEAN: "Switch",
106
- FieldType.NUMBER: "NumberInput",
107
- FieldType.GROUP: "FieldGroup",
108
- }
109
- return mapping.get(field_type, "TextInput")
110
-
111
-
112
- @dataclass
113
- class ReactSessionSchema:
114
- """Schema for session state in React."""
115
-
116
- session_id: str
117
- state: str
118
- goal: str
119
- domain: str | None
120
- confidence: float
121
- current_round: int
122
- requirements_completeness: float
123
- ready_to_generate: bool
124
- ambiguities: list[str]
125
- assumptions: list[str]
126
-
127
- @classmethod
128
- def from_session(cls, session: SocraticSession) -> ReactSessionSchema:
129
- """Create schema from a SocraticSession."""
130
- return cls(
131
- session_id=session.session_id,
132
- state=session.state.value,
133
- goal=session.goal,
134
- domain=session.goal_analysis.domain if session.goal_analysis else None,
135
- confidence=session.goal_analysis.confidence if session.goal_analysis else 0,
136
- current_round=session.current_round,
137
- requirements_completeness=session.requirements.completeness_score(),
138
- ready_to_generate=session.can_generate(),
139
- ambiguities=session.goal_analysis.ambiguities if session.goal_analysis else [],
140
- assumptions=session.goal_analysis.assumptions if session.goal_analysis else [],
141
- )
142
-
143
- def to_json(self) -> str:
144
- """Serialize to JSON."""
145
- return json.dumps(asdict(self), indent=2)
146
-
147
-
148
- @dataclass
149
- class ReactBlueprintSchema:
150
- """Schema for blueprint display in React."""
151
-
152
- id: str
153
- name: str
154
- description: str
155
- domain: str
156
- languages: list[str]
157
- quality_focus: list[str]
158
- automation_level: str
159
- agents: list[dict[str, Any]]
160
- stages: list[dict[str, Any]]
161
- success_criteria: dict[str, Any] | None
162
-
163
- @classmethod
164
- def from_blueprint(cls, blueprint: WorkflowBlueprint) -> ReactBlueprintSchema:
165
- """Create schema from a WorkflowBlueprint."""
166
- agents = []
167
- for agent in blueprint.agents:
168
- agents.append({
169
- "id": agent.spec.id,
170
- "name": agent.spec.name,
171
- "role": agent.spec.role.value,
172
- "goal": agent.spec.goal,
173
- "backstory": agent.spec.backstory[:200] + "..." if len(agent.spec.backstory) > 200 else agent.spec.backstory,
174
- "modelTier": agent.spec.model_tier,
175
- "tools": [t.name for t in agent.spec.tools],
176
- })
177
-
178
- stages = []
179
- for stage in blueprint.stages:
180
- stages.append({
181
- "id": stage.id,
182
- "name": stage.name,
183
- "description": stage.description,
184
- "agents": stage.agent_ids,
185
- "parallel": stage.parallel,
186
- "dependsOn": stage.depends_on,
187
- })
188
-
189
- success_criteria = None
190
- if blueprint.success_criteria:
191
- success_criteria = blueprint.success_criteria.to_dict()
192
-
193
- return cls(
194
- id=blueprint.id,
195
- name=blueprint.name,
196
- description=blueprint.description,
197
- domain=blueprint.domain,
198
- languages=blueprint.supported_languages,
199
- quality_focus=blueprint.quality_focus,
200
- automation_level=blueprint.automation_level,
201
- agents=agents,
202
- stages=stages,
203
- success_criteria=success_criteria,
204
- )
205
-
206
- def to_json(self) -> str:
207
- """Serialize to JSON."""
208
- return json.dumps(asdict(self), indent=2)
209
-
210
-
211
- # =============================================================================
212
- # HTML TEMPLATE RENDERING
213
- # =============================================================================
214
-
215
-
216
- def render_form_html(form: Form, action_url: str = "/api/socratic/submit") -> str:
217
- """Render a form as HTML.
218
-
219
- Args:
220
- form: Form to render
221
- action_url: Form submission URL
222
-
223
- Returns:
224
- HTML string
225
- """
226
- html_parts = [
227
- f'<form id="{form.id}" action="{action_url}" method="POST" class="socratic-form">',
228
- ' <div class="form-header">',
229
- f' <h2>{_escape_html(form.title)}</h2>',
230
- f' <p class="form-description">{_escape_html(form.description)}</p>',
231
- ' <div class="progress-bar">',
232
- f' <div class="progress-fill" style="width: {form.progress * 100}%"></div>',
233
- f' <span class="progress-text">{form.progress:.0%}</span>',
234
- ' </div>',
235
- ' </div>',
236
- ' <div class="form-fields">',
237
- ]
238
-
239
- # Group fields by category
240
- fields_by_category = form.get_fields_by_category()
241
-
242
- for category, fields in fields_by_category.items():
243
- if len(fields_by_category) > 1:
244
- html_parts.append(f' <fieldset class="field-category" data-category="{category}">')
245
- html_parts.append(f' <legend>{category.title()}</legend>')
246
-
247
- for field in fields:
248
- html_parts.append(_render_field_html(field))
249
-
250
- if len(fields_by_category) > 1:
251
- html_parts.append(' </fieldset>')
252
-
253
- html_parts.extend([
254
- ' </div>',
255
- ' <div class="form-actions">',
256
- ' <button type="submit" class="btn-primary">Continue</button>',
257
- ' </div>',
258
- '</form>',
259
- ])
260
-
261
- return "\n".join(html_parts)
262
-
263
-
264
- def _render_field_html(field: FormField) -> str:
265
- """Render a single field as HTML."""
266
- required = 'required' if field.validation.required else ''
267
- required_indicator = '<span class="required">*</span>' if field.validation.required else ''
268
-
269
- # Show when data attribute
270
- show_when = ""
271
- if field.show_when:
272
- show_when = f' data-show-when=\'{json.dumps(field.show_when)}\''
273
-
274
- parts = [
275
- f' <div class="form-field" data-field-id="{field.id}"{show_when}>',
276
- f' <label for="{field.id}">{_escape_html(field.label)}{required_indicator}</label>',
277
- ]
278
-
279
- if field.help_text:
280
- parts.append(f' <p class="help-text">{_escape_html(field.help_text)}</p>')
281
-
282
- # Render input based on type
283
- if field.field_type == FieldType.SINGLE_SELECT:
284
- parts.append(' <div class="radio-group">')
285
- for opt in field.options:
286
- rec_class = ' recommended' if opt.recommended else ''
287
- parts.append(f' <label class="radio-option{rec_class}">')
288
- parts.append(f' <input type="radio" name="{field.id}" value="{opt.value}" {required}>')
289
- parts.append(f' <span class="option-label">{_escape_html(opt.label)}</span>')
290
- if opt.description:
291
- parts.append(f' <span class="option-desc">{_escape_html(opt.description)}</span>')
292
- parts.append(' </label>')
293
- parts.append(' </div>')
294
-
295
- elif field.field_type == FieldType.MULTI_SELECT:
296
- parts.append(' <div class="checkbox-group">')
297
- for opt in field.options:
298
- rec_class = ' recommended' if opt.recommended else ''
299
- parts.append(f' <label class="checkbox-option{rec_class}">')
300
- parts.append(f' <input type="checkbox" name="{field.id}" value="{opt.value}">')
301
- parts.append(f' <span class="option-label">{_escape_html(opt.label)}</span>')
302
- if opt.description:
303
- parts.append(f' <span class="option-desc">{_escape_html(opt.description)}</span>')
304
- parts.append(' </label>')
305
- parts.append(' </div>')
306
-
307
- elif field.field_type == FieldType.TEXT_AREA:
308
- max_len = f' maxlength="{field.validation.max_length}"' if field.validation.max_length else ''
309
- parts.append(f' <textarea id="{field.id}" name="{field.id}" placeholder="{_escape_html(field.placeholder)}"{max_len} {required}></textarea>')
310
-
311
- elif field.field_type == FieldType.BOOLEAN:
312
- parts.append(' <div class="switch-container">')
313
- parts.append(' <label class="switch">')
314
- parts.append(f' <input type="checkbox" id="{field.id}" name="{field.id}" value="true">')
315
- parts.append(' <span class="slider"></span>')
316
- parts.append(' </label>')
317
- parts.append(' </div>')
318
-
319
- elif field.field_type == FieldType.SLIDER:
320
- min_val = field.validation.min_value or 0
321
- max_val = field.validation.max_value or 100
322
- parts.append(' <div class="slider-container">')
323
- parts.append(f' <input type="range" id="{field.id}" name="{field.id}" min="{min_val}" max="{max_val}">')
324
- parts.append(f' <output for="{field.id}"></output>')
325
- parts.append(' </div>')
326
-
327
- else: # TEXT, NUMBER
328
- input_type = "number" if field.field_type == FieldType.NUMBER else "text"
329
- max_len = f' maxlength="{field.validation.max_length}"' if field.validation.max_length else ''
330
- parts.append(f' <input type="{input_type}" id="{field.id}" name="{field.id}" placeholder="{_escape_html(field.placeholder)}"{max_len} {required}>')
331
-
332
- parts.append(' </div>')
333
-
334
- return "\n".join(parts)
335
-
336
-
337
- def _escape_html(text: str) -> str:
338
- """Escape HTML special characters."""
339
- return (text
340
- .replace("&", "&amp;")
341
- .replace("<", "&lt;")
342
- .replace(">", "&gt;")
343
- .replace('"', "&quot;")
344
- .replace("'", "&#x27;"))
345
-
346
-
347
- # =============================================================================
348
- # CSS STYLES
349
- # =============================================================================
350
-
351
-
352
- FORM_CSS = """
353
- /* Socratic Form Styles */
354
-
355
- .socratic-form {
356
- max-width: 800px;
357
- margin: 0 auto;
358
- padding: 2rem;
359
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
360
- }
361
-
362
- .form-header {
363
- margin-bottom: 2rem;
364
- }
365
-
366
- .form-header h2 {
367
- margin: 0 0 0.5rem 0;
368
- font-size: 1.75rem;
369
- color: #1a1a2e;
370
- }
371
-
372
- .form-description {
373
- color: #666;
374
- margin: 0 0 1rem 0;
375
- }
376
-
377
- .progress-bar {
378
- background: #e0e0e0;
379
- border-radius: 10px;
380
- height: 20px;
381
- position: relative;
382
- overflow: hidden;
383
- }
384
-
385
- .progress-fill {
386
- background: linear-gradient(90deg, #4CAF50, #8BC34A);
387
- height: 100%;
388
- border-radius: 10px;
389
- transition: width 0.3s ease;
390
- }
391
-
392
- .progress-text {
393
- position: absolute;
394
- right: 10px;
395
- top: 50%;
396
- transform: translateY(-50%);
397
- font-size: 0.75rem;
398
- font-weight: 600;
399
- color: #333;
400
- }
401
-
402
- .form-fields {
403
- display: flex;
404
- flex-direction: column;
405
- gap: 1.5rem;
406
- }
407
-
408
- .form-field {
409
- background: #f8f9fa;
410
- padding: 1.25rem;
411
- border-radius: 8px;
412
- border: 1px solid #e0e0e0;
413
- }
414
-
415
- .form-field label {
416
- display: block;
417
- font-weight: 600;
418
- margin-bottom: 0.5rem;
419
- color: #1a1a2e;
420
- }
421
-
422
- .form-field .required {
423
- color: #e53935;
424
- margin-left: 0.25rem;
425
- }
426
-
427
- .help-text {
428
- font-size: 0.875rem;
429
- color: #666;
430
- margin: 0 0 0.75rem 0;
431
- }
432
-
433
- /* Text inputs */
434
- input[type="text"],
435
- input[type="number"],
436
- textarea {
437
- width: 100%;
438
- padding: 0.75rem;
439
- border: 1px solid #ccc;
440
- border-radius: 6px;
441
- font-size: 1rem;
442
- transition: border-color 0.2s, box-shadow 0.2s;
443
- }
444
-
445
- input[type="text"]:focus,
446
- input[type="number"]:focus,
447
- textarea:focus {
448
- outline: none;
449
- border-color: #4CAF50;
450
- box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
451
- }
452
-
453
- textarea {
454
- min-height: 100px;
455
- resize: vertical;
456
- }
457
-
458
- /* Radio and checkbox groups */
459
- .radio-group,
460
- .checkbox-group {
461
- display: flex;
462
- flex-direction: column;
463
- gap: 0.75rem;
464
- }
465
-
466
- .radio-option,
467
- .checkbox-option {
468
- display: flex;
469
- align-items: flex-start;
470
- gap: 0.75rem;
471
- padding: 0.75rem;
472
- background: white;
473
- border: 1px solid #e0e0e0;
474
- border-radius: 6px;
475
- cursor: pointer;
476
- transition: border-color 0.2s, background 0.2s;
477
- }
478
-
479
- .radio-option:hover,
480
- .checkbox-option:hover {
481
- border-color: #4CAF50;
482
- background: #f0f7f0;
483
- }
484
-
485
- .radio-option.recommended,
486
- .checkbox-option.recommended {
487
- border-color: #4CAF50;
488
- }
489
-
490
- .radio-option input,
491
- .checkbox-option input {
492
- margin-top: 0.25rem;
493
- }
494
-
495
- .option-label {
496
- font-weight: 500;
497
- color: #1a1a2e;
498
- }
499
-
500
- .option-desc {
501
- display: block;
502
- font-size: 0.875rem;
503
- color: #666;
504
- margin-top: 0.25rem;
505
- }
506
-
507
- /* Switch/toggle */
508
- .switch-container {
509
- display: flex;
510
- align-items: center;
511
- }
512
-
513
- .switch {
514
- position: relative;
515
- width: 50px;
516
- height: 28px;
517
- }
518
-
519
- .switch input {
520
- opacity: 0;
521
- width: 0;
522
- height: 0;
523
- }
524
-
525
- .switch .slider {
526
- position: absolute;
527
- cursor: pointer;
528
- top: 0;
529
- left: 0;
530
- right: 0;
531
- bottom: 0;
532
- background-color: #ccc;
533
- border-radius: 28px;
534
- transition: 0.3s;
535
- }
536
-
537
- .switch .slider:before {
538
- position: absolute;
539
- content: "";
540
- height: 22px;
541
- width: 22px;
542
- left: 3px;
543
- bottom: 3px;
544
- background-color: white;
545
- border-radius: 50%;
546
- transition: 0.3s;
547
- }
548
-
549
- .switch input:checked + .slider {
550
- background-color: #4CAF50;
551
- }
552
-
553
- .switch input:checked + .slider:before {
554
- transform: translateX(22px);
555
- }
556
-
557
- /* Slider/range */
558
- .slider-container {
559
- display: flex;
560
- align-items: center;
561
- gap: 1rem;
562
- }
563
-
564
- .slider-container input[type="range"] {
565
- flex: 1;
566
- height: 6px;
567
- -webkit-appearance: none;
568
- background: #e0e0e0;
569
- border-radius: 3px;
570
- }
571
-
572
- .slider-container input[type="range"]::-webkit-slider-thumb {
573
- -webkit-appearance: none;
574
- width: 20px;
575
- height: 20px;
576
- background: #4CAF50;
577
- border-radius: 50%;
578
- cursor: pointer;
579
- }
580
-
581
- /* Category fieldsets */
582
- .field-category {
583
- border: none;
584
- padding: 0;
585
- margin: 0;
586
- }
587
-
588
- .field-category legend {
589
- font-size: 1.25rem;
590
- font-weight: 600;
591
- color: #1a1a2e;
592
- padding: 0;
593
- margin-bottom: 1rem;
594
- }
595
-
596
- /* Form actions */
597
- .form-actions {
598
- margin-top: 2rem;
599
- display: flex;
600
- justify-content: flex-end;
601
- gap: 1rem;
602
- }
603
-
604
- .btn-primary {
605
- background: linear-gradient(90deg, #4CAF50, #45a049);
606
- color: white;
607
- border: none;
608
- padding: 0.875rem 2rem;
609
- font-size: 1rem;
610
- font-weight: 600;
611
- border-radius: 6px;
612
- cursor: pointer;
613
- transition: transform 0.2s, box-shadow 0.2s;
614
- }
615
-
616
- .btn-primary:hover {
617
- transform: translateY(-1px);
618
- box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
619
- }
620
-
621
- .btn-primary:active {
622
- transform: translateY(0);
623
- }
624
-
625
- /* Conditional field visibility */
626
- .form-field[data-show-when] {
627
- display: none;
628
- }
629
-
630
- .form-field[data-show-when].visible {
631
- display: block;
632
- }
633
- """
634
-
635
-
636
- # =============================================================================
637
- # JAVASCRIPT FOR FORM INTERACTIVITY
638
- # =============================================================================
639
-
640
-
641
- FORM_JS = """
642
- // Socratic Form Interactivity
643
-
644
- class SocraticForm {
645
- constructor(formElement) {
646
- this.form = formElement;
647
- this.fields = {};
648
- this.init();
649
- }
650
-
651
- init() {
652
- // Index all fields
653
- this.form.querySelectorAll('.form-field').forEach(field => {
654
- const fieldId = field.dataset.fieldId;
655
- this.fields[fieldId] = {
656
- element: field,
657
- showWhen: field.dataset.showWhen ? JSON.parse(field.dataset.showWhen) : null,
658
- };
659
- });
660
-
661
- // Add change listeners
662
- this.form.addEventListener('change', (e) => this.handleChange(e));
663
-
664
- // Initial visibility check
665
- this.updateVisibility();
666
-
667
- // Slider output sync
668
- this.form.querySelectorAll('input[type="range"]').forEach(slider => {
669
- const output = slider.nextElementSibling;
670
- if (output && output.tagName === 'OUTPUT') {
671
- output.textContent = slider.value;
672
- slider.addEventListener('input', () => {
673
- output.textContent = slider.value;
674
- });
675
- }
676
- });
677
- }
678
-
679
- handleChange(event) {
680
- this.updateVisibility();
681
- }
682
-
683
- getValues() {
684
- const values = {};
685
- const formData = new FormData(this.form);
686
-
687
- for (const [key, value] of formData.entries()) {
688
- if (values[key]) {
689
- // Multi-select: convert to array
690
- if (!Array.isArray(values[key])) {
691
- values[key] = [values[key]];
692
- }
693
- values[key].push(value);
694
- } else {
695
- values[key] = value;
696
- }
697
- }
698
-
699
- return values;
700
- }
701
-
702
- updateVisibility() {
703
- const values = this.getValues();
704
-
705
- Object.entries(this.fields).forEach(([fieldId, field]) => {
706
- if (!field.showWhen) {
707
- field.element.classList.add('visible');
708
- return;
709
- }
710
-
711
- const shouldShow = this.evaluateCondition(field.showWhen, values);
712
- field.element.classList.toggle('visible', shouldShow);
713
-
714
- // Disable hidden inputs to exclude from submission
715
- const inputs = field.element.querySelectorAll('input, textarea, select');
716
- inputs.forEach(input => {
717
- input.disabled = !shouldShow;
718
- });
719
- });
720
- }
721
-
722
- evaluateCondition(condition, values) {
723
- for (const [fieldId, expected] of Object.entries(condition)) {
724
- const actual = values[fieldId];
725
-
726
- if (Array.isArray(expected)) {
727
- // Any of condition
728
- if (Array.isArray(actual)) {
729
- if (!actual.some(v => expected.includes(v))) return false;
730
- } else {
731
- if (!expected.includes(actual)) return false;
732
- }
733
- } else {
734
- // Exact match
735
- if (Array.isArray(actual)) {
736
- if (!actual.includes(expected)) return false;
737
- } else {
738
- if (actual !== expected) return false;
739
- }
740
- }
741
- }
742
- return true;
743
- }
744
- }
745
-
746
- // Auto-initialize forms
747
- document.addEventListener('DOMContentLoaded', () => {
748
- document.querySelectorAll('.socratic-form').forEach(form => {
749
- new SocraticForm(form);
750
- });
751
- });
752
-
753
- // Async form submission
754
- async function submitSocraticForm(form, url) {
755
- const socraticForm = form._socraticForm || new SocraticForm(form);
756
- const values = socraticForm.getValues();
757
-
758
- try {
759
- const response = await fetch(url, {
760
- method: 'POST',
761
- headers: {
762
- 'Content-Type': 'application/json',
763
- },
764
- body: JSON.stringify(values),
765
- });
766
-
767
- if (!response.ok) {
768
- throw new Error(`HTTP ${response.status}`);
769
- }
770
-
771
- return await response.json();
772
- } catch (error) {
773
- console.error('Form submission failed:', error);
774
- throw error;
775
- }
776
- }
777
- """
778
-
779
-
780
- # =============================================================================
781
- # API HELPERS
782
- # =============================================================================
783
-
784
-
785
- @dataclass
786
- class APIResponse:
787
- """Standard API response format."""
788
-
789
- success: bool
790
- data: dict[str, Any] | None = None
791
- error: str | None = None
792
- next_action: str | None = None # "continue", "generate", "complete"
793
-
794
- def to_json(self) -> str:
795
- """Serialize to JSON."""
796
- return json.dumps(asdict(self), indent=2)
797
-
798
-
799
- def create_form_response(
800
- session: SocraticSession,
801
- form: Form | None,
802
- builder: Any, # SocraticWorkflowBuilder
803
- ) -> APIResponse:
804
- """Create API response for form request.
805
-
806
- Args:
807
- session: Current session
808
- form: Form to display (or None if ready to generate)
809
- builder: SocraticWorkflowBuilder instance
810
-
811
- Returns:
812
- APIResponse with form data or generation prompt
813
- """
814
- session_schema = ReactSessionSchema.from_session(session)
815
-
816
- if form:
817
- form_schema = ReactFormSchema.from_form(form)
818
- return APIResponse(
819
- success=True,
820
- data={
821
- "session": asdict(session_schema),
822
- "form": asdict(form_schema),
823
- },
824
- next_action="continue",
825
- )
826
- elif builder.is_ready_to_generate(session):
827
- return APIResponse(
828
- success=True,
829
- data={
830
- "session": asdict(session_schema),
831
- "message": "Ready to generate workflow",
832
- },
833
- next_action="generate",
834
- )
835
- else:
836
- return APIResponse(
837
- success=False,
838
- error="Unable to determine next step",
839
- )
840
-
841
-
842
- def create_blueprint_response(
843
- blueprint: WorkflowBlueprint,
844
- session: SocraticSession,
845
- ) -> APIResponse:
846
- """Create API response for generated blueprint.
847
-
848
- Args:
849
- blueprint: Generated blueprint
850
- session: Source session
851
-
852
- Returns:
853
- APIResponse with blueprint data
854
- """
855
- blueprint_schema = ReactBlueprintSchema.from_blueprint(blueprint)
856
- session_schema = ReactSessionSchema.from_session(session)
857
-
858
- return APIResponse(
859
- success=True,
860
- data={
861
- "session": asdict(session_schema),
862
- "blueprint": asdict(blueprint_schema),
863
- },
864
- next_action="complete",
865
- )
866
-
867
-
868
- # =============================================================================
869
- # EXPORT FUNCTIONS
870
- # =============================================================================
871
-
872
-
873
- def get_form_assets() -> dict[str, str]:
874
- """Get CSS and JS assets for forms.
875
-
876
- Returns:
877
- Dictionary with 'css' and 'js' keys
878
- """
879
- return {
880
- "css": FORM_CSS,
881
- "js": FORM_JS,
882
- }
883
-
884
-
885
- def render_complete_page(form: Form, session: SocraticSession) -> str:
886
- """Render a complete HTML page with form.
887
-
888
- Args:
889
- form: Form to render
890
- session: Current session
891
-
892
- Returns:
893
- Complete HTML page
894
- """
895
- form_html = render_form_html(form)
896
- session_data = ReactSessionSchema.from_session(session)
897
-
898
- return f"""<!DOCTYPE html>
899
- <html lang="en">
900
- <head>
901
- <meta charset="UTF-8">
902
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
903
- <title>Socratic Workflow Builder</title>
904
- <style>
905
- {FORM_CSS}
906
- </style>
907
- </head>
908
- <body>
909
- <div class="container">
910
- <div class="session-info">
911
- <span class="domain-badge">{session_data.domain or 'General'}</span>
912
- <span class="confidence">Confidence: {session_data.confidence:.0%}</span>
913
- </div>
914
-
915
- {form_html}
916
- </div>
917
-
918
- <script>
919
- {FORM_JS}
920
-
921
- // Session data for client-side use
922
- window.socraticSession = {session_data.to_json()};
923
- </script>
924
- </body>
925
- </html>"""