Reubencf commited on
Commit
2a1df12
·
1 Parent(s): 7f20600

fixing the UI issues

Browse files
app/api/claude/generate-flutter/route.ts DELETED
@@ -1,716 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server'
2
-
3
- export async function POST(request: NextRequest) {
4
- try {
5
- const { prompt } = await request.json()
6
-
7
- if (!prompt) {
8
- return NextResponse.json(
9
- { error: 'Prompt is required' },
10
- { status: 400 }
11
- )
12
- }
13
-
14
- // Check if we have an API key
15
- const apiKey = process.env.ANTHROPIC_API_KEY
16
-
17
- if (apiKey) {
18
- // Call Claude API
19
- try {
20
- const response = await fetch('https://api.anthropic.com/v1/messages', {
21
- method: 'POST',
22
- headers: {
23
- 'Content-Type': 'application/json',
24
- 'x-api-key': apiKey,
25
- 'anthropic-version': '2023-06-01'
26
- },
27
- body: JSON.stringify({
28
- model: 'claude-3-haiku-20240307',
29
- max_tokens: 2048,
30
- messages: [
31
- {
32
- role: 'user',
33
- content: `Generate clean, complete Flutter/Dart code for the following request.
34
- Return ONLY the code without any explanations, markdown formatting, or comments about the code.
35
- The code should be ready to run in DartPad.
36
-
37
- Request: ${prompt}`
38
- }
39
- ],
40
- system: `You are a Flutter code generator. Generate clean, working Flutter/Dart code that can run directly in DartPad.
41
- - Always include necessary imports
42
- - Use Material Design components
43
- - Ensure the code is complete and runnable
44
- - Do not include pubspec.yaml as DartPad handles dependencies
45
- - Do not include any explanations or markdown formatting
46
- - Return only pure Dart/Flutter code`
47
- })
48
- })
49
-
50
- if (response.ok) {
51
- const data = await response.json()
52
- const code = data.content[0].text
53
-
54
- // Clean the code if it has any markdown formatting
55
- const cleanedCode = code
56
- .replace(/```dart\n?/g, '')
57
- .replace(/```flutter\n?/g, '')
58
- .replace(/```\n?/g, '')
59
- .trim()
60
-
61
- return NextResponse.json({ code: cleanedCode })
62
- }
63
- } catch (error) {
64
- console.error('Claude API error:', error)
65
- }
66
- }
67
-
68
- // Fallback: Generate mock Flutter code based on the prompt
69
- const mockCode = generateMockFlutterCode(prompt)
70
- return NextResponse.json({ code: mockCode })
71
-
72
- } catch (error) {
73
- console.error('Error generating Flutter code:', error)
74
- return NextResponse.json(
75
- { error: 'Failed to generate Flutter code' },
76
- { status: 500 }
77
- )
78
- }
79
- }
80
-
81
- function generateMockFlutterCode(prompt: string): string {
82
- // Extract key words from the prompt to customize the generated code
83
- const promptLower = prompt.toLowerCase()
84
-
85
- // Check for specific UI elements mentioned
86
- const hasButton = promptLower.includes('button')
87
- const hasList = promptLower.includes('list')
88
- const hasForm = promptLower.includes('form') || promptLower.includes('input')
89
- const hasCounter = promptLower.includes('counter')
90
- const hasTabs = promptLower.includes('tab')
91
- const hasDrawer = promptLower.includes('drawer') || promptLower.includes('menu')
92
-
93
- // Generate appropriate code based on keywords
94
- if (hasForm) {
95
- return generateFormCode(prompt)
96
- } else if (hasList) {
97
- return generateListCode(prompt)
98
- } else if (hasTabs) {
99
- return generateTabsCode(prompt)
100
- } else if (hasDrawer) {
101
- return generateDrawerCode(prompt)
102
- } else if (hasCounter) {
103
- return generateCounterCode(prompt)
104
- } else {
105
- return generateBasicCode(prompt)
106
- }
107
- }
108
-
109
- function generateBasicCode(prompt: string): string {
110
- return `import 'package:flutter/material.dart';
111
-
112
- void main() => runApp(MyApp());
113
-
114
- class MyApp extends StatelessWidget {
115
- @override
116
- Widget build(BuildContext context) {
117
- return MaterialApp(
118
- title: '${prompt}',
119
- theme: ThemeData(
120
- primarySwatch: Colors.blue,
121
- visualDensity: VisualDensity.adaptivePlatformDensity,
122
- ),
123
- home: HomePage(),
124
- );
125
- }
126
- }
127
-
128
- class HomePage extends StatelessWidget {
129
- @override
130
- Widget build(BuildContext context) {
131
- return Scaffold(
132
- appBar: AppBar(
133
- title: Text('${prompt}'),
134
- elevation: 0,
135
- ),
136
- body: Center(
137
- child: Column(
138
- mainAxisAlignment: MainAxisAlignment.center,
139
- children: [
140
- Icon(
141
- Icons.flutter_dash,
142
- size: 100,
143
- color: Colors.blue,
144
- ),
145
- SizedBox(height: 20),
146
- Text(
147
- '${prompt}',
148
- style: Theme.of(context).textTheme.headline4,
149
- textAlign: TextAlign.center,
150
- ),
151
- SizedBox(height: 20),
152
- ElevatedButton(
153
- onPressed: () {
154
- ScaffoldMessenger.of(context).showSnackBar(
155
- SnackBar(content: Text('Button clicked!')),
156
- );
157
- },
158
- child: Text('Get Started'),
159
- ),
160
- ],
161
- ),
162
- ),
163
- );
164
- }
165
- }`
166
- }
167
-
168
- function generateCounterCode(prompt: string): string {
169
- return `import 'package:flutter/material.dart';
170
-
171
- void main() => runApp(MyApp());
172
-
173
- class MyApp extends StatelessWidget {
174
- @override
175
- Widget build(BuildContext context) {
176
- return MaterialApp(
177
- title: 'Counter App',
178
- theme: ThemeData(
179
- primarySwatch: Colors.purple,
180
- ),
181
- home: CounterPage(),
182
- );
183
- }
184
- }
185
-
186
- class CounterPage extends StatefulWidget {
187
- @override
188
- _CounterPageState createState() => _CounterPageState();
189
- }
190
-
191
- class _CounterPageState extends State<CounterPage> {
192
- int _counter = 0;
193
-
194
- void _incrementCounter() {
195
- setState(() {
196
- _counter++;
197
- });
198
- }
199
-
200
- void _decrementCounter() {
201
- setState(() {
202
- _counter--;
203
- });
204
- }
205
-
206
- void _resetCounter() {
207
- setState(() {
208
- _counter = 0;
209
- });
210
- }
211
-
212
- @override
213
- Widget build(BuildContext context) {
214
- return Scaffold(
215
- appBar: AppBar(
216
- title: Text('${prompt}'),
217
- ),
218
- body: Center(
219
- child: Column(
220
- mainAxisAlignment: MainAxisAlignment.center,
221
- children: [
222
- Text(
223
- 'Counter Value:',
224
- style: TextStyle(fontSize: 20),
225
- ),
226
- Text(
227
- '$_counter',
228
- style: TextStyle(fontSize: 72, fontWeight: FontWeight.bold),
229
- ),
230
- SizedBox(height: 40),
231
- Row(
232
- mainAxisAlignment: MainAxisAlignment.center,
233
- children: [
234
- FloatingActionButton(
235
- onPressed: _decrementCounter,
236
- tooltip: 'Decrement',
237
- child: Icon(Icons.remove),
238
- ),
239
- SizedBox(width: 20),
240
- FloatingActionButton(
241
- onPressed: _resetCounter,
242
- tooltip: 'Reset',
243
- child: Icon(Icons.refresh),
244
- ),
245
- SizedBox(width: 20),
246
- FloatingActionButton(
247
- onPressed: _incrementCounter,
248
- tooltip: 'Increment',
249
- child: Icon(Icons.add),
250
- ),
251
- ],
252
- ),
253
- ],
254
- ),
255
- ),
256
- );
257
- }
258
- }`
259
- }
260
-
261
- function generateListCode(prompt: string): string {
262
- return `import 'package:flutter/material.dart';
263
-
264
- void main() => runApp(MyApp());
265
-
266
- class MyApp extends StatelessWidget {
267
- @override
268
- Widget build(BuildContext context) {
269
- return MaterialApp(
270
- title: 'List App',
271
- theme: ThemeData(
272
- primarySwatch: Colors.indigo,
273
- ),
274
- home: ListPage(),
275
- );
276
- }
277
- }
278
-
279
- class ListPage extends StatefulWidget {
280
- @override
281
- _ListPageState createState() => _ListPageState();
282
- }
283
-
284
- class _ListPageState extends State<ListPage> {
285
- final List<String> items = List.generate(20, (i) => 'Item \${i + 1}');
286
-
287
- @override
288
- Widget build(BuildContext context) {
289
- return Scaffold(
290
- appBar: AppBar(
291
- title: Text('${prompt}'),
292
- actions: [
293
- IconButton(
294
- icon: Icon(Icons.search),
295
- onPressed: () {},
296
- ),
297
- ],
298
- ),
299
- body: ListView.builder(
300
- itemCount: items.length,
301
- itemBuilder: (context, index) {
302
- return Card(
303
- margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
304
- child: ListTile(
305
- leading: CircleAvatar(
306
- child: Text('\${index + 1}'),
307
- backgroundColor: Colors.indigo,
308
- ),
309
- title: Text(items[index]),
310
- subtitle: Text('Description for item \${index + 1}'),
311
- trailing: Icon(Icons.arrow_forward_ios),
312
- onTap: () {
313
- ScaffoldMessenger.of(context).showSnackBar(
314
- SnackBar(content: Text('Tapped on \${items[index]}')),
315
- );
316
- },
317
- ),
318
- );
319
- },
320
- ),
321
- floatingActionButton: FloatingActionButton(
322
- onPressed: () {
323
- setState(() {
324
- items.add('Item \${items.length + 1}');
325
- });
326
- },
327
- child: Icon(Icons.add),
328
- ),
329
- );
330
- }
331
- }`
332
- }
333
-
334
- function generateFormCode(prompt: string): string {
335
- return `import 'package:flutter/material.dart';
336
-
337
- void main() => runApp(MyApp());
338
-
339
- class MyApp extends StatelessWidget {
340
- @override
341
- Widget build(BuildContext context) {
342
- return MaterialApp(
343
- title: 'Form App',
344
- theme: ThemeData(
345
- primarySwatch: Colors.teal,
346
- ),
347
- home: FormPage(),
348
- );
349
- }
350
- }
351
-
352
- class FormPage extends StatefulWidget {
353
- @override
354
- _FormPageState createState() => _FormPageState();
355
- }
356
-
357
- class _FormPageState extends State<FormPage> {
358
- final _formKey = GlobalKey<FormState>();
359
- final _nameController = TextEditingController();
360
- final _emailController = TextEditingController();
361
- final _messageController = TextEditingController();
362
- bool _agreeToTerms = false;
363
-
364
- @override
365
- Widget build(BuildContext context) {
366
- return Scaffold(
367
- appBar: AppBar(
368
- title: Text('${prompt}'),
369
- ),
370
- body: SingleChildScrollView(
371
- padding: EdgeInsets.all(16),
372
- child: Form(
373
- key: _formKey,
374
- child: Column(
375
- crossAxisAlignment: CrossAxisAlignment.stretch,
376
- children: [
377
- TextFormField(
378
- controller: _nameController,
379
- decoration: InputDecoration(
380
- labelText: 'Name',
381
- prefixIcon: Icon(Icons.person),
382
- border: OutlineInputBorder(),
383
- ),
384
- validator: (value) {
385
- if (value == null || value.isEmpty) {
386
- return 'Please enter your name';
387
- }
388
- return null;
389
- },
390
- ),
391
- SizedBox(height: 16),
392
- TextFormField(
393
- controller: _emailController,
394
- decoration: InputDecoration(
395
- labelText: 'Email',
396
- prefixIcon: Icon(Icons.email),
397
- border: OutlineInputBorder(),
398
- ),
399
- keyboardType: TextInputType.emailAddress,
400
- validator: (value) {
401
- if (value == null || value.isEmpty) {
402
- return 'Please enter your email';
403
- }
404
- if (!value.contains('@')) {
405
- return 'Please enter a valid email';
406
- }
407
- return null;
408
- },
409
- ),
410
- SizedBox(height: 16),
411
- TextFormField(
412
- controller: _messageController,
413
- decoration: InputDecoration(
414
- labelText: 'Message',
415
- prefixIcon: Icon(Icons.message),
416
- border: OutlineInputBorder(),
417
- ),
418
- maxLines: 4,
419
- validator: (value) {
420
- if (value == null || value.isEmpty) {
421
- return 'Please enter a message';
422
- }
423
- return null;
424
- },
425
- ),
426
- SizedBox(height: 16),
427
- CheckboxListTile(
428
- title: Text('I agree to the terms and conditions'),
429
- value: _agreeToTerms,
430
- onChanged: (value) {
431
- setState(() {
432
- _agreeToTerms = value ?? false;
433
- });
434
- },
435
- ),
436
- SizedBox(height: 24),
437
- ElevatedButton(
438
- onPressed: () {
439
- if (_formKey.currentState!.validate() && _agreeToTerms) {
440
- ScaffoldMessenger.of(context).showSnackBar(
441
- SnackBar(
442
- content: Text('Form submitted successfully!'),
443
- backgroundColor: Colors.green,
444
- ),
445
- );
446
- } else if (!_agreeToTerms) {
447
- ScaffoldMessenger.of(context).showSnackBar(
448
- SnackBar(
449
- content: Text('Please agree to terms and conditions'),
450
- backgroundColor: Colors.red,
451
- ),
452
- );
453
- }
454
- },
455
- child: Padding(
456
- padding: EdgeInsets.all(16),
457
- child: Text('Submit'),
458
- ),
459
- ),
460
- ],
461
- ),
462
- ),
463
- ),
464
- );
465
- }
466
-
467
- @override
468
- void dispose() {
469
- _nameController.dispose();
470
- _emailController.dispose();
471
- _messageController.dispose();
472
- super.dispose();
473
- }
474
- }`
475
- }
476
-
477
- function generateTabsCode(prompt: string): string {
478
- return `import 'package:flutter/material.dart';
479
-
480
- void main() => runApp(MyApp());
481
-
482
- class MyApp extends StatelessWidget {
483
- @override
484
- Widget build(BuildContext context) {
485
- return MaterialApp(
486
- title: 'Tabs App',
487
- theme: ThemeData(
488
- primarySwatch: Colors.deepPurple,
489
- ),
490
- home: TabsPage(),
491
- );
492
- }
493
- }
494
-
495
- class TabsPage extends StatelessWidget {
496
- @override
497
- Widget build(BuildContext context) {
498
- return DefaultTabController(
499
- length: 3,
500
- child: Scaffold(
501
- appBar: AppBar(
502
- title: Text('${prompt}'),
503
- bottom: TabBar(
504
- tabs: [
505
- Tab(icon: Icon(Icons.home), text: 'Home'),
506
- Tab(icon: Icon(Icons.star), text: 'Favorites'),
507
- Tab(icon: Icon(Icons.settings), text: 'Settings'),
508
- ],
509
- ),
510
- ),
511
- body: TabBarView(
512
- children: [
513
- // Home Tab
514
- Center(
515
- child: Column(
516
- mainAxisAlignment: MainAxisAlignment.center,
517
- children: [
518
- Icon(Icons.home, size: 100, color: Colors.deepPurple),
519
- SizedBox(height: 20),
520
- Text('Home Page', style: TextStyle(fontSize: 24)),
521
- ],
522
- ),
523
- ),
524
- // Favorites Tab
525
- ListView.builder(
526
- itemCount: 10,
527
- itemBuilder: (context, index) {
528
- return ListTile(
529
- leading: Icon(Icons.star, color: Colors.amber),
530
- title: Text('Favorite Item \${index + 1}'),
531
- subtitle: Text('This is your favorite item'),
532
- );
533
- },
534
- ),
535
- // Settings Tab
536
- ListView(
537
- children: [
538
- SwitchListTile(
539
- title: Text('Enable Notifications'),
540
- subtitle: Text('Receive push notifications'),
541
- value: true,
542
- onChanged: (value) {},
543
- ),
544
- ListTile(
545
- leading: Icon(Icons.language),
546
- title: Text('Language'),
547
- subtitle: Text('English'),
548
- trailing: Icon(Icons.arrow_forward_ios),
549
- ),
550
- ListTile(
551
- leading: Icon(Icons.dark_mode),
552
- title: Text('Dark Mode'),
553
- subtitle: Text('Toggle dark theme'),
554
- trailing: Switch(
555
- value: false,
556
- onChanged: (value) {},
557
- ),
558
- ),
559
- ListTile(
560
- leading: Icon(Icons.info),
561
- title: Text('About'),
562
- subtitle: Text('Version 1.0.0'),
563
- ),
564
- ],
565
- ),
566
- ],
567
- ),
568
- ),
569
- );
570
- }
571
- }`
572
- }
573
-
574
- function generateDrawerCode(prompt: string): string {
575
- return `import 'package:flutter/material.dart';
576
-
577
- void main() => runApp(MyApp());
578
-
579
- class MyApp extends StatelessWidget {
580
- @override
581
- Widget build(BuildContext context) {
582
- return MaterialApp(
583
- title: 'Navigation Drawer App',
584
- theme: ThemeData(
585
- primarySwatch: Colors.orange,
586
- ),
587
- home: DrawerPage(),
588
- );
589
- }
590
- }
591
-
592
- class DrawerPage extends StatefulWidget {
593
- @override
594
- _DrawerPageState createState() => _DrawerPageState();
595
- }
596
-
597
- class _DrawerPageState extends State<DrawerPage> {
598
- int _selectedIndex = 0;
599
-
600
- final List<Widget> _pages = [
601
- Center(
602
- child: Column(
603
- mainAxisAlignment: MainAxisAlignment.center,
604
- children: [
605
- Icon(Icons.home, size: 100, color: Colors.orange),
606
- Text('Home Page', style: TextStyle(fontSize: 24)),
607
- ],
608
- ),
609
- ),
610
- Center(
611
- child: Column(
612
- mainAxisAlignment: MainAxisAlignment.center,
613
- children: [
614
- Icon(Icons.person, size: 100, color: Colors.orange),
615
- Text('Profile Page', style: TextStyle(fontSize: 24)),
616
- ],
617
- ),
618
- ),
619
- Center(
620
- child: Column(
621
- mainAxisAlignment: MainAxisAlignment.center,
622
- children: [
623
- Icon(Icons.settings, size: 100, color: Colors.orange),
624
- Text('Settings Page', style: TextStyle(fontSize: 24)),
625
- ],
626
- ),
627
- ),
628
- ];
629
-
630
- @override
631
- Widget build(BuildContext context) {
632
- return Scaffold(
633
- appBar: AppBar(
634
- title: Text('${prompt}'),
635
- ),
636
- drawer: Drawer(
637
- child: ListView(
638
- padding: EdgeInsets.zero,
639
- children: [
640
- DrawerHeader(
641
- decoration: BoxDecoration(
642
- color: Colors.orange,
643
- ),
644
- child: Column(
645
- crossAxisAlignment: CrossAxisAlignment.start,
646
- mainAxisAlignment: MainAxisAlignment.end,
647
- children: [
648
- CircleAvatar(
649
- radius: 30,
650
- backgroundColor: Colors.white,
651
- child: Icon(Icons.person, size: 40, color: Colors.orange),
652
- ),
653
- SizedBox(height: 10),
654
- Text(
655
- 'User Name',
656
- style: TextStyle(color: Colors.white, fontSize: 18),
657
- ),
658
- Text(
659
- 'user@example.com',
660
- style: TextStyle(color: Colors.white70, fontSize: 14),
661
- ),
662
- ],
663
- ),
664
- ),
665
- ListTile(
666
- leading: Icon(Icons.home),
667
- title: Text('Home'),
668
- selected: _selectedIndex == 0,
669
- onTap: () {
670
- setState(() {
671
- _selectedIndex = 0;
672
- });
673
- Navigator.pop(context);
674
- },
675
- ),
676
- ListTile(
677
- leading: Icon(Icons.person),
678
- title: Text('Profile'),
679
- selected: _selectedIndex == 1,
680
- onTap: () {
681
- setState(() {
682
- _selectedIndex = 1;
683
- });
684
- Navigator.pop(context);
685
- },
686
- ),
687
- ListTile(
688
- leading: Icon(Icons.settings),
689
- title: Text('Settings'),
690
- selected: _selectedIndex == 2,
691
- onTap: () {
692
- setState(() {
693
- _selectedIndex = 2;
694
- });
695
- Navigator.pop(context);
696
- },
697
- ),
698
- Divider(),
699
- ListTile(
700
- leading: Icon(Icons.logout),
701
- title: Text('Logout'),
702
- onTap: () {
703
- Navigator.pop(context);
704
- ScaffoldMessenger.of(context).showSnackBar(
705
- SnackBar(content: Text('Logged out')),
706
- );
707
- },
708
- ),
709
- ],
710
- ),
711
- ),
712
- body: _pages[_selectedIndex],
713
- );
714
- }
715
- }`
716
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/claude/generate-latex/route.ts DELETED
@@ -1,650 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server'
2
-
3
- export async function POST(request: NextRequest) {
4
- try {
5
- const { prompt, sessionId } = await request.json()
6
-
7
- if (!prompt) {
8
- return NextResponse.json(
9
- { error: 'Prompt is required' },
10
- { status: 400 }
11
- )
12
- }
13
-
14
- const apiKey = process.env.ANTHROPIC_API_KEY
15
-
16
- if (apiKey) {
17
- try {
18
- const response = await fetch('https://api.anthropic.com/v1/messages', {
19
- method: 'POST',
20
- headers: {
21
- 'Content-Type': 'application/json',
22
- 'x-api-key': apiKey,
23
- 'anthropic-version': '2023-06-01'
24
- },
25
- body: JSON.stringify({
26
- model: 'claude-3-haiku-20240307',
27
- max_tokens: 2048,
28
- messages: [
29
- {
30
- role: 'user',
31
- content: `Generate a complete LaTeX document for the following request.
32
- Return ONLY the LaTeX code without any explanations or markdown formatting.
33
- Include all necessary packages and document structure.
34
-
35
- Request: ${prompt}`
36
- }
37
- ],
38
- system: `You are a LaTeX document generator. Generate clean, complete LaTeX documents.
39
- - Always include \\documentclass and necessary packages
40
- - Use appropriate document structure (title, sections, etc.)
41
- - Include mathematical packages when needed (amsmath, amssymb, etc.)
42
- - Return only pure LaTeX code, no explanations or markdown
43
- - Ensure the document compiles without errors`
44
- })
45
- })
46
-
47
- if (response.ok) {
48
- const data = await response.json()
49
- const latex = data.content[0].text
50
- .replace(/```latex\n?/g, '')
51
- .replace(/```tex\n?/g, '')
52
- .replace(/```\n?/g, '')
53
- .trim()
54
-
55
- return NextResponse.json({ latex, sessionId })
56
- }
57
- } catch (error) {
58
- console.error('Claude API error:', error)
59
- }
60
- }
61
-
62
- // Fallback: Generate appropriate LaTeX based on keywords
63
- const latex = generateLaTeXFromPrompt(prompt)
64
- return NextResponse.json({ latex, sessionId })
65
-
66
- } catch (error) {
67
- console.error('Error generating LaTeX:', error)
68
- return NextResponse.json(
69
- { error: 'Failed to generate LaTeX' },
70
- { status: 500 }
71
- )
72
- }
73
- }
74
-
75
- function generateLaTeXFromPrompt(prompt: string): string {
76
- const promptLower = prompt.toLowerCase()
77
-
78
- if (promptLower.includes('resume') || promptLower.includes('cv')) {
79
- return generateResumeLaTeX()
80
- } else if (promptLower.includes('calculus') || promptLower.includes('derivative') || promptLower.includes('integral')) {
81
- return generateCalculusLaTeX()
82
- } else if (promptLower.includes('physics')) {
83
- return generatePhysicsLaTeX()
84
- } else if (promptLower.includes('chemistry')) {
85
- return generateChemistryLaTeX()
86
- } else if (promptLower.includes('thesis') || promptLower.includes('paper')) {
87
- return generateThesisLaTeX(prompt)
88
- } else if (promptLower.includes('presentation') || promptLower.includes('beamer')) {
89
- return generatePresentationLaTeX(prompt)
90
- } else {
91
- return generateGeneralLaTeX(prompt)
92
- }
93
- }
94
-
95
- function generateGeneralLaTeX(prompt: string): string {
96
- return `\\documentclass[12pt,a4paper]{article}
97
- \\usepackage{amsmath,amssymb,amsthm}
98
- \\usepackage{graphicx}
99
- \\usepackage{hyperref}
100
- \\usepackage{enumitem}
101
-
102
- \\title{${prompt}}
103
- \\author{Generated Document}
104
- \\date{\\today}
105
-
106
- \\begin{document}
107
-
108
- \\maketitle
109
-
110
- \\begin{abstract}
111
- This document was generated based on the request: "${prompt}".
112
- \\end{abstract}
113
-
114
- \\section{Introduction}
115
- This document provides comprehensive information about ${prompt}.
116
-
117
- \\section{Main Content}
118
- \\subsection{Overview}
119
- The topic "${prompt}" encompasses various aspects that we'll explore in this document.
120
-
121
- \\subsection{Key Points}
122
- \\begin{itemize}
123
- \\item First important point about ${prompt}
124
- \\item Second key aspect to consider
125
- \\item Additional relevant information
126
- \\end{itemize}
127
-
128
- \\section{Mathematical Formulation}
129
- When discussing ${prompt}, we can consider:
130
- \\[
131
- f(x) = \\sum_{n=0}^{\\infty} \\frac{x^n}{n!}
132
- \\]
133
-
134
- \\section{Conclusion}
135
- This document has provided an overview of ${prompt}, covering its main aspects and implications.
136
-
137
- \\bibliographystyle{plain}
138
- \\end{document}`
139
- }
140
-
141
- function generateResumeLaTeX(): string {
142
- return `\\documentclass[11pt,a4paper]{article}
143
- \\usepackage[margin=1in]{geometry}
144
- \\usepackage{hyperref}
145
- \\usepackage{enumitem}
146
-
147
- \\pagestyle{empty}
148
-
149
- \\begin{document}
150
-
151
- \\begin{center}
152
- {\\LARGE \\textbf{Your Name}}\\\\[2mm]
153
- your.email@example.com | (123) 456-7890 | LinkedIn: linkedin.com/in/yourname
154
- \\end{center}
155
-
156
- \\section*{Professional Summary}
157
- Experienced professional with expertise in [field]. Proven track record of [achievements].
158
-
159
- \\section*{Education}
160
- \\textbf{University Name} \\hfill 2018 - 2022\\\\
161
- Bachelor of Science in Computer Science\\\\
162
- GPA: 3.8/4.0
163
-
164
- \\section*{Experience}
165
- \\textbf{Software Engineer} \\hfill June 2022 - Present\\\\
166
- \\textit{Company Name, Location}
167
- \\begin{itemize}[leftmargin=*, itemsep=0pt]
168
- \\item Developed and maintained web applications using modern frameworks
169
- \\item Collaborated with cross-functional teams to deliver projects on time
170
- \\item Improved system performance by 40\\% through optimization
171
- \\end{itemize}
172
-
173
- \\section*{Skills}
174
- \\textbf{Programming:} Python, JavaScript, Java, C++\\\\
175
- \\textbf{Technologies:} React, Node.js, Docker, AWS\\\\
176
- \\textbf{Tools:} Git, VS Code, JIRA, Jenkins
177
-
178
- \\section*{Projects}
179
- \\textbf{Project Name} | \\textit{Technologies Used}\\\\
180
- Brief description of the project and your role in it.
181
-
182
- \\end{document}`
183
- }
184
-
185
- function generateCalculusLaTeX(): string {
186
- return `\\documentclass[12pt]{article}
187
- \\usepackage{amsmath,amssymb,amsthm}
188
- \\usepackage{tikz}
189
- \\usepackage{pgfplots}
190
- \\pgfplotsset{compat=1.17}
191
-
192
- \\theoremstyle{definition}
193
- \\newtheorem{definition}{Definition}
194
- \\newtheorem{theorem}{Theorem}
195
- \\newtheorem{example}{Example}
196
-
197
- \\title{Calculus Reference Sheet}
198
- \\author{Mathematics Department}
199
- \\date{\\today}
200
-
201
- \\begin{document}
202
-
203
- \\maketitle
204
-
205
- \\section{Derivatives}
206
-
207
- \\subsection{Basic Rules}
208
- \\begin{align}
209
- \\frac{d}{dx}[c] &= 0\\\\
210
- \\frac{d}{dx}[x^n] &= nx^{n-1}\\\\
211
- \\frac{d}{dx}[e^x] &= e^x\\\\
212
- \\frac{d}{dx}[\\ln x] &= \\frac{1}{x}\\\\
213
- \\frac{d}{dx}[\\sin x] &= \\cos x\\\\
214
- \\frac{d}{dx}[\\cos x] &= -\\sin x
215
- \\end{align}
216
-
217
- \\subsection{Chain Rule}
218
- If $y = f(g(x))$, then:
219
- \\[
220
- \\frac{dy}{dx} = f'(g(x)) \\cdot g'(x)
221
- \\]
222
-
223
- \\subsection{Product Rule}
224
- \\[
225
- \\frac{d}{dx}[f(x)g(x)] = f'(x)g(x) + f(x)g'(x)
226
- \\]
227
-
228
- \\subsection{Quotient Rule}
229
- \\[
230
- \\frac{d}{dx}\\left[\\frac{f(x)}{g(x)}\\right] = \\frac{f'(x)g(x) - f(x)g'(x)}{[g(x)]^2}
231
- \\]
232
-
233
- \\section{Integrals}
234
-
235
- \\subsection{Basic Integrals}
236
- \\begin{align}
237
- \\int x^n\\,dx &= \\frac{x^{n+1}}{n+1} + C, \\quad n \\neq -1\\\\
238
- \\int \\frac{1}{x}\\,dx &= \\ln|x| + C\\\\
239
- \\int e^x\\,dx &= e^x + C\\\\
240
- \\int \\sin x\\,dx &= -\\cos x + C\\\\
241
- \\int \\cos x\\,dx &= \\sin x + C
242
- \\end{align}
243
-
244
- \\subsection{Integration by Parts}
245
- \\[
246
- \\int u\\,dv = uv - \\int v\\,du
247
- \\]
248
-
249
- \\subsection{Substitution Method}
250
- If $u = g(x)$, then $du = g'(x)dx$:
251
- \\[
252
- \\int f(g(x))g'(x)\\,dx = \\int f(u)\\,du
253
- \\]
254
-
255
- \\section{Series}
256
-
257
- \\subsection{Taylor Series}
258
- \\[
259
- f(x) = \\sum_{n=0}^{\\infty} \\frac{f^{(n)}(a)}{n!}(x-a)^n
260
- \\]
261
-
262
- \\subsection{Common Series}
263
- \\begin{align}
264
- e^x &= \\sum_{n=0}^{\\infty} \\frac{x^n}{n!}\\\\
265
- \\sin x &= \\sum_{n=0}^{\\infty} \\frac{(-1)^n x^{2n+1}}{(2n+1)!}\\\\
266
- \\cos x &= \\sum_{n=0}^{\\infty} \\frac{(-1)^n x^{2n}}{(2n)!}
267
- \\end{align}
268
-
269
- \\end{document}`
270
- }
271
-
272
- function generatePhysicsLaTeX(): string {
273
- return `\\documentclass[12pt]{article}
274
- \\usepackage{amsmath,amssymb}
275
- \\usepackage{physics}
276
- \\usepackage{siunitx}
277
- \\usepackage{tikz}
278
-
279
- \\title{Physics Formula Sheet}
280
- \\author{Physics Department}
281
- \\date{\\today}
282
-
283
- \\begin{document}
284
-
285
- \\maketitle
286
-
287
- \\section{Classical Mechanics}
288
-
289
- \\subsection{Newton's Laws}
290
- \\begin{enumerate}
291
- \\item First Law: $\\vec{F}_{net} = 0 \\implies \\vec{v} = \\text{constant}$
292
- \\item Second Law: $\\vec{F} = m\\vec{a}$
293
- \\item Third Law: $\\vec{F}_{12} = -\\vec{F}_{21}$
294
- \\end{enumerate}
295
-
296
- \\subsection{Kinematics}
297
- \\begin{align}
298
- v &= v_0 + at\\\\
299
- x &= x_0 + v_0t + \\frac{1}{2}at^2\\\\
300
- v^2 &= v_0^2 + 2a(x - x_0)
301
- \\end{align}
302
-
303
- \\subsection{Energy}
304
- \\begin{itemize}
305
- \\item Kinetic Energy: $KE = \\frac{1}{2}mv^2$
306
- \\item Potential Energy: $PE = mgh$
307
- \\item Work: $W = \\vec{F} \\cdot \\vec{d} = Fd\\cos\\theta$
308
- \\item Power: $P = \\frac{dW}{dt} = \\vec{F} \\cdot \\vec{v}$
309
- \\end{itemize}
310
-
311
- \\section{Electromagnetism}
312
-
313
- \\subsection{Maxwell's Equations}
314
- \\begin{align}
315
- \\nabla \\cdot \\vec{E} &= \\frac{\\rho}{\\epsilon_0}\\\\
316
- \\nabla \\cdot \\vec{B} &= 0\\\\
317
- \\nabla \\times \\vec{E} &= -\\frac{\\partial \\vec{B}}{\\partial t}\\\\
318
- \\nabla \\times \\vec{B} &= \\mu_0\\vec{J} + \\mu_0\\epsilon_0\\frac{\\partial \\vec{E}}{\\partial t}
319
- \\end{align}
320
-
321
- \\subsection{Electric Fields and Forces}
322
- \\begin{itemize}
323
- \\item Coulomb's Law: $F = k\\frac{q_1q_2}{r^2}$
324
- \\item Electric Field: $\\vec{E} = \\frac{\\vec{F}}{q}$
325
- \\item Electric Potential: $V = \\frac{U}{q}$
326
- \\end{itemize}
327
-
328
- \\section{Quantum Mechanics}
329
-
330
- \\subsection{Schrödinger Equation}
331
- Time-dependent:
332
- \\[
333
- i\\hbar\\frac{\\partial\\Psi}{\\partial t} = \\hat{H}\\Psi
334
- \\]
335
-
336
- Time-independent:
337
- \\[
338
- \\hat{H}\\psi = E\\psi
339
- \\]
340
-
341
- \\subsection{Uncertainty Principle}
342
- \\[
343
- \\Delta x \\cdot \\Delta p \\geq \\frac{\\hbar}{2}
344
- \\]
345
-
346
- \\end{document}`
347
- }
348
-
349
- function generateChemistryLaTeX(): string {
350
- return `\\documentclass[12pt]{article}
351
- \\usepackage{amsmath,amssymb}
352
- \\usepackage{chemformula}
353
- \\usepackage{chemfig}
354
- \\usepackage{siunitx}
355
-
356
- \\title{Chemistry Reference Guide}
357
- \\author{Chemistry Department}
358
- \\date{\\today}
359
-
360
- \\begin{document}
361
-
362
- \\maketitle
363
-
364
- \\section{Chemical Equations}
365
-
366
- \\subsection{Balancing Equations}
367
- Example reaction:
368
- \\[
369
- \\ch{2 H2 + O2 -> 2 H2O}
370
- \\]
371
-
372
- \\subsection{Combustion}
373
- \\[
374
- \\ch{CH4 + 2 O2 -> CO2 + 2 H2O}
375
- \\]
376
-
377
- \\section{Stoichiometry}
378
-
379
- \\subsection{Molar Relationships}
380
- \\begin{itemize}
381
- \\item Molar mass: Mass of one mole of substance
382
- \\item Avogadro's number: $N_A = 6.022 \\times 10^{23}$ mol$^{-1}$
383
- \\item Moles: $n = \\frac{m}{M}$ where $m$ is mass and $M$ is molar mass
384
- \\end{itemize}
385
-
386
- \\section{Thermodynamics}
387
-
388
- \\subsection{First Law}
389
- \\[
390
- \\Delta U = q + w
391
- \\]
392
-
393
- \\subsection{Enthalpy}
394
- \\[
395
- \\Delta H = \\Delta U + P\\Delta V
396
- \\]
397
-
398
- \\subsection{Gibbs Free Energy}
399
- \\[
400
- \\Delta G = \\Delta H - T\\Delta S
401
- \\]
402
- \\begin{itemize}
403
- \\item $\\Delta G < 0$: Spontaneous reaction
404
- \\item $\\Delta G = 0$: Equilibrium
405
- \\item $\\Delta G > 0$: Non-spontaneous
406
- \\end{itemize}
407
-
408
- \\section{Kinetics}
409
-
410
- \\subsection{Rate Laws}
411
- First order: $\\text{Rate} = k[A]$
412
-
413
- Second order: $\\text{Rate} = k[A]^2$ or $\\text{Rate} = k[A][B]$
414
-
415
- \\subsection{Arrhenius Equation}
416
- \\[
417
- k = Ae^{-E_a/RT}
418
- \\]
419
-
420
- \\section{Equilibrium}
421
-
422
- \\subsection{Equilibrium Constant}
423
- For reaction: \\ch{aA + bB <=> cC + dD}
424
- \\[
425
- K_c = \\frac{[C]^c[D]^d}{[A]^a[B]^b}
426
- \\]
427
-
428
- \\subsection{Le Chatelier's Principle}
429
- System at equilibrium will shift to counteract any imposed change.
430
-
431
- \\end{document}`
432
- }
433
-
434
- function generateThesisLaTeX(topic: string): string {
435
- return `\\documentclass[12pt,a4paper]{report}
436
- \\usepackage{amsmath,amssymb,amsthm}
437
- \\usepackage{graphicx}
438
- \\usepackage{hyperref}
439
- \\usepackage{cite}
440
- \\usepackage{setspace}
441
- \\usepackage[margin=1in]{geometry}
442
-
443
- \\doublespacing
444
-
445
- \\title{${topic}}
446
- \\author{Your Name}
447
- \\date{\\today}
448
-
449
- \\begin{document}
450
-
451
- \\maketitle
452
-
453
- \\begin{abstract}
454
- This thesis investigates ${topic}. We present novel findings that contribute to the understanding of this field. Our research demonstrates significant improvements over existing methods.
455
-
456
- \\textbf{Keywords:} ${topic}, research, analysis, methodology
457
- \\end{abstract}
458
-
459
- \\tableofcontents
460
- \\listoffigures
461
- \\listoftables
462
-
463
- \\chapter{Introduction}
464
-
465
- \\section{Background}
466
- The study of ${topic} has gained significant attention in recent years. This research aims to address key challenges in the field.
467
-
468
- \\section{Research Questions}
469
- \\begin{enumerate}
470
- \\item What are the fundamental principles underlying ${topic}?
471
- \\item How can we improve current approaches?
472
- \\item What are the practical applications?
473
- \\end{enumerate}
474
-
475
- \\section{Thesis Structure}
476
- This thesis is organized as follows:
477
- \\begin{itemize}
478
- \\item Chapter 2: Literature Review
479
- \\item Chapter 3: Methodology
480
- \\item Chapter 4: Results and Analysis
481
- \\item Chapter 5: Discussion
482
- \\item Chapter 6: Conclusion
483
- \\end{itemize}
484
-
485
- \\chapter{Literature Review}
486
-
487
- \\section{Historical Context}
488
- Previous work in ${topic} has established several important foundations...
489
-
490
- \\section{Current State of Research}
491
- Recent developments have shown that...
492
-
493
- \\chapter{Methodology}
494
-
495
- \\section{Research Design}
496
- Our approach involves...
497
-
498
- \\section{Data Collection}
499
- Data was collected using...
500
-
501
- \\chapter{Results and Analysis}
502
-
503
- \\section{Findings}
504
- Our analysis reveals...
505
-
506
- \\section{Statistical Analysis}
507
- The results show statistical significance with $p < 0.05$.
508
-
509
- \\chapter{Discussion}
510
-
511
- \\section{Interpretation of Results}
512
- The findings suggest...
513
-
514
- \\section{Limitations}
515
- This study has several limitations...
516
-
517
- \\chapter{Conclusion}
518
-
519
- \\section{Summary}
520
- This thesis has presented...
521
-
522
- \\section{Future Work}
523
- Future research should explore...
524
-
525
- \\bibliographystyle{plain}
526
- \\bibliography{references}
527
-
528
- \\appendix
529
- \\chapter{Additional Data}
530
-
531
- \\end{document}`
532
- }
533
-
534
- function generatePresentationLaTeX(topic: string): string {
535
- return `\\documentclass{beamer}
536
- \\usetheme{Madrid}
537
- \\usecolortheme{seahorse}
538
- \\usepackage{amsmath,amssymb}
539
- \\usepackage{graphicx}
540
- \\usepackage{tikz}
541
-
542
- \\title{${topic}}
543
- \\author{Presenter Name}
544
- \\institute{Your Institution}
545
- \\date{\\today}
546
-
547
- \\begin{document}
548
-
549
- \\frame{\\titlepage}
550
-
551
- \\begin{frame}
552
- \\frametitle{Outline}
553
- \\tableofcontents
554
- \\end{frame}
555
-
556
- \\section{Introduction}
557
-
558
- \\begin{frame}
559
- \\frametitle{Introduction}
560
- \\begin{itemize}
561
- \\item Overview of ${topic}
562
- \\item Why it matters
563
- \\item Our approach
564
- \\end{itemize}
565
- \\end{frame}
566
-
567
- \\section{Background}
568
-
569
- \\begin{frame}
570
- \\frametitle{Background}
571
- \\begin{columns}
572
- \\column{0.5\\textwidth}
573
- Key concepts:
574
- \\begin{itemize}
575
- \\item Concept 1
576
- \\item Concept 2
577
- \\item Concept 3
578
- \\end{itemize}
579
-
580
- \\column{0.5\\textwidth}
581
- \\begin{block}{Definition}
582
- ${topic} is defined as...
583
- \\end{block}
584
- \\end{columns}
585
- \\end{frame}
586
-
587
- \\section{Methodology}
588
-
589
- \\begin{frame}
590
- \\frametitle{Our Approach}
591
- \\begin{enumerate}
592
- \\item<1-> Step 1: Initial analysis
593
- \\item<2-> Step 2: Data collection
594
- \\item<3-> Step 3: Processing
595
- \\item<4-> Step 4: Evaluation
596
- \\end{enumerate}
597
- \\end{frame}
598
-
599
- \\section{Results}
600
-
601
- \\begin{frame}
602
- \\frametitle{Key Findings}
603
- \\begin{theorem}
604
- Our main result shows that...
605
- \\end{theorem}
606
-
607
- \\begin{proof}
608
- The proof follows from...
609
- \\end{proof}
610
- \\end{frame}
611
-
612
- \\begin{frame}
613
- \\frametitle{Performance Metrics}
614
- \\begin{center}
615
- \\begin{tikzpicture}
616
- \\begin{axis}[
617
- ybar,
618
- ylabel={Performance},
619
- xlabel={Method},
620
- symbolic x coords={Baseline, Our Method, State-of-Art},
621
- xtick=data
622
- ]
623
- \\addplot coordinates {
624
- (Baseline, 65)
625
- (Our Method, 85)
626
- (State-of-Art, 78)
627
- };
628
- \\end{axis}
629
- \\end{tikzpicture}
630
- \\end{center}
631
- \\end{frame}
632
-
633
- \\section{Conclusion}
634
-
635
- \\begin{frame}
636
- \\frametitle{Conclusion}
637
- \\begin{itemize}
638
- \\item Successfully demonstrated ${topic}
639
- \\item Achieved 20\\% improvement
640
- \\item Future work includes...
641
- \\end{itemize}
642
-
643
- \\vspace{1cm}
644
- \\begin{center}
645
- \\Large{Thank you! Questions?}
646
- \\end{center}
647
- \\end{frame}
648
-
649
- \\end{document}`
650
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/components/Desktop.tsx CHANGED
@@ -34,7 +34,10 @@ import {
34
  Key,
35
  Brain,
36
  Code,
37
- FileText
 
 
 
38
  } from '@phosphor-icons/react'
39
 
40
  export function Desktop() {
@@ -437,8 +440,12 @@ export function Desktop() {
437
  id: 'flutter-runner',
438
  label: 'Flutter Runner',
439
  icon: (
440
- <div className="bg-gradient-to-br from-cyan-400 to-blue-500 w-full h-full rounded-xl flex items-center justify-center">
441
- <Code size={20} weight="bold" className="text-white" />
 
 
 
 
442
  </div>
443
  ),
444
  onRestore: () => setFlutterRunnerMinimized(false)
@@ -450,8 +457,12 @@ export function Desktop() {
450
  id: 'research',
451
  label: 'Research',
452
  icon: (
453
- <div className="bg-gradient-to-br from-purple-400 to-pink-500 w-full h-full rounded-xl flex items-center justify-center">
454
- <Brain size={20} weight="bold" className="text-white" />
 
 
 
 
455
  </div>
456
  ),
457
  onRestore: () => setResearchBrowserMinimized(false)
@@ -463,8 +474,12 @@ export function Desktop() {
463
  id: 'flutter-editor',
464
  label: 'Flutter IDE',
465
  icon: (
466
- <div className="bg-gradient-to-br from-blue-500 to-cyan-500 w-full h-full rounded-xl flex items-center justify-center">
467
- <Code size={20} weight="fill" className="text-white" />
 
 
 
 
468
  </div>
469
  ),
470
  onRestore: () => setFlutterCodeEditorMinimized(false)
@@ -476,8 +491,12 @@ export function Desktop() {
476
  id: 'latex-editor',
477
  label: 'LaTeX Studio',
478
  icon: (
479
- <div className="bg-black w-full h-full rounded-xl flex items-center justify-center">
480
- <FileText size={20} weight="bold" className="text-white" />
 
 
 
 
481
  </div>
482
  ),
483
  onRestore: () => setLaTeXEditorMinimized(false)
@@ -600,7 +619,7 @@ export function Desktop() {
600
  <DraggableDesktopIcon
601
  id="research"
602
  label="Research"
603
- iconType="brain"
604
  initialPosition={{ x: 0, y: 0 }}
605
  onClick={() => { }}
606
  onDoubleClick={openResearchBrowser}
@@ -610,7 +629,7 @@ export function Desktop() {
610
  <DraggableDesktopIcon
611
  id="flutter-editor"
612
  label="Flutter IDE"
613
- iconType="code"
614
  initialPosition={{ x: 0, y: 0 }}
615
  onClick={() => { }}
616
  onDoubleClick={openFlutterCodeEditor}
@@ -620,7 +639,7 @@ export function Desktop() {
620
  <DraggableDesktopIcon
621
  id="latex-editor"
622
  label="LaTeX Studio"
623
- iconType="document"
624
  initialPosition={{ x: 0, y: 0 }}
625
  onClick={() => { }}
626
  onDoubleClick={openLaTeXEditor}
 
34
  Key,
35
  Brain,
36
  Code,
37
+ FileText,
38
+ DeviceMobile,
39
+ Lightning,
40
+ Function
41
  } from '@phosphor-icons/react'
42
 
43
  export function Desktop() {
 
440
  id: 'flutter-runner',
441
  label: 'Flutter Runner',
442
  icon: (
443
+ <div className="bg-gradient-to-b from-[#54C5F8] to-[#29B6F6] w-full h-full rounded-[22%] flex items-center justify-center shadow-lg border-[0.5px] border-white/20 relative overflow-hidden">
444
+ <div className="absolute inset-0 bg-gradient-to-b from-white/20 to-transparent opacity-50" />
445
+ <DeviceMobile size={20} weight="fill" className="text-white relative z-10 drop-shadow-md" />
446
+ <div className="absolute top-1 right-1">
447
+ <Lightning size={8} weight="fill" className="text-yellow-300 drop-shadow-md" />
448
+ </div>
449
  </div>
450
  ),
451
  onRestore: () => setFlutterRunnerMinimized(false)
 
457
  id: 'research',
458
  label: 'Research',
459
  icon: (
460
+ <div className="bg-gradient-to-b from-purple-500 to-indigo-600 w-full h-full rounded-[22%] flex items-center justify-center shadow-lg border-[0.5px] border-white/20 relative overflow-hidden">
461
+ <div className="absolute inset-0 bg-gradient-to-b from-white/20 to-transparent opacity-50" />
462
+ <Globe size={20} weight="duotone" className="text-white relative z-10 drop-shadow-md" />
463
+ <div className="absolute bottom-1 right-1 bg-white/20 p-0.5 rounded-full backdrop-blur-md shadow-sm">
464
+ <Sparkle size={6} weight="fill" className="text-yellow-300" />
465
+ </div>
466
  </div>
467
  ),
468
  onRestore: () => setResearchBrowserMinimized(false)
 
474
  id: 'flutter-editor',
475
  label: 'Flutter IDE',
476
  icon: (
477
+ <div className="bg-gradient-to-b from-[#54C5F8] to-[#29B6F6] w-full h-full rounded-[22%] flex items-center justify-center shadow-lg border-[0.5px] border-white/20 relative overflow-hidden">
478
+ <div className="absolute inset-0 bg-gradient-to-b from-white/20 to-transparent opacity-50" />
479
+ <DeviceMobile size={20} weight="fill" className="text-white relative z-10 drop-shadow-md" />
480
+ <div className="absolute top-1 right-1">
481
+ <Lightning size={8} weight="fill" className="text-yellow-300 drop-shadow-md" />
482
+ </div>
483
  </div>
484
  ),
485
  onRestore: () => setFlutterCodeEditorMinimized(false)
 
491
  id: 'latex-editor',
492
  label: 'LaTeX Studio',
493
  icon: (
494
+ <div className="bg-gradient-to-b from-slate-700 to-slate-900 w-full h-full rounded-[22%] flex items-center justify-center shadow-lg border-[0.5px] border-white/20 relative overflow-hidden">
495
+ <div className="absolute inset-0 bg-gradient-to-b from-white/10 to-transparent opacity-30" />
496
+ <div className="relative z-10 flex flex-col items-center justify-center">
497
+ <Function size={18} weight="bold" className="text-green-400 drop-shadow-md" />
498
+ <span className="text-[5px] font-black text-gray-300 tracking-widest mt-0.5">TEX</span>
499
+ </div>
500
  </div>
501
  ),
502
  onRestore: () => setLaTeXEditorMinimized(false)
 
619
  <DraggableDesktopIcon
620
  id="research"
621
  label="Research"
622
+ iconType="research"
623
  initialPosition={{ x: 0, y: 0 }}
624
  onClick={() => { }}
625
  onDoubleClick={openResearchBrowser}
 
629
  <DraggableDesktopIcon
630
  id="flutter-editor"
631
  label="Flutter IDE"
632
+ iconType="flutter"
633
  initialPosition={{ x: 0, y: 0 }}
634
  onClick={() => { }}
635
  onDoubleClick={openFlutterCodeEditor}
 
639
  <DraggableDesktopIcon
640
  id="latex-editor"
641
  label="LaTeX Studio"
642
+ iconType="latex"
643
  initialPosition={{ x: 0, y: 0 }}
644
  onClick={() => { }}
645
  onDoubleClick={openLaTeXEditor}
app/components/Dock.tsx CHANGED
@@ -94,8 +94,9 @@ export function Dock({
94
  },
95
  {
96
  icon: (
97
- <div className="bg-white w-full h-full rounded-xl flex items-center justify-center border border-gray-200">
98
- <Sparkle size={24} weight="fill" className="text-blue-500 md:scale-110" />
 
99
  </div>
100
  ),
101
  label: 'Gemini',
 
94
  },
95
  {
96
  icon: (
97
+ <div className="bg-gradient-to-b from-white to-blue-50 w-full h-full rounded-xl flex items-center justify-center shadow-lg border-[0.5px] border-white/50 relative overflow-hidden">
98
+ <div className="absolute inset-0 bg-gradient-to-b from-white/40 to-transparent" />
99
+ <Sparkle size={24} weight="fill" className="text-blue-500 md:scale-110 drop-shadow-sm" />
100
  </div>
101
  ),
102
  label: 'Gemini',
app/components/DraggableDesktopIcon.tsx CHANGED
@@ -68,14 +68,39 @@ export function DraggableDesktopIcon({
68
  )
69
  case 'research':
70
  return (
71
- <div className="bg-gradient-to-br from-purple-400 to-pink-500 w-full h-full rounded-xl flex items-center justify-center shadow-lg">
72
- <Flask size={32} weight="fill" className="text-white" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  </div>
74
  )
75
  case 'gemini':
76
  return (
77
- <div className="bg-white w-full h-full rounded-xl flex items-center justify-center border border-gray-200">
78
- <Sparkle size={32} weight="fill" className="text-blue-500" />
 
79
  </div>
80
  )
81
  case 'harddrive':
@@ -103,12 +128,6 @@ export function DraggableDesktopIcon({
103
  <Key size={32} weight="bold" className="text-white" />
104
  </div>
105
  )
106
- case 'flutter':
107
- return (
108
- <div className="bg-gradient-to-br from-cyan-400 to-blue-500 w-full h-full rounded-xl flex items-center justify-center shadow-inner border border-cyan-300/30">
109
- <Code size={32} weight="bold" className="text-white" />
110
- </div>
111
- )
112
  default:
113
  return (
114
  <div className="bg-gray-400 w-full h-full rounded-xl flex items-center justify-center">
 
68
  )
69
  case 'research':
70
  return (
71
+ <div className="bg-gradient-to-b from-purple-500 to-indigo-600 w-full h-full rounded-[22%] flex items-center justify-center shadow-lg border-[0.5px] border-white/20 relative overflow-hidden group-hover:shadow-2xl transition-all duration-300">
72
+ <div className="absolute inset-0 bg-gradient-to-b from-white/20 to-transparent opacity-50" />
73
+ <Globe size={36} weight="duotone" className="text-white relative z-10 drop-shadow-md" />
74
+ <div className="absolute bottom-1.5 right-1.5 bg-white/20 p-1 rounded-full backdrop-blur-md shadow-sm">
75
+ <Sparkle size={10} weight="fill" className="text-yellow-300" />
76
+ </div>
77
+ </div>
78
+ )
79
+ case 'flutter':
80
+ return (
81
+ <div className="bg-gradient-to-b from-[#54C5F8] to-[#29B6F6] w-full h-full rounded-[22%] flex items-center justify-center shadow-lg border-[0.5px] border-white/20 relative overflow-hidden group-hover:shadow-2xl transition-all duration-300">
82
+ <div className="absolute inset-0 bg-gradient-to-b from-white/20 to-transparent opacity-50" />
83
+ <DeviceMobile size={36} weight="fill" className="text-white relative z-10 drop-shadow-md" />
84
+ <div className="absolute top-1.5 right-1.5">
85
+ <Lightning size={12} weight="fill" className="text-yellow-300 drop-shadow-md" />
86
+ </div>
87
+ </div>
88
+ )
89
+ case 'latex':
90
+ return (
91
+ <div className="bg-gradient-to-b from-slate-700 to-slate-900 w-full h-full rounded-[22%] flex items-center justify-center shadow-lg border-[0.5px] border-white/20 relative overflow-hidden group-hover:shadow-2xl transition-all duration-300">
92
+ <div className="absolute inset-0 bg-gradient-to-b from-white/10 to-transparent opacity-30" />
93
+ <div className="relative z-10 flex flex-col items-center justify-center">
94
+ <Function size={32} weight="bold" className="text-green-400 drop-shadow-md" />
95
+ <span className="text-[8px] font-black text-gray-300 tracking-widest mt-0.5">TEX</span>
96
+ </div>
97
  </div>
98
  )
99
  case 'gemini':
100
  return (
101
+ <div className="bg-gradient-to-b from-white to-blue-50 w-full h-full rounded-[22%] flex items-center justify-center shadow-lg border-[0.5px] border-white/50 relative overflow-hidden group-hover:shadow-2xl transition-all duration-300">
102
+ <div className="absolute inset-0 bg-gradient-to-b from-white/40 to-transparent" />
103
+ <Sparkle size={36} weight="fill" className="text-blue-500 relative z-10 drop-shadow-sm" />
104
  </div>
105
  )
106
  case 'harddrive':
 
128
  <Key size={32} weight="bold" className="text-white" />
129
  </div>
130
  )
 
 
 
 
 
 
131
  default:
132
  return (
133
  <div className="bg-gray-400 w-full h-full rounded-xl flex items-center justify-center">
app/components/FileManager.tsx CHANGED
@@ -31,7 +31,8 @@ import {
31
  Function,
32
  Calendar as CalendarIcon,
33
  Clock as ClockIcon,
34
- Sparkle
 
35
  } from '@phosphor-icons/react'
36
  import { motion } from 'framer-motion'
37
  import { FilePreview } from './FilePreview'
@@ -326,13 +327,50 @@ export function FileManager({ currentPath, onNavigate, onClose, onOpenFlutterApp
326
  }
327
 
328
  const applications = [
329
- { id: 'files', name: 'Finder', icon: <FolderIcon size={48} weight="fill" className="text-blue-500" /> },
330
- { id: 'calendar', name: 'Calendar', icon: <CalendarIcon size={48} weight="regular" className="text-red-500" /> },
331
- { id: 'clock', name: 'Clock', icon: <ClockIcon size={48} weight="regular" className="text-black" /> },
332
- { id: 'gemini', name: 'Gemini', icon: <Sparkle size={48} weight="fill" className="text-blue-500" /> },
333
- { id: 'research', name: 'Research', icon: <Flask size={48} weight="fill" className="text-purple-500" /> },
334
- { id: 'flutter-editor', name: 'Flutter IDE', icon: <DeviceMobile size={48} weight="fill" className="text-cyan-500" /> },
335
- { id: 'latex-editor', name: 'LaTeX Studio', icon: <Function size={48} weight="bold" className="text-black" /> },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  ]
337
 
338
  return (
 
31
  Function,
32
  Calendar as CalendarIcon,
33
  Clock as ClockIcon,
34
+ Sparkle,
35
+ Lightning
36
  } from '@phosphor-icons/react'
37
  import { motion } from 'framer-motion'
38
  import { FilePreview } from './FilePreview'
 
327
  }
328
 
329
  const applications = [
330
+ { id: 'files', name: 'Finder', icon: <div className="bg-gradient-to-br from-blue-400 to-cyan-200 w-full h-full rounded-xl flex items-center justify-center border border-white/30"><FolderIcon size={32} weight="regular" className="text-blue-900" /></div> },
331
+ { id: 'calendar', name: 'Calendar', icon: <div className="w-full h-full"><CalendarIcon size={48} weight="regular" className="text-red-500" /></div> },
332
+ { id: 'clock', name: 'Clock', icon: <div className="w-full h-full"><ClockIcon size={48} weight="regular" className="text-black" /></div> },
333
+ {
334
+ id: 'gemini', name: 'Gemini', icon: (
335
+ <div className="bg-gradient-to-b from-white to-blue-50 w-full h-full rounded-[22%] flex items-center justify-center shadow-lg border-[0.5px] border-white/50 relative overflow-hidden">
336
+ <div className="absolute inset-0 bg-gradient-to-b from-white/40 to-transparent" />
337
+ <Sparkle size={32} weight="fill" className="text-blue-500 drop-shadow-sm" />
338
+ </div>
339
+ )
340
+ },
341
+ {
342
+ id: 'research', name: 'Research', icon: (
343
+ <div className="bg-gradient-to-b from-purple-500 to-indigo-600 w-full h-full rounded-[22%] flex items-center justify-center shadow-lg border-[0.5px] border-white/20 relative overflow-hidden">
344
+ <div className="absolute inset-0 bg-gradient-to-b from-white/20 to-transparent opacity-50" />
345
+ <Globe size={32} weight="duotone" className="text-white relative z-10 drop-shadow-md" />
346
+ <div className="absolute bottom-1.5 right-1.5 bg-white/20 p-1 rounded-full backdrop-blur-md shadow-sm">
347
+ <Sparkle size={10} weight="fill" className="text-yellow-300" />
348
+ </div>
349
+ </div>
350
+ )
351
+ },
352
+ {
353
+ id: 'flutter-editor', name: 'Flutter IDE', icon: (
354
+ <div className="bg-gradient-to-b from-[#54C5F8] to-[#29B6F6] w-full h-full rounded-[22%] flex items-center justify-center shadow-lg border-[0.5px] border-white/20 relative overflow-hidden">
355
+ <div className="absolute inset-0 bg-gradient-to-b from-white/20 to-transparent opacity-50" />
356
+ <DeviceMobile size={32} weight="fill" className="text-white relative z-10 drop-shadow-md" />
357
+ <div className="absolute top-1.5 right-1.5">
358
+ <Lightning size={12} weight="fill" className="text-yellow-300 drop-shadow-md" />
359
+ </div>
360
+ </div>
361
+ )
362
+ },
363
+ {
364
+ id: 'latex-editor', name: 'LaTeX Studio', icon: (
365
+ <div className="bg-gradient-to-b from-slate-700 to-slate-900 w-full h-full rounded-[22%] flex items-center justify-center shadow-lg border-[0.5px] border-white/20 relative overflow-hidden">
366
+ <div className="absolute inset-0 bg-gradient-to-b from-white/10 to-transparent opacity-30" />
367
+ <div className="relative z-10 flex flex-col items-center justify-center">
368
+ <Function size={32} weight="bold" className="text-green-400 drop-shadow-md" />
369
+ <span className="text-[8px] font-black text-gray-300 tracking-widest mt-0.5">TEX</span>
370
+ </div>
371
+ </div>
372
+ )
373
+ },
374
  ]
375
 
376
  return (
app/components/FlutterCodeEditor.tsx CHANGED
@@ -129,23 +129,10 @@ class _MyHomePageState extends State<MyHomePage> {
129
 
130
  setIsGenerating(true)
131
  try {
132
- // Call your Claude API endpoint here
133
- const response = await fetch('/api/claude/generate-flutter', {
134
- method: 'POST',
135
- headers: { 'Content-Type': 'application/json' },
136
- body: JSON.stringify({ prompt: claudePrompt })
137
- })
138
-
139
- if (response.ok) {
140
- const data = await response.json()
141
- setGeneratedCode(data.code || '')
142
- // Optionally auto-apply generated code
143
- // setCode(data.code || code)
144
- }
145
- } catch (error) {
146
- console.error('Failed to generate code:', error)
147
- // For demo purposes, generate mock code
148
- const mockGenerated = `// Generated code for: ${claudePrompt}
149
  import 'package:flutter/material.dart';
150
 
151
  void main() => runApp(MyApp());
@@ -155,10 +142,31 @@ class MyApp extends StatelessWidget {
155
  Widget build(BuildContext context) {
156
  return MaterialApp(
157
  title: 'Generated App',
 
 
 
 
158
  home: Scaffold(
159
  appBar: AppBar(title: Text('${claudePrompt}')),
160
  body: Center(
161
- child: Text('Generated Flutter app for: ${claudePrompt}'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  ),
163
  ),
164
  );
 
129
 
130
  setIsGenerating(true)
131
  try {
132
+ // Note: Using MCP for direct Claude communication
133
+ // Generate a simple template based on prompt
134
+ const mockGenerated = `// Template for: ${claudePrompt}
135
+ // Use MCP to communicate directly with Claude Desktop for custom generation
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  import 'package:flutter/material.dart';
137
 
138
  void main() => runApp(MyApp());
 
142
  Widget build(BuildContext context) {
143
  return MaterialApp(
144
  title: 'Generated App',
145
+ theme: ThemeData(
146
+ primarySwatch: Colors.blue,
147
+ useMaterial3: true,
148
+ ),
149
  home: Scaffold(
150
  appBar: AppBar(title: Text('${claudePrompt}')),
151
  body: Center(
152
+ child: Column(
153
+ mainAxisAlignment: MainAxisAlignment.center,
154
+ children: [
155
+ Icon(Icons.flutter_dash, size: 80, color: Colors.blue),
156
+ SizedBox(height: 20),
157
+ Text(
158
+ '${claudePrompt}',
159
+ style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
160
+ textAlign: TextAlign.center,
161
+ ),
162
+ SizedBox(height: 10),
163
+ Text(
164
+ 'Use MCP in Claude Desktop for custom code generation',
165
+ style: TextStyle(fontSize: 14, color: Colors.grey),
166
+ textAlign: TextAlign.center,
167
+ ),
168
+ ],
169
+ ),
170
  ),
171
  ),
172
  );
app/components/FlutterRunner.tsx CHANGED
@@ -219,7 +219,7 @@ class _MyHomePageState extends State<MyHomePage> {
219
  </div>
220
  )}
221
 
222
- {/* Main Content */}
223
  <div className="flex-1 flex flex-col min-w-0">
224
  {/* Toolbar */}
225
  <div className="h-10 bg-[#2d2d2d] border-b border-[#1e1e1e] flex items-center justify-between px-4">
@@ -245,13 +245,6 @@ class _MyHomePageState extends State<MyHomePage> {
245
  </div>
246
 
247
  <div className="flex items-center gap-2">
248
- <button
249
- onClick={handleRun}
250
- className="flex items-center gap-2 px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white rounded text-xs font-medium transition-colors"
251
- >
252
- <Play size={14} weight="fill" />
253
- Run
254
- </button>
255
  <button
256
  onClick={handleDownload}
257
  className="p-1.5 text-gray-400 hover:text-white hover:bg-[#3e3e42] rounded transition-colors"
@@ -262,63 +255,15 @@ class _MyHomePageState extends State<MyHomePage> {
262
  </div>
263
  </div>
264
 
265
- {/* Split View: Editor & Preview */}
266
- <div className="flex-1 flex overflow-hidden">
267
- {/* Editor */}
268
- <div className="flex-1 border-r border-[#333]">
269
- <Editor
270
- height="100%"
271
- defaultLanguage="dart"
272
- theme="vs-dark"
273
- value={code}
274
- onChange={(value) => setCode(value || '')}
275
- options={{
276
- minimap: { enabled: false },
277
- fontSize: 14,
278
- fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
279
- lineNumbers: 'on',
280
- scrollBeyondLastLine: false,
281
- automaticLayout: true,
282
- padding: { top: 16, bottom: 16 },
283
- renderLineHighlight: 'all',
284
- smoothScrolling: true,
285
- cursorBlinking: 'smooth',
286
- cursorSmoothCaretAnimation: 'on'
287
- }}
288
- />
289
- </div>
290
-
291
- {/* Preview */}
292
- <div className="w-[400px] bg-[#1e1e1e] flex flex-col border-l border-[#333]">
293
- <div className="h-8 bg-[#252526] border-b border-[#333] flex items-center justify-between px-3">
294
- <span className="text-xs font-bold text-gray-500 uppercase">Preview</span>
295
- <div className="flex gap-1">
296
- <div className="w-2 h-2 rounded-full bg-red-500/50" />
297
- <div className="w-2 h-2 rounded-full bg-yellow-500/50" />
298
- <div className="w-2 h-2 rounded-full bg-green-500/50" />
299
- </div>
300
- </div>
301
- <div className="flex-1 bg-white relative overflow-hidden">
302
- <iframe
303
- key={key}
304
- src={`https://dartpad.dev/embed-flutter.html?theme=light&run=true&split=0&code=${encodeURIComponent(code)}`}
305
- className="w-full h-full border-0"
306
- sandbox="allow-scripts allow-same-origin allow-popups"
307
- title="Flutter Preview"
308
- />
309
- {!isRunning && (
310
- <div className="absolute inset-0 bg-black/5 flex items-center justify-center backdrop-blur-[1px]">
311
- <button
312
- onClick={handleRun}
313
- className="px-4 py-2 bg-blue-500 text-white rounded-lg shadow-lg hover:bg-blue-600 transition-colors flex items-center gap-2 text-sm font-medium"
314
- >
315
- <Play size={16} weight="fill" />
316
- Run App
317
- </button>
318
- </div>
319
- )}
320
- </div>
321
- </div>
322
  </div>
323
  </div>
324
  </div>
 
219
  </div>
220
  )}
221
 
222
+ {/* Main Content - DartPad Embed */}
223
  <div className="flex-1 flex flex-col min-w-0">
224
  {/* Toolbar */}
225
  <div className="h-10 bg-[#2d2d2d] border-b border-[#1e1e1e] flex items-center justify-between px-4">
 
245
  </div>
246
 
247
  <div className="flex items-center gap-2">
 
 
 
 
 
 
 
248
  <button
249
  onClick={handleDownload}
250
  className="p-1.5 text-gray-400 hover:text-white hover:bg-[#3e3e42] rounded transition-colors"
 
255
  </div>
256
  </div>
257
 
258
+ {/* DartPad Embed */}
259
+ <div className="flex-1 bg-[#1e1e1e] relative overflow-hidden">
260
+ <iframe
261
+ key={key}
262
+ src={`https://dartpad.dev/embed-flutter.html?theme=dark&run=true&split=50&code=${encodeURIComponent(code)}`}
263
+ className="w-full h-full border-0"
264
+ sandbox="allow-scripts allow-same-origin allow-popups"
265
+ title="Flutter Preview"
266
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  </div>
268
  </div>
269
  </div>
app/components/LaTeXEditor.tsx CHANGED
@@ -1,36 +1,44 @@
1
  'use client'
2
 
3
  import React, { useState, useEffect, useRef } from 'react'
4
- import Window from './Window'
5
- import { MathOperations, FloppyDisk, Download, Sparkle, Copy, Eye, FileText, Trash, FolderOpen } from '@phosphor-icons/react'
6
  import Editor from '@monaco-editor/react'
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  import katex from 'katex'
8
  import 'katex/dist/katex.min.css'
9
 
10
- interface LaTeXDocument {
11
- id: string
12
- name: string
13
- content: string
14
- createdAt: Date
15
- modifiedAt: Date
16
- sessionId?: string
17
- }
18
-
19
  interface LaTeXEditorProps {
20
  onClose: () => void
21
- sessionId: string
22
  onMinimize?: () => void
23
  onMaximize?: () => void
 
24
  }
25
 
26
- export function LaTeXEditor({ onClose, sessionId, onMinimize, onMaximize }: LaTeXEditorProps) {
27
- const [content, setContent] = useState(`\\documentclass{article}
28
- \\usepackage{amsmath}
29
- \\usepackage{amssymb}
30
- \\usepackage{graphicx}
 
 
 
31
 
 
 
 
32
  \\title{My LaTeX Document}
33
- \\author{Your Name}
34
  \\date{\\today}
35
 
36
  \\begin{document}
@@ -38,527 +46,258 @@ export function LaTeXEditor({ onClose, sessionId, onMinimize, onMaximize }: LaTe
38
  \\maketitle
39
 
40
  \\section{Introduction}
41
- This is a sample LaTeX document with mathematical expressions.
42
 
43
  \\section{Mathematics}
44
- Here's the quadratic formula:
45
- \\[
46
- x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}
47
- \\]
48
-
49
- And here's an inline equation: $E = mc^2$
50
-
51
- \\section{Conclusion}
52
- LaTeX makes mathematical typesetting beautiful!
53
 
54
  \\end{document}`)
55
 
56
- const [renderedContent, setRenderedContent] = useState('')
57
- const [claudePrompt, setClaudePrompt] = useState('')
58
- const [isGenerating, setIsGenerating] = useState(false)
59
- const [documents, setDocuments] = useState<LaTeXDocument[]>([])
60
- const [currentDocument, setCurrentDocument] = useState<LaTeXDocument | null>(null)
61
- const [saving, setSaving] = useState(false)
62
- const [copySuccess, setCopySuccess] = useState(false)
63
- const [showPreview, setShowPreview] = useState(true)
64
- const previewRef = useRef<HTMLDivElement>(null)
65
-
66
- // Load documents from localStorage (session-specific)
67
- useEffect(() => {
68
- const storageKey = `latex_documents_${sessionId}`
69
- const docs = localStorage.getItem(storageKey)
70
- if (docs) {
71
- const parsed = JSON.parse(docs)
72
- setDocuments(parsed.map((d: any) => ({
73
- ...d,
74
- createdAt: new Date(d.createdAt),
75
- modifiedAt: new Date(d.modifiedAt)
76
- })))
77
  }
78
- }, [sessionId])
 
 
 
79
 
80
- // Render LaTeX content
81
  useEffect(() => {
82
- renderLaTeX()
83
- }, [content])
84
-
85
- const renderLaTeX = () => {
86
- try {
87
- // Extract math expressions and render them
88
- let html = content
89
-
90
- // Remove LaTeX document structure for preview
91
- html = html.replace(/\\documentclass\{.*?\}/g, '')
92
- html = html.replace(/\\usepackage\{.*?\}/g, '')
93
- html = html.replace(/\\title\{(.*?)\}/g, '<h1>$1</h1>')
94
- html = html.replace(/\\author\{(.*?)\}/g, '<p class="author">Author: $1</p>')
95
- html = html.replace(/\\date\{.*?\}/g, '')
96
- html = html.replace(/\\maketitle/g, '')
97
- html = html.replace(/\\begin\{document\}/g, '')
98
- html = html.replace(/\\end\{document\}/g, '')
99
-
100
- // Convert sections
101
- html = html.replace(/\\section\{(.*?)\}/g, '<h2>$1</h2>')
102
- html = html.replace(/\\subsection\{(.*?)\}/g, '<h3>$1</h3>')
103
- html = html.replace(/\\subsubsection\{(.*?)\}/g, '<h4>$1</h4>')
104
-
105
- // Convert lists
106
- html = html.replace(/\\begin\{itemize\}/g, '<ul>')
107
- html = html.replace(/\\end\{itemize\}/g, '</ul>')
108
- html = html.replace(/\\begin\{enumerate\}/g, '<ol>')
109
- html = html.replace(/\\end\{enumerate\}/g, '</ol>')
110
- html = html.replace(/\\item\s+(.*?)(?=\\item|<\/ul>|<\/ol>|$)/g, '<li>$1</li>')
111
-
112
- // Convert display math
113
- html = html.replace(/\\\[([\s\S]*?)\\\]/g, (match, math) => {
114
- try {
115
- return `<div class="math-display">${katex.renderToString(math, { displayMode: true, throwOnError: false })}</div>`
116
- } catch {
117
- return `<div class="math-error">Error rendering: ${math}</div>`
118
- }
119
- })
120
-
121
- // Convert inline math
122
- html = html.replace(/\$(.*?)\$/g, (match, math) => {
123
- try {
124
- return katex.renderToString(math, { displayMode: false, throwOnError: false })
125
- } catch {
126
- return `<span class="math-error">Error: ${math}</span>`
127
- }
128
- })
129
-
130
- // Convert paragraphs
131
- html = html.split('\n\n').map(p => p.trim() ? `<p>${p}</p>` : '').join('\n')
132
-
133
- setRenderedContent(html)
134
- } catch (error) {
135
- console.error('LaTeX rendering error:', error)
136
- setRenderedContent('<p class="error">Error rendering LaTeX</p>')
137
- }
138
- }
139
-
140
- const handleGenerateLaTeX = async () => {
141
- if (!claudePrompt.trim()) return
142
-
143
- setIsGenerating(true)
144
- try {
145
- const response = await fetch('/api/claude/generate-latex', {
146
- method: 'POST',
147
- headers: { 'Content-Type': 'application/json' },
148
- body: JSON.stringify({
149
- prompt: claudePrompt,
150
- sessionId
151
  })
152
- })
153
-
154
- if (response.ok) {
155
- const data = await response.json()
156
- setContent(data.latex || content)
157
- setClaudePrompt('')
158
  }
159
- } catch (error) {
160
- console.error('Failed to generate LaTeX:', error)
161
- // Generate mock LaTeX for demo
162
- const mockLaTeX = generateMockLaTeX(claudePrompt)
163
- setContent(mockLaTeX)
164
- setClaudePrompt('')
165
- } finally {
166
- setIsGenerating(false)
167
  }
168
- }
169
-
170
- const generateMockLaTeX = (prompt: string): string => {
171
- return `\\documentclass{article}
172
- \\usepackage{amsmath}
173
- \\usepackage{amssymb}
174
-
175
- \\title{${prompt}}
176
- \\author{Generated by Claude}
177
- \\date{\\today}
178
-
179
- \\begin{document}
180
-
181
- \\maketitle
182
-
183
- \\section{Generated Content}
184
- This LaTeX document was generated based on: "${prompt}"
185
-
186
- \\section{Example Mathematics}
187
- Here are some mathematical expressions related to your request:
188
-
189
- \\subsection{Equations}
190
- \\[
191
- f(x) = \\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}
192
- \\]
193
-
194
- \\[
195
- \\nabla \\times \\vec{E} = -\\frac{\\partial \\vec{B}}{\\partial t}
196
- \\]
197
-
198
- \\section{Conclusion}
199
- This document demonstrates LaTeX capabilities for: ${prompt}
200
-
201
- \\end{document}`
202
- }
203
-
204
- const handleSaveDocument = async () => {
205
- setSaving(true)
206
- try {
207
- const docName = prompt('Enter document name:', currentDocument?.name || 'document.tex')
208
- if (!docName) return
209
 
210
- const newDoc: LaTeXDocument = {
211
- id: currentDocument?.id || Date.now().toString(),
212
- name: docName,
213
- content: content,
214
- createdAt: currentDocument?.createdAt || new Date(),
215
- modifiedAt: new Date(),
216
- sessionId
217
- }
218
-
219
- const updatedDocs = currentDocument
220
- ? documents.map(d => d.id === currentDocument.id ? newDoc : d)
221
- : [...documents, newDoc]
222
-
223
- setDocuments(updatedDocs)
224
- setCurrentDocument(newDoc)
225
-
226
- // Auto-save to session
227
- useEffect(() => {
228
- const saveToSession = async () => {
229
- if (!sessionId) return
230
- setIsSaving(true)
231
- try {
232
- await fetch('/api/session/code', {
233
- method: 'POST',
234
- headers: {
235
- 'Content-Type': 'application/json',
236
- 'x-session-id': sessionId
237
- },
238
- body: JSON.stringify({
239
- type: 'latex',
240
- code,
241
- filename: 'main.tex'
242
- })
243
- })
244
- } catch (error) {
245
- console.error('Failed to save to session:', error)
246
- } finally {
247
- setIsSaving(false)
248
- }
249
- }
250
-
251
- const debounce = setTimeout(saveToSession, 2000)
252
- return () => clearTimeout(debounce)
253
- }, [code, sessionId])
254
-
255
- // Render LaTeX preview
256
- useEffect(() => {
257
- if (previewRef.current) {
258
- try {
259
- // Simple regex-based rendering for demo purposes
260
- // In a real app, you'd use a full LaTeX engine or server-side rendering
261
- const text = code
262
- .replace(/\\section\{([^}]+)\}/g, '<h2>$1</h2>')
263
- .replace(/\\subsection\{([^}]+)\}/g, '<h3>$1</h3>')
264
- .replace(/\\textbf\{([^}]+)\}/g, '<b>$1</b>')
265
- .replace(/\\textit\{([^}]+)\}/g, '<i>$1</i>')
266
- .replace(/\\maketitle/, '<div class="title"><h1>My LaTeX Document</h1><p>Reuben OS</p><p>' + new Date().toLocaleDateString() + '</p></div>')
267
- .replace(/\\begin\{document\}/, '')
268
- .replace(/\\end\{document\}/, '')
269
- .replace(/\\documentclass\{[^}]+\}/, '')
270
- .replace(/\\usepackage\{[^}]+\}/, '')
271
- .replace(/\\title\{[^}]+\}/, '')
272
- .replace(/\\author\{[^}]+\}/, '')
273
- .replace(/\\date\{[^}]+\}/, '')
274
 
275
- // Split by math delimiters to render KaTeX
276
- const parts = text.split(/(\\\[[\s\S]*?\\\]|\$[^$]+\$)/)
277
- previewRef.current.innerHTML = ''
278
-
279
- parts.forEach(part => {
280
- if (part.startsWith('\\[') || part.startsWith('$')) {
281
- const math = part.replace(/^\\\[|\\\]$|^\$|\$$/g, '')
282
- const span = document.createElement('span')
283
- try {
284
- katex.render(math, span, { throwOnError: false, displayMode: part.startsWith('\\[') })
285
- previewRef.current?.appendChild(span)
286
- } catch (e) {
287
- previewRef.current?.appendChild(document.createTextNode(part))
288
- }
289
- } else {
290
- const div = document.createElement('div')
291
- div.innerHTML = part
292
- previewRef.current?.appendChild(div)
293
- }
294
- })
295
- } catch (e) {
296
- console.error('Render error:', e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  }
298
- }
299
- }, [code])
300
-
301
- const handleDownload = () => {
302
- const blob = new Blob([code], { type: 'text/plain' })
303
- const url = URL.createObjectURL(blob)
304
- const a = document.createElement('a')
305
- a.href = url
306
- a.download = 'main.tex'
307
- a.click()
308
- URL.revokeObjectURL(url)
309
  }
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
- return (
312
- <Window
313
- id="latex-editor"
314
- title="LaTeX Studio"
315
- isOpen={true}
316
- onClose={onClose}
317
- onMinimize={onMinimize}
318
- onMaximize={onMaximize}
319
- width={1200}
320
- height={800}
321
- x={60}
322
- y={60}
323
- className="latex-studio-window"
324
- >
325
- <div className="flex h-full bg-[#1e1e1e] text-gray-300 font-sans">
326
- {/* Sidebar */}
327
- {showSidebar && (
328
- <div className="w-64 bg-[#252526] border-r border-[#333] flex flex-col">
329
- <div className="h-9 px-4 flex items-center text-xs font-bold text-gray-500 uppercase tracking-wider">
330
- Explorer
 
 
 
 
 
 
331
  </div>
332
- <div className="flex-1 overflow-y-auto py-2">
333
- <div className="px-2">
334
- <div className="flex items-center gap-1 py-1 px-2 text-sm text-gray-300 hover:bg-[#2a2d2e] rounded cursor-pointer">
335
- <CaretDown size={12} weight="bold" />
336
- <span className="font-bold">PROJECT</span>
337
- </div>
338
- <div className="pl-4">
339
- {files.map(file => (
340
- <div key={file.id}>
341
- <div className="flex items-center gap-2 py-1 px-2 text-sm hover:bg-[#2a2d2e] rounded cursor-pointer text-blue-400">
342
- <CaretDown size={12} weight="bold" />
343
- {file.name}
344
- </div>
345
- {file.children?.map(child => (
346
- <div
347
- key={child.id}
348
- onClick={() => setActiveFileId(child.id)}
349
- className={`flex items-center gap-2 py-1 px-2 ml-4 text-sm rounded cursor-pointer ${activeFileId === child.id ? 'bg-[#37373d] text-white' : 'text-gray-400 hover:bg-[#2a2d2e]'
350
- }`}
351
- >
352
- <FileText size={14} />
353
- {child.name}
354
- </div>
355
- ))}
356
  </div>
357
  ))}
358
  </div>
359
- </div>
360
  </div>
361
  </div>
362
- )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
 
364
- {/* Main Content */}
365
- <div className="flex-1 flex flex-col min-w-0">
366
- {/* Toolbar */}
367
- <div className="h-10 bg-[#2d2d2d] border-b border-[#1e1e1e] flex items-center justify-between px-4">
368
- <div className="flex items-center gap-2">
369
- <button
370
- onClick={() => setShowSidebar(!showSidebar)}
371
- className={`p-1.5 rounded hover:bg-[#3e3e42] ${showSidebar ? 'text-white' : 'text-gray-500'}`}
372
- title="Toggle Sidebar"
373
- >
374
- <SidebarSimple size={16} />
375
- </button>
376
- <div className="h-4 w-[1px] bg-gray-600 mx-2" />
377
- <div className="flex items-center gap-2 px-3 py-1 bg-[#1e1e1e] rounded text-xs text-gray-400 border border-[#333]">
378
- <FileText size={14} className="text-green-400" />
379
- main.tex
380
- </div>
381
- {isSaving && (
382
- <span className="text-xs text-gray-500 flex items-center gap-1">
383
- <ArrowsClockwise size={12} className="animate-spin" />
384
- Syncing...
385
- </span>
386
- )}
387
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
 
 
 
 
 
389
  <div className="flex items-center gap-2">
390
- <button
391
- className="flex items-center gap-2 px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white rounded text-xs font-medium transition-colors"
392
- >
393
- <Play size={14} weight="fill" />
394
- Compile
395
- </button>
396
- <button
397
- onClick={handleDownload}
398
- className="p-1.5 text-gray-400 hover:text-white hover:bg-[#3e3e42] rounded transition-colors"
399
- title="Download PDF"
400
- >
401
- <Download size={16} />
402
- </button>
403
  </div>
404
  </div>
405
-
406
- {/* Split View: Editor & Preview */}
407
- <div className="flex-1 flex overflow-hidden">
408
- {/* Editor */}
409
- <div className="flex-1 border-r border-[#333]">
410
- <Editor
411
- height="100%"
412
- defaultLanguage="latex"
413
- theme="vs-dark"
414
- value={code}
415
- onChange={(value) => setCode(value || '')}
416
- options={{
417
- minimap: { enabled: false },
418
- fontSize: 14,
419
- fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
420
- lineNumbers: 'on',
421
- scrollBeyondLastLine: false,
422
- automaticLayout: true,
423
- padding: { top: 16, bottom: 16 },
424
- renderLineHighlight: 'all',
425
- smoothScrolling: true,
426
- cursorBlinking: 'smooth',
427
- cursorSmoothCaretAnimation: 'on'
428
- }}
429
- />
430
- </div>
431
-
432
- {/* Preview */}
433
- <div className="w-[500px] bg-[#525659] flex flex-col border-l border-[#333]">
434
- <div className="h-8 bg-[#323639] border-b border-[#2b2b2b] flex items-center justify-between px-3 shadow-md z-10">
435
- <span className="text-xs font-bold text-gray-400 uppercase">PDF Preview</span>
436
- <div className="flex items-center gap-2">
437
- <span className="text-xs text-gray-400">100%</span>
438
- </div>
439
- </div>
440
- <div className="flex-1 overflow-y-auto p-8 flex justify-center">
441
- <div
442
- className="bg-white w-full max-w-[800px] min-h-[1000px] shadow-2xl p-12 text-black font-serif"
443
- ref={previewRef}
444
- style={{
445
- boxShadow: '0 0 20px rgba(0,0,0,0.5)'
446
- }}
447
- dangerouslySetInnerHTML={{ __html: renderedContent }}
448
- />
449
- </div>
450
- </div>
451
- )}
452
-
453
- {/* Documents Sidebar */}
454
- <div className="w-64 bg-gray-50 border-l border-gray-200 flex flex-col">
455
- <div className="p-4 border-b border-gray-200">
456
- <span className="text-xs font-bold text-gray-400 uppercase tracking-wider">Saved Documents</span>
457
- </div>
458
-
459
- <div className="flex-1 overflow-y-auto p-3 space-y-2">
460
- {documents.length === 0 ? (
461
- <div className="text-gray-400 text-sm text-center py-8 italic">
462
- No saved documents
463
- </div>
464
- ) : (
465
- documents.map(doc => (
466
- <div
467
- key={doc.id}
468
- className={`group rounded-lg p-3 cursor-pointer border transition-all ${currentDocument?.id === doc.id
469
- ? 'bg-white border-purple-200 shadow-sm ring-1 ring-purple-500/20'
470
- : 'bg-transparent border-transparent hover:bg-white hover:border-gray-200'
471
- }`}
472
- onClick={() => handleLoadDocument(doc)}
473
- >
474
- <div className="flex items-center justify-between mb-1">
475
- <div className="flex items-center gap-2 overflow-hidden">
476
- <FileText size={16} className={currentDocument?.id === doc.id ? "text-purple-500" : "text-gray-400"} weight={currentDocument?.id === doc.id ? "fill" : "regular"} />
477
- <span className={`text-sm font-medium truncate ${currentDocument?.id === doc.id ? "text-gray-900" : "text-gray-600"}`}>
478
- {doc.name}
479
- </span>
480
- </div>
481
- <button
482
- onClick={(e) => {
483
- e.stopPropagation()
484
- handleDeleteDocument(doc.id)
485
- }}
486
- className="opacity-0 group-hover:opacity-100 p-1 hover:bg-red-50 rounded text-gray-400 hover:text-red-500 transition-all"
487
- >
488
- <Trash size={14} />
489
- </button>
490
- </div>
491
- <div className="text-[10px] text-gray-400 pl-6">
492
- {doc.modifiedAt.toLocaleDateString()}
493
- </div>
494
- </div>
495
- ))
496
- )}
497
- </div>
498
  </div>
499
  </div>
500
-
501
- {/* Style for rendered content */}
502
- <style jsx global>{`
503
- .math-display {
504
- margin: 1.5em 0;
505
- text-align: center;
506
- overflow-x: auto;
507
- padding: 1em 0;
508
- }
509
- .math-error {
510
- color: #ef4444;
511
- font-style: italic;
512
- background: #fef2f2;
513
- padding: 0.2em 0.5em;
514
- border-radius: 4px;
515
- }
516
- .author {
517
- text-align: center;
518
- font-style: italic;
519
- margin: 1em 0 2em;
520
- color: #4b5563;
521
- }
522
- h1 {
523
- font-size: 2.5em;
524
- margin: 1em 0 0.5em;
525
- font-weight: 700;
526
- line-height: 1.2;
527
- color: #111827;
528
- }
529
- h2 {
530
- font-size: 1.75em;
531
- margin: 1.5em 0 0.8em;
532
- font-weight: 600;
533
- color: #1f2937;
534
- border-bottom: 1px solid #e5e7eb;
535
- padding-bottom: 0.3em;
536
- }
537
- h3 {
538
- font-size: 1.4em;
539
- margin: 1.2em 0 0.6em;
540
- font-weight: 600;
541
- color: #374151;
542
- }
543
- h4 {
544
- font-size: 1.2em;
545
- margin: 1em 0 0.5em;
546
- font-weight: 600;
547
- color: #4b5563;
548
- }
549
- p {
550
- margin-bottom: 1em;
551
- text-align: justify;
552
- }
553
- ul, ol {
554
- margin: 1em 0;
555
- padding-left: 2em;
556
- }
557
- li {
558
- margin-bottom: 0.5em;
559
- }
560
- `}</style>
561
  </div>
562
- </Window>
563
- )
564
- }
 
 
 
 
1
  'use client'
2
 
3
  import React, { useState, useEffect, useRef } from 'react'
 
 
4
  import Editor from '@monaco-editor/react'
5
+ import {
6
+ Play,
7
+ Download,
8
+ X,
9
+ Minus,
10
+ Square,
11
+ SidebarSimple,
12
+ FileText,
13
+ ArrowsClockwise,
14
+ CaretDown,
15
+ FilePdf
16
+ } from '@phosphor-icons/react'
17
+ import Window from './Window'
18
  import katex from 'katex'
19
  import 'katex/dist/katex.min.css'
20
 
 
 
 
 
 
 
 
 
 
21
  interface LaTeXEditorProps {
22
  onClose: () => void
 
23
  onMinimize?: () => void
24
  onMaximize?: () => void
25
+ sessionId?: string
26
  }
27
 
28
+ interface FileNode {
29
+ id: string
30
+ name: string
31
+ type: 'file' | 'folder'
32
+ content?: string
33
+ children?: FileNode[]
34
+ isOpen?: boolean
35
+ }
36
 
37
+ export function LaTeXEditor({ onClose, onMinimize, onMaximize, sessionId }: LaTeXEditorProps) {
38
+ const [code, setCode] = useState(`\\documentclass{article}
39
+ \\usepackage{amsmath}
40
  \\title{My LaTeX Document}
41
+ \\author{Reuben OS}
42
  \\date{\\today}
43
 
44
  \\begin{document}
 
46
  \\maketitle
47
 
48
  \\section{Introduction}
49
+ Welcome to LaTeX Studio on Reuben OS.
50
 
51
  \\section{Mathematics}
52
+ Here is an equation:
53
+ \\[ E = mc^2 \\]
 
 
 
 
 
 
 
54
 
55
  \\end{document}`)
56
 
57
+ const [showSidebar, setShowSidebar] = useState(true)
58
+ const [files, setFiles] = useState<FileNode[]>([
59
+ {
60
+ id: 'root',
61
+ name: 'Project',
62
+ type: 'folder',
63
+ isOpen: true,
64
+ children: [
65
+ { id: 'main', name: 'main.tex', type: 'file', content: code }
66
+ ]
 
 
 
 
 
 
 
 
 
 
 
67
  }
68
+ ])
69
+ const [activeFileId, setActiveFileId] = useState('main')
70
+ const [isSaving, setIsSaving] = useState(false)
71
+ const previewRef = useRef<HTMLDivElement>(null)
72
 
73
+ // Auto-save to session
74
  useEffect(() => {
75
+ const saveToSession = async () => {
76
+ if (!sessionId) return
77
+ setIsSaving(true)
78
+ try {
79
+ await fetch('/api/session/code', {
80
+ method: 'POST',
81
+ headers: {
82
+ 'Content-Type': 'application/json',
83
+ 'x-session-id': sessionId
84
+ },
85
+ body: JSON.stringify({
86
+ type: 'latex',
87
+ code,
88
+ filename: 'main.tex'
89
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  })
91
+ } catch (error) {
92
+ console.error('Failed to save to session:', error)
93
+ } finally {
94
+ setIsSaving(false)
 
 
95
  }
 
 
 
 
 
 
 
 
96
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
+ const debounce = setTimeout(saveToSession, 2000)
99
+ return () => clearTimeout(debounce)
100
+ }, [code, sessionId])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
+ // Render LaTeX preview
103
+ useEffect(() => {
104
+ if (previewRef.current) {
105
+ try {
106
+ // Simple regex-based rendering for demo purposes
107
+ const text = code
108
+ .replace(/\\section\{([^}]+)\}/g, '<h2>$1</h2>')
109
+ .replace(/\\subsection\{([^}]+)\}/g, '<h3>$1</h3>')
110
+ .replace(/\\textbf\{([^}]+)\}/g, '<b>$1</b>')
111
+ .replace(/\\textit\{([^}]+)\}/g, '<i>$1</i>')
112
+ .replace(/\\maketitle/, '<div class="title"><h1>My LaTeX Document</h1><p>Reuben OS</p><p>' + new Date().toLocaleDateString() + '</p></div>')
113
+ .replace(/\\begin\{document\}/, '')
114
+ .replace(/\\end\{document\}/, '')
115
+ .replace(/\\documentclass\{[^}]+\}/, '')
116
+ .replace(/\\usepackage\{[^}]+\}/, '')
117
+ .replace(/\\title\{[^}]+\}/, '')
118
+ .replace(/\\author\{[^}]+\}/, '')
119
+ .replace(/\\date\{[^}]+\}/, '')
120
+
121
+ // Split by math delimiters to render KaTeX
122
+ const parts = text.split(/(\\\[[\s\S]*?\\\]|\$[^$]+\$)/)
123
+ previewRef.current.innerHTML = ''
124
+
125
+ parts.forEach(part => {
126
+ if (part.startsWith('\\[') || part.startsWith('$')) {
127
+ const math = part.replace(/^\\\[|\\\]$|^\$|\$$/g, '')
128
+ const span = document.createElement('span')
129
+ try {
130
+ katex.render(math, span, { throwOnError: false, displayMode: part.startsWith('\\[') })
131
+ previewRef.current?.appendChild(span)
132
+ } catch (e) {
133
+ previewRef.current?.appendChild(document.createTextNode(part))
134
+ }
135
+ } else {
136
+ const div = document.createElement('div')
137
+ div.innerHTML = part
138
+ previewRef.current?.appendChild(div)
139
  }
140
+ })
141
+ } catch (e) {
142
+ console.error('Render error:', e)
 
 
 
 
 
 
 
 
143
  }
144
+ }
145
+ }, [code])
146
+
147
+ const handleDownload = () => {
148
+ const blob = new Blob([code], { type: 'text/plain' })
149
+ const url = URL.createObjectURL(blob)
150
+ const a = document.createElement('a')
151
+ a.href = url
152
+ a.download = 'main.tex'
153
+ a.click()
154
+ URL.revokeObjectURL(url)
155
+ }
156
 
157
+ return (
158
+ <Window
159
+ id="latex-editor"
160
+ title="LaTeX Studio"
161
+ isOpen={true}
162
+ onClose={onClose}
163
+ onMinimize={onMinimize}
164
+ onMaximize={onMaximize}
165
+ width={1200}
166
+ height={800}
167
+ x={60}
168
+ y={60}
169
+ className="latex-studio-window"
170
+ >
171
+ <div className="flex h-full bg-[#1e1e1e] text-gray-300 font-sans">
172
+ {/* Sidebar */}
173
+ {showSidebar && (
174
+ <div className="w-64 bg-[#252526] border-r border-[#333] flex flex-col">
175
+ <div className="h-9 px-4 flex items-center text-xs font-bold text-gray-500 uppercase tracking-wider">
176
+ Explorer
177
+ </div>
178
+ <div className="flex-1 overflow-y-auto py-2">
179
+ <div className="px-2">
180
+ <div className="flex items-center gap-1 py-1 px-2 text-sm text-gray-300 hover:bg-[#2a2d2e] rounded cursor-pointer">
181
+ <CaretDown size={12} weight="bold" />
182
+ <span className="font-bold">PROJECT</span>
183
  </div>
184
+ <div className="pl-4">
185
+ {files.map(file => (
186
+ <div key={file.id}>
187
+ <div className="flex items-center gap-2 py-1 px-2 text-sm hover:bg-[#2a2d2e] rounded cursor-pointer text-blue-400">
188
+ <CaretDown size={12} weight="bold" />
189
+ {file.name}
190
+ </div>
191
+ {file.children?.map(child => (
192
+ <div
193
+ key={child.id}
194
+ onClick={() => setActiveFileId(child.id)}
195
+ className={`flex items-center gap-2 py-1 px-2 ml-4 text-sm rounded cursor-pointer ${activeFileId === child.id ? 'bg-[#37373d] text-white' : 'text-gray-400 hover:bg-[#2a2d2e]'
196
+ }`}
197
+ >
198
+ <FileText size={14} />
199
+ {child.name}
 
 
 
 
 
 
 
 
200
  </div>
201
  ))}
202
  </div>
203
+ ))}
204
  </div>
205
  </div>
206
+ </div>
207
+ </div>
208
+ )}
209
+
210
+ {/* Main Content */}
211
+ <div className="flex-1 flex flex-col min-w-0">
212
+ {/* Toolbar */}
213
+ <div className="h-10 bg-[#2d2d2d] border-b border-[#1e1e1e] flex items-center justify-between px-4">
214
+ <div className="flex items-center gap-2">
215
+ <button
216
+ onClick={() => setShowSidebar(!showSidebar)}
217
+ className={`p-1.5 rounded hover:bg-[#3e3e42] ${showSidebar ? 'text-white' : 'text-gray-500'}`}
218
+ title="Toggle Sidebar"
219
+ >
220
+ <SidebarSimple size={16} />
221
+ </button>
222
+ <div className="h-4 w-[1px] bg-gray-600 mx-2" />
223
+ <div className="flex items-center gap-2 px-3 py-1 bg-[#1e1e1e] rounded text-xs text-gray-400 border border-[#333]">
224
+ <FileText size={14} className="text-green-400" />
225
+ main.tex
226
+ </div>
227
+ {isSaving && (
228
+ <span className="text-xs text-gray-500 flex items-center gap-1">
229
+ <ArrowsClockwise size={12} className="animate-spin" />
230
+ Syncing...
231
+ </span>
232
+ )}
233
+ </div>
234
 
235
+ <div className="flex items-center gap-2">
236
+ <button
237
+ className="flex items-center gap-2 px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white rounded text-xs font-medium transition-colors"
238
+ >
239
+ <Play size={14} weight="fill" />
240
+ Compile
241
+ </button>
242
+ <button
243
+ onClick={handleDownload}
244
+ className="p-1.5 text-gray-400 hover:text-white hover:bg-[#3e3e42] rounded transition-colors"
245
+ title="Download PDF"
246
+ >
247
+ <Download size={16} />
248
+ </button>
249
+ </div>
250
+ </div>
251
+
252
+ {/* Split View: Editor & Preview */}
253
+ <div className="flex-1 flex overflow-hidden">
254
+ {/* Editor */}
255
+ <div className="flex-1 border-r border-[#333]">
256
+ <Editor
257
+ height="100%"
258
+ defaultLanguage="latex"
259
+ theme="vs-dark"
260
+ value={code}
261
+ onChange={(value) => setCode(value || '')}
262
+ options={{
263
+ minimap: { enabled: false },
264
+ fontSize: 14,
265
+ fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
266
+ lineNumbers: 'on',
267
+ scrollBeyondLastLine: false,
268
+ automaticLayout: true,
269
+ padding: { top: 16, bottom: 16 },
270
+ renderLineHighlight: 'all',
271
+ smoothScrolling: true,
272
+ cursorBlinking: 'smooth',
273
+ cursorSmoothCaretAnimation: 'on'
274
+ }}
275
+ />
276
+ </div>
277
 
278
+ {/* Preview */}
279
+ <div className="w-[500px] bg-[#525659] flex flex-col border-l border-[#333]">
280
+ <div className="h-8 bg-[#323639] border-b border-[#2b2b2b] flex items-center justify-between px-3 shadow-md z-10">
281
+ <span className="text-xs font-bold text-gray-400 uppercase">PDF Preview</span>
282
  <div className="flex items-center gap-2">
283
+ <span className="text-xs text-gray-400">100%</span>
 
 
 
 
 
 
 
 
 
 
 
 
284
  </div>
285
  </div>
286
+ <div className="flex-1 overflow-y-auto p-8 flex justify-center">
287
+ <div
288
+ className="bg-white w-full max-w-[800px] min-h-[1000px] shadow-2xl p-12 text-black font-serif"
289
+ ref={previewRef}
290
+ style={{
291
+ boxShadow: '0 0 20px rgba(0,0,0,0.5)'
292
+ }}
293
+ >
294
+ {/* Content rendered via useEffect */}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  </div>
296
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  </div>
298
+ </div>
299
+ </div>
300
+ </div>
301
+ </Window>
302
+ )
303
+ }
app/components/SpotlightSearch.tsx CHANGED
@@ -1,7 +1,17 @@
1
  'use client'
2
 
3
  import React, { useState, useRef, useEffect } from 'react'
4
- import { MagnifyingGlass, X } from '@phosphor-icons/react'
 
 
 
 
 
 
 
 
 
 
5
  import { motion, AnimatePresence } from 'framer-motion'
6
 
7
  interface SpotlightSearchProps {
@@ -23,13 +33,13 @@ export function SpotlightSearch({ isOpen, onClose, onOpenApp }: SpotlightSearchP
23
  const inputRef = useRef<HTMLInputElement>(null)
24
 
25
  const apps: SearchResult[] = [
26
- { id: 'files', name: 'Files', type: 'app' },
27
- { id: 'calendar', name: 'Calendar', type: 'app' },
28
- { id: 'clock', name: 'Clock', type: 'app' },
29
- { id: 'browser', name: 'Web Browser', type: 'app' },
30
- { id: 'gemini', name: 'Gemini Chat', type: 'app' },
31
- { id: 'terminal', name: 'Terminal', type: 'app' },
32
- { id: 'vscode', name: 'VS Code', type: 'app' },
33
  ]
34
 
35
  const files: SearchResult[] = [
@@ -132,10 +142,14 @@ export function SpotlightSearch({ isOpen, onClose, onOpenApp }: SpotlightSearchP
132
  <div
133
  key={result.id}
134
  onClick={() => handleSelect(result)}
135
- className={`flex items-center px-3 py-2.5 hover:bg-blue-500 hover:text-white rounded-lg cursor-pointer transition-colors ${
136
- index === 0 ? 'bg-blue-500/10' : ''
137
- }`}
138
  >
 
 
 
 
 
139
  <div className="flex-1 flex flex-col">
140
  <span className="font-medium">{result.name}</span>
141
  <span className="text-xs opacity-70 capitalize">{result.type}</span>
 
1
  'use client'
2
 
3
  import React, { useState, useRef, useEffect } from 'react'
4
+ import {
5
+ MagnifyingGlass,
6
+ X,
7
+ Folder,
8
+ Calendar,
9
+ Clock,
10
+ Sparkle,
11
+ Globe,
12
+ DeviceMobile,
13
+ Function as FunctionIcon
14
+ } from '@phosphor-icons/react'
15
  import { motion, AnimatePresence } from 'framer-motion'
16
 
17
  interface SpotlightSearchProps {
 
33
  const inputRef = useRef<HTMLInputElement>(null)
34
 
35
  const apps: SearchResult[] = [
36
+ { id: 'files', name: 'Files', type: 'app', icon: <div className="w-6 h-6 bg-gradient-to-br from-blue-400 to-cyan-200 rounded-md flex items-center justify-center border border-white/30"><Folder size={16} weight="fill" className="text-blue-900" /></div> },
37
+ { id: 'calendar', name: 'Calendar', type: 'app', icon: <div className="w-6 h-6 bg-white rounded-md flex items-center justify-center border border-gray-200"><Calendar size={16} weight="regular" className="text-red-500" /></div> },
38
+ { id: 'clock', name: 'Clock', type: 'app', icon: <div className="w-6 h-6 bg-white rounded-full flex items-center justify-center border border-gray-200"><Clock size={16} weight="regular" className="text-black" /></div> },
39
+ { id: 'gemini', name: 'Gemini Chat', type: 'app', icon: <div className="w-6 h-6 bg-gradient-to-b from-white to-blue-50 rounded-md flex items-center justify-center border border-white/50"><Sparkle size={16} weight="fill" className="text-blue-500" /></div> },
40
+ { id: 'research', name: 'Research', type: 'app', icon: <div className="w-6 h-6 bg-gradient-to-b from-purple-500 to-indigo-600 rounded-md flex items-center justify-center border border-white/20"><Globe size={16} weight="duotone" className="text-white" /></div> },
41
+ { id: 'flutter-editor', name: 'Flutter IDE', type: 'app', icon: <div className="w-6 h-6 bg-gradient-to-b from-[#54C5F8] to-[#29B6F6] rounded-md flex items-center justify-center border border-white/20"><DeviceMobile size={16} weight="fill" className="text-white" /></div> },
42
+ { id: 'latex-editor', name: 'LaTeX Studio', type: 'app', icon: <div className="w-6 h-6 bg-gradient-to-b from-slate-700 to-slate-900 rounded-md flex items-center justify-center border border-white/20"><FunctionIcon size={16} weight="bold" className="text-green-400" /></div> },
43
  ]
44
 
45
  const files: SearchResult[] = [
 
142
  <div
143
  key={result.id}
144
  onClick={() => handleSelect(result)}
145
+ className={`flex items-center px-3 py-2.5 hover:bg-blue-500 hover:text-white rounded-lg cursor-pointer transition-colors gap-3 ${index === 0 ? 'bg-blue-500/10' : ''
146
+ }`}
 
147
  >
148
+ {result.icon && (
149
+ <div className="w-8 h-8 flex items-center justify-center">
150
+ {result.icon}
151
+ </div>
152
+ )}
153
  <div className="flex-1 flex flex-col">
154
  <span className="font-medium">{result.name}</span>
155
  <span className="text-xs opacity-70 capitalize">{result.type}</span>