File size: 26,999 Bytes
9cc23a0
fe875af
81ff144
 
 
ad68e43
d83ce16
81ff144
9cc23a0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fd19d92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0797c65
 
 
 
fd19d92
 
 
 
 
 
 
 
d83ce16
81ff144
9cc23a0
81ff144
 
 
9cc23a0
 
 
 
 
81ff144
fd19d92
 
ad68e43
fd19d92
81ff144
 
0797c65
 
 
fd19d92
 
 
 
 
 
 
d40bc1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0797c65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fd19d92
 
 
 
 
 
 
 
 
 
 
0797c65
 
fd19d92
0797c65
fd19d92
 
 
81ff144
9cc23a0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81ff144
 
 
 
 
 
 
 
 
 
9cc23a0
 
81ff144
 
 
9cc23a0
81ff144
fd19d92
81ff144
 
 
 
 
 
 
 
 
 
9cc23a0
 
 
 
 
81ff144
fd19d92
81ff144
 
 
 
 
 
 
 
 
 
 
 
 
ad68e43
 
 
 
 
81ff144
 
 
 
fd19d92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ad68e43
 
 
0797c65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fd19d92
 
 
 
 
 
 
 
 
 
81ff144
fd19d92
 
 
 
 
 
 
 
 
 
 
81ff144
0797c65
fd19d92
 
 
 
 
 
 
 
 
 
81ff144
0797c65
fd19d92
9cc23a0
 
 
 
fd19d92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9cc23a0
fd19d92
 
 
 
 
 
 
 
 
 
 
 
ad68e43
fd19d92
 
 
 
 
 
 
 
 
 
 
 
9cc23a0
fd19d92
 
 
 
 
 
 
 
 
 
9cc23a0
 
 
 
 
 
 
 
 
 
fd19d92
9cc23a0
 
 
 
 
 
 
 
 
 
 
fd19d92
9cc23a0
0797c65
fd19d92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0797c65
fd19d92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ad68e43
81ff144
 
 
 
 
 
 
 
fd19d92
fe875af
 
 
 
 
 
 
 
 
 
fd19d92
fe875af
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fd19d92
 
 
 
81ff144
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
import React, { useRef, useState } from 'react';
import { ArrowLeft, ArrowRight, CheckCircle2, FileText, Link2, Paperclip, PlusCircle, RefreshCw, StickyNote, Trash2 } from 'lucide-react';
import { motion } from 'framer-motion';
import { supabase } from '../services/supabase';
import { useAuth } from '../context/useAuth';
import type { UiMode } from '../services/uiMode';
import { getApiUrl } from '../services/runtimeConfig';

type ProjectSource =
  | {
      id: string;
      kind: 'link';
      label: string;
      url: string;
    }
  | {
      id: string;
      kind: 'note';
      label: string;
      content: string;
    }
  | {
      id: string;
      kind: 'file';
      label: string;
      fileName: string;
      mimeType: string;
      size: number;
      content?: string;
      extracted: boolean;
    };

const supportedTextMimeTypes = new Set([
  'text/plain',
  'text/markdown',
  'text/csv',
  'application/json',
]);

const formatFileSize = (size: number) => {
  if (size < 1024) return `${size} B`;
  if (size < 1024 * 1024) return `${Math.round(size / 1024)} KB`;
  return `${(size / (1024 * 1024)).toFixed(1)} MB`;
};

const buildContextPayload = (baseContext: string, sources: ProjectSource[]) => {
  const sections: string[] = [];

  const trimmedContext = baseContext.trim();
  if (trimmedContext) {
    sections.push(trimmedContext);
  }

  if (sources.length) {
    const sourceLines = sources.flatMap((source, index) => {
      if (source.kind === 'link') {
        return [`${index + 1}. [${source.label}](${source.url})`];
      }

      if (source.kind === 'note') {
        return [
          `${index + 1}. ${source.label}`,
          source.content,
        ];
      }

      const metadata = `${source.fileName} (${source.mimeType || 'unknown'}, ${formatFileSize(source.size)})`;
      if (source.extracted && source.content) {
        return [
          `${index + 1}. ${source.label} - ${metadata}`,
          source.content,
        ];
      }

      return [`${index + 1}. ${source.label} - ${metadata}`];
    });

    sections.push(`Project Sources:\n${sourceLines.join('\n\n')}`);
  }

  return sections.join('\n\n').trim();
};

const FieldHelp: React.FC<{ title: string; children: React.ReactNode }> = ({ title, children }) => (
  <aside className="field-help">
    <strong>{title}</strong>
    <p>{children}</p>
  </aside>
);

const wizardSteps = [
  {
    title: 'Basics',
    description: 'Name the workspace and describe the business outcome. Agents use this to understand what success looks like.'
  },
  {
    title: 'Context',
    description: 'Add constraints, acceptance criteria, tone, risks, and assumptions. Good context reduces generic task plans.'
  },
  {
    title: 'Sources',
    description: 'Attach links, notes, or files that should influence planning. This step is optional when the description is enough.'
  },
  {
    title: 'Review',
    description: 'Check the setup before creating the project. You will generate tasks from the project page after creation.'
  },
  {
    title: 'Magic Generation',
    description: 'Describe your project in natural language and attach reference docs. AI will pre-configure the workspace for you.'
  }
];

const expertAccessStep = {
  title: 'Workspace',
  description: 'Decide whether this project is personal or belongs to a team workspace.'
};

const NewProject: React.FC<{ uiMode: UiMode; initialData?: any; onCreated?: () => void }> = ({ uiMode, initialData, onCreated }) => {
  const { user } = useAuth();
  const fileInputRef = useRef<HTMLInputElement | null>(null);
  const [name, setName] = useState('');
  const [description, setDescription] = useState('');
  const [context, setContext] = useState('');
  const [sourceLabel, setSourceLabel] = useState('');
  const [sourceUrl, setSourceUrl] = useState('');
  const [noteLabel, setNoteLabel] = useState('');
  const [noteContent, setNoteContent] = useState('');
  const [sources, setSources] = useState<ProjectSource[]>([]);
  const [isPublic, setIsPublic] = useState(false);
  const [teams, setTeams] = useState<{ id: string; name: string }[]>([]);
  const [selectedTeamId, setSelectedTeamId] = useState<string | null>(null);
  const [showAdvancedSources, setShowAdvancedSources] = useState(uiMode === 'expert');
  const [wizardStep, setWizardStep] = useState(0);
  const [saving, setSaving] = useState(false);
  const [message, setMessage] = useState<string | null>(null);
  const [aiPrompt, setAiPrompt] = useState('');
  const [isGenerating, setIsGenerating] = useState(false);
  const [generationFiles, setGenerationFiles] = useState<File[]>([]);
  
  React.useEffect(() => {
    if (uiMode === 'expert') {
      fetchTeams();
    }
  }, [uiMode]);

  // Hydrate from Magic Bar / external data
  React.useEffect(() => {
    if (initialData) {
      if (initialData.name) setName(initialData.name);
      if (initialData.description) setDescription(initialData.description);
      if (initialData.context) setContext(initialData.context);
      if (initialData.sources && Array.isArray(initialData.sources)) {
        const aiSources: ProjectSource[] = initialData.sources.map((s: any) => ({
          id: crypto.randomUUID(),
          ...s
        }));
        setSources(aiSources);
      }
      // If we have initial data, jump to step 0 of the wizard (Basics) 
      // but ensure we are in the wizard view
      setWizardStep(0);
    }
  }, [initialData]);

  const handleAiGenerate = async () => {
    if (!aiPrompt.trim()) return;
    setIsGenerating(true);
    setMessage('AI is analyzing your request and documents...');

    try {
      const formData = new FormData();
      formData.append('prompt', aiPrompt);
      generationFiles.forEach(file => {
        formData.append('files', file);
      });

      const response = await fetch(`${getApiUrl()}/generator/generate-project`, {
        method: 'POST',
        body: formData
      });

      if (!response.ok) throw new Error('AI generation failed');
      
      const data = await response.json();
      
      setName(data.name || '');
      setDescription(data.description || '');
      setContext(data.context || '');
      
      if (data.sources && Array.isArray(data.sources)) {
        const aiSources: ProjectSource[] = data.sources.map((s: any) => ({
          id: crypto.randomUUID(),
          ...s
        }));
        setSources(prev => [...prev, ...aiSources]);
      }

      setMessage('Success! AI has drafted your project. Review the fields in the next steps.');
      setWizardStep(1);
    } catch (err: any) {
      console.error('AI Generation Error:', err);
      setMessage(`AI Error: ${err.message}`);
    } finally {
      setIsGenerating(false);
    }
  };

  const fetchTeams = async () => {
    try {
      const { data, error } = await supabase.from('teams').select('id, name');
      if (error) throw error;
      setTeams(data || []);
    } catch (err) {
      console.error('Failed to fetch teams:', err);
    }
  };
  const isWizard = true;
  const projectWizardSteps = uiMode === 'expert'
    ? [wizardSteps[4], wizardSteps[0], wizardSteps[1], wizardSteps[2], expertAccessStep, wizardSteps[3]]
    : [wizardSteps[4], wizardSteps[0], wizardSteps[1], wizardSteps[2], wizardSteps[3]];
  const reviewStepIndex = projectWizardSteps.length - 1;
  const accessStepIndex = uiMode === 'expert' ? 4 : -1;
  const currentWizardStep = projectWizardSteps[wizardStep] ?? projectWizardSteps[0];
  const isFirstWizardStep = wizardStep === 0;
  const isLastWizardStep = wizardStep === reviewStepIndex;

  const appendSource = (source: ProjectSource) => {
    setSources((current) => [...current, source]);
  };

  const handleAddLink = () => {
    if (!sourceUrl.trim()) {
      setMessage('Add a valid link before saving it.');
      return;
    }

    appendSource({
      id: crypto.randomUUID(),
      kind: 'link',
      label: sourceLabel.trim() || sourceUrl.trim(),
      url: sourceUrl.trim(),
    });

    setSourceLabel('');
    setSourceUrl('');
    setMessage(null);
  };

  const handleAddNote = () => {
    if (!noteContent.trim()) {
      setMessage('Write some note content before saving it.');
      return;
    }

    appendSource({
      id: crypto.randomUUID(),
      kind: 'note',
      label: noteLabel.trim() || 'Inline note',
      content: noteContent.trim(),
    });

    setNoteLabel('');
    setNoteContent('');
    setMessage(null);
  };

  const handleFileSelection = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(event.target.files ?? []);
    const nextSources: ProjectSource[] = [];

    for (const file of files) {
      const canExtractText =
        supportedTextMimeTypes.has(file.type) ||
        /\.(md|txt|csv|json)$/i.test(file.name);

      let content: string | undefined;
      if (canExtractText) {
        content = (await file.text()).slice(0, 12000);
      }

      nextSources.push({
        id: crypto.randomUUID(),
        kind: 'file',
        label: file.name,
        fileName: file.name,
        mimeType: file.type,
        size: file.size,
        content,
        extracted: Boolean(content),
      });
    }

    if (nextSources.length) {
      setSources((current) => [...current, ...nextSources]);
      setMessage(null);
    }

    event.target.value = '';
  };

  const removeSource = (id: string) => {
    setSources((current) => current.filter((source) => source.id !== id));
  };

  const handleSubmit = async (event: React.FormEvent) => {
    event.preventDefault();
    if (!user) {
      setMessage('You must be signed in to create a project.');
      return;
    }

    setSaving(true);
    setMessage(null);

    const contextPayload = buildContextPayload(context, sources);

    const { error } = await supabase.from('projects').insert({
      name,
      description,
      context: contextPayload,
      owner_id: user.id,
      team_id: selectedTeamId,
      is_public: isPublic,
      status: 'active'
    });

    if (error) {
      setMessage(error.message);
    } else {
      setName('');
      setDescription('');
      setContext('');
      setSourceLabel('');
      setSourceUrl('');
      setNoteLabel('');
      setNoteContent('');
      setSources([]);
      setIsPublic(false);
      setWizardStep(0);
      setMessage('Project created successfully.');
      window.setTimeout(() => onCreated?.(), 500);
    }

    setSaving(false);
  };

  return (
    <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} className="form-panel form-panel-wide">
      <div className="panel-heading">
        <PlusCircle size={32} color="var(--accent)" />
        <div>
          <h2>Create Project</h2>
          <p style={{ color: 'var(--text-dim)', fontSize: '0.9rem' }}>
            {uiMode === 'guided'
              ? 'Describe the goal, add relevant context, and create the workspace.'
              : 'Start a workspace for agents, tasks, context, and reviews.'}
          </p>
        </div>
      </div>

      <form className="glass-panel project-form" onSubmit={handleSubmit}>
        {isWizard && (
          <div className="wizard-panel">
            <div className="wizard-steps" aria-label="Create project steps">
              {projectWizardSteps.map((step, index) => (
                <button
                  key={step.title}
                  className={`wizard-step ${wizardStep === index ? 'active' : ''} ${wizardStep > index ? 'complete' : ''}`}
                  type="button"
                  onClick={() => setWizardStep(index)}
                >
                  <span>{index + 1}</span>
                  {step.title}
                </button>
              ))}
            </div>
            <div className="wizard-explanation">
              <strong>{currentWizardStep.title}</strong>
              <p>{currentWizardStep.description}</p>
            </div>
          </div>
        )}

        {wizardStep === 0 && (
          <motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} className="wizard-form">
            <div className="form-group">
              <label>What would you like to build?</label>
              <textarea 
                placeholder='e.g., "Make me a security audit project for a Fintech app. Use the attached compliance docs as reference. I need to focus on OWASP Top 10."'
                value={aiPrompt}
                onChange={(e) => setAiPrompt(e.target.value)}
                style={{ height: '160px', resize: 'none' }}
              />
            </div>
            
            <div className="form-group">
              <label>Reference Documents (Optional)</label>
              <div 
                className="drop-zone"
                onDragOver={(e) => e.preventDefault()}
                onDrop={(e) => {
                  e.preventDefault();
                  const dropped = Array.from(e.dataTransfer.files);
                  setGenerationFiles(prev => [...prev, ...dropped]);
                }}
                onClick={() => {
                   const input = document.createElement('input');
                   input.type = 'file';
                   input.multiple = true;
                   input.onchange = (e) => {
                     const selected = Array.from((e.target as HTMLInputElement).files || []);
                     setGenerationFiles(prev => [...prev, ...selected]);
                   };
                   input.click();
                }}
                style={{
                  border: '2px dashed var(--border)',
                  borderRadius: 'var(--radius-md)',
                  padding: 'var(--space-xl)',
                  textAlign: 'center',
                  cursor: 'pointer',
                  background: 'rgba(255,255,255,0.02)'
                }}
              >
                <Paperclip size={24} style={{ marginBottom: '8px', opacity: 0.5 }} />
                <p style={{ margin: 0 }}>Click or drag files to use as AI context</p>
                <span style={{ fontSize: '0.8rem', opacity: 0.6 }}>Supports PDF, Text, Markdown, JSON</span>
              </div>
              
              {generationFiles.length > 0 && (
                <div style={{ marginTop: 'var(--space-md)', display: 'flex', flexDirection: 'column', gap: '4px' }}>
                  {generationFiles.map((f, i) => (
                    <div key={i} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: 'rgba(255,255,255,0.04)', padding: '8px 12px', borderRadius: '4px' }}>
                      <div style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.9rem' }}>
                        <FileText size={14} />
                        <span>{f.name}</span>
                      </div>
                      <button 
                        className="btn-icon" 
                        onClick={() => setGenerationFiles(prev => prev.filter((_, idx) => idx !== i))}
                        style={{ color: 'var(--danger)' }}
                      >
                        <Trash2 size={14} />
                      </button>
                    </div>
                  ))}
                </div>
              )}
            </div>

            <div style={{ marginTop: 'var(--space-xl)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
              <button className="btn btn-glass" type="button" onClick={() => setWizardStep(1)}>
                Skip to manual setup
              </button>
              <button 
                className="btn btn-primary" 
                type="button"
                onClick={handleAiGenerate}
                disabled={!aiPrompt.trim() || isGenerating}
              >
                {isGenerating ? <RefreshCw className="spin" size={18} /> : <PlusCircle size={18} />}
                {isGenerating ? 'Generating...' : 'Generate Project Structure'}
              </button>
            </div>
          </motion.div>
        )}

        {wizardStep === 1 && (
        <>
        <div className="field-with-help">
          <label>
            <span>Project Name</span>
            <input value={name} onChange={(event) => setName(event.target.value)} required placeholder="Customer onboarding automation" />
          </label>
          <FieldHelp title="What this controls">
            This becomes the workspace title shown on the dashboard, task pages, reports, and spatial view. Use a short outcome-oriented name.
          </FieldHelp>
        </div>

        <div className="field-with-help">
          <label>
            <span>Description</span>
            <textarea value={description} onChange={(event) => setDescription(event.target.value)} placeholder="What should this project accomplish?" rows={4} />
          </label>
          <FieldHelp title="How agents use it">
            The planner reads this as the main objective when decomposing work. Include the desired result, audience, and success criteria.
          </FieldHelp>
        </div>
        </>
        )}

        {wizardStep === 2 && (
        <div className="field-with-help">
          <label>
            <span>Context</span>
            <textarea value={context} onChange={(event) => setContext(event.target.value)} placeholder="Business constraints, preferred tone, source links, acceptance criteria..." rows={6} />
          </label>
          <FieldHelp title="When to add context">
            Add constraints, assumptions, tone, links, examples, acceptance criteria, and known risks. This reduces generic agent output.
          </FieldHelp>
        </div>
        )}

        {wizardStep === 3 && (
        <div className="default-agent-panel project-sources-panel" style={{ gap: 'var(--space-lg)' }}>
          <div className="settings-section-title">
            <Paperclip size={20} color="var(--accent)" />
            <h3>Project Sources</h3>
          </div>
          <div className="field-with-help field-with-help-compact">
            <div>
              {uiMode === 'guided' && !showAdvancedSources && (
                <>
                  <p style={{ color: 'var(--text-dim)', fontSize: '0.9rem' }}>
                    Add the links, notes, or files that should shape the plan. Skip this if the description already contains enough context.
                  </p>
                  <button className="btn btn-glass" type="button" onClick={() => setShowAdvancedSources(true)}>
                    <Paperclip size={16} />
                    Add Sources
                  </button>
                </>
              )}

              {(uiMode === 'expert' || showAdvancedSources) && (
                <>
                  <div className="responsive-two-col">
                    <label>
                      <span>Link Label</span>
                      <input value={sourceLabel} onChange={(event) => setSourceLabel(event.target.value)} placeholder="Market report" />
                    </label>
                    <label>
                      <span>Link URL</span>
                      <input value={sourceUrl} onChange={(event) => setSourceUrl(event.target.value)} placeholder="https://..." />
                    </label>
                  </div>
                  <button className="btn btn-glass" type="button" onClick={handleAddLink}>
                    <Link2 size={16} />
                    Add Link
                  </button>

                  <label>
                    <span>Quick Note</span>
                    <input value={noteLabel} onChange={(event) => setNoteLabel(event.target.value)} placeholder="Stakeholder note" />
                  </label>
                  <label>
                    <span>Note Content</span>
                    <textarea value={noteContent} onChange={(event) => setNoteContent(event.target.value)} rows={3} placeholder="Paste text, markdown, requirements, or snippets..." />
                  </label>
                  <button className="btn btn-glass" type="button" onClick={handleAddNote}>
                    <StickyNote size={16} />
                    Add Text
                  </button>

                  <input
                    ref={fileInputRef}
                    type="file"
                    multiple
                    accept=".pdf,.md,.txt,.doc,.docx,.xls,.xlsx,.csv,.json,.rtf"
                    onChange={handleFileSelection}
                    style={{ display: 'none' }}
                  />
                  <button className="btn btn-glass" type="button" onClick={() => fileInputRef.current?.click()}>
                    <Paperclip size={16} />
                    Add Files
                  </button>

                  <p style={{ color: 'var(--text-dim)', fontSize: '0.85rem', marginTop: '-0.25rem' }}>
                    Text and markdown files are embedded into project context. PDF, Word, and Excel files are stored as named references in the context.
                  </p>
                </>
              )}
            </div>
            <FieldHelp title="What sources do">
              Sources are appended to the project context before agents plan work. Use links for references, notes for stakeholder input, and files for specs or datasets.
            </FieldHelp>
          </div>

          {sources.length > 0 && (
            <div className="task-list">
              {sources.map((source) => (
                <div key={source.id} className="task-row">
                  <div style={{ flex: 1 }}>
                    <strong>{source.label}</strong>
                    <p>
                      {source.kind === 'link' && source.url}
                      {source.kind === 'note' && source.content}
                      {source.kind === 'file' && `${source.fileName} - ${formatFileSize(source.size)}${source.extracted ? ' - text imported' : ' - reference only'}`}
                    </p>
                  </div>
                  <button className="btn btn-glass btn-sm" type="button" onClick={() => removeSource(source.id)}>
                    <Trash2 size={14} />
                    Remove
                  </button>
                </div>
              ))}
            </div>
          )}
        </div>
        )}

        {uiMode === 'expert' && wizardStep === accessStepIndex && (
          <div className="expert-access-fields" style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-lg)' }}>
            <div className="field-with-help">
              <label>
                <span>Team Workspace (Optional)</span>
                <select 
                  className="glass-input" 
                  value={selectedTeamId || ''} 
                  onChange={(e) => setSelectedTeamId(e.target.value || null)}
                >
                  <option value="">Personal Project (No Team)</option>
                  {teams.map(team => (
                    <option key={team.id} value={team.id}>{team.name}</option>
                  ))}
                </select>
              </label>
              <FieldHelp title="Shared Context">
                Projects assigned to a team are visible to all team members according to their roles (admin, editor, viewer).
              </FieldHelp>
            </div>

            <div className="field-with-help">
              <label className="toggle-row">
                <input type="checkbox" checked={isPublic} onChange={(event) => setIsPublic(event.target.checked)} />
                <span>Make project visible to all authenticated users (Public)</span>
              </label>
              <FieldHelp title="Global Visibility">
                Public projects can be read by any authenticated user in the entire platform. Use this for open templates or public datasets.
              </FieldHelp>
            </div>
          </div>
        )}

        {wizardStep === reviewStepIndex && (
          <div className="wizard-review">
            <div>
              <span>Project name</span>
              <strong>{name.trim() || 'Missing project name'}</strong>
            </div>
            <div>
              <span>Description</span>
              <p>{description.trim() || 'No description provided.'}</p>
            </div>
            <div>
              <span>Workspace</span>
              <p>{selectedTeamId ? `Team: ${teams.find(t => t.id === selectedTeamId)?.name}` : 'Personal project'}</p>
            </div>
            <div>
              <span>Visibility</span>
              <p>{isPublic ? 'Public' : 'Private'}</p>
            </div>
            <div>
              <span>Context</span>
              <p>{context.trim() || 'No extra context provided.'}</p>
            </div>
            <div>
              <span>Sources</span>
              <p>{sources.length > 0 ? `${sources.length} source${sources.length === 1 ? '' : 's'} attached.` : 'No sources attached.'}</p>
            </div>
          </div>
        )}

        {message && (
          <div className="inline-status">
            {message.includes('success') ? <CheckCircle2 size={16} color="var(--success)" /> : <FileText size={16} color="var(--warning)" />}
            <span>{message}</span>
          </div>
        )}

        <div className="field-with-help field-with-help-action">
          <div className="wizard-actions" style={{ display: 'flex', gap: 'var(--space-md)', width: '100%' }}>
            <button 
              className="btn btn-glass" 
              type="button" 
              onClick={() => setWizardStep((step) => Math.max(0, step - 1))} 
              disabled={isFirstWizardStep || saving}
              style={{ flex: 1 }}
            >
              <ArrowLeft size={18} />
              Back
            </button>
            {!isLastWizardStep ? (
              <button
                className="btn btn-primary"
                type="button"
                onClick={() => {
                  if (wizardStep === 1 && !name.trim()) {
                    setMessage('Project name is required');
                    return;
                  }
                  setWizardStep((step) => Math.min(projectWizardSteps.length - 1, step + 1));
                }}
                disabled={saving}
                style={{ flex: 1 }}
              >
                Next
                <ArrowRight size={18} />
              </button>
            ) : (
              <button 
                className="btn btn-primary" 
                type="submit" 
                disabled={saving || !name.trim()}
                style={{ flex: 1 }}
              >
                {saving ? <RefreshCw className="spin" size={18} /> : <PlusCircle size={18} />}
                {saving ? 'Creating...' : 'Create Project'}
              </button>
            )}
          </div>
          <FieldHelp title="Next step">
            After creation, open the project and run the orchestrator to generate tasks. Review outputs before approving the final report.
          </FieldHelp>
        </div>
      </form>
    </motion.div>
  );
};

export default NewProject;