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

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +1280 -0
app.py ADDED
@@ -0,0 +1,1280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, model_names, model_ids)
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
+ print(f"Models found: {model_names}")
949
+ print(f"Model IDs: {model_ids}")
950
+
951
+ # Use default models if none are returned
952
+ if not model_names or not model_ids:
953
+ if provider == "gemini":
954
+ model_names = ["Gemini 2.5 Pro", "Gemini 2.0 Flash", "Gemini 2.0 Flash-Lite"]
955
+ model_ids = ["gemini-2.5-pro", "gemini-2.0-flash", "gemini-2.0-flash-lite"]
956
+ elif provider == "openai":
957
+ model_names = ["GPT-4o", "GPT-4 Turbo", "GPT-3.5 Turbo"]
958
+ model_ids = ["gpt-4o", "gpt-4-turbo", "gpt-3.5-turbo"]
959
+ elif provider == "openrouter":
960
+ model_names = ["OpenAI GPT-4o", "Anthropic Claude 3 Opus", "Google Gemini 2.5 Pro"]
961
+ model_ids = ["openai/gpt-4o", "anthropic/claude-3-opus", "google/gemini-2.5-pro"]
962
+
963
+ print(f"Using default models: {model_names}")
964
+
965
+ return True, "API anahtarı doğrulandı.", model_names, model_ids
966
+ else:
967
+ error_msg = getattr(adapter, 'last_error', 'Bilinmeyen hata')
968
+ return False, f"Geçersiz API anahtarı. Hata: {error_msg}", [], []
969
+ except Exception as e:
970
+ error_details = traceback.format_exc()
971
+ print(f"Exception in validate_api_key: {str(e)}\n{error_details}")
972
+ return False, f"Hata: {str(e)}", [], []
973
+
974
+ def generate_rule(provider, api_key, model_index, model_ids, rule_type, description, content, globs, referenced_files, prompt, temperature):
975
+ """Generate a Cursor Rule.
976
+
977
+ Args:
978
+ provider: The LLM provider
979
+ api_key: The API key for the provider
980
+ model_index: The index of the selected model
981
+ model_ids: The list of model IDs
982
+ rule_type: The type of rule to generate
983
+ description: A short description of the rule's purpose
984
+ content: The main content of the rule
985
+ globs: Glob patterns for Auto Attached rules
986
+ referenced_files: Referenced files
987
+ prompt: Additional instructions for the LLM
988
+ temperature: Temperature parameter for generation
989
+
990
+ Returns:
991
+ tuple: (success, message, rule)
992
+ """
993
+ print(f"Generate rule called with model_index: {model_index}, model_ids: {model_ids}")
994
+
995
+ if not provider or not api_key:
996
+ return False, "Lütfen bir sağlayıcı seçin ve API anahtarı girin.", ""
997
+
998
+ if model_index is None or model_index == "":
999
+ return False, "Lütfen bir model seçin. Model seçimi yapılamıyorsa, API anahtarını tekrar doğrulayın.", ""
1000
+
1001
+ if not rule_type or not description or not content:
1002
+ return False, "Lütfen kural tipi, açıklama ve içerik alanlarını doldurun.", ""
1003
+
1004
+ # Convert model_index to integer if it's a string
1005
+ try:
1006
+ if isinstance(model_index, str) and model_index.isdigit():
1007
+ model_index = int(model_index)
1008
+ except:
1009
+ pass
1010
+
1011
+ # Get the model ID
1012
+ if not model_ids:
1013
+ return False, "Model listesi bulunamadı. Lütfen API anahtarını tekrar doğrulayın.", ""
1014
+
1015
+ try:
1016
+ model_index = int(model_index)
1017
+ except:
1018
+ return False, f"Geçersiz model indeksi: {model_index}", ""
1019
+
1020
+ if model_index < 0 or model_index >= len(model_ids):
1021
+ return False, f"Geçersiz model seçimi. İndeks: {model_index}, Mevcut modeller: {len(model_ids)}", ""
1022
+
1023
+ model = model_ids[model_index]
1024
+
1025
+ # Validate rule type
1026
+ if not rule_generator.validate_rule_type(rule_type):
1027
+ return False, f"Geçersiz kural tipi: {rule_type}", ""
1028
+
1029
+ # Validate globs for Auto Attached rule type
1030
+ if rule_type == 'Auto Attached' and not globs:
1031
+ return False, "Auto Attached kural tipi için glob desenleri gereklidir.", ""
1032
+
1033
+ try:
1034
+ # Prepare parameters
1035
+ parameters = {
1036
+ 'globs': globs,
1037
+ 'referenced_files': referenced_files,
1038
+ 'prompt': prompt,
1039
+ 'temperature': float(temperature) if temperature else 0.7
1040
+ }
1041
+
1042
+ # Generate the rule
1043
+ rule = rule_generator.create_rule(
1044
+ provider=provider,
1045
+ model=model,
1046
+ rule_type=rule_type,
1047
+ description=description,
1048
+ content=content,
1049
+ api_key=api_key,
1050
+ parameters=parameters
1051
+ )
1052
+
1053
+ return True, "Kural başarıyla oluşturuldu.", rule
1054
+ except Exception as e:
1055
+ error_details = traceback.format_exc()
1056
+ print(f"Exception in generate_rule: {str(e)}\n{error_details}")
1057
+ return False, f"Kural oluşturulurken bir hata oluştu: {str(e)}", ""
1058
+
1059
+ def update_rule_type_info(rule_type):
1060
+ """Update the rule type information.
1061
+
1062
+ Args:
1063
+ rule_type: The selected rule type
1064
+
1065
+ Returns:
1066
+ str: Information about the selected rule type
1067
+ """
1068
+ if rule_type == 'Always':
1069
+ return "Her zaman model bağlamına dahil edilir."
1070
+ elif rule_type == 'Auto Attached':
1071
+ return "Glob desenine uyan dosyalar referans alındığında dahil edilir."
1072
+ elif rule_type == 'Agent Requested':
1073
+ return "Kural AI'ya sunulur, dahil edilip edilmeyeceğine AI karar verir."
1074
+ elif rule_type == 'Manual':
1075
+ return "Yalnızca @ruleName kullanılarak açıkça belirtildiğinde dahil edilir."
1076
+ else:
1077
+ return ""
1078
+
1079
+ def update_globs_visibility(rule_type):
1080
+ """Update the visibility of the globs input.
1081
+
1082
+ Args:
1083
+ rule_type: The selected rule type
1084
+
1085
+ Returns:
1086
+ bool: Whether the globs input should be visible
1087
+ """
1088
+ return rule_type == 'Auto Attached'
1089
+
1090
+ # Create Gradio interface
1091
+ with gr.Blocks(title="Cursor Rules Oluşturucu") as demo:
1092
+ gr.Markdown("# Cursor Rules Oluşturucu")
1093
+ gr.Markdown("Gemini, OpenRouter, OpenAI API ve tüm modellerini destekleyen dinamik bir Cursor Rules oluşturucu.")
1094
+
1095
+ with gr.Row():
1096
+ with gr.Column():
1097
+ provider = gr.Dropdown(
1098
+ choices=provider_choices,
1099
+ label="LLM Sağlayıcı",
1100
+ value=provider_choices[0] if provider_choices else None
1101
+ )
1102
+
1103
+ api_key = gr.Textbox(
1104
+ label="API Anahtarı",
1105
+ placeholder="API anahtarınızı girin",
1106
+ type="password"
1107
+ )
1108
+
1109
+ validate_btn = gr.Button("API Anahtarını Doğrula")
1110
+
1111
+ api_status = gr.Textbox(
1112
+ label="API Durumu",
1113
+ interactive=False
1114
+ )
1115
+
1116
+ # Default model choices for each provider
1117
+ default_models = {
1118
+ "gemini": ["Gemini 2.5 Pro", "Gemini 2.0 Flash", "Gemini 2.0 Flash-Lite"],
1119
+ "openai": ["GPT-4o", "GPT-4 Turbo", "GPT-3.5 Turbo"],
1120
+ "openrouter": ["OpenAI GPT-4o", "Anthropic Claude 3 Opus", "Google Gemini 2.5 Pro"]
1121
+ }
1122
+
1123
+ model_dropdown = gr.Dropdown(
1124
+ label="Model",
1125
+ choices=default_models.get(provider_choices[0] if provider_choices else "gemini", []),
1126
+ interactive=True
1127
+ )
1128
+
1129
+ # Hidden field to store model IDs
1130
+ model_ids = gr.State([])
1131
+
1132
+ rule_type = gr.Dropdown(
1133
+ choices=rule_type_choices,
1134
+ label="Kural Tipi",
1135
+ value=rule_type_choices[0] if rule_type_choices else None
1136
+ )
1137
+
1138
+ rule_type_info = gr.Textbox(
1139
+ label="Kural Tipi Bilgisi",
1140
+ interactive=False,
1141
+ value=update_rule_type_info(rule_type_choices[0] if rule_type_choices else "")
1142
+ )
1143
+
1144
+ description = gr.Textbox(
1145
+ label="Açıklama",
1146
+ placeholder="Kuralın amacını açıklayan kısa bir açıklama"
1147
+ )
1148
+
1149
+ globs = gr.Textbox(
1150
+ label="Glob Desenleri (Auto Attached için)",
1151
+ placeholder="Örn: *.ts, src/*.js",
1152
+ visible=False
1153
+ )
1154
+
1155
+ content = gr.Textbox(
1156
+ label="Kural İçeriği",
1157
+ placeholder="Kuralın ana içeriği",
1158
+ lines=10
1159
+ )
1160
+
1161
+ referenced_files = gr.Textbox(
1162
+ label="Referans Dosyaları (İsteğe bağlı)",
1163
+ placeholder="Her satıra bir dosya adı girin, örn: @service-template.ts",
1164
+ lines=3
1165
+ )
1166
+
1167
+ prompt = gr.Textbox(
1168
+ label="AI Prompt (İsteğe bağlı)",
1169
+ placeholder="AI'ya özel talimatlar verin",
1170
+ lines=3
1171
+ )
1172
+
1173
+ temperature = gr.Slider(
1174
+ label="Sıcaklık",
1175
+ minimum=0.0,
1176
+ maximum=1.0,
1177
+ value=0.7,
1178
+ step=0.1
1179
+ )
1180
+
1181
+ generate_btn = gr.Button("Kural Oluştur")
1182
+
1183
+ with gr.Column():
1184
+ generation_status = gr.Textbox(
1185
+ label="Durum",
1186
+ interactive=False
1187
+ )
1188
+
1189
+ rule_output = gr.Textbox(
1190
+ label="Oluşturulan Kural",
1191
+ lines=20,
1192
+ interactive=False
1193
+ )
1194
+
1195
+ download_btn = gr.Button("İndir")
1196
+
1197
+ # Provider change handler to update default models
1198
+ def update_default_models(provider_value):
1199
+ if provider_value == "gemini":
1200
+ return gr.Dropdown.update(choices=default_models["gemini"], value=default_models["gemini"][0] if default_models["gemini"] else None)
1201
+ elif provider_value == "openai":
1202
+ return gr.Dropdown.update(choices=default_models["openai"], value=default_models["openai"][0] if default_models["openai"] else None)
1203
+ elif provider_value == "openrouter":
1204
+ return gr.Dropdown.update(choices=default_models["openrouter"], value=default_models["openrouter"][0] if default_models["openrouter"] else None)
1205
+ else:
1206
+ return gr.Dropdown.update(choices=[], value=None)
1207
+
1208
+ provider.change(
1209
+ fn=update_default_models,
1210
+ inputs=[provider],
1211
+ outputs=[model_dropdown]
1212
+ )
1213
+
1214
+ # API key validation
1215
+ validate_btn.click(
1216
+ fn=validate_api_key,
1217
+ inputs=[provider, api_key],
1218
+ outputs=[api_status, model_dropdown, model_ids]
1219
+ )
1220
+
1221
+ # Rule type change
1222
+ rule_type.change(
1223
+ fn=update_rule_type_info,
1224
+ inputs=[rule_type],
1225
+ outputs=[rule_type_info]
1226
+ )
1227
+
1228
+ rule_type.change(
1229
+ fn=update_globs_visibility,
1230
+ inputs=[rule_type],
1231
+ outputs=[globs]
1232
+ )
1233
+
1234
+ # Generate rule
1235
+ generate_btn.click(
1236
+ fn=generate_rule,
1237
+ inputs=[
1238
+ provider,
1239
+ api_key,
1240
+ model_dropdown,
1241
+ model_ids,
1242
+ rule_type,
1243
+ description,
1244
+ content,
1245
+ globs,
1246
+ referenced_files,
1247
+ prompt,
1248
+ temperature
1249
+ ],
1250
+ outputs=[generation_status, rule_output]
1251
+ )
1252
+
1253
+ # Download rule
1254
+ def download_rule(rule, description):
1255
+ if not rule:
1256
+ return None
1257
+
1258
+ # Create file name from description
1259
+ file_name = description.lower().replace(" ", "-").replace("/", "-")
1260
+ if not file_name:
1261
+ file_name = "cursor-rule"
1262
+
1263
+ return {
1264
+ "name": f"{file_name}.mdc",
1265
+ "data": rule
1266
+ }
1267
+
1268
+ download_btn.click(
1269
+ fn=download_rule,
1270
+ inputs=[rule_output, description],
1271
+ outputs=[gr.File()]
1272
+ )
1273
+
1274
+ # Launch the app
1275
+ if __name__ == "__main__":
1276
+ demo.launch(
1277
+ server_name="0.0.0.0",
1278
+ server_port=int(os.environ.get("PORT", 7860)),
1279
+ share=True
1280
+ )