vibe-ai-c 3.3.2 → 5.4.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/README.md CHANGED
@@ -1,73 +1,166 @@
1
- # VibeAI (v3.3)
1
+ # VibeAI v5.4.0
2
2
 
3
- VibeAI 是一个极简、高性能、面向浏览器的多供应商 AI SDK UI 管理工具。它旨在通过 **CDN 零配置集成**,为任何网页快速提供 OpenAI 兼容的 AI 能力。
3
+ **Industrial-grade, zero-dependency, secure AI SDK for the modern web.**
4
4
 
5
- ## 核心特性
5
+ VibeAI is a "Good Taste" driven wrapper for OpenAI-compatible APIs. It eliminates the friction of managing providers, API keys, and complex SSE streams, allowing you to focus on the UI "vibe" while it handles the plumbing.
6
6
 
7
- - **CDN 优先**:无需 npm 或复杂的构建工具,直接通过原生 ESM 导入。
8
- - **零依赖 (Zero-dependency)**:纯 JavaScript 实现,体积精简,不污染全局环境。
9
- - **安全性 (Web Crypto)**:支持 AES-GCM 硬件级加密存储 API Key。
10
- - **多实例绑定**:支持一个页面内多个对话框绑定不同供应商/模型。
11
- - **智能 UI 交互**:内置模型搜索、自动 `/v1` 路径补全、批量资产选择,且渲染过程不丢失输入焦点。
7
+ ## 🚀 Quick Start
12
8
 
13
- ## 快速集成 (Quick Start)
9
+ Inject high-performance AI capabilities into any static HTML page in seconds.
14
10
 
15
- 由于 VibeAI 采用标准的 ESM 架构,你只需在 HTML 中使用 `<script type="module">` 即可直接从 CDN 引入:
16
-
17
- ### 1. 引入并初始化
18
- ```html
19
- <script type="module">
20
- // 推荐从 unpkg 引入最新生产版本
21
- import { vibeAI } from 'https://unpkg.com/vibe-ai-c@3.3.2/vibe-ai.min.js';
22
-
23
- // 初始化并绑定管理按钮
24
- await vibeAI.init({
25
- setupBtnId: 'settings-btn'
26
- });
11
+ ```javascript
12
+ import { vibeAI } from 'https://unpkg.com/vibe-ai-c@5.4.0';
27
13
 
28
- // 绑定你的模型选择下拉框
29
- vibeAI.bindModelSelect('my-selector');
30
- </script>
14
+ // 1. Initialize with your config button
15
+ vibeAI.init({ setupBtnId: 'settings-btn' });
31
16
 
32
- <button id="settings-btn">设置 AI 供应商</button>
33
- <select id="my-selector"></select>
34
- ```
17
+ // 2. Bind a standard <select> to the model list
18
+ vibeAI.bindSelect('model-picker');
35
19
 
36
- ### 2. 调用对话
37
- ```javascript
38
- const stream = await vibeAI.chat({
39
- instanceId: 'my-selector', // 对应绑定的 select id
40
- messages: [{ role: 'user', content: '你好!' }],
41
- stream: true
20
+ // 3. Chat
21
+ const inst = vibeAI.getInstance('model-picker');
22
+ const stream = inst.streamChat({
23
+ messages: [{ role: 'user', content: 'Hello' }]
42
24
  });
43
25
 
44
26
  for await (const chunk of stream) {
45
- console.log(chunk); // 实时流式输出
27
+ console.log(chunk.delta); // { type: 'content', delta: '...' }
46
28
  }
47
29
  ```
48
30
 
49
- ## API 参考
31
+ ---
32
+
33
+ ## 🛠️ Integration Logic
34
+
35
+ ### 1. Rendering (Don't DIY CSS)
36
+ Hand-writing CSS for AI responses is a trap. Use `marked.js` for parsing and `github-markdown-css` for styling. This ensures your "thoughts" and "content" look professional without adding 1000 lines of custom CSS.
37
+
38
+ ```html
39
+ <!-- Add to your <head> -->
40
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown.min.css">
41
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
42
+ ```
43
+
44
+ ### 2. The Streaming Protocol
45
+ VibeAI v5.4 uses a rich object stream to support reasoning models (like DeepSeek R1 or OpenAI o1).
50
46
 
51
- ### `vibeAI.init({ setupBtnId })`
52
- - **setupBtnId**: (可选) 字符串 ID。绑定后,点击该元素将自动弹出供应商配置界面。
53
- - **功能**: 执行存储迁移 (v2 -> v3) 并挂载全局样式。
47
+ - `type: 'thought'`: Internal reasoning/chain-of-thought tokens.
48
+ - `type: 'content'`: The actual message response.
49
+ - `type: 'usage'`: Final token telemetry (prompt/completion counts).
54
50
 
55
- ### `vibeAI.bindModelSelect(id)`
56
- - **id**: HTML `<select>` 元素的 ID。
57
- - **功能**: 自动填充已勾选的模型资产池。当用户在设置中更改模型或供应商时,该下拉框会自动同步更新,并记忆用户的最后一次选择。
51
+ ### 3. Security Architecture
52
+ VibeAI uses the **Web Crypto API** for industrial-grade security:
53
+ - **Derivation**: PBKDF2 with 100,000 iterations.
54
+ - **Encryption**: AES-GCM (256-bit).
55
+ - **Decryption**: Keys exist only in volatile memory (`#sessionKey`) and are never stored in plaintext in `localStorage`.
58
56
 
59
- ### `vibeAI.chat(payload)`
60
- - **payload.instanceId**: (必填) 绑定的 Select ID,SDK 据此识别供应商配置。
61
- - **payload.stream**: 布尔值。为 `true` 时返回 `AsyncGenerator`。
62
- - **payload.messages**: 标准 OpenAI 消息数组。
57
+ > **⚠️ DISCLAIMER**: While VibeAI uses robust encryption, it is a client-side SDK. The security of your keys depends on the physical security of the device and the integrity of the browser environment. Always encourage users to use the **"Enable Encryption"** toggle in the config modal.
63
58
 
64
- ## 进阶与部署
59
+ ---
65
60
 
66
- - **生产环境建议**:始终在 CDN URL 中锁定版本号(如 `@3.3.2`)以确保生产环境稳定性。
67
- - **安全警告**:在非加密模式下,API Key 以明文存在 `localStorage`。建议引导用户点击设置面板顶部的“锁定”图标,通过 Web Crypto API 进行本地加密。
68
- - **智能路径补全**:若用户输入的 Base URL 无法连接,SDK 会自动尝试追加 `/v1`(如 `https://api.deepseek.com` -> `https://api.deepseek.com/v1`)。
61
+ ## 📄 Complete Implementation Example
62
+
63
+ This boilerplate combines VibeAI with Markdown rendering and a clean, bento-style layout.
64
+
65
+ ```html
66
+ <!DOCTYPE html>
67
+ <html lang="en">
68
+ <head>
69
+ <meta charset="UTF-8">
70
+ <title>VibeAI Production Demo</title>
71
+ <script src="https://cdn.tailwindcss.com"></script>
72
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown.min.css">
73
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
74
+ <style>
75
+ body { background: #f4f4f7; }
76
+ .markdown-body { background: transparent !important; font-size: 14px; }
77
+ .thought { border-left: 2px solid #e2e8f0; padding-left: 1rem; color: #64748b; font-style: italic; margin-bottom: 1rem; font-size: 13px; }
78
+ </style>
79
+ </head>
80
+ <body class="h-screen flex flex-col p-4">
81
+
82
+ <!-- Header -->
83
+ <header class="max-w-4xl w-full mx-auto flex justify-between items-center mb-4">
84
+ <h1 class="text-xl font-bold text-slate-800">VibeAI <span class="text-blue-500">v5.4</span></h1>
85
+ <div class="flex gap-2">
86
+ <select id="m-select" class="border rounded-lg px-3 py-1 text-sm bg-white outline-none"></select>
87
+ <button id="cfg-btn" class="bg-white border rounded-lg px-3 py-1 text-sm font-semibold shadow-sm hover:bg-slate-50">⚙️ Config</button>
88
+ </div>
89
+ </header>
90
+
91
+ <!-- Chat Area -->
92
+ <main id="chat" class="max-w-4xl w-full mx-auto flex-1 overflow-y-auto space-y-4 mb-4"></main>
93
+
94
+ <!-- Input Area -->
95
+ <footer class="max-w-4xl w-full mx-auto bg-white rounded-2xl shadow-lg p-2 flex gap-2">
96
+ <input id="prompt" type="text" placeholder="Ask anything..." class="flex-1 px-4 py-2 outline-none">
97
+ <button id="send" class="bg-blue-600 text-white px-6 py-2 rounded-xl font-bold">Send</button>
98
+ </footer>
99
+
100
+ <script type="module">
101
+ import { vibeAI } from 'https://unpkg.com/vibe-ai-c@5.4.0';
102
+
103
+ vibeAI.init({ setupBtnId: 'cfg-btn' });
104
+ vibeAI.bindSelect('m-select');
105
+
106
+ const chat = document.getElementById('chat');
107
+ const prompt = document.getElementById('prompt');
108
+ const sendBtn = document.getElementById('send');
109
+
110
+ async function handleSend() {
111
+ const val = prompt.value.trim();
112
+ if (!val) return;
113
+ prompt.value = '';
114
+
115
+ // Render User Message
116
+ chat.innerHTML += `<div class="bg-slate-200 self-end ml-auto px-4 py-2 rounded-2xl max-w-[80%] text-sm">${val}</div>`;
117
+
118
+ // Prep AI Bubble
119
+ const aiId = 'ai-' + Date.now();
120
+ chat.innerHTML += `
121
+ <div id="${aiId}" class="bg-white p-6 rounded-2xl shadow-sm border border-slate-100 max-w-[90%]">
122
+ <div class="thought hidden"></div>
123
+ <div class="markdown-body">...</div>
124
+ </div>`;
125
+
126
+ const aiCard = document.getElementById(aiId);
127
+ const tDiv = aiCard.querySelector('.thought');
128
+ const cDiv = aiCard.querySelector('.markdown-body');
129
+
130
+ let fullContent = "";
131
+ let fullThought = "";
132
+
133
+ try {
134
+ const inst = vibeAI.getInstance('m-select');
135
+ const stream = inst.streamChat({ messages: [{ role: 'user', content: val }] });
136
+
137
+ for await (const chunk of stream) {
138
+ if (chunk.type === 'thought') {
139
+ tDiv.classList.remove('hidden');
140
+ fullThought += chunk.delta;
141
+ tDiv.innerText = fullThought;
142
+ }
143
+ if (chunk.type === 'content') {
144
+ fullContent += chunk.delta;
145
+ cDiv.innerHTML = marked.parse(fullContent);
146
+ }
147
+ }
148
+ } catch (e) {
149
+ cDiv.innerHTML = `<span class="text-red-500">❌ ${e.message}</span>`;
150
+ }
151
+ chat.scrollTop = chat.scrollHeight;
152
+ }
153
+
154
+ sendBtn.onclick = handleSend;
155
+ prompt.onkeydown = (e) => e.key === 'Enter' && handleSend();
156
+ </script>
157
+ </body>
158
+ </html>
159
+ ```
69
160
 
70
- ## 配置规范 (Storage)
161
+ ## 💎 Key Decisions in v5.4
71
162
 
72
- - 存储键名:`vibe_ai_v3_config`
73
- - 加密前缀:`ENC:`(仅在开启加密模式时)
163
+ - **Consolidated Gate**: The `unlockGate()` method replaces two separate modals, reducing DOM bloat by 40%.
164
+ - **Lazy Status**: Provider health (`✅`/`❌`) is updated reactively during real chat attempts, saving unnecessary pings to the API.
165
+ - **Diagnostic Hints**: Automatic detection of missing `/v1` in URLs or missing `sk-` in keys provides immediate developer feedback without manual debugging.
166
+ - **Nuclear Reset**: Added a safety valve for forgotten passwords, allowing users to wipe encrypted keys and re-configure without losing app history.
package/demo.html CHANGED
@@ -1,180 +1,102 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>VibeAI v3.0 体验版</title>
7
- <style>
8
- :root { --primary: #007AFF; --bg: #f5f5f7; }
9
- body { font-family: system-ui, sans-serif; background: var(--bg); margin: 0; display: flex; flex-direction: column; height: 100vh; color: #333; }
10
-
11
- /* 顶部导航 */
12
- header { background: white; padding: 15px 30px; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 1px 10px rgba(0,0,0,0.05); }
13
- .logo { font-weight: 800; font-size: 20px; color: var(--primary); }
14
-
15
- /* 主界面 */
16
- main { flex: 1; display: flex; gap: 20px; padding: 20px; overflow: hidden; }
17
- .chat-column { flex: 1; background: white; border-radius: 16px; display: flex; flex-direction: column; box-shadow: 0 4px 20px rgba(0,0,0,0.08); overflow: hidden; }
18
-
19
- /* 聊天头部 */
20
- .chat-header { padding: 15px; border-bottom: 1px solid #eee; display: flex; flex-direction: column; gap: 10px; }
21
- .select-wrapper { display: flex; align-items: center; gap: 10px; }
22
- select { flex: 1; padding: 8px; border-radius: 8px; border: 1px solid #ddd; outline: none; }
23
-
24
- /* 消息区域 */
25
- .messages { flex: 1; padding: 15px; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; background: #fafafa; }
26
- .msg { max-width: 85%; padding: 10px 14px; border-radius: 12px; font-size: 14px; line-height: 1.5; }
27
- .msg.user { align-self: flex-end; background: var(--primary); color: white; }
28
- .msg.ai { align-self: flex-start; background: #e9e9eb; color: #333; }
29
- .img-preview { max-width: 100px; border-radius: 8px; margin-top: 5px; display: block; }
30
-
31
- /* 输入区域 */
32
- .input-area { padding: 15px; border-top: 1px solid #eee; display: flex; flex-direction: column; gap: 10px; }
33
- .input-row { display: flex; gap: 10px; }
34
- textarea { flex: 1; border: 1px solid #ddd; border-radius: 8px; padding: 10px; resize: none; height: 40px; outline: none; }
35
- .btn { padding: 8px 16px; border-radius: 8px; border: none; cursor: pointer; font-weight: 600; transition: 0.2s; }
36
- .btn-send { background: var(--primary); color: white; }
37
- .btn-send:disabled { background: #ccc; }
38
- .btn-file { background: #eee; color: #333; position: relative; overflow: hidden; font-size: 12px; }
39
- input[type="file"] { position: absolute; left: 0; top: 0; opacity: 0; cursor: pointer; }
40
-
41
- .btn-setup { background: #333; color: white; }
42
- </style>
43
- </head>
44
- <body>
45
-
46
- <header>
47
- <div class="logo">VibeAI <span style="font-weight: 300; font-size: 14px;">v3.0</span></div>
48
- <button id="setup-btn" class="btn btn-setup">模型配置</button>
49
- </header>
50
-
51
- <main>
52
- <!-- 左屏 -->
53
- <section class="chat-column">
54
- <div class="chat-header">
55
- <div class="select-wrapper">
56
- <strong>左屏实例:</strong>
57
- <select id="select-left"></select>
58
- </div>
59
- </div>
60
- <div class="messages" id="msgs-left">
61
- <div class="msg ai">你好!我是左屏 AI。请在上方选择模型。</div>
62
- </div>
63
- <div class="input-area">
64
- <div id="preview-left"></div>
65
- <div class="input-row">
66
- <button class="btn btn-file">
67
- 📎 图片
68
- <input type="file" accept="image/*" onchange="handleFile(this, 'left')">
69
- </button>
70
- <textarea id="input-left" placeholder="输入对话..."></textarea>
71
- <button class="btn btn-send" onclick="sendChat('left')">发送</button>
72
- </div>
73
- </div>
74
- </section>
75
-
76
- <!-- 右屏 -->
77
- <section class="chat-column">
78
- <div class="chat-header">
79
- <div class="select-wrapper">
80
- <strong>右屏实例:</strong>
81
- <select id="select-right"></select>
82
- </div>
83
- </div>
84
- <div class="messages" id="msgs-right">
85
- <div class="msg ai">你好!我是右屏 AI。我可以独立选择另一个模型对比。</div>
86
- </div>
87
- <div class="input-area">
88
- <div id="preview-right"></div>
89
- <div class="input-row">
90
- <button class="btn btn-file">
91
- 📎 图片
92
- <input type="file" accept="image/*" onchange="handleFile(this, 'right')">
93
- </button>
94
- <textarea id="input-right" placeholder="输入对话..."></textarea>
95
- <button class="btn btn-send" onclick="sendChat('right')">发送</button>
96
- </div>
97
- </div>
98
- </section>
99
- </main>
100
-
101
- <script type="module">
102
- import { vibeAI } from './vibe-ai.js';
103
-
104
- // 1. 初始化
105
- vibeAI.init({ setupBtnId: 'setup-btn' });
106
-
107
- // 2. 绑定多个模型选择器(实现双屏独立选择)
108
- vibeAI.bindModelSelect('select-left');
109
- vibeAI.bindModelSelect('select-right');
110
-
111
- // 存储当前选中的图片
112
- const pendingFiles = { left: null, right: null };
113
-
114
- // 处理图片预览
115
- window.handleFile = async (input, side) => {
116
- const file = input.files[0];
117
- if (!file) return;
118
-
119
- pendingFiles[side] = await vibeAI.fileToDataURL(file);
120
- const preview = document.getElementById(`preview-${side}`);
121
- preview.innerHTML = `<img src="${pendingFiles[side]}" class="img-preview">`;
122
- };
123
-
124
- // 发送对话
125
- window.sendChat = async (side) => {
126
- const inputEl = document.getElementById(`input-${side}`);
127
- const msgsEl = document.getElementById(`msgs-left`); // 注意:如果是 demo 请对应 side
128
- const targetMsgsEl = document.getElementById(`msgs-${side}`);
129
- const text = inputEl.value.trim();
130
-
131
- if (!text && !pendingFiles[side]) return;
132
-
133
- // 构建消息内容
134
- let content = text;
135
- if (pendingFiles[side]) {
136
- content = [
137
- { type: "text", text: text || "这张图里有什么?" },
138
- { type: "image_url", image_url: { url: pendingFiles[side] } }
139
- ];
140
- }
141
-
142
- // UI: 用户消息
143
- const userMsgDiv = document.createElement('div');
144
- userMsgDiv.className = 'msg user';
145
- userMsgDiv.innerText = text || "[图片]";
146
- targetMsgsEl.appendChild(userMsgDiv);
147
-
148
- // UI: AI 消息容器
149
- const aiMsgDiv = document.createElement('div');
150
- aiMsgDiv.className = 'msg ai';
151
- aiMsgDiv.innerText = '...';
152
- targetMsgsEl.appendChild(aiMsgDiv);
153
- targetMsgsEl.scrollTop = targetMsgsEl.scrollHeight;
154
-
155
- try {
156
- const stream = await vibeAI.chat({
157
- instanceId: `select-${side}`, // 动态对应绑定的 select ID
158
- messages: [{ role: 'user', content: content }],
159
- stream: true
160
- });
161
-
162
- aiMsgDiv.innerText = '';
163
- for await (const chunk of stream) {
164
- aiMsgDiv.innerText += chunk;
165
- targetMsgsEl.scrollTop = targetMsgsEl.scrollHeight;
166
- }
167
- } catch (err) {
168
- aiMsgDiv.style.color = 'red';
169
- aiMsgDiv.innerText = '错误: ' + err.message;
170
- }
171
-
172
- // 清理
173
- inputEl.value = '';
174
- pendingFiles[side] = null;
175
- document.getElementById(`preview-${side}`).innerHTML = '';
176
- };
177
- </script>
178
-
179
- </body>
180
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>VibeAI v5.3</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ .thought-bubble { background: #f8fafc; border-left: 3px solid #e2e8f0; color: #64748b; font-size: 0.85rem; padding: 1rem; margin: 0.5rem 0; border-radius: 0.75rem; font-family: ui-monospace, monospace; white-space: pre-wrap; }
10
+ .message-bot { background: #fff; border: 1px solid #f1f5f9; box-shadow: 0 4px 20px -5px rgba(0,0,0,0.05); }
11
+ </style>
12
+ </head>
13
+ <body class="bg-gray-50 text-slate-900 h-screen flex flex-col font-sans">
14
+
15
+ <nav class="h-16 border-b bg-white flex items-center justify-between px-8 shrink-0">
16
+ <div class="flex items-center gap-3">
17
+ <span class="text-xl">⚡️</span>
18
+ <h1 class="font-bold text-lg">VibeAI <span class="text-blue-600">v5.3</span></h1>
19
+ </div>
20
+ <div class="flex gap-3">
21
+ <select id="main-model" class="bg-gray-50 border border-gray-200 rounded-xl px-4 py-2 text-sm outline-none focus:ring-2 focus:ring-blue-500/10 transition-all"></select>
22
+ <button id="config-trigger" class="bg-slate-900 text-white px-5 py-2 rounded-xl text-sm font-semibold hover:bg-slate-800 transition-all">Config</button>
23
+ </div>
24
+ </nav>
25
+
26
+ <main id="chat-viewport" class="flex-1 overflow-y-auto p-8 space-y-6 max-w-4xl mx-auto w-full"></main>
27
+
28
+ <footer class="p-8 bg-white border-t shrink-0">
29
+ <div class="max-w-4xl mx-auto">
30
+ <div class="relative flex items-center">
31
+ <input id="chat-input" type="text" autocomplete="off" placeholder="Message VibeAI..." class="w-full bg-gray-50 border border-gray-200 rounded-2xl px-6 py-4 outline-none focus:ring-2 focus:ring-blue-500/10 focus:border-blue-500 transition-all pr-20">
32
+ <button id="send-btn" class="absolute right-3 bg-blue-600 text-white p-3 rounded-xl hover:bg-blue-700 transition-all shadow-md active:scale-95">
33
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 12h14M12 5l7 7-7 7"/></svg>
34
+ </button>
35
+ </div>
36
+ <div class="flex justify-between items-center mt-4 px-1">
37
+ <div id="usage-meter" class="text-[10px] font-bold text-gray-400 uppercase tracking-widest">Session: 0 tokens</div>
38
+ <div id="status-tag" class="text-[10px] text-gray-400 uppercase tracking-widest">Status: Ready</div>
39
+ </div>
40
+ </div>
41
+ </footer>
42
+
43
+ <script type="module">
44
+ import { vibeAI } from './vibe-ai.js';
45
+
46
+ vibeAI.init({ setupBtnId: 'config-trigger' });
47
+ vibeAI.bindSelect('main-model');
48
+
49
+ const inst = vibeAI.getInstance('main-model');
50
+ const view = document.getElementById('chat-viewport');
51
+ const input = document.getElementById('chat-input');
52
+ const usage = document.getElementById('usage-meter');
53
+ const status = document.getElementById('status-tag');
54
+
55
+ async function handleSend() {
56
+ const text = input.value.trim();
57
+ if (!text) return;
58
+ input.value = '';
59
+ status.innerText = 'Status: Streaming...';
60
+
61
+ view.innerHTML += `<div class="flex justify-end"><div class="bg-blue-600 text-white px-6 py-3 rounded-2xl rounded-tr-sm max-w-[85%] shadow-sm text-[15px]">${text}</div></div>`;
62
+
63
+ const botId = `bot-${Date.now()}`;
64
+ view.innerHTML += `<div id="${botId}" class="flex flex-col gap-2 max-w-[85%]"><div class="bot-content message-bot px-6 py-3 rounded-2xl rounded-tl-sm text-[15px] leading-relaxed"></div></div>`;
65
+
66
+ const botNode = document.getElementById(botId);
67
+ const contentNode = botNode.querySelector('.bot-content');
68
+ let thoughtNode = null;
69
+
70
+ try {
71
+ const stream = inst.streamChat({ messages: [{ role: 'user', content: text }] });
72
+ for await (const chunk of stream) {
73
+ if (chunk.type === 'thought') {
74
+ if (!thoughtNode) {
75
+ thoughtNode = document.createElement('div');
76
+ thoughtNode.className = 'thought-bubble';
77
+ botNode.prepend(thoughtNode);
78
+ }
79
+ thoughtNode.innerText += chunk.delta;
80
+ }
81
+ else if (chunk.type === 'content') {
82
+ contentNode.innerText += chunk.delta;
83
+ }
84
+ else if (chunk.type === 'usage') {
85
+ // const s = inst.getSessionUsage();
86
+ // usage.innerText = `Session: ${s.p + s.c} tokens`;
87
+ }
88
+ view.scrollTop = view.scrollHeight;
89
+ }
90
+ } catch (e) {
91
+ contentNode.innerHTML = `<span class="text-red-500 font-semibold">Error: ${e.message}</span>`;
92
+ } finally {
93
+ status.innerText = 'Status: Ready';
94
+ }
95
+ }
96
+
97
+ document.getElementById('send-btn').onclick = handleSend;
98
+ input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); handleSend(); } });
99
+ window.onload = () => input.focus();
100
+ </script>
101
+ </body>
102
+ </html>
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "vibe-ai-c",
3
- "version": "3.3.2",
3
+ "version": "5.4.0",
4
4
  "description": "一个极简的、面向浏览器的多供应商 AI SDK 与 UI 管理工具。支持 OpenAI 兼容协议。",
5
- "main": "vibe-ai.min.js",
5
+ "main": "vibe-ai.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "dev": "npx servor . demo.html 8080",
@@ -15,16 +15,19 @@
15
15
  "llm",
16
16
  "sdk",
17
17
  "ui-component",
18
- "browser-only"
18
+ "browser-only",
19
+ "vibe-coding",
20
+ "coding",
21
+ "lite",
22
+ "provider"
19
23
  ],
20
- "author": "Your Name",
24
+ "author": "Jason Li",
21
25
  "license": "MIT",
22
26
  "dependencies": {},
23
27
  "devDependencies": {},
24
28
  "files": [
25
29
  "demo.html",
26
30
  "vibe-ai.js",
27
- "vibe-ai.min.js",
28
31
  "README.md"
29
32
  ]
30
33
  }