@dilipod/ui 0.4.21 → 0.4.23

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.
@@ -0,0 +1,389 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { useState } from 'react'
5
+ import {
6
+ Target,
7
+ Crosshair,
8
+ ListNumbers,
9
+ TreeStructure,
10
+ Lightning,
11
+ Wrench,
12
+ Plugs,
13
+ ShieldCheck,
14
+ CaretDown,
15
+ CaretRight,
16
+ Clock,
17
+ TrendUp,
18
+ CurrencyEur,
19
+ Sparkle,
20
+ Lightbulb,
21
+ Robot,
22
+ } from '@phosphor-icons/react'
23
+ import { FlowchartDiagram } from './flowchart-diagram'
24
+ import { Badge } from './badge'
25
+
26
+ // ============================================
27
+ // Types
28
+ // ============================================
29
+
30
+ export interface WorkerSpecDocumentation {
31
+ scope: string | null
32
+ goal: string | null
33
+ steps: Array<{
34
+ step: number
35
+ title: string
36
+ description: string
37
+ tools_used?: string[]
38
+ }> | null
39
+ diagram: string | null
40
+ expected_impact: {
41
+ time_saved_per_occurrence_minutes: number
42
+ frequency: string
43
+ yearly_hours_saved: number
44
+ yearly_cost_savings_euros: number
45
+ qualitative_benefits: string[]
46
+ } | null
47
+ technical_requirements: string[] | null
48
+ integration_points: string[] | null
49
+ edge_cases_handled: Array<{
50
+ scenario: string
51
+ handling: string
52
+ }> | null
53
+ version: number
54
+ model_used: string | null
55
+ updated_at: string
56
+ }
57
+
58
+ export interface WorkerSpecProps {
59
+ /** The Knowledge Builder final documentation */
60
+ documentation: WorkerSpecDocumentation | null
61
+ /** Optional className for the container */
62
+ className?: string
63
+ }
64
+
65
+ // ============================================
66
+ // Frequency labels for impact display
67
+ // ============================================
68
+
69
+ const frequencyLabels: Record<string, string> = {
70
+ multiple_daily: 'occurrence',
71
+ daily: 'day',
72
+ weekly: 'week',
73
+ monthly: 'month',
74
+ quarterly: 'quarter',
75
+ yearly: 'year',
76
+ }
77
+
78
+ // ============================================
79
+ // Section Header Component
80
+ // ============================================
81
+
82
+ function SectionHeader({
83
+ icon,
84
+ title,
85
+ count,
86
+ expanded,
87
+ onToggle,
88
+ iconColor = 'text-[var(--cyan)]',
89
+ }: {
90
+ icon: React.ReactNode
91
+ title: string
92
+ count?: number
93
+ expanded: boolean
94
+ onToggle: () => void
95
+ iconColor?: string
96
+ }) {
97
+ return (
98
+ <button
99
+ onClick={onToggle}
100
+ className="flex items-center gap-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wide hover:text-[var(--black)] transition-colors w-full"
101
+ >
102
+ {expanded ? <CaretDown size={12} /> : <CaretRight size={12} />}
103
+ <span className={iconColor}>{icon}</span>
104
+ {title}
105
+ {count !== undefined && (
106
+ <span className="text-muted-foreground font-normal">({count})</span>
107
+ )}
108
+ </button>
109
+ )
110
+ }
111
+
112
+ // ============================================
113
+ // Main Component
114
+ // ============================================
115
+
116
+ export function WorkerSpec({ documentation, className }: WorkerSpecProps) {
117
+ const [expandedSections, setExpandedSections] = useState<Set<string>>(
118
+ new Set(['goal', 'scope', 'steps', 'diagram', 'impact', 'requirements', 'edge_cases'])
119
+ )
120
+
121
+ const toggleSection = (section: string) => {
122
+ setExpandedSections(prev => {
123
+ const next = new Set(prev)
124
+ if (next.has(section)) {
125
+ next.delete(section)
126
+ } else {
127
+ next.add(section)
128
+ }
129
+ return next
130
+ })
131
+ }
132
+
133
+ // Empty state
134
+ if (!documentation) {
135
+ return (
136
+ <div className={className}>
137
+ <div className="flex items-center gap-3 p-6 rounded-sm border border-dashed border-gray-300 bg-gray-50/50">
138
+ <div className="w-10 h-10 rounded-sm bg-gray-100 flex items-center justify-center">
139
+ <Robot size={20} className="text-gray-400" />
140
+ </div>
141
+ <div>
142
+ <h3 className="font-semibold text-[var(--black)]">Worker Spec Pending</h3>
143
+ <p className="text-sm text-muted-foreground">
144
+ The final specification will be generated automatically after the documentation is approved.
145
+ </p>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ )
150
+ }
151
+
152
+ const freqLabel = documentation.expected_impact?.frequency
153
+ ? frequencyLabels[documentation.expected_impact.frequency] || documentation.expected_impact.frequency
154
+ : 'occurrence'
155
+
156
+ return (
157
+ <div className={className}>
158
+ <div className="space-y-5">
159
+ {/* Goal */}
160
+ {documentation.goal && (
161
+ <div>
162
+ <SectionHeader
163
+ icon={<Target size={12} weight="fill" />}
164
+ title="Goal"
165
+ expanded={expandedSections.has('goal')}
166
+ onToggle={() => toggleSection('goal')}
167
+ />
168
+ {expandedSections.has('goal') && (
169
+ <p className="text-sm text-muted-foreground leading-relaxed pl-5 mt-2">
170
+ {documentation.goal}
171
+ </p>
172
+ )}
173
+ </div>
174
+ )}
175
+
176
+ {/* Scope */}
177
+ {documentation.scope && (
178
+ <div>
179
+ <SectionHeader
180
+ icon={<Crosshair size={12} weight="fill" />}
181
+ title="Scope"
182
+ expanded={expandedSections.has('scope')}
183
+ onToggle={() => toggleSection('scope')}
184
+ />
185
+ {expandedSections.has('scope') && (
186
+ <p className="text-sm text-muted-foreground leading-relaxed pl-5 mt-2">
187
+ {documentation.scope}
188
+ </p>
189
+ )}
190
+ </div>
191
+ )}
192
+
193
+ {/* Detailed Steps */}
194
+ {documentation.steps && documentation.steps.length > 0 && (
195
+ <div>
196
+ <SectionHeader
197
+ icon={<ListNumbers size={12} weight="fill" />}
198
+ title="Steps"
199
+ count={documentation.steps.length}
200
+ expanded={expandedSections.has('steps')}
201
+ onToggle={() => toggleSection('steps')}
202
+ />
203
+ {expandedSections.has('steps') && (
204
+ <div className="space-y-3 pl-5 mt-2">
205
+ {documentation.steps.map((step, i) => (
206
+ <div key={i} className="flex items-start gap-3">
207
+ <span className="w-6 h-6 rounded-sm bg-[var(--cyan)]/10 flex items-center justify-center text-xs font-bold text-[var(--cyan)] shrink-0 mt-0.5">
208
+ {step.step || i + 1}
209
+ </span>
210
+ <div className="flex-1 min-w-0">
211
+ <p className="text-sm font-medium text-[var(--black)]">{step.title}</p>
212
+ <p className="text-sm text-muted-foreground mt-0.5">{step.description}</p>
213
+ {step.tools_used && step.tools_used.length > 0 && (
214
+ <div className="flex flex-wrap gap-1 mt-1.5">
215
+ {step.tools_used.map((tool, j) => (
216
+ <span key={j} className="inline-flex items-center gap-1 px-2 py-0.5 rounded-sm bg-gray-100 text-[10px] font-medium text-gray-600">
217
+ <Wrench size={10} />
218
+ {tool}
219
+ </span>
220
+ ))}
221
+ </div>
222
+ )}
223
+ </div>
224
+ </div>
225
+ ))}
226
+ </div>
227
+ )}
228
+ </div>
229
+ )}
230
+
231
+ {/* Workflow Diagram */}
232
+ {documentation.diagram && (
233
+ <div>
234
+ <SectionHeader
235
+ icon={<TreeStructure size={12} weight="fill" />}
236
+ title="Workflow Diagram"
237
+ expanded={expandedSections.has('diagram')}
238
+ onToggle={() => toggleSection('diagram')}
239
+ iconColor="text-purple-500"
240
+ />
241
+ {expandedSections.has('diagram') && (
242
+ <div className="pl-5 mt-2">
243
+ <div className="bg-white border border-gray-100 rounded-sm p-4 overflow-x-auto">
244
+ <FlowchartDiagram mermaid={documentation.diagram} />
245
+ </div>
246
+ </div>
247
+ )}
248
+ </div>
249
+ )}
250
+
251
+ {/* Expected Impact */}
252
+ {documentation.expected_impact && (
253
+ <div>
254
+ <SectionHeader
255
+ icon={<Lightning size={12} weight="fill" />}
256
+ title="Expected Impact"
257
+ expanded={expandedSections.has('impact')}
258
+ onToggle={() => toggleSection('impact')}
259
+ iconColor="text-purple-500"
260
+ />
261
+ {expandedSections.has('impact') && (
262
+ <div className="pl-5 mt-2">
263
+ <div className="bg-emerald-50/50 border border-emerald-100 rounded-sm p-4">
264
+ <div className="grid grid-cols-3 gap-3 mb-3">
265
+ <div className="bg-white rounded-sm p-3 border border-emerald-100 text-center">
266
+ <Clock size={18} className="text-emerald-600 mx-auto mb-1" />
267
+ <p className="text-lg font-bold text-[var(--black)]">
268
+ {documentation.expected_impact.time_saved_per_occurrence_minutes} min
269
+ </p>
270
+ <p className="text-[10px] text-muted-foreground">saved per {freqLabel}</p>
271
+ </div>
272
+ <div className="bg-white rounded-sm p-3 border border-emerald-100 text-center">
273
+ <TrendUp size={18} className="text-emerald-600 mx-auto mb-1" />
274
+ <p className="text-lg font-bold text-[var(--black)]">
275
+ {documentation.expected_impact.yearly_hours_saved}h
276
+ </p>
277
+ <p className="text-[10px] text-muted-foreground">saved per year</p>
278
+ </div>
279
+ <div className="bg-white rounded-sm p-3 border border-emerald-100 text-center">
280
+ <CurrencyEur size={18} className="text-emerald-600 mx-auto mb-1" />
281
+ <p className="text-lg font-bold text-[var(--black)]">
282
+ €{documentation.expected_impact.yearly_cost_savings_euros.toLocaleString()}
283
+ </p>
284
+ <p className="text-[10px] text-muted-foreground">estimated yearly savings</p>
285
+ </div>
286
+ </div>
287
+ {documentation.expected_impact.qualitative_benefits && documentation.expected_impact.qualitative_benefits.length > 0 && (
288
+ <div className="space-y-1">
289
+ {documentation.expected_impact.qualitative_benefits.map((benefit, i) => (
290
+ <div key={i} className="flex items-start gap-2 text-sm text-emerald-700">
291
+ <Sparkle size={14} className="text-emerald-500 shrink-0 mt-0.5" weight="fill" />
292
+ {benefit}
293
+ </div>
294
+ ))}
295
+ </div>
296
+ )}
297
+ </div>
298
+ </div>
299
+ )}
300
+ </div>
301
+ )}
302
+
303
+ {/* Technical Requirements & Integration Points */}
304
+ {((documentation.technical_requirements && documentation.technical_requirements.length > 0) ||
305
+ (documentation.integration_points && documentation.integration_points.length > 0)) && (
306
+ <div>
307
+ <SectionHeader
308
+ icon={<Plugs size={12} weight="fill" />}
309
+ title="Requirements & Integrations"
310
+ expanded={expandedSections.has('requirements')}
311
+ onToggle={() => toggleSection('requirements')}
312
+ />
313
+ {expandedSections.has('requirements') && (
314
+ <div className="grid md:grid-cols-2 gap-4 pl-5 mt-2">
315
+ {documentation.technical_requirements && documentation.technical_requirements.length > 0 && (
316
+ <div>
317
+ <p className="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2">
318
+ Technical Requirements
319
+ </p>
320
+ <ul className="space-y-1.5">
321
+ {documentation.technical_requirements.map((req, i) => (
322
+ <li key={i} className="flex items-start gap-2 text-sm text-muted-foreground">
323
+ <Wrench size={12} className="text-gray-400 shrink-0 mt-1" />
324
+ {req}
325
+ </li>
326
+ ))}
327
+ </ul>
328
+ </div>
329
+ )}
330
+ {documentation.integration_points && documentation.integration_points.length > 0 && (
331
+ <div>
332
+ <p className="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2">
333
+ Integration Points
334
+ </p>
335
+ <ul className="space-y-1.5">
336
+ {documentation.integration_points.map((point, i) => (
337
+ <li key={i} className="flex items-start gap-2 text-sm text-muted-foreground">
338
+ <Plugs size={12} className="text-gray-400 shrink-0 mt-1" />
339
+ {point}
340
+ </li>
341
+ ))}
342
+ </ul>
343
+ </div>
344
+ )}
345
+ </div>
346
+ )}
347
+ </div>
348
+ )}
349
+
350
+ {/* Edge Cases Handled */}
351
+ {documentation.edge_cases_handled && documentation.edge_cases_handled.length > 0 && (
352
+ <div>
353
+ <SectionHeader
354
+ icon={<ShieldCheck size={12} weight="fill" />}
355
+ title="Edge Cases Handled"
356
+ count={documentation.edge_cases_handled.length}
357
+ expanded={expandedSections.has('edge_cases')}
358
+ onToggle={() => toggleSection('edge_cases')}
359
+ iconColor="text-amber-500"
360
+ />
361
+ {expandedSections.has('edge_cases') && (
362
+ <div className="space-y-2 pl-5 mt-2">
363
+ {documentation.edge_cases_handled.map((ec, i) => (
364
+ <div key={i} className="text-sm p-3 bg-gray-50 rounded-sm border border-gray-100">
365
+ <p className="font-medium text-[var(--black)]">{ec.scenario}</p>
366
+ <p className="text-muted-foreground mt-1">→ {ec.handling}</p>
367
+ </div>
368
+ ))}
369
+ </div>
370
+ )}
371
+ </div>
372
+ )}
373
+
374
+ {/* Version footer */}
375
+ <div className="pt-3 border-t border-gray-100 flex items-center gap-2 text-xs text-muted-foreground">
376
+ <Badge variant="outline" size="sm">v{documentation.version}</Badge>
377
+ {documentation.model_used && (
378
+ <>
379
+ <span>•</span>
380
+ <span>{documentation.model_used}</span>
381
+ </>
382
+ )}
383
+ <span>•</span>
384
+ <span>Updated {new Date(documentation.updated_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}</span>
385
+ </div>
386
+ </div>
387
+ </div>
388
+ )
389
+ }
@@ -97,7 +97,7 @@ export interface SimWorkflow {
97
97
  }>
98
98
  }
99
99
 
100
- export type WorkflowTemplate = 'blank' | 'request_analyzer' | 'documentation_updater' | 'execution_monitor' | 'usage_reporter' | 'custom'
100
+ export type WorkflowTemplate = 'blank' | 'request_analyzer' | 'knowledge_builder' | 'execution_monitor' | 'usage_reporter' | 'custom'
101
101
 
102
102
  export interface WorkflowViewerProps {
103
103
  /** The workflow definition to display */
package/src/index.ts CHANGED
@@ -293,6 +293,14 @@ export type {
293
293
  WorkflowTemplate
294
294
  } from './components/workflow-viewer'
295
295
 
296
+ // Worker Spec Components
297
+ export { WorkerSpec } from './components/worker-spec'
298
+ export type { WorkerSpecProps, WorkerSpecDocumentation } from './components/worker-spec'
299
+
300
+ // Flowchart Diagram Components
301
+ export { FlowchartDiagram } from './components/flowchart-diagram'
302
+ export type { FlowchartDiagramProps } from './components/flowchart-diagram'
303
+
296
304
  // Utilities
297
305
  export { cn } from './lib/utils'
298
306