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.
- {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/METADATA +53 -11
- {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/RECORD +32 -57
- empathy_llm_toolkit/agent_factory/crews/health_check.py +7 -4
- empathy_llm_toolkit/agent_factory/decorators.py +3 -2
- empathy_llm_toolkit/agent_factory/memory_integration.py +6 -2
- empathy_llm_toolkit/contextual_patterns.py +5 -2
- empathy_llm_toolkit/git_pattern_extractor.py +8 -4
- empathy_llm_toolkit/providers.py +4 -3
- empathy_os/__init__.py +1 -1
- empathy_os/cli/__init__.py +306 -0
- empathy_os/cli/__main__.py +26 -0
- empathy_os/cli/commands/__init__.py +8 -0
- empathy_os/cli/commands/inspection.py +48 -0
- empathy_os/cli/commands/memory.py +56 -0
- empathy_os/cli/commands/provider.py +86 -0
- empathy_os/cli/commands/utilities.py +94 -0
- empathy_os/cli/core.py +32 -0
- empathy_os/cli.py +18 -6
- empathy_os/cli_unified.py +19 -3
- empathy_os/memory/short_term.py +12 -2
- empathy_os/project_index/scanner.py +151 -49
- empathy_os/socratic/visual_editor.py +9 -4
- empathy_os/workflows/bug_predict.py +70 -1
- empathy_os/workflows/pr_review.py +6 -0
- empathy_os/workflows/security_audit.py +13 -0
- empathy_os/workflows/tier_tracking.py +50 -2
- wizards/discharge_summary_wizard.py +4 -2
- wizards/incident_report_wizard.py +4 -2
- empathy_os/meta_workflows/agent_creator 2.py +0 -254
- empathy_os/meta_workflows/builtin_templates 2.py +0 -567
- empathy_os/meta_workflows/cli_meta_workflows 2.py +0 -1551
- empathy_os/meta_workflows/form_engine 2.py +0 -304
- empathy_os/meta_workflows/intent_detector 2.py +0 -298
- empathy_os/meta_workflows/pattern_learner 2.py +0 -754
- empathy_os/meta_workflows/session_context 2.py +0 -398
- empathy_os/meta_workflows/template_registry 2.py +0 -229
- empathy_os/meta_workflows/workflow 2.py +0 -980
- empathy_os/orchestration/pattern_learner 2.py +0 -699
- empathy_os/orchestration/real_tools 2.py +0 -938
- empathy_os/socratic/__init__ 2.py +0 -273
- empathy_os/socratic/ab_testing 2.py +0 -969
- empathy_os/socratic/blueprint 2.py +0 -532
- empathy_os/socratic/cli 2.py +0 -689
- empathy_os/socratic/collaboration 2.py +0 -1112
- empathy_os/socratic/domain_templates 2.py +0 -916
- empathy_os/socratic/embeddings 2.py +0 -734
- empathy_os/socratic/engine 2.py +0 -729
- empathy_os/socratic/explainer 2.py +0 -663
- empathy_os/socratic/feedback 2.py +0 -767
- empathy_os/socratic/forms 2.py +0 -624
- empathy_os/socratic/generator 2.py +0 -716
- empathy_os/socratic/llm_analyzer 2.py +0 -635
- empathy_os/socratic/mcp_server 2.py +0 -751
- empathy_os/socratic/session 2.py +0 -306
- empathy_os/socratic/storage 2.py +0 -635
- empathy_os/socratic/success 2.py +0 -719
- empathy_os/socratic/visual_editor 2.py +0 -812
- empathy_os/socratic/web_ui 2.py +0 -925
- empathy_os/workflows/batch_processing 2.py +0 -310
- empathy_os/workflows/release_prep_crew 2.py +0 -968
- empathy_os/workflows/test_coverage_boost_crew 2.py +0 -848
- {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/WHEEL +0 -0
- {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/entry_points.txt +0 -0
- {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/licenses/LICENSE +0 -0
- {empathy_framework-4.6.3.dist-info → empathy_framework-4.6.5.dist-info}/top_level.txt +0 -0
empathy_os/socratic/web_ui 2.py
DELETED
|
@@ -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("&", "&")
|
|
341
|
-
.replace("<", "<")
|
|
342
|
-
.replace(">", ">")
|
|
343
|
-
.replace('"', """)
|
|
344
|
-
.replace("'", "'"))
|
|
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>"""
|