@symbo.ls/connect 3.4.9 → 3.5.0
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/dist/manifest.json +1 -1
- package/dist/panel.css +275 -0
- package/dist/panel.html +117 -4
- package/dist/panel.js +485 -20
- package/dist/service_worker.js +57 -1
- package/dist/service_worker.js.map +2 -2
- package/package.json +2 -2
- package/src/service_worker.js +57 -1
- package/static/panel.css +275 -0
- package/static/panel.html +117 -4
- package/static/panel.js +485 -20
package/dist/panel.js
CHANGED
|
@@ -4624,15 +4624,7 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
4624
4624
|
|
|
4625
4625
|
// ---- Claude API (direct or via platform) ----
|
|
4626
4626
|
async function callClaudeAPI (prompt, context) {
|
|
4627
|
-
const contextStr =
|
|
4628
|
-
'\nElement key: ' + (context.elementKey || 'none') +
|
|
4629
|
-
'\nCurrent props: ' + JSON.stringify(context.props) +
|
|
4630
|
-
'\nCurrent state: ' + JSON.stringify(context.state) +
|
|
4631
|
-
'\nTag: ' + (context.tag || 'unknown') +
|
|
4632
|
-
(context.scope === 'section' && context.children
|
|
4633
|
-
? '\nChildren: ' + JSON.stringify(context.children)
|
|
4634
|
-
: '') +
|
|
4635
|
-
'\n\nUser request: ' + prompt
|
|
4627
|
+
const contextStr = buildContextStr(prompt, context)
|
|
4636
4628
|
|
|
4637
4629
|
// Try 1: own API key
|
|
4638
4630
|
const apiKey = await getApiKey('anthropic')
|
|
@@ -4693,15 +4685,155 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
4693
4685
|
throw new Error('No Claude access. Enter your API key or sign in to symbols.app.')
|
|
4694
4686
|
}
|
|
4695
4687
|
|
|
4688
|
+
// ---- OpenAI API ----
|
|
4689
|
+
async function callOpenAI (prompt, context) {
|
|
4690
|
+
const contextStr = buildContextStr(prompt, context)
|
|
4691
|
+
const apiKey = await getApiKey('openai')
|
|
4692
|
+
if (!apiKey) throw new Error('No OpenAI API key. Add it in AI Settings.')
|
|
4693
|
+
|
|
4694
|
+
const resp = await swFetch('https://api.openai.com/v1/chat/completions', {
|
|
4695
|
+
method: 'POST',
|
|
4696
|
+
headers: {
|
|
4697
|
+
'Content-Type': 'application/json',
|
|
4698
|
+
'Authorization': 'Bearer ' + apiKey
|
|
4699
|
+
},
|
|
4700
|
+
body: JSON.stringify({
|
|
4701
|
+
model: 'gpt-4o',
|
|
4702
|
+
messages: [
|
|
4703
|
+
{ role: 'system', content: AI_SYSTEM_PROMPT },
|
|
4704
|
+
{ role: 'user', content: contextStr }
|
|
4705
|
+
],
|
|
4706
|
+
max_tokens: 1024
|
|
4707
|
+
})
|
|
4708
|
+
})
|
|
4709
|
+
|
|
4710
|
+
if (!resp.ok) {
|
|
4711
|
+
if (resp.status === 401) throw new Error('Invalid OpenAI API key.')
|
|
4712
|
+
throw new Error('OpenAI API error: HTTP ' + resp.status)
|
|
4713
|
+
}
|
|
4714
|
+
if (resp.data && resp.data.choices && resp.data.choices[0]) {
|
|
4715
|
+
return resp.data.choices[0].message.content
|
|
4716
|
+
}
|
|
4717
|
+
throw new Error('Unexpected OpenAI response')
|
|
4718
|
+
}
|
|
4719
|
+
|
|
4720
|
+
// ---- Google Gemini API ----
|
|
4721
|
+
async function callGemini (prompt, context) {
|
|
4722
|
+
const contextStr = buildContextStr(prompt, context)
|
|
4723
|
+
const apiKey = await getApiKey('gemini')
|
|
4724
|
+
if (!apiKey) throw new Error('No Gemini API key. Add it in AI Settings.')
|
|
4725
|
+
|
|
4726
|
+
const resp = await swFetch('https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=' + apiKey, {
|
|
4727
|
+
method: 'POST',
|
|
4728
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4729
|
+
body: JSON.stringify({
|
|
4730
|
+
systemInstruction: { parts: [{ text: AI_SYSTEM_PROMPT }] },
|
|
4731
|
+
contents: [{ parts: [{ text: contextStr }] }],
|
|
4732
|
+
generationConfig: { maxOutputTokens: 1024 }
|
|
4733
|
+
})
|
|
4734
|
+
})
|
|
4735
|
+
|
|
4736
|
+
if (!resp.ok) {
|
|
4737
|
+
if (resp.status === 400 || resp.status === 403) throw new Error('Invalid Gemini API key.')
|
|
4738
|
+
throw new Error('Gemini API error: HTTP ' + resp.status)
|
|
4739
|
+
}
|
|
4740
|
+
if (resp.data && resp.data.candidates && resp.data.candidates[0]) {
|
|
4741
|
+
return resp.data.candidates[0].content.parts[0].text
|
|
4742
|
+
}
|
|
4743
|
+
throw new Error('Unexpected Gemini response')
|
|
4744
|
+
}
|
|
4745
|
+
|
|
4746
|
+
// ---- DeepSeek API (OpenAI-compatible) ----
|
|
4747
|
+
async function callDeepSeek (prompt, context) {
|
|
4748
|
+
const contextStr = buildContextStr(prompt, context)
|
|
4749
|
+
const apiKey = await getApiKey('deepseek')
|
|
4750
|
+
if (!apiKey) throw new Error('No DeepSeek API key. Add it in AI Settings.')
|
|
4751
|
+
|
|
4752
|
+
const resp = await swFetch('https://api.deepseek.com/chat/completions', {
|
|
4753
|
+
method: 'POST',
|
|
4754
|
+
headers: {
|
|
4755
|
+
'Content-Type': 'application/json',
|
|
4756
|
+
'Authorization': 'Bearer ' + apiKey
|
|
4757
|
+
},
|
|
4758
|
+
body: JSON.stringify({
|
|
4759
|
+
model: 'deepseek-chat',
|
|
4760
|
+
messages: [
|
|
4761
|
+
{ role: 'system', content: AI_SYSTEM_PROMPT },
|
|
4762
|
+
{ role: 'user', content: contextStr }
|
|
4763
|
+
],
|
|
4764
|
+
max_tokens: 1024
|
|
4765
|
+
})
|
|
4766
|
+
})
|
|
4767
|
+
|
|
4768
|
+
if (!resp.ok) {
|
|
4769
|
+
if (resp.status === 401) throw new Error('Invalid DeepSeek API key.')
|
|
4770
|
+
throw new Error('DeepSeek API error: HTTP ' + resp.status)
|
|
4771
|
+
}
|
|
4772
|
+
if (resp.data && resp.data.choices && resp.data.choices[0]) {
|
|
4773
|
+
return resp.data.choices[0].message.content
|
|
4774
|
+
}
|
|
4775
|
+
throw new Error('Unexpected DeepSeek response')
|
|
4776
|
+
}
|
|
4777
|
+
|
|
4778
|
+
// ---- Groq API (OpenAI-compatible) ----
|
|
4779
|
+
async function callGroq (prompt, context) {
|
|
4780
|
+
const contextStr = buildContextStr(prompt, context)
|
|
4781
|
+
const apiKey = await getApiKey('groq')
|
|
4782
|
+
if (!apiKey) throw new Error('No Groq API key. Add it in AI Settings.')
|
|
4783
|
+
|
|
4784
|
+
const resp = await swFetch('https://api.groq.com/openai/v1/chat/completions', {
|
|
4785
|
+
method: 'POST',
|
|
4786
|
+
headers: {
|
|
4787
|
+
'Content-Type': 'application/json',
|
|
4788
|
+
'Authorization': 'Bearer ' + apiKey
|
|
4789
|
+
},
|
|
4790
|
+
body: JSON.stringify({
|
|
4791
|
+
model: 'llama-3.3-70b-versatile',
|
|
4792
|
+
messages: [
|
|
4793
|
+
{ role: 'system', content: AI_SYSTEM_PROMPT },
|
|
4794
|
+
{ role: 'user', content: contextStr }
|
|
4795
|
+
],
|
|
4796
|
+
max_tokens: 1024
|
|
4797
|
+
})
|
|
4798
|
+
})
|
|
4799
|
+
|
|
4800
|
+
if (!resp.ok) {
|
|
4801
|
+
if (resp.status === 401) throw new Error('Invalid Groq API key.')
|
|
4802
|
+
throw new Error('Groq API error: HTTP ' + resp.status)
|
|
4803
|
+
}
|
|
4804
|
+
if (resp.data && resp.data.choices && resp.data.choices[0]) {
|
|
4805
|
+
return resp.data.choices[0].message.content
|
|
4806
|
+
}
|
|
4807
|
+
throw new Error('Unexpected Groq response')
|
|
4808
|
+
}
|
|
4809
|
+
|
|
4810
|
+
// ---- Shared context builder ----
|
|
4811
|
+
function buildContextStr (prompt, context) {
|
|
4812
|
+
return 'Element path: ' + (context.elementPath || 'none') +
|
|
4813
|
+
'\nElement key: ' + (context.elementKey || 'none') +
|
|
4814
|
+
'\nCurrent props: ' + JSON.stringify(context.props) +
|
|
4815
|
+
'\nCurrent state: ' + JSON.stringify(context.state) +
|
|
4816
|
+
'\nTag: ' + (context.tag || 'unknown') +
|
|
4817
|
+
(context.scope === 'section' && context.children
|
|
4818
|
+
? '\nChildren: ' + JSON.stringify(context.children)
|
|
4819
|
+
: '') +
|
|
4820
|
+
'\n\nUser request: ' + prompt
|
|
4821
|
+
}
|
|
4822
|
+
|
|
4696
4823
|
// ---- AI settings dialog ----
|
|
4824
|
+
const AI_PROVIDERS = ['anthropic', 'openai', 'gemini', 'deepseek', 'groq']
|
|
4825
|
+
|
|
4697
4826
|
function openAISettings () {
|
|
4698
4827
|
const dialog = document.getElementById('ai-settings-dialog')
|
|
4699
4828
|
dialog.style.display = ''
|
|
4700
4829
|
|
|
4701
|
-
// Load saved
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4830
|
+
// Load saved keys for all providers
|
|
4831
|
+
for (const provider of AI_PROVIDERS) {
|
|
4832
|
+
getApiKey(provider).then(key => {
|
|
4833
|
+
const el = document.getElementById('ai-key-' + provider)
|
|
4834
|
+
if (el) el.value = key || ''
|
|
4835
|
+
})
|
|
4836
|
+
}
|
|
4705
4837
|
|
|
4706
4838
|
// Show auth status
|
|
4707
4839
|
const statusEl = document.getElementById('ai-dialog-auth-status')
|
|
@@ -4712,12 +4844,12 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
4712
4844
|
statusEl.textContent = 'Signed in to symbols.app — Claude available via platform'
|
|
4713
4845
|
} else {
|
|
4714
4846
|
statusEl.className = 'ai-dialog-auth-status signed-out'
|
|
4715
|
-
statusEl.textContent = 'Not signed in
|
|
4847
|
+
statusEl.textContent = 'Not signed in to symbols.app'
|
|
4716
4848
|
}
|
|
4717
4849
|
})
|
|
4718
4850
|
} else {
|
|
4719
4851
|
statusEl.className = 'ai-dialog-auth-status signed-out'
|
|
4720
|
-
statusEl.textContent = '
|
|
4852
|
+
statusEl.textContent = ''
|
|
4721
4853
|
}
|
|
4722
4854
|
}
|
|
4723
4855
|
|
|
@@ -4725,11 +4857,15 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
4725
4857
|
document.getElementById('ai-settings-dialog').style.display = 'none'
|
|
4726
4858
|
}
|
|
4727
4859
|
|
|
4728
|
-
function saveAISettings () {
|
|
4729
|
-
const
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4860
|
+
async function saveAISettings () {
|
|
4861
|
+
for (const provider of AI_PROVIDERS) {
|
|
4862
|
+
const el = document.getElementById('ai-key-' + provider)
|
|
4863
|
+
if (el) {
|
|
4864
|
+
const key = el.value.trim()
|
|
4865
|
+
await setApiKey(provider, key || null)
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
closeAISettings()
|
|
4733
4869
|
}
|
|
4734
4870
|
|
|
4735
4871
|
function getActiveScope (containerId) {
|
|
@@ -4854,6 +4990,14 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
4854
4990
|
response = starterAI(prompt, context)
|
|
4855
4991
|
} else if (model === 'claude') {
|
|
4856
4992
|
response = await callClaudeAPI(prompt, context)
|
|
4993
|
+
} else if (model === 'openai') {
|
|
4994
|
+
response = await callOpenAI(prompt, context)
|
|
4995
|
+
} else if (model === 'gemini') {
|
|
4996
|
+
response = await callGemini(prompt, context)
|
|
4997
|
+
} else if (model === 'deepseek') {
|
|
4998
|
+
response = await callDeepSeek(prompt, context)
|
|
4999
|
+
} else if (model === 'groq') {
|
|
5000
|
+
response = await callGroq(prompt, context)
|
|
4857
5001
|
} else if (model === 'chrome') {
|
|
4858
5002
|
response = await runChromeAI(prompt, context)
|
|
4859
5003
|
}
|
|
@@ -5322,6 +5466,8 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
5322
5466
|
if (mode === 'chat') updateChatContextLabel()
|
|
5323
5467
|
if (mode === 'gallery') renderGallery()
|
|
5324
5468
|
if (mode === 'content') renderContent()
|
|
5469
|
+
if (mode === 'network') renderNetwork()
|
|
5470
|
+
if (mode === 'integrations') renderIntegrations()
|
|
5325
5471
|
}
|
|
5326
5472
|
|
|
5327
5473
|
// ============================================================
|
|
@@ -5436,6 +5582,14 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
5436
5582
|
response = starterAI(prompt, context)
|
|
5437
5583
|
} else if (model === 'claude') {
|
|
5438
5584
|
response = await callClaudeAPI(prompt, context)
|
|
5585
|
+
} else if (model === 'openai') {
|
|
5586
|
+
response = await callOpenAI(prompt, context)
|
|
5587
|
+
} else if (model === 'gemini') {
|
|
5588
|
+
response = await callGemini(prompt, context)
|
|
5589
|
+
} else if (model === 'deepseek') {
|
|
5590
|
+
response = await callDeepSeek(prompt, context)
|
|
5591
|
+
} else if (model === 'groq') {
|
|
5592
|
+
response = await callGroq(prompt, context)
|
|
5439
5593
|
} else if (model === 'chrome') {
|
|
5440
5594
|
response = await runChromeAI(prompt, context)
|
|
5441
5595
|
}
|
|
@@ -5579,6 +5733,20 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
5579
5733
|
}
|
|
5580
5734
|
document.getElementById('btn-app-disconnect').addEventListener('click', disconnect)
|
|
5581
5735
|
|
|
5736
|
+
// Network controls
|
|
5737
|
+
const networkTestBtn = document.getElementById('network-test')
|
|
5738
|
+
if (networkTestBtn) networkTestBtn.addEventListener('click', testNetworkConnection)
|
|
5739
|
+
const networkSaveBtn = document.getElementById('network-save')
|
|
5740
|
+
if (networkSaveBtn) networkSaveBtn.addEventListener('click', saveNetworkConfig)
|
|
5741
|
+
const networkDisconnectBtn = document.getElementById('network-disconnect')
|
|
5742
|
+
if (networkDisconnectBtn) networkDisconnectBtn.addEventListener('click', disconnectNetwork)
|
|
5743
|
+
|
|
5744
|
+
// Integrations search
|
|
5745
|
+
const intgSearch = document.getElementById('integrations-search')
|
|
5746
|
+
if (intgSearch) {
|
|
5747
|
+
intgSearch.addEventListener('input', () => renderMarketplace(intgSearch.value))
|
|
5748
|
+
}
|
|
5749
|
+
|
|
5582
5750
|
// Tree pane tabs (Active Nodes / State Tree / Design System)
|
|
5583
5751
|
document.querySelectorAll('.tree-pane-tab').forEach(tab => {
|
|
5584
5752
|
tab.addEventListener('click', () => {
|
|
@@ -6230,6 +6398,303 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
6230
6398
|
}
|
|
6231
6399
|
}
|
|
6232
6400
|
|
|
6401
|
+
// ============================================================
|
|
6402
|
+
// Network mode — backend connection & server info
|
|
6403
|
+
// ============================================================
|
|
6404
|
+
let networkConfig = { url: '', token: '' }
|
|
6405
|
+
|
|
6406
|
+
async function renderNetwork () {
|
|
6407
|
+
// Load saved config
|
|
6408
|
+
const data = await chrome.storage.local.get('network_config')
|
|
6409
|
+
if (data.network_config) networkConfig = data.network_config
|
|
6410
|
+
|
|
6411
|
+
const urlInput = document.getElementById('network-url')
|
|
6412
|
+
const tokenInput = document.getElementById('network-token')
|
|
6413
|
+
if (urlInput) urlInput.value = networkConfig.url || ''
|
|
6414
|
+
if (tokenInput) tokenInput.value = networkConfig.token || ''
|
|
6415
|
+
|
|
6416
|
+
// Try to load config from symbols.json or runtime
|
|
6417
|
+
loadNetworkInfo()
|
|
6418
|
+
}
|
|
6419
|
+
|
|
6420
|
+
async function loadNetworkInfo () {
|
|
6421
|
+
// Try reading from fileCache (symbols.json)
|
|
6422
|
+
let config = null
|
|
6423
|
+
if (typeof fileCache !== 'undefined') {
|
|
6424
|
+
for (const [path, content] of Object.entries(fileCache)) {
|
|
6425
|
+
if (path.endsWith('symbols.json') || path.endsWith('symbols.config.js')) {
|
|
6426
|
+
try {
|
|
6427
|
+
const parsed = JSON.parse(content)
|
|
6428
|
+
if (parsed.server || parsed.api || parsed.backend || parsed.network) {
|
|
6429
|
+
config = parsed.server || parsed.api || parsed.backend || parsed.network
|
|
6430
|
+
break
|
|
6431
|
+
}
|
|
6432
|
+
} catch (e) {}
|
|
6433
|
+
}
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
6436
|
+
|
|
6437
|
+
// Try reading from runtime context
|
|
6438
|
+
if (!config) {
|
|
6439
|
+
try {
|
|
6440
|
+
const raw = await pageEval(`(function(){
|
|
6441
|
+
var I = window.__DOMQL_INSPECTOR__;
|
|
6442
|
+
if (!I) return 'null';
|
|
6443
|
+
var el = I.findRoot();
|
|
6444
|
+
if (!el) return 'null';
|
|
6445
|
+
var ctx = el.context || {};
|
|
6446
|
+
var cfg = ctx.server || ctx.api || ctx.backend || ctx.network || {};
|
|
6447
|
+
if (!cfg || typeof cfg !== 'object') return 'null';
|
|
6448
|
+
try { return JSON.stringify(cfg); } catch(e) { return 'null'; }
|
|
6449
|
+
})()`)
|
|
6450
|
+
if (raw && raw !== 'null') config = JSON.parse(raw)
|
|
6451
|
+
} catch (e) {}
|
|
6452
|
+
}
|
|
6453
|
+
|
|
6454
|
+
// Populate server info from config
|
|
6455
|
+
if (config) {
|
|
6456
|
+
setNetworkInfoField('host', config.host || config.url || config.hostname || '—')
|
|
6457
|
+
setNetworkInfoField('ip', config.ip || config.address || '—')
|
|
6458
|
+
setNetworkInfoField('platform', config.platform || config.provider || config.hosting || '—')
|
|
6459
|
+
setNetworkInfoField('region', config.region || config.location || '—')
|
|
6460
|
+
|
|
6461
|
+
// Auto-fill URL if empty
|
|
6462
|
+
const urlInput = document.getElementById('network-url')
|
|
6463
|
+
if (urlInput && !urlInput.value && (config.url || config.host)) {
|
|
6464
|
+
urlInput.value = config.url || config.host
|
|
6465
|
+
}
|
|
6466
|
+
}
|
|
6467
|
+
|
|
6468
|
+
// Try to get deployment info
|
|
6469
|
+
const deployEl = document.getElementById('network-deployment')
|
|
6470
|
+
if (config && (config.deploy || config.deployment || config.ci)) {
|
|
6471
|
+
const deploy = config.deploy || config.deployment || config.ci
|
|
6472
|
+
deployEl.innerHTML = ''
|
|
6473
|
+
for (const [k, v] of Object.entries(deploy)) {
|
|
6474
|
+
const row = document.createElement('div')
|
|
6475
|
+
row.className = 'network-info-row'
|
|
6476
|
+
row.innerHTML = '<span class="network-info-label">' + escapeHtml(k) + '</span><span class="network-info-value">' + escapeHtml(String(v)) + '</span>'
|
|
6477
|
+
deployEl.appendChild(row)
|
|
6478
|
+
}
|
|
6479
|
+
}
|
|
6480
|
+
}
|
|
6481
|
+
|
|
6482
|
+
function setNetworkInfoField (field, value) {
|
|
6483
|
+
const el = document.getElementById('network-info-' + field)
|
|
6484
|
+
if (el) el.textContent = value || '—'
|
|
6485
|
+
}
|
|
6486
|
+
|
|
6487
|
+
async function testNetworkConnection () {
|
|
6488
|
+
const url = document.getElementById('network-url').value.trim()
|
|
6489
|
+
if (!url) return
|
|
6490
|
+
const statusEl = document.getElementById('network-status')
|
|
6491
|
+
const statusText = document.getElementById('network-status-text')
|
|
6492
|
+
statusText.textContent = 'Testing...'
|
|
6493
|
+
statusEl.className = 'network-status'
|
|
6494
|
+
|
|
6495
|
+
const start = Date.now()
|
|
6496
|
+
try {
|
|
6497
|
+
const resp = await swFetch(url + (url.includes('?') ? '&' : '?') + '_t=' + Date.now(), {
|
|
6498
|
+
method: 'GET',
|
|
6499
|
+
headers: networkConfig.token ? { 'Authorization': 'Bearer ' + networkConfig.token } : {}
|
|
6500
|
+
})
|
|
6501
|
+
const latency = Date.now() - start
|
|
6502
|
+
setNetworkInfoField('latency', latency + 'ms')
|
|
6503
|
+
|
|
6504
|
+
if (resp.ok) {
|
|
6505
|
+
statusEl.className = 'network-status connected'
|
|
6506
|
+
statusText.textContent = 'Connected (' + latency + 'ms)'
|
|
6507
|
+
setNetworkInfoField('status', 'OK (' + (resp.status || 200) + ')')
|
|
6508
|
+
|
|
6509
|
+
// Try to extract host from URL
|
|
6510
|
+
try {
|
|
6511
|
+
const u = new URL(url)
|
|
6512
|
+
setNetworkInfoField('host', u.hostname)
|
|
6513
|
+
} catch (e) {}
|
|
6514
|
+
} else {
|
|
6515
|
+
statusEl.className = 'network-status error'
|
|
6516
|
+
statusText.textContent = 'Error: HTTP ' + (resp.status || 'unknown')
|
|
6517
|
+
setNetworkInfoField('status', 'Error ' + (resp.status || ''))
|
|
6518
|
+
}
|
|
6519
|
+
} catch (e) {
|
|
6520
|
+
statusEl.className = 'network-status error'
|
|
6521
|
+
statusText.textContent = 'Failed: ' + (e.message || 'connection error')
|
|
6522
|
+
setNetworkInfoField('status', 'Unreachable')
|
|
6523
|
+
setNetworkInfoField('latency', '—')
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6526
|
+
|
|
6527
|
+
async function saveNetworkConfig () {
|
|
6528
|
+
networkConfig.url = document.getElementById('network-url').value.trim()
|
|
6529
|
+
networkConfig.token = document.getElementById('network-token').value.trim()
|
|
6530
|
+
await chrome.storage.local.set({ network_config: networkConfig })
|
|
6531
|
+
setStatus('Network config saved')
|
|
6532
|
+
testNetworkConnection()
|
|
6533
|
+
}
|
|
6534
|
+
|
|
6535
|
+
async function disconnectNetwork () {
|
|
6536
|
+
networkConfig = { url: '', token: '' }
|
|
6537
|
+
await chrome.storage.local.set({ network_config: networkConfig })
|
|
6538
|
+
document.getElementById('network-url').value = ''
|
|
6539
|
+
document.getElementById('network-token').value = ''
|
|
6540
|
+
document.getElementById('network-status').className = 'network-status'
|
|
6541
|
+
document.getElementById('network-status-text').textContent = 'Not connected'
|
|
6542
|
+
;['host', 'ip', 'status', 'latency', 'platform', 'region'].forEach(f => setNetworkInfoField(f, '—'))
|
|
6543
|
+
document.getElementById('network-deployment').innerHTML = '<div class="empty-message">Connect to view deployment configuration</div>'
|
|
6544
|
+
setStatus('Disconnected')
|
|
6545
|
+
}
|
|
6546
|
+
|
|
6547
|
+
// ============================================================
|
|
6548
|
+
// Integrations mode — marketplace & active integrations
|
|
6549
|
+
// ============================================================
|
|
6550
|
+
const MARKETPLACE_ITEMS = [
|
|
6551
|
+
{ id: 'google-analytics', name: 'Google Analytics', icon: '\uD83D\uDCCA', desc: 'Track page views, events, and user behavior', category: 'Analytics' },
|
|
6552
|
+
{ id: 'mixpanel', name: 'Mixpanel', icon: '\uD83D\uDD0D', desc: 'Product analytics and user tracking', category: 'Analytics' },
|
|
6553
|
+
{ id: 'hotjar', name: 'Hotjar', icon: '\uD83D\uDD25', desc: 'Heatmaps, recordings, and user feedback', category: 'Analytics' },
|
|
6554
|
+
{ id: 'sentry', name: 'Sentry', icon: '\uD83D\uDEE1', desc: 'Error tracking and performance monitoring', category: 'Monitoring' },
|
|
6555
|
+
{ id: 'intercom', name: 'Intercom', icon: '\uD83D\uDCAC', desc: 'Customer messaging and live chat', category: 'Chatbot' },
|
|
6556
|
+
{ id: 'crisp', name: 'Crisp', icon: '\uD83D\uDDE8', desc: 'Live chat and helpdesk widget', category: 'Chatbot' },
|
|
6557
|
+
{ id: 'stripe', name: 'Stripe', icon: '\uD83D\uDCB3', desc: 'Payment processing and subscriptions', category: 'Payments' },
|
|
6558
|
+
{ id: 'auth0', name: 'Auth0', icon: '\uD83D\uDD10', desc: 'Authentication and authorization', category: 'Auth' },
|
|
6559
|
+
{ id: 'firebase', name: 'Firebase', icon: '\uD83D\uDD25', desc: 'Backend services, auth, and real-time database', category: 'Backend' },
|
|
6560
|
+
{ id: 'supabase', name: 'Supabase', icon: '\u26A1', desc: 'Open-source Firebase alternative with Postgres', category: 'Backend' },
|
|
6561
|
+
{ id: 'cloudflare', name: 'Cloudflare', icon: '\u2601', desc: 'CDN, DNS, and edge computing', category: 'Infrastructure' },
|
|
6562
|
+
{ id: 'vercel', name: 'Vercel', icon: '\u25B2', desc: 'Deployment and serverless functions', category: 'Infrastructure' },
|
|
6563
|
+
{ id: 'mailchimp', name: 'Mailchimp', icon: '\uD83D\uDCE7', desc: 'Email marketing and automation', category: 'Marketing' },
|
|
6564
|
+
{ id: 'segment', name: 'Segment', icon: '\uD83D\uDD00', desc: 'Customer data platform and event routing', category: 'Analytics' },
|
|
6565
|
+
{ id: 'posthog', name: 'PostHog', icon: '\uD83E\uDDA4', desc: 'Open-source product analytics and feature flags', category: 'Analytics' },
|
|
6566
|
+
{ id: 'recaptcha', name: 'reCAPTCHA', icon: '\uD83E\uDD16', desc: 'Bot protection and spam prevention', category: 'Security' },
|
|
6567
|
+
]
|
|
6568
|
+
|
|
6569
|
+
let enabledIntegrations = {}
|
|
6570
|
+
|
|
6571
|
+
async function renderIntegrations () {
|
|
6572
|
+
const data = await chrome.storage.local.get('integrations')
|
|
6573
|
+
enabledIntegrations = (data.integrations) || {}
|
|
6574
|
+
|
|
6575
|
+
renderActiveIntegrations()
|
|
6576
|
+
renderMarketplace()
|
|
6577
|
+
}
|
|
6578
|
+
|
|
6579
|
+
function renderActiveIntegrations () {
|
|
6580
|
+
const container = document.getElementById('integrations-active')
|
|
6581
|
+
container.innerHTML = ''
|
|
6582
|
+
const activeIds = Object.keys(enabledIntegrations)
|
|
6583
|
+
|
|
6584
|
+
if (activeIds.length === 0) {
|
|
6585
|
+
container.innerHTML = '<div class="empty-message">No integrations enabled</div>'
|
|
6586
|
+
return
|
|
6587
|
+
}
|
|
6588
|
+
|
|
6589
|
+
for (const id of activeIds) {
|
|
6590
|
+
const item = MARKETPLACE_ITEMS.find(m => m.id === id) || { name: id, icon: '\uD83D\uDD0C', desc: '' }
|
|
6591
|
+
const config = enabledIntegrations[id] || {}
|
|
6592
|
+
|
|
6593
|
+
const el = document.createElement('div')
|
|
6594
|
+
el.className = 'integration-item'
|
|
6595
|
+
|
|
6596
|
+
el.innerHTML =
|
|
6597
|
+
'<div class="integration-icon">' + item.icon + '</div>' +
|
|
6598
|
+
'<div class="integration-info">' +
|
|
6599
|
+
'<div class="integration-name">' + escapeHtml(item.name) + '</div>' +
|
|
6600
|
+
'<div class="integration-desc">' + escapeHtml(config.key ? 'Configured' : 'Not configured') + '</div>' +
|
|
6601
|
+
'</div>'
|
|
6602
|
+
|
|
6603
|
+
const actions = document.createElement('div')
|
|
6604
|
+
actions.className = 'integration-actions'
|
|
6605
|
+
|
|
6606
|
+
const configBtn = document.createElement('button')
|
|
6607
|
+
configBtn.className = 'integration-btn'
|
|
6608
|
+
configBtn.textContent = 'Configure'
|
|
6609
|
+
configBtn.addEventListener('click', () => openIntegrationConfig(id, item))
|
|
6610
|
+
actions.appendChild(configBtn)
|
|
6611
|
+
|
|
6612
|
+
const removeBtn = document.createElement('button')
|
|
6613
|
+
removeBtn.className = 'integration-btn integration-btn-danger'
|
|
6614
|
+
removeBtn.textContent = 'Remove'
|
|
6615
|
+
removeBtn.addEventListener('click', async () => {
|
|
6616
|
+
delete enabledIntegrations[id]
|
|
6617
|
+
await chrome.storage.local.set({ integrations: enabledIntegrations })
|
|
6618
|
+
renderActiveIntegrations()
|
|
6619
|
+
renderMarketplace()
|
|
6620
|
+
})
|
|
6621
|
+
actions.appendChild(removeBtn)
|
|
6622
|
+
|
|
6623
|
+
el.appendChild(actions)
|
|
6624
|
+
container.appendChild(el)
|
|
6625
|
+
}
|
|
6626
|
+
}
|
|
6627
|
+
|
|
6628
|
+
function renderMarketplace (filter) {
|
|
6629
|
+
const container = document.getElementById('integrations-marketplace')
|
|
6630
|
+
container.innerHTML = ''
|
|
6631
|
+
const query = (filter || '').toLowerCase()
|
|
6632
|
+
|
|
6633
|
+
for (const item of MARKETPLACE_ITEMS) {
|
|
6634
|
+
if (enabledIntegrations[item.id]) continue
|
|
6635
|
+
if (query && !item.name.toLowerCase().includes(query) && !item.category.toLowerCase().includes(query) && !item.desc.toLowerCase().includes(query)) continue
|
|
6636
|
+
|
|
6637
|
+
const card = document.createElement('div')
|
|
6638
|
+
card.className = 'integration-card'
|
|
6639
|
+
card.innerHTML =
|
|
6640
|
+
'<div class="integration-card-header">' +
|
|
6641
|
+
'<div class="integration-card-icon">' + item.icon + '</div>' +
|
|
6642
|
+
'<div class="integration-card-name">' + escapeHtml(item.name) + '</div>' +
|
|
6643
|
+
'</div>' +
|
|
6644
|
+
'<div class="integration-card-desc">' + escapeHtml(item.desc) + '</div>' +
|
|
6645
|
+
'<span class="integration-card-tag">' + escapeHtml(item.category) + '</span>'
|
|
6646
|
+
|
|
6647
|
+
card.addEventListener('click', () => openIntegrationConfig(item.id, item))
|
|
6648
|
+
container.appendChild(card)
|
|
6649
|
+
}
|
|
6650
|
+
|
|
6651
|
+
if (container.children.length === 0) {
|
|
6652
|
+
container.innerHTML = '<div class="empty-message">' + (query ? 'No matching integrations' : 'All integrations enabled') + '</div>'
|
|
6653
|
+
}
|
|
6654
|
+
}
|
|
6655
|
+
|
|
6656
|
+
function openIntegrationConfig (id, item) {
|
|
6657
|
+
const config = enabledIntegrations[id] || {}
|
|
6658
|
+
const isNew = !enabledIntegrations[id]
|
|
6659
|
+
|
|
6660
|
+
// Simple config dialog inline
|
|
6661
|
+
const overlay = document.createElement('div')
|
|
6662
|
+
overlay.className = 'ai-dialog-overlay'
|
|
6663
|
+
const dialog = document.createElement('div')
|
|
6664
|
+
dialog.className = 'ai-dialog'
|
|
6665
|
+
dialog.innerHTML =
|
|
6666
|
+
'<div class="ai-dialog-header"><span>' + escapeHtml(item.name) + '</span><button class="dialog-close">×</button></div>' +
|
|
6667
|
+
'<div class="ai-dialog-body">' +
|
|
6668
|
+
'<div class="ai-dialog-section">' +
|
|
6669
|
+
'<div class="ai-dialog-field"><label>API Key / ID</label><input id="intg-key" type="text" placeholder="Enter API key or tracking ID" class="ai-dialog-input" value="' + escapeHtml(config.key || '') + '" /></div>' +
|
|
6670
|
+
'<div class="ai-dialog-field"><label>Config (JSON, optional)</label><textarea id="intg-config" class="method-args-textarea" rows="4" spellcheck="false" placeholder=\'{"option": "value"}\'>' + escapeHtml(config.extra ? JSON.stringify(config.extra, null, 2) : '') + '</textarea></div>' +
|
|
6671
|
+
'</div>' +
|
|
6672
|
+
'<div class="ai-dialog-actions">' +
|
|
6673
|
+
'<button id="intg-save" class="ai-dialog-save">' + (isNew ? 'Enable' : 'Save') + '</button>' +
|
|
6674
|
+
'</div>' +
|
|
6675
|
+
'</div>'
|
|
6676
|
+
|
|
6677
|
+
overlay.appendChild(dialog)
|
|
6678
|
+
document.body.appendChild(overlay)
|
|
6679
|
+
|
|
6680
|
+
dialog.querySelector('.dialog-close').addEventListener('click', () => overlay.remove())
|
|
6681
|
+
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove() })
|
|
6682
|
+
|
|
6683
|
+
dialog.querySelector('#intg-save').addEventListener('click', async () => {
|
|
6684
|
+
const key = dialog.querySelector('#intg-key').value.trim()
|
|
6685
|
+
let extra = null
|
|
6686
|
+
const extraRaw = dialog.querySelector('#intg-config').value.trim()
|
|
6687
|
+
if (extraRaw) {
|
|
6688
|
+
try { extra = JSON.parse(extraRaw) } catch (e) { extra = null }
|
|
6689
|
+
}
|
|
6690
|
+
enabledIntegrations[id] = { key, extra, enabledAt: Date.now() }
|
|
6691
|
+
await chrome.storage.local.set({ integrations: enabledIntegrations })
|
|
6692
|
+
overlay.remove()
|
|
6693
|
+
renderActiveIntegrations()
|
|
6694
|
+
renderMarketplace()
|
|
6695
|
+
})
|
|
6696
|
+
}
|
|
6697
|
+
|
|
6233
6698
|
// Auto-refresh: listen for page navigation and poll for DOM changes
|
|
6234
6699
|
function initAutoRefresh () {
|
|
6235
6700
|
// Refresh tree when user navigates to a new page
|
package/dist/service_worker.js
CHANGED
|
@@ -73,7 +73,7 @@ chrome.runtime.onInstalled.addListener(({ previousVersion, reason }) => {
|
|
|
73
73
|
if (["install", "update"].includes(reason)) {
|
|
74
74
|
initSettings();
|
|
75
75
|
chrome.declarativeNetRequest.updateDynamicRules({
|
|
76
|
-
removeRuleIds: [1, 2, 3],
|
|
76
|
+
removeRuleIds: [1, 2, 3, 4, 5, 6, 7],
|
|
77
77
|
addRules: [
|
|
78
78
|
{
|
|
79
79
|
id: 1,
|
|
@@ -102,6 +102,62 @@ chrome.runtime.onInstalled.addListener(({ previousVersion, reason }) => {
|
|
|
102
102
|
urlFilter: "https://api.anthropic.com/*",
|
|
103
103
|
resourceTypes: ["xmlhttprequest", "other"]
|
|
104
104
|
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 3,
|
|
108
|
+
priority: 1,
|
|
109
|
+
action: {
|
|
110
|
+
type: "modifyHeaders",
|
|
111
|
+
requestHeaders: [
|
|
112
|
+
{ header: "Origin", operation: "remove" }
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
condition: {
|
|
116
|
+
urlFilter: "https://api.openai.com/*",
|
|
117
|
+
resourceTypes: ["xmlhttprequest", "other"]
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 4,
|
|
122
|
+
priority: 1,
|
|
123
|
+
action: {
|
|
124
|
+
type: "modifyHeaders",
|
|
125
|
+
requestHeaders: [
|
|
126
|
+
{ header: "Origin", operation: "remove" }
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
condition: {
|
|
130
|
+
urlFilter: "https://generativelanguage.googleapis.com/*",
|
|
131
|
+
resourceTypes: ["xmlhttprequest", "other"]
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 5,
|
|
136
|
+
priority: 1,
|
|
137
|
+
action: {
|
|
138
|
+
type: "modifyHeaders",
|
|
139
|
+
requestHeaders: [
|
|
140
|
+
{ header: "Origin", operation: "remove" }
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
condition: {
|
|
144
|
+
urlFilter: "https://api.deepseek.com/*",
|
|
145
|
+
resourceTypes: ["xmlhttprequest", "other"]
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: 6,
|
|
150
|
+
priority: 1,
|
|
151
|
+
action: {
|
|
152
|
+
type: "modifyHeaders",
|
|
153
|
+
requestHeaders: [
|
|
154
|
+
{ header: "Origin", operation: "remove" }
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
condition: {
|
|
158
|
+
urlFilter: "https://api.groq.com/*",
|
|
159
|
+
resourceTypes: ["xmlhttprequest", "other"]
|
|
160
|
+
}
|
|
105
161
|
}
|
|
106
162
|
]
|
|
107
163
|
});
|