draply-dev 1.5.3 → 1.5.5

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.
package/bin/cli.js CHANGED
@@ -112,10 +112,11 @@ async function callAI(cfg, prompt) {
112
112
  });
113
113
  }
114
114
 
115
- // OpenAI / Groq — compatible API
115
+ // OpenAI / Groq / Gemini — compatible API
116
116
  const hosts = {
117
117
  openai: ['api.openai.com', '/v1/chat/completions', 'gpt-4o-mini'],
118
- groq: ['api.groq.com', '/openai/v1/chat/completions', 'llama-3.3-70b-versatile']
118
+ groq: ['api.groq.com', '/openai/v1/chat/completions', 'llama-3.3-70b-versatile'],
119
+ gemini: ['generativelanguage.googleapis.com', '/v1beta/openai/chat/completions', 'gemini-2.5-flash']
119
120
  };
120
121
  const [hostname, apiPath, defaultModel] = hosts[provider] || hosts.groq;
121
122
  const body = JSON.stringify({ model: cfg.model || defaultModel, messages: [{ role: 'user', content: prompt }], temperature: 0.1, max_tokens: 8192 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "draply-dev",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
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",
package/src/overlay.js CHANGED
@@ -678,6 +678,7 @@
678
678
 
679
679
  <div class="ps-foot">
680
680
  <button id="__ps_sv__" disabled>SAVE CHANGES</button>
681
+ <button id="__ps_ai_cfg__" style="width:100%;margin-top:8px;background:none;border:1px solid #2a2a3a;border-radius:6px;padding:6px;color:#5a5a7a;font-size:9px;cursor:pointer;letter-spacing:1px;text-transform:uppercase;transition:color .15s, border-color .15s" onmouseover="this.style.color='#7fff6e'; this.style.borderColor='#7fff6e'" onmouseout="this.style.color='#5a5a7a'; this.style.borderColor='#2a2a3a'">⚙️ AI Provider</button>
681
682
  </div>
682
683
  </div>
683
684
 
@@ -1640,6 +1641,7 @@ document.addEventListener('mouseup', () => {
1640
1641
  // ══════════════════════════════════════════
1641
1642
  // Full history — every individual action
1642
1643
  const history = [];
1644
+ const redoHistory = [];
1643
1645
 
1644
1646
  function rec(el, props, prevPropsOverride, isCreate = false) {
1645
1647
  const selector = el.dataset.pixelshiftId ? null : gsel(el);
@@ -1695,7 +1697,14 @@ function rec(el, props, prevPropsOverride, isCreate = false) {
1695
1697
 
1696
1698
  // Push to history
1697
1699
  const hid = Date.now() + Math.random();
1698
- history.push({ hid, el, props, prevProps, selector: key, isCreate });
1700
+ history.push({
1701
+ hid, el, props, prevProps, selector: key, isCreate,
1702
+ parent: isCreate ? el.parentElement : null,
1703
+ nextSibling: isCreate ? el.nextElementSibling : null
1704
+ });
1705
+
1706
+ // Clear redo stack on new action
1707
+ redoHistory.length = 0;
1699
1708
 
1700
1709
  updateUnsUI();
1701
1710
  }
@@ -1736,9 +1745,12 @@ function revertChange(h) {
1736
1745
  if (h.prevProps.innerHTML !== undefined) h.el.innerHTML = h.prevProps.innerHTML;
1737
1746
  if (h.prevProps.innerText !== undefined) h.el.innerText = h.prevProps.innerText;
1738
1747
  }
1739
- // Remove from history
1748
+ // Remove from history and add to redo stack
1740
1749
  const idx = history.findIndex(x => x.hid === h.hid);
1741
- if (idx >= 0) history.splice(idx, 1);
1750
+ if (idx >= 0) {
1751
+ redoHistory.push(history[idx]);
1752
+ history.splice(idx, 1);
1753
+ }
1742
1754
  // Rebuild state.changes — preserve all original fields (#1, #2)
1743
1755
  state.changes = [];
1744
1756
  history.forEach(x => {
@@ -1758,9 +1770,91 @@ function revertChange(h) {
1758
1770
  }
1759
1771
  });
1760
1772
  updateUnsUI();
1773
+
1774
+ if (state.selectedEl) {
1775
+ const isVisible = document.body.contains(state.selectedEl) && getComputedStyle(state.selectedEl).display !== 'none';
1776
+ if (isVisible) {
1777
+ if (state.tool === 'mov') placeHdl(state.selectedEl);
1778
+ if (state.tool === 'rsz') placeRH(state.selectedEl);
1779
+ } else if (state.selectedEl === h.el) {
1780
+ state.selectedEl.classList.remove('__ps__', '__ps_multi__');
1781
+ state.selectedEl = null;
1782
+ state.selectedEls = [];
1783
+ hdl.classList.remove('v');
1784
+ Object.values(rhs).forEach(rh => rh.classList.remove('v'));
1785
+ }
1786
+ }
1787
+
1761
1788
  toast('↩ Reverted');
1762
1789
  }
1763
1790
 
1791
+ function redoChange() {
1792
+ if (redoHistory.length === 0) {
1793
+ toast('Nothing to redo');
1794
+ return;
1795
+ }
1796
+ const h = redoHistory.pop();
1797
+
1798
+ if (h.isCreate) {
1799
+ if (h.parent) {
1800
+ if (h.nextSibling) {
1801
+ h.parent.insertBefore(h.el, h.nextSibling);
1802
+ } else {
1803
+ h.parent.appendChild(h.el);
1804
+ }
1805
+ } else {
1806
+ document.body.appendChild(h.el);
1807
+ }
1808
+ } else {
1809
+ // Re-apply properties
1810
+ Object.entries(h.props).forEach(([prop, val]) => {
1811
+ const camel = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1812
+ h.el.style[camel] = val;
1813
+ });
1814
+ if (h.props.innerHTML !== undefined) h.el.innerHTML = h.props.innerHTML;
1815
+ if (h.props.innerText !== undefined) h.el.innerText = h.props.innerText;
1816
+ }
1817
+
1818
+ history.push(h);
1819
+
1820
+ // Rebuild state.changes
1821
+ state.changes = [];
1822
+ history.forEach(x => {
1823
+ const key = x.selector;
1824
+ const i = state.changes.findIndex(c => (c.pixelshiftId || c.selector) === key);
1825
+ if (i >= 0) {
1826
+ Object.assign(state.changes[i].props, x.props);
1827
+ } else {
1828
+ state.changes.push({
1829
+ type: x.isCreate ? 'create' : 'css',
1830
+ isCreate: x.isCreate || false,
1831
+ selector: key,
1832
+ props: { ...x.props },
1833
+ outerHTML: x.isCreate && x.el ? x.el.outerHTML : undefined,
1834
+ tagName: x.el ? x.el.tagName?.toLowerCase() : undefined
1835
+ });
1836
+ }
1837
+ });
1838
+
1839
+ updateUnsUI();
1840
+
1841
+ if (state.selectedEl) {
1842
+ const isVisible = document.body.contains(state.selectedEl) && getComputedStyle(state.selectedEl).display !== 'none';
1843
+ if (isVisible) {
1844
+ if (state.tool === 'mov') placeHdl(state.selectedEl);
1845
+ if (state.tool === 'rsz') placeRH(state.selectedEl);
1846
+ } else if (state.selectedEl === h.el) {
1847
+ state.selectedEl.classList.remove('__ps__', '__ps_multi__');
1848
+ state.selectedEl = null;
1849
+ state.selectedEls = [];
1850
+ hdl.classList.remove('v');
1851
+ Object.values(rhs).forEach(rh => rh.classList.remove('v'));
1852
+ }
1853
+ }
1854
+
1855
+ toast('↷ Redone');
1856
+ }
1857
+
1764
1858
  sv.addEventListener('click', async () => {
1765
1859
  // Check key config status
1766
1860
  let hasKey = false;
@@ -1774,7 +1868,7 @@ sv.addEventListener('click', async () => {
1774
1868
 
1775
1869
  if (!hasKey) {
1776
1870
  // Ask for provider first (#8)
1777
- const provider = prompt('Draply AI Save: Choose provider (groq / openai / anthropic / ollama):', 'groq');
1871
+ const provider = prompt('Draply AI Save: Choose provider (groq / openai / anthropic / gemini / ollama):', 'groq');
1778
1872
  if (!provider) { toast('Save aborted'); return; }
1779
1873
  const key = provider === 'ollama' ? 'local' : prompt(`Enter your ${provider.toUpperCase()} API key:`);
1780
1874
  if (key) {
@@ -1819,18 +1913,41 @@ sv.addEventListener('click', async () => {
1819
1913
  toast('⚠ Server unreachable');
1820
1914
  }
1821
1915
 
1822
- state.changes = []; history.length = 0;
1916
+ state.changes = []; history.length = 0; redoHistory.length = 0;
1823
1917
  sv.innerHTML = 'Save'; sv.textContent = 'Save';
1824
1918
  updateUnsUI();
1825
1919
  });
1826
1920
 
1827
1921
  document.getElementById('__uns_clear__').onclick = () => {
1828
- state.changes = []; history.length = 0; updateUnsUI();
1922
+ state.changes = []; history.length = 0; redoHistory.length = 0; updateUnsUI();
1829
1923
  toast('🗑 History cleared');
1830
1924
  };
1831
1925
 
1832
1926
  document.getElementById('__uns_save__').onclick = () => sv.click();
1833
1927
 
1928
+ document.getElementById('__ps_ai_cfg__').onclick = async () => {
1929
+ const provider = prompt('Change AI Provider (groq / openai / anthropic / gemini / ollama):', 'groq');
1930
+ if (!provider) return;
1931
+ const key = provider === 'ollama' ? 'local' : prompt(`Enter your ${provider.toUpperCase()} API key:`);
1932
+
1933
+ if (!key && provider !== 'ollama') {
1934
+ toast('⚠ API Key required');
1935
+ return;
1936
+ }
1937
+
1938
+ toast('Verifying...');
1939
+ try {
1940
+ const vRes = await fetch('/draply-validate-key', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey: key.trim(), provider: provider.trim() }) });
1941
+ const vData = await vRes.json();
1942
+ if (!vData.valid && provider !== 'ollama') {
1943
+ toast('⚠ Invalid API key'); return;
1944
+ }
1945
+ } catch { /* ignore validation fail */ }
1946
+
1947
+ await fetch('/draply-config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey: key.trim(), provider: provider.trim() }) });
1948
+ toast('✅ AI Provider saved!');
1949
+ };
1950
+
1834
1951
  // ══════════════════════════════════════════
1835
1952
  // KEYBOARD SHORTCUTS (#4, #5, #15)
1836
1953
  // ══════════════════════════════════════════
@@ -1840,7 +1957,7 @@ document.addEventListener('keydown', e => {
1840
1957
  if (tag === 'input' || tag === 'textarea' || e.target.isContentEditable) return;
1841
1958
 
1842
1959
  // Ctrl+Z — Undo last change (#4)
1843
- if ((e.ctrlKey || e.metaKey) && !e.shiftKey && e.key === 'z') {
1960
+ if ((e.ctrlKey || e.metaKey) && !e.shiftKey && e.key.toLowerCase() === 'z') {
1844
1961
  e.preventDefault();
1845
1962
  if (history.length > 0) {
1846
1963
  revertChange(history[history.length - 1]);
@@ -1850,10 +1967,10 @@ document.addEventListener('keydown', e => {
1850
1967
  return;
1851
1968
  }
1852
1969
 
1853
- // Ctrl+Shift+Z — Redo (not implemented yet, just prevent default)
1854
- if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z') {
1970
+ // Ctrl+Shift+Z — Redo (implemented)
1971
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === 'z') {
1855
1972
  e.preventDefault();
1856
- toast('Redo not available yet');
1973
+ redoChange();
1857
1974
  return;
1858
1975
  }
1859
1976