aimevzulari commited on
Commit
9a803f8
·
verified ·
1 Parent(s): 008543b

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -1216
app.py DELETED
@@ -1,1216 +0,0 @@
1
- """
2
- Cursor Rules Generator - Hugging Face Spaces App
3
-
4
- This module implements the Gradio interface for Hugging Face Spaces deployment.
5
- All code is self-contained in this file to avoid import issues.
6
- """
7
-
8
- import os
9
- import gradio as gr
10
- import json
11
- import requests
12
- import traceback
13
- from dotenv import load_dotenv
14
- from abc import ABC, abstractmethod
15
- from typing import Dict, List, Optional, Any
16
-
17
- # Load environment variables
18
- load_dotenv()
19
-
20
- # Configuration settings
21
- class Settings:
22
- """Application settings."""
23
-
24
- # Application settings
25
- APP_NAME = "Cursor Rules Generator"
26
- DEBUG = os.getenv("DEBUG", "False").lower() == "true"
27
-
28
- # API keys
29
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
30
- OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
31
- OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
32
-
33
- # Default settings
34
- DEFAULT_PROVIDER = os.getenv("DEFAULT_PROVIDER", "gemini")
35
- DEFAULT_RULE_TYPE = os.getenv("DEFAULT_RULE_TYPE", "Always")
36
-
37
- # LLM provider settings
38
- GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta"
39
- OPENAI_API_URL = "https://api.openai.com/v1"
40
- OPENROUTER_API_URL = "https://openrouter.ai/api/v1"
41
-
42
- # LLM model settings
43
- DEFAULT_GEMINI_MODEL = os.getenv("DEFAULT_GEMINI_MODEL", "gemini-2.0-flash")
44
- DEFAULT_OPENAI_MODEL = os.getenv("DEFAULT_OPENAI_MODEL", "gpt-4o")
45
- DEFAULT_OPENROUTER_MODEL = os.getenv("DEFAULT_OPENROUTER_MODEL", "openai/gpt-4o")
46
-
47
- # Rule generation settings
48
- MAX_RULE_LENGTH = int(os.getenv("MAX_RULE_LENGTH", "10000"))
49
- DEFAULT_TEMPERATURE = float(os.getenv("DEFAULT_TEMPERATURE", "0.7"))
50
-
51
- # LLM Adapter Interface
52
- class LLMAdapter(ABC):
53
- """Base adapter interface for LLM providers."""
54
-
55
- @abstractmethod
56
- def initialize(self, api_key: str, **kwargs) -> None:
57
- """Initialize the adapter with API key and optional parameters."""
58
- pass
59
-
60
- @abstractmethod
61
- def validate_api_key(self, api_key: str) -> bool:
62
- """Validate the API key."""
63
- pass
64
-
65
- @abstractmethod
66
- def get_available_models(self) -> List[Dict[str, str]]:
67
- """Get a list of available models from the provider."""
68
- pass
69
-
70
- @abstractmethod
71
- def generate_rule(
72
- self,
73
- model: str,
74
- rule_type: str,
75
- description: str,
76
- content: str,
77
- parameters: Optional[Dict[str, Any]] = None
78
- ) -> str:
79
- """Generate a Cursor Rule using the LLM provider."""
80
- pass
81
-
82
- # Gemini Adapter
83
- class GeminiAdapter(LLMAdapter):
84
- """Adapter for Google's Gemini API."""
85
-
86
- def __init__(self):
87
- """Initialize the Gemini adapter."""
88
- self.api_key = None
89
- self.api_url = Settings.GEMINI_API_URL
90
- self.initialized = False
91
- self.last_error = None
92
-
93
- def initialize(self, api_key: str, **kwargs) -> None:
94
- """Initialize the adapter with API key and optional parameters."""
95
- self.api_key = api_key
96
- self.api_url = kwargs.get('api_url', Settings.GEMINI_API_URL)
97
- self.initialized = True
98
-
99
- def validate_api_key(self, api_key: str) -> bool:
100
- """Validate the Gemini API key."""
101
- try:
102
- # Try to list models with the provided API key
103
- url = f"{self.api_url}/models?key={api_key}"
104
- response = requests.get(url)
105
-
106
- # Check if the request was successful
107
- if response.status_code == 200:
108
- return True
109
-
110
- # Store error details for debugging
111
- self.last_error = f"API Error: Status {response.status_code}, Response: {response.text}"
112
- print(f"Gemini API validation failed: {self.last_error}")
113
- return False
114
- except Exception as e:
115
- # Store exception details for debugging
116
- self.last_error = f"Exception: {str(e)}\n{traceback.format_exc()}"
117
- print(f"Gemini API validation exception: {self.last_error}")
118
- return False
119
-
120
- def get_available_models(self) -> List[Dict[str, str]]:
121
- """Get a list of available Gemini models."""
122
- if not self.initialized:
123
- raise ValueError("Adapter not initialized. Call initialize() first.")
124
-
125
- try:
126
- # Get available models
127
- url = f"{self.api_url}/models?key={self.api_key}"
128
- response = requests.get(url)
129
-
130
- if response.status_code != 200:
131
- print(f"Failed to get models: Status {response.status_code}, Response: {response.text}")
132
- raise ValueError(f"Failed to get models: {response.text}")
133
-
134
- data = response.json()
135
-
136
- # Filter for Gemini models and format the response
137
- models = []
138
- for model in data.get('models', []):
139
- if 'gemini' in model.get('name', '').lower():
140
- model_id = model.get('name').split('/')[-1]
141
- models.append({
142
- 'id': model_id,
143
- 'name': self._format_model_name(model_id)
144
- })
145
-
146
- # If no models found, return default models
147
- if not models:
148
- models = [
149
- {'id': 'gemini-2.5-pro', 'name': 'Gemini 2.5 Pro'},
150
- {'id': 'gemini-2.0-flash', 'name': 'Gemini 2.0 Flash'},
151
- {'id': 'gemini-2.0-flash-lite', 'name': 'Gemini 2.0 Flash-Lite'}
152
- ]
153
-
154
- return models
155
- except Exception as e:
156
- print(f"Exception in get_available_models: {str(e)}\n{traceback.format_exc()}")
157
- # Return default models on error
158
- return [
159
- {'id': 'gemini-2.5-pro', 'name': 'Gemini 2.5 Pro'},
160
- {'id': 'gemini-2.0-flash', 'name': 'Gemini 2.0 Flash'},
161
- {'id': 'gemini-2.0-flash-lite', 'name': 'Gemini 2.0 Flash-Lite'}
162
- ]
163
-
164
- def generate_rule(
165
- self,
166
- model: str,
167
- rule_type: str,
168
- description: str,
169
- content: str,
170
- parameters: Optional[Dict[str, Any]] = None
171
- ) -> str:
172
- """Generate a Cursor Rule using Gemini."""
173
- if not self.initialized:
174
- raise ValueError("Adapter not initialized. Call initialize() first.")
175
-
176
- # Set default parameters if not provided
177
- if parameters is None:
178
- parameters = {}
179
-
180
- # Extract parameters
181
- temperature = parameters.get('temperature', Settings.DEFAULT_TEMPERATURE)
182
- globs = parameters.get('globs', '')
183
- referenced_files = parameters.get('referenced_files', '')
184
- prompt = parameters.get('prompt', '')
185
-
186
- # Prepare the prompt for Gemini
187
- system_prompt = """
188
- You are a Cursor Rules expert. Create a rule in MDC format based on the provided information.
189
-
190
- MDC format example:
191
- ---
192
- description: RPC Service boilerplate
193
- globs:
194
- alwaysApply: false
195
- ---
196
-
197
- - Use our internal RPC pattern when defining services
198
- - Always use snake_case for service names.
199
-
200
- @service-template.ts
201
- """
202
-
203
- user_prompt = f"""
204
- Create a Cursor Rule with the following details:
205
-
206
- Rule Type: {rule_type}
207
- Description: {description}
208
- Content: {content}
209
- """
210
-
211
- if globs:
212
- user_prompt += f"\nGlobs: {globs}"
213
-
214
- if referenced_files:
215
- user_prompt += f"\nReferenced Files: {referenced_files}"
216
-
217
- if prompt:
218
- user_prompt += f"\nAdditional Instructions: {prompt}"
219
-
220
- # Prepare the API request
221
- url = f"{self.api_url}/models/{model}:generateContent?key={self.api_key}"
222
-
223
- payload = {
224
- "contents": [
225
- {
226
- "role": "user",
227
- "parts": [
228
- {"text": system_prompt + "\n\n" + user_prompt}
229
- ]
230
- }
231
- ],
232
- "generationConfig": {
233
- "temperature": temperature,
234
- "topP": 0.8,
235
- "topK": 40,
236
- "maxOutputTokens": 2048
237
- }
238
- }
239
-
240
- # Make the API request
241
- try:
242
- response = requests.post(url, json=payload)
243
-
244
- if response.status_code != 200:
245
- print(f"Failed to generate rule: Status {response.status_code}, Response: {response.text}")
246
- raise ValueError(f"Failed to generate rule: {response.text}")
247
-
248
- data = response.json()
249
-
250
- # Extract the generated text
251
- generated_text = data.get('candidates', [{}])[0].get('content', {}).get('parts', [{}])[0].get('text', '')
252
-
253
- # If no text was generated, create a basic rule
254
- if not generated_text:
255
- return self._create_basic_rule(rule_type, description, content, globs, referenced_files)
256
-
257
- return generated_text
258
- except Exception as e:
259
- print(f"Exception in generate_rule: {str(e)}\n{traceback.format_exc()}")
260
- # Create a basic rule on error
261
- return self._create_basic_rule(rule_type, description, content, globs, referenced_files)
262
-
263
- def _format_model_name(self, model_id: str) -> str:
264
- """Format a model ID into a human-readable name."""
265
- # Replace hyphens with spaces and capitalize each word
266
- name = model_id.replace('-', ' ').title()
267
-
268
- # Special case handling
269
- name = name.replace('Gemini ', 'Gemini ')
270
- name = name.replace('Pro ', 'Pro ')
271
- name = name.replace('Flash ', 'Flash ')
272
- name = name.replace('Lite', 'Lite')
273
-
274
- return name
275
-
276
- def _create_basic_rule(
277
- self,
278
- rule_type: str,
279
- description: str,
280
- content: str,
281
- globs: str = '',
282
- referenced_files: str = ''
283
- ) -> str:
284
- """Create a basic rule in MDC format without using the LLM."""
285
- # Create MDC format
286
- mdc = '---\n'
287
- mdc += f'description: {description}\n'
288
-
289
- if rule_type == 'Auto Attached' and globs:
290
- mdc += f'globs: {globs}\n'
291
-
292
- if rule_type == 'Always':
293
- mdc += 'alwaysApply: true\n'
294
- else:
295
- mdc += 'alwaysApply: false\n'
296
-
297
- mdc += '---\n\n'
298
- mdc += content + '\n'
299
-
300
- # Add referenced files
301
- if referenced_files:
302
- mdc += '\n' + referenced_files
303
-
304
- return mdc
305
-
306
- # OpenAI Adapter
307
- class OpenAIAdapter(LLMAdapter):
308
- """Adapter for OpenAI API."""
309
-
310
- def __init__(self):
311
- """Initialize the OpenAI adapter."""
312
- self.api_key = None
313
- self.api_url = Settings.OPENAI_API_URL
314
- self.initialized = False
315
- self.last_error = None
316
-
317
- def initialize(self, api_key: str, **kwargs) -> None:
318
- """Initialize the adapter with API key and optional parameters."""
319
- self.api_key = api_key
320
- self.api_url = kwargs.get('api_url', Settings.OPENAI_API_URL)
321
- self.initialized = True
322
-
323
- def validate_api_key(self, api_key: str) -> bool:
324
- """Validate the OpenAI API key."""
325
- try:
326
- # Try to list models with the provided API key
327
- url = f"{self.api_url}/models"
328
- headers = {
329
- "Authorization": f"Bearer {api_key}"
330
- }
331
- response = requests.get(url, headers=headers)
332
-
333
- # Check if the request was successful
334
- if response.status_code == 200:
335
- return True
336
-
337
- # Store error details for debugging
338
- self.last_error = f"API Error: Status {response.status_code}, Response: {response.text}"
339
- print(f"OpenAI API validation failed: {self.last_error}")
340
- return False
341
- except Exception as e:
342
- # Store exception details for debugging
343
- self.last_error = f"Exception: {str(e)}\n{traceback.format_exc()}"
344
- print(f"OpenAI API validation exception: {self.last_error}")
345
- return False
346
-
347
- def get_available_models(self) -> List[Dict[str, str]]:
348
- """Get a list of available OpenAI models."""
349
- if not self.initialized:
350
- raise ValueError("Adapter not initialized. Call initialize() first.")
351
-
352
- try:
353
- # Get available models
354
- url = f"{self.api_url}/models"
355
- headers = {
356
- "Authorization": f"Bearer {self.api_key}"
357
- }
358
- response = requests.get(url, headers=headers)
359
-
360
- if response.status_code != 200:
361
- print(f"Failed to get models: Status {response.status_code}, Response: {response.text}")
362
- raise ValueError(f"Failed to get models: {response.text}")
363
-
364
- data = response.json()
365
-
366
- # Filter for chat models and format the response
367
- models = []
368
- for model in data.get('data', []):
369
- model_id = model.get('id')
370
- if any(prefix in model_id for prefix in ['gpt-4', 'gpt-3.5']):
371
- models.append({
372
- 'id': model_id,
373
- 'name': self._format_model_name(model_id)
374
- })
375
-
376
- # If no models found, return default models
377
- if not models:
378
- models = [
379
- {'id': 'gpt-4o', 'name': 'GPT-4o'},
380
- {'id': 'gpt-4-turbo', 'name': 'GPT-4 Turbo'},
381
- {'id': 'gpt-3.5-turbo', 'name': 'GPT-3.5 Turbo'}
382
- ]
383
-
384
- return models
385
- except Exception as e:
386
- print(f"Exception in get_available_models: {str(e)}\n{traceback.format_exc()}")
387
- # Return default models on error
388
- return [
389
- {'id': 'gpt-4o', 'name': 'GPT-4o'},
390
- {'id': 'gpt-4-turbo', 'name': 'GPT-4 Turbo'},
391
- {'id': 'gpt-3.5-turbo', 'name': 'GPT-3.5 Turbo'}
392
- ]
393
-
394
- def generate_rule(
395
- self,
396
- model: str,
397
- rule_type: str,
398
- description: str,
399
- content: str,
400
- parameters: Optional[Dict[str, Any]] = None
401
- ) -> str:
402
- """Generate a Cursor Rule using OpenAI."""
403
- if not self.initialized:
404
- raise ValueError("Adapter not initialized. Call initialize() first.")
405
-
406
- # Set default parameters if not provided
407
- if parameters is None:
408
- parameters = {}
409
-
410
- # Extract parameters
411
- temperature = parameters.get('temperature', Settings.DEFAULT_TEMPERATURE)
412
- globs = parameters.get('globs', '')
413
- referenced_files = parameters.get('referenced_files', '')
414
- prompt = parameters.get('prompt', '')
415
-
416
- # Prepare the prompt for OpenAI
417
- system_prompt = """
418
- You are a Cursor Rules expert. Create a rule in MDC format based on the provided information.
419
-
420
- MDC format example:
421
- ---
422
- description: RPC Service boilerplate
423
- globs:
424
- alwaysApply: false
425
- ---
426
-
427
- - Use our internal RPC pattern when defining services
428
- - Always use snake_case for service names.
429
-
430
- @service-template.ts
431
- """
432
-
433
- user_prompt = f"""
434
- Create a Cursor Rule with the following details:
435
-
436
- Rule Type: {rule_type}
437
- Description: {description}
438
- Content: {content}
439
- """
440
-
441
- if globs:
442
- user_prompt += f"\nGlobs: {globs}"
443
-
444
- if referenced_files:
445
- user_prompt += f"\nReferenced Files: {referenced_files}"
446
-
447
- if prompt:
448
- user_prompt += f"\nAdditional Instructions: {prompt}"
449
-
450
- # Prepare the API request
451
- url = f"{self.api_url}/chat/completions"
452
- headers = {
453
- "Authorization": f"Bearer {self.api_key}",
454
- "Content-Type": "application/json"
455
- }
456
-
457
- payload = {
458
- "model": model,
459
- "messages": [
460
- {
461
- "role": "system",
462
- "content": system_prompt
463
- },
464
- {
465
- "role": "user",
466
- "content": user_prompt
467
- }
468
- ],
469
- "temperature": temperature,
470
- "max_tokens": 2048
471
- }
472
-
473
- # Make the API request
474
- try:
475
- response = requests.post(url, headers=headers, json=payload)
476
-
477
- if response.status_code != 200:
478
- print(f"Failed to generate rule: Status {response.status_code}, Response: {response.text}")
479
- raise ValueError(f"Failed to generate rule: {response.text}")
480
-
481
- data = response.json()
482
-
483
- # Extract the generated text
484
- generated_text = data.get('choices', [{}])[0].get('message', {}).get('content', '')
485
-
486
- # If no text was generated, create a basic rule
487
- if not generated_text:
488
- return self._create_basic_rule(rule_type, description, content, globs, referenced_files)
489
-
490
- return generated_text
491
- except Exception as e:
492
- print(f"Exception in generate_rule: {str(e)}\n{traceback.format_exc()}")
493
- # Create a basic rule on error
494
- return self._create_basic_rule(rule_type, description, content, globs, referenced_files)
495
-
496
- def _format_model_name(self, model_id: str) -> str:
497
- """Format a model ID into a human-readable name."""
498
- # Replace hyphens with spaces and capitalize each word
499
- name = model_id.replace('-', ' ').title()
500
-
501
- # Special case handling
502
- name = name.replace('Gpt ', 'GPT ')
503
- name = name.replace('Gpt4', 'GPT-4')
504
- name = name.replace('Gpt3', 'GPT-3')
505
- name = name.replace('Gpt 4', 'GPT-4')
506
- name = name.replace('Gpt 3', 'GPT-3')
507
- name = name.replace('Turbo', 'Turbo')
508
- name = name.replace('O', 'o')
509
-
510
- return name
511
-
512
- def _create_basic_rule(
513
- self,
514
- rule_type: str,
515
- description: str,
516
- content: str,
517
- globs: str = '',
518
- referenced_files: str = ''
519
- ) -> str:
520
- """Create a basic rule in MDC format without using the LLM."""
521
- # Create MDC format
522
- mdc = '---\n'
523
- mdc += f'description: {description}\n'
524
-
525
- if rule_type == 'Auto Attached' and globs:
526
- mdc += f'globs: {globs}\n'
527
-
528
- if rule_type == 'Always':
529
- mdc += 'alwaysApply: true\n'
530
- else:
531
- mdc += 'alwaysApply: false\n'
532
-
533
- mdc += '---\n\n'
534
- mdc += content + '\n'
535
-
536
- # Add referenced files
537
- if referenced_files:
538
- mdc += '\n' + referenced_files
539
-
540
- return mdc
541
-
542
- # OpenRouter Adapter
543
- class OpenRouterAdapter(LLMAdapter):
544
- """Adapter for OpenRouter API."""
545
-
546
- def __init__(self):
547
- """Initialize the OpenRouter adapter."""
548
- self.api_key = None
549
- self.api_url = Settings.OPENROUTER_API_URL
550
- self.initialized = False
551
- self.last_error = None
552
-
553
- def initialize(self, api_key: str, **kwargs) -> None:
554
- """Initialize the adapter with API key and optional parameters."""
555
- self.api_key = api_key
556
- self.api_url = kwargs.get('api_url', Settings.OPENROUTER_API_URL)
557
- self.site_url = kwargs.get('site_url', 'https://cursor-rules-generator.example.com')
558
- self.site_name = kwargs.get('site_name', 'Cursor Rules Generator')
559
- self.initialized = True
560
-
561
- def validate_api_key(self, api_key: str) -> bool:
562
- """Validate the OpenRouter API key."""
563
- try:
564
- # Try to list models with the provided API key
565
- url = f"{self.api_url}/models"
566
- headers = {
567
- "Authorization": f"Bearer {api_key}"
568
- }
569
- response = requests.get(url, headers=headers)
570
-
571
- # Check if the request was successful
572
- if response.status_code == 200:
573
- return True
574
-
575
- # Store error details for debugging
576
- self.last_error = f"API Error: Status {response.status_code}, Response: {response.text}"
577
- print(f"OpenRouter API validation failed: {self.last_error}")
578
- return False
579
- except Exception as e:
580
- # Store exception details for debugging
581
- self.last_error = f"Exception: {str(e)}\n{traceback.format_exc()}"
582
- print(f"OpenRouter API validation exception: {self.last_error}")
583
- return False
584
-
585
- def get_available_models(self) -> List[Dict[str, str]]:
586
- """Get a list of available OpenRouter models."""
587
- if not self.initialized:
588
- raise ValueError("Adapter not initialized. Call initialize() first.")
589
-
590
- try:
591
- # Get available models
592
- url = f"{self.api_url}/models"
593
- headers = {
594
- "Authorization": f"Bearer {self.api_key}"
595
- }
596
- response = requests.get(url, headers=headers)
597
-
598
- if response.status_code != 200:
599
- print(f"Failed to get models: Status {response.status_code}, Response: {response.text}")
600
- raise ValueError(f"Failed to get models: {response.text}")
601
-
602
- data = response.json()
603
-
604
- # Format the response
605
- models = []
606
- for model in data.get('data', []):
607
- model_id = model.get('id')
608
- model_name = model.get('name', model_id)
609
-
610
- # Skip non-chat models
611
- if not model.get('capabilities', {}).get('chat'):
612
- continue
613
-
614
- models.append({
615
- 'id': model_id,
616
- 'name': model_name
617
- })
618
-
619
- # If no models found, return default models
620
- if not models:
621
- models = [
622
- {'id': 'openai/gpt-4o', 'name': 'OpenAI GPT-4o'},
623
- {'id': 'anthropic/claude-3-opus', 'name': 'Anthropic Claude 3 Opus'},
624
- {'id': 'google/gemini-2.5-pro', 'name': 'Google Gemini 2.5 Pro'},
625
- {'id': 'meta-llama/llama-3-70b-instruct', 'name': 'Meta Llama 3 70B'}
626
- ]
627
-
628
- return models
629
- except Exception as e:
630
- print(f"Exception in get_available_models: {str(e)}\n{traceback.format_exc()}")
631
- # Return default models on error
632
- return [
633
- {'id': 'openai/gpt-4o', 'name': 'OpenAI GPT-4o'},
634
- {'id': 'anthropic/claude-3-opus', 'name': 'Anthropic Claude 3 Opus'},
635
- {'id': 'google/gemini-2.5-pro', 'name': 'Google Gemini 2.5 Pro'},
636
- {'id': 'meta-llama/llama-3-70b-instruct', 'name': 'Meta Llama 3 70B'}
637
- ]
638
-
639
- def generate_rule(
640
- self,
641
- model: str,
642
- rule_type: str,
643
- description: str,
644
- content: str,
645
- parameters: Optional[Dict[str, Any]] = None
646
- ) -> str:
647
- """Generate a Cursor Rule using OpenRouter."""
648
- if not self.initialized:
649
- raise ValueError("Adapter not initialized. Call initialize() first.")
650
-
651
- # Set default parameters if not provided
652
- if parameters is None:
653
- parameters = {}
654
-
655
- # Extract parameters
656
- temperature = parameters.get('temperature', Settings.DEFAULT_TEMPERATURE)
657
- globs = parameters.get('globs', '')
658
- referenced_files = parameters.get('referenced_files', '')
659
- prompt = parameters.get('prompt', '')
660
-
661
- # Prepare the prompt for OpenRouter
662
- system_prompt = """
663
- You are a Cursor Rules expert. Create a rule in MDC format based on the provided information.
664
-
665
- MDC format example:
666
- ---
667
- description: RPC Service boilerplate
668
- globs:
669
- alwaysApply: false
670
- ---
671
-
672
- - Use our internal RPC pattern when defining services
673
- - Always use snake_case for service names.
674
-
675
- @service-template.ts
676
- """
677
-
678
- user_prompt = f"""
679
- Create a Cursor Rule with the following details:
680
-
681
- Rule Type: {rule_type}
682
- Description: {description}
683
- Content: {content}
684
- """
685
-
686
- if globs:
687
- user_prompt += f"\nGlobs: {globs}"
688
-
689
- if referenced_files:
690
- user_prompt += f"\nReferenced Files: {referenced_files}"
691
-
692
- if prompt:
693
- user_prompt += f"\nAdditional Instructions: {prompt}"
694
-
695
- # Prepare the API request
696
- url = f"{self.api_url}/chat/completions"
697
- headers = {
698
- "Authorization": f"Bearer {self.api_key}",
699
- "Content-Type": "application/json",
700
- "HTTP-Referer": self.site_url,
701
- "X-Title": self.site_name
702
- }
703
-
704
- payload = {
705
- "model": model,
706
- "messages": [
707
- {
708
- "role": "system",
709
- "content": system_prompt
710
- },
711
- {
712
- "role": "user",
713
- "content": user_prompt
714
- }
715
- ],
716
- "temperature": temperature,
717
- "max_tokens": 2048
718
- }
719
-
720
- # Make the API request
721
- try:
722
- response = requests.post(url, headers=headers, json=payload)
723
-
724
- if response.status_code != 200:
725
- print(f"Failed to generate rule: Status {response.status_code}, Response: {response.text}")
726
- raise ValueError(f"Failed to generate rule: {response.text}")
727
-
728
- data = response.json()
729
-
730
- # Extract the generated text
731
- generated_text = data.get('choices', [{}])[0].get('message', {}).get('content', '')
732
-
733
- # If no text was generated, create a basic rule
734
- if not generated_text:
735
- return self._create_basic_rule(rule_type, description, content, globs, referenced_files)
736
-
737
- return generated_text
738
- except Exception as e:
739
- print(f"Exception in generate_rule: {str(e)}\n{traceback.format_exc()}")
740
- # Create a basic rule on error
741
- return self._create_basic_rule(rule_type, description, content, globs, referenced_files)
742
-
743
- def _create_basic_rule(
744
- self,
745
- rule_type: str,
746
- description: str,
747
- content: str,
748
- globs: str = '',
749
- referenced_files: str = ''
750
- ) -> str:
751
- """Create a basic rule in MDC format without using the LLM."""
752
- # Create MDC format
753
- mdc = '---\n'
754
- mdc += f'description: {description}\n'
755
-
756
- if rule_type == 'Auto Attached' and globs:
757
- mdc += f'globs: {globs}\n'
758
-
759
- if rule_type == 'Always':
760
- mdc += 'alwaysApply: true\n'
761
- else:
762
- mdc += 'alwaysApply: false\n'
763
-
764
- mdc += '---\n\n'
765
- mdc += content + '\n'
766
-
767
- # Add referenced files
768
- if referenced_files:
769
- mdc += '\n' + referenced_files
770
-
771
- return mdc
772
-
773
- # LLM Adapter Factory
774
- class LLMAdapterFactory:
775
- """Factory for creating LLM adapters."""
776
-
777
- @staticmethod
778
- def create_adapter(provider_name: str) -> LLMAdapter:
779
- """Create an adapter for the specified provider."""
780
- provider_name = provider_name.lower()
781
-
782
- if provider_name == "gemini":
783
- return GeminiAdapter()
784
- elif provider_name == "openai":
785
- return OpenAIAdapter()
786
- elif provider_name == "openrouter":
787
- return OpenRouterAdapter()
788
- else:
789
- raise ValueError(f"Unsupported provider: {provider_name}")
790
-
791
- @staticmethod
792
- def get_supported_providers() -> Dict[str, str]:
793
- """Get a dictionary of supported providers."""
794
- return {
795
- "gemini": "Google Gemini",
796
- "openai": "OpenAI",
797
- "openrouter": "OpenRouter"
798
- }
799
-
800
- # Rule Generator
801
- class RuleGenerator:
802
- """Engine for generating Cursor Rules."""
803
-
804
- def __init__(self):
805
- """Initialize the rule generator."""
806
- self.factory = LLMAdapterFactory()
807
-
808
- def create_rule(
809
- self,
810
- provider: str,
811
- model: str,
812
- rule_type: str,
813
- description: str,
814
- content: str,
815
- api_key: str,
816
- parameters: Optional[Dict[str, Any]] = None
817
- ) -> str:
818
- """Create a Cursor Rule using the specified LLM provider."""
819
- # Set default parameters if not provided
820
- if parameters is None:
821
- parameters = {}
822
-
823
- try:
824
- # Create and initialize the adapter
825
- adapter = self.factory.create_adapter(provider)
826
- adapter.initialize(api_key)
827
-
828
- # Generate the rule using the adapter
829
- rule = adapter.generate_rule(model, rule_type, description, content, parameters)
830
-
831
- return rule
832
- except Exception as e:
833
- print(f"Exception in create_rule: {str(e)}\n{traceback.format_exc()}")
834
- # If LLM generation fails, create a basic rule
835
- return self._create_basic_rule(rule_type, description, content, parameters)
836
-
837
- def _create_basic_rule(
838
- self,
839
- rule_type: str,
840
- description: str,
841
- content: str,
842
- parameters: Optional[Dict[str, Any]] = None
843
- ) -> str:
844
- """Create a basic rule in MDC format without using an LLM."""
845
- # Set default parameters if not provided
846
- if parameters is None:
847
- parameters = {}
848
-
849
- # Extract parameters
850
- globs = parameters.get('globs', '')
851
- referenced_files = parameters.get('referenced_files', '')
852
-
853
- # Create MDC format
854
- mdc = '---\n'
855
- mdc += f'description: {description}\n'
856
-
857
- if rule_type == 'Auto Attached' and globs:
858
- mdc += f'globs: {globs}\n'
859
-
860
- if rule_type == 'Always':
861
- mdc += 'alwaysApply: true\n'
862
- else:
863
- mdc += 'alwaysApply: false\n'
864
-
865
- mdc += '---\n\n'
866
- mdc += content + '\n'
867
-
868
- # Add referenced files
869
- if referenced_files:
870
- mdc += '\n' + referenced_files
871
-
872
- return mdc
873
-
874
- def validate_rule_type(self, rule_type: str) -> bool:
875
- """Validate if the rule type is supported."""
876
- valid_types = ['Always', 'Auto Attached', 'Agent Requested', 'Manual']
877
- return rule_type in valid_types
878
-
879
- def get_rule_types(self) -> List[Dict[str, str]]:
880
- """Get a list of supported rule types."""
881
- return [
882
- {
883
- 'id': 'Always',
884
- 'name': 'Always',
885
- 'description': 'Always included in the model context'
886
- },
887
- {
888
- 'id': 'Auto Attached',
889
- 'name': 'Auto Attached',
890
- 'description': 'Included when files matching glob patterns are referenced'
891
- },
892
- {
893
- 'id': 'Agent Requested',
894
- 'name': 'Agent Requested',
895
- 'description': 'Rule is presented to the AI, which decides whether to include it'
896
- },
897
- {
898
- 'id': 'Manual',
899
- 'name': 'Manual',
900
- 'description': 'Only included when explicitly referenced using @ruleName'
901
- }
902
- ]
903
-
904
- # Initialize components
905
- rule_generator = RuleGenerator()
906
- factory = LLMAdapterFactory()
907
-
908
- # Get supported providers
909
- providers = factory.get_supported_providers()
910
- provider_choices = list(providers.keys())
911
-
912
- # Get rule types
913
- rule_types = rule_generator.get_rule_types()
914
- rule_type_choices = [rt['id'] for rt in rule_types]
915
-
916
- def validate_api_key(provider, api_key):
917
- """Validate an API key for a specific provider.
918
-
919
- Args:
920
- provider: The LLM provider
921
- api_key: The API key to validate
922
-
923
- Returns:
924
- tuple: (success, message, models)
925
- """
926
- if not provider or not api_key:
927
- return False, "Lütfen bir sağlayıcı seçin ve API anahtarı girin.", [], []
928
-
929
- try:
930
- # Create the adapter
931
- adapter = factory.create_adapter(provider)
932
-
933
- # Print debug info
934
- print(f"Validating {provider} API key: {api_key[:5]}...{api_key[-5:] if len(api_key) > 10 else ''}")
935
-
936
- # Validate the API key
937
- valid = adapter.validate_api_key(api_key)
938
-
939
- if valid:
940
- # Initialize the adapter
941
- adapter.initialize(api_key)
942
-
943
- # Get available models
944
- models = adapter.get_available_models()
945
- model_names = [model['name'] for model in models]
946
- model_ids = [model['id'] for model in models]
947
-
948
- return True, "API anahtarı doğrulandı.", model_names, model_ids
949
- else:
950
- error_msg = getattr(adapter, 'last_error', 'Bilinmeyen hata')
951
- return False, f"Geçersiz API anahtarı. Hata: {error_msg}", [], []
952
- except Exception as e:
953
- error_details = traceback.format_exc()
954
- print(f"Exception in validate_api_key: {str(e)}\n{error_details}")
955
- return False, f"Hata: {str(e)}", [], []
956
-
957
- def generate_rule(provider, api_key, model_index, model_ids, rule_type, description, content, globs, referenced_files, prompt, temperature):
958
- """Generate a Cursor Rule.
959
-
960
- Args:
961
- provider: The LLM provider
962
- api_key: The API key for the provider
963
- model_index: The index of the selected model
964
- model_ids: The list of model IDs
965
- rule_type: The type of rule to generate
966
- description: A short description of the rule's purpose
967
- content: The main content of the rule
968
- globs: Glob patterns for Auto Attached rules
969
- referenced_files: Referenced files
970
- prompt: Additional instructions for the LLM
971
- temperature: Temperature parameter for generation
972
-
973
- Returns:
974
- tuple: (success, message, rule)
975
- """
976
- if not provider or not api_key or model_index is None or not rule_type or not description or not content:
977
- return False, "Lütfen tüm gerekli alanları doldurun.", ""
978
-
979
- # Get the model ID
980
- if not model_ids or model_index >= len(model_ids):
981
- return False, "Geçersiz model seçimi.", ""
982
-
983
- model = model_ids[model_index]
984
-
985
- # Validate rule type
986
- if not rule_generator.validate_rule_type(rule_type):
987
- return False, f"Geçersiz kural tipi: {rule_type}", ""
988
-
989
- # Validate globs for Auto Attached rule type
990
- if rule_type == 'Auto Attached' and not globs:
991
- return False, "Auto Attached kural tipi için glob desenleri gereklidir.", ""
992
-
993
- try:
994
- # Prepare parameters
995
- parameters = {
996
- 'globs': globs,
997
- 'referenced_files': referenced_files,
998
- 'prompt': prompt,
999
- 'temperature': float(temperature)
1000
- }
1001
-
1002
- # Generate the rule
1003
- rule = rule_generator.create_rule(
1004
- provider=provider,
1005
- model=model,
1006
- rule_type=rule_type,
1007
- description=description,
1008
- content=content,
1009
- api_key=api_key,
1010
- parameters=parameters
1011
- )
1012
-
1013
- return True, "Kural başarıyla oluşturuldu.", rule
1014
- except Exception as e:
1015
- error_details = traceback.format_exc()
1016
- print(f"Exception in generate_rule: {str(e)}\n{error_details}")
1017
- return False, f"Kural oluşturulurken bir hata oluştu: {str(e)}", ""
1018
-
1019
- def update_rule_type_info(rule_type):
1020
- """Update the rule type information.
1021
-
1022
- Args:
1023
- rule_type: The selected rule type
1024
-
1025
- Returns:
1026
- str: Information about the selected rule type
1027
- """
1028
- if rule_type == 'Always':
1029
- return "Her zaman model bağlamına dahil edilir."
1030
- elif rule_type == 'Auto Attached':
1031
- return "Glob desenine uyan dosyalar referans alındığında dahil edilir."
1032
- elif rule_type == 'Agent Requested':
1033
- return "Kural AI'ya sunulur, dahil edilip edilmeyeceğine AI karar verir."
1034
- elif rule_type == 'Manual':
1035
- return "Yalnızca @ruleName kullanılarak açıkça belirtildiğinde dahil edilir."
1036
- else:
1037
- return ""
1038
-
1039
- def update_globs_visibility(rule_type):
1040
- """Update the visibility of the globs input.
1041
-
1042
- Args:
1043
- rule_type: The selected rule type
1044
-
1045
- Returns:
1046
- bool: Whether the globs input should be visible
1047
- """
1048
- return rule_type == 'Auto Attached'
1049
-
1050
- # Create Gradio interface
1051
- with gr.Blocks(title="Cursor Rules Oluşturucu") as demo:
1052
- gr.Markdown("# Cursor Rules Oluşturucu")
1053
- gr.Markdown("Gemini, OpenRouter, OpenAI API ve tüm modellerini destekleyen dinamik bir Cursor Rules oluşturucu.")
1054
-
1055
- with gr.Row():
1056
- with gr.Column():
1057
- provider = gr.Dropdown(
1058
- choices=provider_choices,
1059
- label="LLM Sağlayıcı",
1060
- value=provider_choices[0] if provider_choices else None
1061
- )
1062
-
1063
- api_key = gr.Textbox(
1064
- label="API Anahtarı",
1065
- placeholder="API anahtarınızı girin",
1066
- type="password"
1067
- )
1068
-
1069
- validate_btn = gr.Button("API Anahtarını Doğrula")
1070
-
1071
- api_status = gr.Textbox(
1072
- label="API Durumu",
1073
- interactive=False
1074
- )
1075
-
1076
- model_dropdown = gr.Dropdown(
1077
- label="Model",
1078
- choices=[],
1079
- interactive=False
1080
- )
1081
-
1082
- # Hidden field to store model IDs
1083
- model_ids = gr.State([])
1084
-
1085
- rule_type = gr.Dropdown(
1086
- choices=rule_type_choices,
1087
- label="Kural Tipi",
1088
- value=rule_type_choices[0] if rule_type_choices else None
1089
- )
1090
-
1091
- rule_type_info = gr.Textbox(
1092
- label="Kural Tipi Bilgisi",
1093
- interactive=False,
1094
- value=update_rule_type_info(rule_type_choices[0] if rule_type_choices else "")
1095
- )
1096
-
1097
- description = gr.Textbox(
1098
- label="Açıklama",
1099
- placeholder="Kuralın amacını açıklayan kısa bir açıklama"
1100
- )
1101
-
1102
- globs = gr.Textbox(
1103
- label="Glob Desenleri (Auto Attached için)",
1104
- placeholder="Örn: *.ts, src/*.js",
1105
- visible=False
1106
- )
1107
-
1108
- content = gr.Textbox(
1109
- label="Kural İçeriği",
1110
- placeholder="Kuralın ana içeriği",
1111
- lines=10
1112
- )
1113
-
1114
- referenced_files = gr.Textbox(
1115
- label="Referans Dosyaları (İsteğe bağlı)",
1116
- placeholder="Her satıra bir dosya adı girin, örn: @service-template.ts",
1117
- lines=3
1118
- )
1119
-
1120
- prompt = gr.Textbox(
1121
- label="AI Prompt (İsteğe bağlı)",
1122
- placeholder="AI'ya özel talimatlar verin",
1123
- lines=3
1124
- )
1125
-
1126
- temperature = gr.Slider(
1127
- label="Sıcaklık",
1128
- minimum=0.0,
1129
- maximum=1.0,
1130
- value=0.7,
1131
- step=0.1
1132
- )
1133
-
1134
- generate_btn = gr.Button("Kural Oluştur")
1135
-
1136
- with gr.Column():
1137
- generation_status = gr.Textbox(
1138
- label="Durum",
1139
- interactive=False
1140
- )
1141
-
1142
- rule_output = gr.Textbox(
1143
- label="Oluşturulan Kural",
1144
- lines=20,
1145
- interactive=False
1146
- )
1147
-
1148
- download_btn = gr.Button("İndir")
1149
-
1150
- # API key validation
1151
- validate_btn.click(
1152
- fn=validate_api_key,
1153
- inputs=[provider, api_key],
1154
- outputs=[api_status, model_dropdown, model_ids]
1155
- )
1156
-
1157
- # Rule type change
1158
- rule_type.change(
1159
- fn=update_rule_type_info,
1160
- inputs=[rule_type],
1161
- outputs=[rule_type_info]
1162
- )
1163
-
1164
- rule_type.change(
1165
- fn=update_globs_visibility,
1166
- inputs=[rule_type],
1167
- outputs=[globs]
1168
- )
1169
-
1170
- # Generate rule
1171
- generate_btn.click(
1172
- fn=generate_rule,
1173
- inputs=[
1174
- provider,
1175
- api_key,
1176
- model_dropdown,
1177
- model_ids,
1178
- rule_type,
1179
- description,
1180
- content,
1181
- globs,
1182
- referenced_files,
1183
- prompt,
1184
- temperature
1185
- ],
1186
- outputs=[generation_status, rule_output]
1187
- )
1188
-
1189
- # Download rule
1190
- def download_rule(rule, description):
1191
- if not rule:
1192
- return None
1193
-
1194
- # Create file name from description
1195
- file_name = description.lower().replace(" ", "-").replace("/", "-")
1196
- if not file_name:
1197
- file_name = "cursor-rule"
1198
-
1199
- return {
1200
- "name": f"{file_name}.mdc",
1201
- "data": rule
1202
- }
1203
-
1204
- download_btn.click(
1205
- fn=download_rule,
1206
- inputs=[rule_output, description],
1207
- outputs=[gr.File()]
1208
- )
1209
-
1210
- # Launch the app
1211
- if __name__ == "__main__":
1212
- demo.launch(
1213
- server_name="0.0.0.0",
1214
- server_port=int(os.environ.get("PORT", 7860)),
1215
- share=True
1216
- )