draply-dev 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/cli.js +33 -32
  2. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -100,7 +100,7 @@ function extractClassName(selector) {
100
100
  // ── Call Gemini API ───────────────────────────────────────────────────────────
101
101
  function callGemini(apiKey, prompt) {
102
102
  return new Promise((resolve, reject) => {
103
- const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
103
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key=${apiKey}`;
104
104
  const body = JSON.stringify({
105
105
  contents: [{ parts: [{ text: prompt }] }],
106
106
  generationConfig: { temperature: 0.1, maxOutputTokens: 8192 }
@@ -190,30 +190,40 @@ const server = http.createServer((req, res) => {
190
190
  return;
191
191
  }
192
192
 
193
+ // Group changes by file
193
194
  const results = [];
195
+ const fileChanges = new Map(); // file -> { found, allProps }
194
196
 
195
- // Group changes by class
196
197
  for (const ch of (changes || [])) {
197
198
  if (!ch.selector || !ch.props) continue;
198
-
199
199
  const className = extractClassName(ch.selector);
200
200
  if (!className) {
201
201
  results.push({ selector: ch.selector, ok: false, reason: 'No class name found' });
202
202
  continue;
203
203
  }
204
-
205
- // Find the source file
206
204
  const found = findFileForClass(projectRoot, className);
207
205
  if (!found) {
208
- results.push({ selector: ch.selector, ok: false, reason: `"${className}" not found in source files` });
206
+ results.push({ selector: ch.selector, ok: false, reason: `"${className}" not found in source` });
209
207
  continue;
210
208
  }
209
+ if (!fileChanges.has(found.file)) {
210
+ fileChanges.set(found.file, { found, items: [] });
211
+ }
212
+ fileChanges.get(found.file).items.push({ selector: ch.selector, className, props: ch.props });
213
+ }
214
+
215
+ // One AI call per file (saves tokens & rate limit)
216
+ for (const [filePath, { found, items }] of fileChanges) {
217
+ console.log(` \x1b[36m🤖\x1b[0m AI applying ${items.length} changes → ${found.relativePath}`);
211
218
 
212
- console.log(` \x1b[36m🤖\x1b[0m AI applying: .${className} → ${found.relativePath}`);
219
+ // Build combined prompt
220
+ let changesBlock = '';
221
+ items.forEach(item => {
222
+ const propsStr = Object.entries(item.props).map(([k, v]) => ` ${k}: ${v}`).join('\n');
223
+ changesBlock += `\nElement .${item.className}:\n${propsStr}\n`;
224
+ });
213
225
 
214
- // Build the prompt
215
- const propsStr = Object.entries(ch.props).map(([k, v]) => ` ${k}: ${v}`).join('\n');
216
- const prompt = `You are a code editor. Modify the following source file to apply CSS style changes.
226
+ const prompt = `You are a code editor. Modify the file to apply CSS style changes.
217
227
 
218
228
  FILE: ${found.relativePath}
219
229
  \`\`\`
@@ -221,44 +231,35 @@ ${found.content}
221
231
  \`\`\`
222
232
 
223
233
  CHANGES TO APPLY:
224
- The element with class "${className}" should have these CSS properties:
225
- ${propsStr}
226
-
234
+ ${changesBlock}
227
235
  RULES:
228
- - If the file is CSS/SCSS: find the .${className} rule and update/add the properties. If the rule doesn't exist, add it.
229
- - If the file is JSX/TSX: apply changes in the most appropriate way for this file:
230
- - If the file already uses inline styles for this element, update the style prop
231
- - If the file imports a CSS file, add the rule to that CSS file instead (tell me the filename in a comment)
232
- - If the file uses Tailwind classes, update the className with appropriate Tailwind classes
233
- - Otherwise, add a style prop to the element with className="${className}"
234
- - Keep ALL existing code intact. Only modify what's needed for the style changes.
235
- - Do NOT add comments explaining what you changed.
236
+ - If CSS/SCSS file: find each class rule and update/add properties. Add rules if missing.
237
+ - If JSX/TSX file: apply in the most appropriate way:
238
+ - Existing inline styles update them
239
+ - File imports CSS note which CSS file to edit
240
+ - Tailwind classes update className with Tailwind classes
241
+ - Otherwise add style prop
242
+ - Keep ALL existing code intact. Only modify what's needed.
243
+ - Do NOT add extra comments.
236
244
 
237
- Return ONLY the complete modified file content. No markdown fences, no explanations, just the raw code.`;
245
+ Return ONLY the complete modified file. No markdown fences, no explanations.`;
238
246
 
239
247
  try {
240
248
  const result = await callGemini(cfg.apiKey, prompt);
241
-
242
- // Clean up response — remove markdown fences if AI added them
243
249
  let code = result.trim();
244
250
  if (code.startsWith('```')) {
245
251
  code = code.replace(/^```[a-z]*\n?/, '').replace(/\n?```$/, '');
246
252
  }
247
-
248
- // Safety: don't write empty or tiny responses
249
253
  if (code.length < 20) {
250
- results.push({ selector: ch.selector, ok: false, reason: 'AI returned empty/invalid response' });
254
+ items.forEach(item => results.push({ selector: item.selector, ok: false, reason: 'AI returned invalid response' }));
251
255
  continue;
252
256
  }
253
-
254
- // Write the modified file
255
257
  fs.writeFileSync(found.file, code, 'utf8');
256
258
  console.log(` \x1b[32m✓\x1b[0m Applied to ${found.relativePath}`);
257
- results.push({ selector: ch.selector, ok: true, file: found.relativePath });
258
-
259
+ items.forEach(item => results.push({ selector: item.selector, ok: true, file: found.relativePath }));
259
260
  } catch (aiErr) {
260
261
  console.log(` \x1b[31m✗\x1b[0m AI error: ${aiErr.message}`);
261
- results.push({ selector: ch.selector, ok: false, reason: aiErr.message });
262
+ items.forEach(item => results.push({ selector: item.selector, ok: false, reason: aiErr.message }));
262
263
  }
263
264
  }
264
265
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "draply-dev",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Visual overlay for any frontend project — move, resize, restyle live in the browser, save to CSS",
5
5
  "author": "Arman",
6
6
  "type": "commonjs",