vibe-ai-c 3.3.1 → 4.0.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/vibe-ai.min.js DELETED
@@ -1 +0,0 @@
1
- const LOCAL_STORAGE_KEY_V2='vibe_ai_v2_config';const LOCAL_STORAGE_KEY_V3='vibe_ai_v3_config';const PRESETS=[{name:'OpenAI',url:'https://api.openai.com/v1'},{name:'DeepSeek',url:'https://api.deepseek.com/v1'},{name:'Gemini',url:'https://generativelanguage.googleapis.com/v1beta/openai'},{name:'Groq',url:'https://api.groq.com/openai/v1'},{name:'Qwen',url:'https://dashscope.aliyuncs.com/compatible-mode/v1'}];const STYLE=`.vibe-modal*{box-sizing:border-box}.vibe-modal{position:fixed;inset:0;background:rgba(0,0,0,0.6);backdrop-filter:blur(8px);display:none;align-items:center;justify-content:center;z-index:9999;font-family:system-ui,sans-serif}.vibe-card{background:rgba(255,255,255,0.95);width:90%;max-width:600px;max-height:85vh;border-radius:20px;box-shadow:0 20px 50px rgba(0,0,0,0.3);display:flex;flex-direction:column;overflow:hidden;color:#333}.vibe-header{padding:20px;border-bottom:1px solid#eee;display:flex;justify-content:space-between;align-items:center;flex-shrink:0}.vibe-body{padding:20px;overflow-y:auto;flex:1}.vibe-footer{padding:15px;border-top:1px solid#eee;display:flex;justify-content:space-between;gap:10px;flex-shrink:0}.vibe-profile{border:1px solid#ddd;border-radius:12px;padding:15px;margin-bottom:15px;background:#fff;width:100%;overflow:hidden}.vibe-profile-head{display:flex;gap:10px;margin-bottom:10px}.vibe-input{border:1px solid#ccc;border-radius:6px;padding:8px 12px;font-size:14px;flex:1;min-width:0;outline:none;width:100%}.vibe-input:focus{border-color:#007AFF;box-shadow:0 0 0 2px rgba(0,122,255,0.1)}.vibe-btn{border:none;border-radius:8px;padding:8px 16px;cursor:pointer;font-weight:500;font-size:13px;transition:0.2s;white-space:nowrap}.vibe-btn-primary{background:#007AFF;color:white}.vibe-btn-danger{background:#FF3B30;color:white}.vibe-btn-ghost{background:#f0f0f0;color:#333}.vibe-badge{padding:2px 8px;background:#f5f5f5;border-radius:4px;font-size:11px;cursor:pointer;border:1px solid transparent}.vibe-badge:hover{border-color:#007AFF;color:#007AFF}.vibe-models-area{margin-top:12px;border-top:1px dashed#eee;padding-top:12px}.vibe-model-tools{display:flex;align-items:center;gap:8px;margin-bottom:10px}.vibe-models-grid{display:flex;flex-wrap:wrap;gap:6px;max-height:200px;overflow-y:auto;padding:4px}.vibe-tag{font-size:11px;padding:4px 10px;border-radius:14px;border:1px solid#ddd;cursor:pointer;background:#fff;white-space:nowrap;transition:0.1s}.vibe-tag.active{background:#E1F5FE;border-color:#007AFF;color:#007AFF}.vibe-tag.vibe-hidden{display:none}.vibe-lock-status{font-size:12px;color:#666;display:flex;align-items:center;gap:6px;cursor:pointer}.vibe-status-dot{width:8px;height:8px;border-radius:50%;display:inline-block}`;class VibeAI{constructor(){this.config={version:'3.3',isEncrypted:false,profiles:[],instanceStates:{}};this.sessionKey=null;this.boundSelects=new Set();this._initStyle()}async init({setupBtnId}){this._migrate();this._load();if(setupBtnId)document.getElementById(setupBtnId)?.addEventListener('click',()=>this.openSettings());window.vibeAI=this}bindModelSelect(id){const el=document.getElementById(id);if(!el)return;this.boundSelects.add(id);this._renderSelect(el);el.addEventListener('change',(e)=>{this.config.instanceStates[id]=e.target.value;this._save()})}async chat({instanceId,messages,stream=false,...rest}){const target=this.config.instanceStates[instanceId];if(!target)throw new Error("未选择模型");const[pId,mId]=target.split('|');const p=this.config.profiles.find(x=>x.id===pId);if(!p)throw new Error("供应商配置失效");let key=p.apiKey;if(this.config.isEncrypted)key=await this._getDecryptedKey(key);const response=await fetch(`${p.baseUrl}/chat/completions`,{method:'POST',headers:{'Content-Type':'application/json','Authorization':`Bearer ${key}`},body:JSON.stringify({model:mId,messages,stream,...rest})});if(!response.ok){const err=await response.json().catch(()=>({}));throw new Error(err.error?.message||`HTTP ${response.status}`)}return stream?this._handleStream(response):response.json()}openSettings(){this._renderModal();document.getElementById('vibe-modal').style.display='flex'}_renderModal(){let m=document.getElementById('vibe-modal');if(!m){m=document.createElement('div');m.id='vibe-modal';m.className='vibe-modal';document.body.appendChild(m)}const isEncrypted=this.config.isEncrypted;m.innerHTML=`<div class="vibe-card"><div class="vibe-header"><strong style="font-size:18px">VibeAI v3.3</strong><div class="vibe-lock-status"onclick="window.vibeAI._toggleEncryption()"><span class="vibe-status-dot"style="background:${isEncrypted ? '#34C759' : '#ccc'}"></span>${isEncrypted?'加密已开启':'明文存储 (点击加密)'}</div></div><div class="vibe-body"id="vibe-profiles-container"></div><div class="vibe-footer"><div><button class="vibe-btn vibe-btn-ghost"onclick="window.vibeAI._exportConfig()">导出</button><button class="vibe-btn vibe-btn-ghost"onclick="window.vibeAI._importConfig()">导入</button></div><button class="vibe-btn vibe-btn-primary"onclick="window.vibeAI._closeModal()">保存并关闭</button></div></div>`;this._renderProfiles()}_renderProfiles(){const container=document.getElementById('vibe-profiles-container');container.innerHTML=this.config.profiles.map((p,i)=>`<div class="vibe-profile"data-index="${i}"><div class="vibe-profile-head"><input type="text"class="vibe-input vibe-name"placeholder="名称"value="${p.name}"oninput="window.vibeAI._updateConfig(${i}, 'name', this.value)"><input type="password"class="vibe-input vibe-key"placeholder="${this.config.isEncrypted ? '已加密' : 'API Key'}"value="${this.config.isEncrypted ? '' : p.apiKey}"oninput="window.vibeAI._updateKey(${i}, this.value)"><button class="vibe-btn vibe-btn-danger"onclick="window.vibeAI._removeProfile(${i})">删除</button></div><input type="text"class="vibe-input vibe-url"style="margin-bottom:10px;"placeholder="Base URL"value="${p.baseUrl}"oninput="window.vibeAI._updateConfig(${i}, 'baseUrl', this.value)"><div style="display:flex; gap:5px; flex-wrap:wrap;">${PRESETS.map(pre=>`<span class="vibe-badge"onclick="window.vibeAI._applyPreset(${i},'${pre.name}','${pre.url}')">${pre.name}</span>`).join('')}</div><div class="vibe-models-area"id="vibe-models-area-${i}"></div><button class="vibe-btn vibe-btn-ghost"style="width:100%; margin-top:10px"onclick="window.vibeAI._fetchModels(${i})">获取并校验资产</button></div>`).join('')+`<button class="vibe-btn vibe-btn-ghost"style="width:100%; border:2px dashed #ccc; padding:12px"onclick="window.vibeAI._addProfile()">+添加供应商</button>`;this.config.profiles.forEach((p,i)=>this._renderModelList(i))}_renderModelList(idx){const area=document.getElementById(`vibe-models-area-${idx}`);const p=this.config.profiles[idx];if(!area||!p)return;area.innerHTML=`${p.models.length>=10?`<div class="vibe-model-tools"><input type="text"class="vibe-input vibe-search"placeholder="在 ${p.models.length} 个模型中搜索..."oninput="window.vibeAI._onModelSearch(${idx}, this.value)"><button class="vibe-btn vibe-btn-ghost"style="padding:4px 8px"onclick="window.vibeAI._batchSelect(${idx}, true)">全选</button><button class="vibe-btn vibe-btn-ghost"style="padding:4px 8px"onclick="window.vibeAI._batchSelect(${idx}, false)">清空</button></div>`:''}<div class="vibe-models-grid">${p.models.map(m=>`<div class="vibe-tag ${m.selected ? 'active' : ''}"data-id="${m.id}"onclick="window.vibeAI._toggleModel(${idx}, '${m.id}', this)">${m.id}${m.status==='pass'?'✅':''}</div>`).join('')||'<div style="font-size:12px;color:#999">未获取模型资产</div>'}</div>`}_updateConfig(i,key,val){this.config.profiles[i][key]=val}async _updateKey(i,val){if(this.config.isEncrypted&&val){if(!this.sessionKey)this.sessionKey=await this._deriveKey(prompt("设置主密码:")||"");val="ENC:"+await this._encrypt(val)}this.config.profiles[i].apiKey=val}_applyPreset(idx,name,url){const p=this.config.profiles[idx];const dom=document.querySelector(`.vibe-profile[data-index="${idx}"]`);p.baseUrl=url;dom.querySelector('.vibe-url').value=url;if(p.name==='新供应商'||!p.name){p.name=name;dom.querySelector('.vibe-name').value=name}}async _fetchModels(idx){const p=this.config.profiles[idx];const area=document.getElementById(`vibe-models-area-${idx}`);area.innerHTML=`<div style="font-size:12px;color:#007AFF">正在连接终端并获取资产清单...</div>`;const fetchFn=async(url)=>{let key=p.apiKey;if(this.config.isEncrypted)key=await this._getDecryptedKey(key);const r=await fetch(`${url}/models`,{headers:{'Authorization':`Bearer ${key}`}});if(!r.ok){const errBody=await r.json().catch(()=>({}));throw new Error(errBody.error?.message||`HTTP ${r.status}`)}return r.json()};try{let res,base=p.baseUrl.replace(/\/+$/,'');try{res=await fetchFn(base)}catch(e){if(!base.toLowerCase().endsWith('/v1')){const fixed=base+'/v1';res=await fetchFn(fixed);p.baseUrl=fixed;document.querySelector(`.vibe-profile[data-index="${idx}"].vibe-url`).value=fixed}else throw e;}const sorted=res.data.sort((a,b)=>a.id.localeCompare(b.id));const autoSel=sorted.length<10;p.models=sorted.map(m=>({id:m.id,selected:autoSel,status:'pass'}));this._renderModelList(idx)}catch(e){area.innerHTML=`<div style="font-size:12px;color:#FF3B30;padding:8px;background:#FFF5F5;border-radius:6px;border:1px solid #FFD6D6">获取失败:${e.message}</div>`}}_onModelSearch(idx,val){const grid=document.querySelector(`.vibe-profile[data-index="${idx}"].vibe-models-grid`);const tags=grid.querySelectorAll('.vibe-tag');const q=val.toLowerCase();tags.forEach(t=>t.classList.toggle('vibe-hidden',!t.dataset.id.toLowerCase().includes(q)))}_batchSelect(idx,val){const p=this.config.profiles[idx];const dom=document.querySelector(`.vibe-profile[data-index="${idx}"]`);const searchInput=dom.querySelector('.vibe-search');const q=searchInput?searchInput.value.toLowerCase():'';p.models.forEach(m=>{if(m.id.toLowerCase().includes(q))m.selected=val});this._renderModelList(idx);const newSearchInput=dom.querySelector('.vibe-search');if(newSearchInput){newSearchInput.value=q;this._onModelSearch(idx,q)}}_toggleModel(pIdx,mId,el){const m=this.config.profiles[pIdx].models.find(x=>x.id===mId);if(m){m.selected=!m.selected;el.classList.toggle('active',m.selected)}}_addProfile(){this.config.profiles.push({id:Math.random().toString(36).slice(2,9),name:'新供应商',baseUrl:'',apiKey:'',models:[]});this._renderProfiles()}_removeProfile(idx){this.config.profiles.splice(idx,1);this._renderProfiles()}async _getDecryptedKey(encStr){if(!encStr.startsWith('ENC:'))return encStr;if(!this.sessionKey){const p=prompt("请输入主密码授权:");if(!p)throw new Error("Need Password");this.sessionKey=await this._deriveKey(p)}try{return await this._decrypt(encStr.replace('ENC:',''))}catch(e){this.sessionKey=null;throw new Error("密码解密失败");}}async _deriveKey(pwd){const enc=new TextEncoder();const mat=await crypto.subtle.importKey("raw",enc.encode(pwd),"PBKDF2",false,["deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:enc.encode('vibe-v3'),iterations:100000,hash:"SHA-256"},mat,{name:"AES-GCM",length:256},false,["encrypt","decrypt"])}async _encrypt(text){const iv=crypto.getRandomValues(new Uint8Array(12));const enc=await crypto.subtle.encrypt({name:"AES-GCM",iv},this.sessionKey,new TextEncoder().encode(text));return btoa(JSON.stringify({iv:Array.from(iv),data:Array.from(new Uint8Array(enc))}))}async _decrypt(json){const{iv,data}=JSON.parse(atob(json));const dec=await crypto.subtle.decrypt({name:"AES-GCM",iv:new Uint8Array(iv)},this.sessionKey,new Uint8Array(data));return new TextDecoder().decode(dec)}async _toggleEncryption(){if(!this.config.isEncrypted){const p=prompt("请设置主密码(此密码用于加密本地 Key,不会上传服务器):");if(!p)return;this.sessionKey=await this._deriveKey(p);for(let x of this.config.profiles)if(x.apiKey&&!x.apiKey.startsWith('ENC:'))x.apiKey="ENC:"+await this._encrypt(x.apiKey);this.config.isEncrypted=true}else{if(confirm("关闭加密后 Key 将以明文存储,是否继续?")){for(let x of this.config.profiles)if(x.apiKey.startsWith('ENC:'))x.apiKey=await this._getDecryptedKey(x.apiKey);this.config.isEncrypted=false;this.sessionKey=null}}this.openSettings()}_load(){const r=localStorage.getItem(LOCAL_STORAGE_KEY_V3);if(r)this.config=JSON.parse(r)}_save(){localStorage.setItem(LOCAL_STORAGE_KEY_V3,JSON.stringify(this.config));this.boundSelects.forEach(id=>this._renderSelect(document.getElementById(id)))}_migrate(){const v2=localStorage.getItem(LOCAL_STORAGE_KEY_V2);if(v2){try{const old=JSON.parse(v2);this.config.profiles=old.profiles.map(p=>({id:Math.random().toString(36).slice(2,9),name:p.name,baseUrl:p.baseUrl,apiKey:p.apiKey,models:p.models.map(m=>({id:m,selected:true,status:'pass'}))}));localStorage.removeItem(LOCAL_STORAGE_KEY_V2);this._save()}catch(e){}}}_renderSelect(el){if(!el)return;const cur=this.config.instanceStates[el.id]||"";let h=`<option value="">选择模型...</option>`;this.config.profiles.forEach(p=>{const sel=p.models.filter(m=>m.selected);if(sel.length){h+=`<optgroup label="${p.name}">`;sel.forEach(m=>{const val=`${p.id}|${m.id}`;h+=`<option value="${val}"${val===cur?'selected':''}>${m.id}</option>`;});h+=`</optgroup>`}});el.innerHTML=h}_closeModal(){document.getElementById('vibe-modal').style.display='none';this._save()}_initStyle(){const s=document.createElement('style');s.textContent=STYLE;document.head.appendChild(s)}_exportConfig(){const a=document.createElement('a');a.href=URL.createObjectURL(new Blob([JSON.stringify(this.config,null,2)],{type:'application/json'}));a.download=`vibe-ai-config.json`;a.click()}_importConfig(){const i=document.createElement('input');i.type='file';i.onchange=(e)=>{const r=new FileReader();r.onload=(ev)=>{const d=JSON.parse(ev.target.result);if(d.version){this.config=d;this._save();this.openSettings()}};r.readAsText(e.target.files[0])};i.click()}async*_handleStream(res){const r=res.body.getReader();const dec=new TextDecoder();let buf='';while(true){const{done,value}=await r.read();if(done)break;buf+=dec.decode(value,{stream:true});const lines=buf.split('\n');buf=lines.pop();for(const l of lines){const s=l.trim();if(!s||s==='data: [DONE]')continue;if(s.startsWith('data: ')){try{const c=JSON.parse(s.slice(6)).choices[0]?.delta?.content;if(c)yield c}catch(e){}}}}}}export const vibeAI=new VibeAI();