vibe-ai-c 3.3.2 → 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/README.md CHANGED
@@ -1,73 +1,175 @@
1
- # VibeAI (v3.3)
1
+ # # VibeAI v4.0 - Orchestrator SDK
2
2
 
3
- VibeAI 是一个极简、高性能、面向浏览器的多供应商 AI SDK UI 管理工具。它旨在通过 **CDN 零配置集成**,为任何网页快速提供 OpenAI 兼容的 AI 能力。
3
+ VibeAI is a zero-dependency, browser-based AI management SDK. It handles multiple LLM providers (OpenAI, DeepSeek, Gemini, etc.), secure API key storage (AES-GCM), and complex task orchestration like concurrent batch processing.
4
4
 
5
- ## 核心特性
5
+ ### 📦 Installation (Recommended)
6
6
 
7
- - **CDN 优先**:无需 npm 或复杂的构建工具,直接通过原生 ESM 导入。
8
- - **零依赖 (Zero-dependency)**:纯 JavaScript 实现,体积精简,不污染全局环境。
9
- - **安全性 (Web Crypto)**:支持 AES-GCM 硬件级加密存储 API Key。
10
- - **多实例绑定**:支持一个页面内多个对话框绑定不同供应商/模型。
11
- - **智能 UI 交互**:内置模型搜索、自动 `/v1` 路径补全、批量资产选择,且渲染过程不丢失输入焦点。
7
+ For the most stable and optimized experience, always import from **unpkg**:
12
8
 
13
- ## 快速集成 (Quick Start)
9
+ ```javascript
10
+ import { vibeAI } from 'https://unpkg.com/vibe-ai-c@4.0.0';
11
+ ```
12
+
13
+ ---
14
+
15
+ ## 🏗 Core Architecture
16
+
17
+ VibeAI v4.0 follows a three-tier hierarchy:
18
+
19
+ 1. **`vibeAI` (The Manager)**: Handles global configuration, UI modal rendering, encryption, and provider persistence.
20
+ 2. **`VibeInstance` (The Context)**: A bound interface to a specific UI element (e.g., a dropdown selector). It manages its own `AbortController` and state.
21
+ 3. **`VibeBatch` (The Orchestrator)**: Handles high-concurrency tasks, retries, and progress tracking.
14
22
 
15
- 由于 VibeAI 采用标准的 ESM 架构,你只需在 HTML 中使用 `<script type="module">` 即可直接从 CDN 引入:
23
+ ---
24
+
25
+ ## 🛠 Quick Start
26
+
27
+ ### 1. Initialization and UI Binding
28
+
29
+ Bind the SDK to your UI elements. VibeAI will automatically populate selects and handle focus-safe updates.
16
30
 
17
- ### 1. 引入并初始化
18
31
  ```html
19
- <script type="module">
20
- // 推荐从 unpkg 引入最新生产版本
21
- import { vibeAI } from 'https://unpkg.com/vibe-ai-c@3.3.2/vibe-ai.min.js';
32
+ <!-- HTML -->
33
+ <button id="settings-btn">Open AI Settings</button>
34
+ <select id="chat-model-selector"></select>
22
35
 
23
- // 初始化并绑定管理按钮
24
- await vibeAI.init({
25
- setupBtnId: 'settings-btn'
26
- });
36
+ <script type="module">
37
+ import { vibeAI } from 'https://unpkg.com/vibe-ai-c@4.0.0';
27
38
 
28
- // 绑定你的模型选择下拉框
29
- vibeAI.bindModelSelect('my-selector');
39
+ await vibeAI.init({ setupBtnId: 'settings-btn' });
40
+ // Bind a selector to manage model choices
41
+ vibeAI.bindModelSelect('chat-model-selector');
30
42
  </script>
31
-
32
- <button id="settings-btn">设置 AI 供应商</button>
33
- <select id="my-selector"></select>
34
43
  ```
35
44
 
36
- ### 2. 调用对话
45
+ ### 2. Single Chat (Simple vs Stream)
46
+
47
+ Use an **Instance** to perform chat operations.
48
+
37
49
  ```javascript
38
- const stream = await vibeAI.chat({
39
- instanceId: 'my-selector', // 对应绑定的 select id
40
- messages: [{ role: 'user', content: '你好!' }],
41
- stream: true
42
- });
50
+ const instance = vibeAI.getInstance('chat-model-selector');
51
+
52
+ // A. Simple Ask (Returns string)
53
+ const response = await instance.ask("What is the capital of France?");
54
+ console.log(response);
55
+
56
+ // B. Streaming Chat
57
+ const stream = await instance.chat({
58
+ messages: [{ role: 'user', content: 'Write a poem.' }]
59
+ }, { stream: true });
43
60
 
44
61
  for await (const chunk of stream) {
45
- console.log(chunk); // 实时流式输出
62
+ process.stdout.write(chunk); // UI: update your text container here
46
63
  }
47
64
  ```
48
65
 
49
- ## API 参考
66
+ ### 3. Concurrent Batch Processing (Orchestrator)
67
+
68
+ Process hundreds of tasks with built-in concurrency limits and retries.
69
+
70
+ ```javascript
71
+ const batch = vibeAI.createBatch('chat-model-selector', {
72
+ concurrency: 5, // Process 5 tasks at a time
73
+ retry: 2 // Retry failed tasks twice
74
+ });
75
+
76
+ const tasks = ["Summarize A", "Summarize B", "Summarize C"];
77
+ tasks.forEach(t => batch.add(t));
78
+
79
+ const results = await batch.run((done, total, last) => {
80
+ console.log(`Progress: ${done}/${total} | Success: ${last.success}`);
81
+ });
82
+ ```
83
+
84
+ ---
85
+
86
+ ## 🖼 Vision Support (Multimodal)
87
+
88
+ VibeAI provides a helper to convert `File` objects into LLM-compatible vision payloads.
89
+
90
+ ```javascript
91
+ const fileInput = document.querySelector('input[type="file"]');
92
+ const imagePart = await VibeAI.fileToVisionPayload(fileInput.files[0]);
93
+
94
+ const instance = vibeAI.getInstance('chat-model-selector');
95
+ await instance.chat({
96
+ messages: [{
97
+ role: 'user',
98
+ content: [
99
+ { type: 'text', text: 'What is in this image?' },
100
+ imagePart
101
+ ]
102
+ }]
103
+ });
104
+ ```
105
+
106
+ ---
50
107
 
51
- ### `vibeAI.init({ setupBtnId })`
52
- - **setupBtnId**: (可选) 字符串 ID。绑定后,点击该元素将自动弹出供应商配置界面。
53
- - **功能**: 执行存储迁移 (v2 -> v3) 并挂载全局样式。
108
+ ## 📖 API Reference for LLMs
54
109
 
55
- ### `vibeAI.bindModelSelect(id)`
56
- - **id**: HTML `<select>` 元素的 ID。
57
- - **功能**: 自动填充已勾选的模型资产池。当用户在设置中更改模型或供应商时,该下拉框会自动同步更新,并记忆用户的最后一次选择。
110
+ ### `vibeAI` Methods
58
111
 
59
- ### `vibeAI.chat(payload)`
60
- - **payload.instanceId**: (必填) 绑定的 Select ID,SDK 据此识别供应商配置。
61
- - **payload.stream**: 布尔值。为 `true` 时返回 `AsyncGenerator`。
62
- - **payload.messages**: 标准 OpenAI 消息数组。
112
+ - `init({ setupBtnId: string })`: Initializes storage and binds the settings button.
113
+ - `bindModelSelect(elementId: string)`: Automatically populates a `<select>` and persists its choice.
114
+ - `getInstance(id: string): VibeInstance`: Returns a controlled instance for a specific selector.
115
+ - `createBatch(instanceId: string, options: {concurrency, retry}): VibeBatch`: Creates a batch processor.
116
+ - `static fileToVisionPayload(file: File): Promise<object>`: Converts File to OpenAI Vision format.
63
117
 
64
- ## 进阶与部署
118
+ ### `VibeInstance` Methods
65
119
 
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`)。
120
+ - `chat(payload: object, options: {stream: boolean})`: Returns `Promise<JSON>` or `AsyncGenerator<string>`.
121
+ - `ask(prompt: string)`: Convenience method for quick text prompts.
122
+ - `abort()`: Aborts the current active request for this instance.
69
123
 
70
- ## 配置规范 (Storage)
124
+ ### `VibeBatch` Methods
125
+
126
+ - `add(promptOrPayload: string|object)`: Enqueues a task.
127
+ - `run(onProgress: function)`: Executes tasks. Callback returns `(done, total, lastResult)`.
128
+
129
+ ---
130
+
131
+ ## 🛡 Security & Privacy
132
+
133
+ - **Zero External Dependencies**: No tracking, no third-party scripts.
134
+ - **Optional Encryption**: Uses **AES-GCM (Web Crypto API)** to encrypt API keys locally. Keys are never sent to any server except the AI provider's endpoint.
135
+ - **Local Persistence**: All configurations are stored in `localStorage` under `vibe_ai_v3_config`.
136
+
137
+ ---
138
+
139
+ ## 🧩 Comprehensive Integration Example
140
+
141
+ ```javascript
142
+ import { vibeAI } from 'https://unpkg.com/vibe-ai-c@4.0.0';
143
+
144
+ async function main() {
145
+ // 1. Setup
146
+ await vibeAI.init({ setupBtnId: 'ai-config-btn' });
147
+ vibeAI.bindModelSelect('primary-selector');
148
+
149
+ const appInstance = vibeAI.getInstance('primary-selector');
150
+
151
+ // 2. High-level usage
152
+ try {
153
+ const result = await appInstance.ask("System check: Online.");
154
+ console.log("AI Status:", result);
155
+ } catch (err) {
156
+ console.error("Initialization failed:", err.message);
157
+ // Tips: Health Check in UI will show specific HTTP error codes
158
+ }
159
+
160
+ // 3. Automated Batching
161
+ document.getElementById('batch-process-btn').onclick = async () => {
162
+ const orchestrator = vibeAI.createBatch('primary-selector', { concurrency: 3 });
163
+
164
+ ['Task 1', 'Task 2', 'Task 3'].forEach(job => orchestrator.add(job));
165
+
166
+ await orchestrator.run((done, total) => {
167
+ document.getElementById('progress-bar').style.width = `${(done/total)*100}%`;
168
+ });
169
+ };
170
+ }
171
+
172
+ main();
173
+ ```
71
174
 
72
- - 存储键名:`vibe_ai_v3_config`
73
- - 加密前缀:`ENC:`(仅在开启加密模式时)
175
+ *Focus on UX. Protect the Input. Master the Stream.*
package/demo.html CHANGED
@@ -3,177 +3,222 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>VibeAI v3.0 体验版</title>
6
+ <title>VibeAI v4.0 旗舰版演示</title>
7
7
  <style>
8
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; }
9
+ body { font-family: system-ui, -apple-system, sans-serif; background: var(--bg); color: #1d1d1f; margin: 0; padding: 20px; line-height: 1.5; }
10
+ .container { max-width: 900px; margin: 0 auto; }
11
+ .card { background: white; border-radius: 16px; padding: 24px; box-shadow: 0 4px 20px rgba(0,0,0,0.05); margin-bottom: 20px; }
12
+ h1 { font-weight: 700; letter-spacing: -0.5px; }
13
+ .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
14
+ @media (max-width: 600px) { .grid { grid-template-columns: 1fr; } }
10
15
 
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); }
16
+ label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 8px; color: #86868b; }
17
+ select, textarea, input[type="text"] { width: 100%; padding: 12px; border: 1px solid #d2d2d7; border-radius: 8px; font-size: 14px; outline: none; transition: 0.2s; box-sizing: border-box; }
18
+ select:focus, textarea:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(0,122,255,0.1); }
14
19
 
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; }
20
+ .btn-group { display: flex; gap: 10px; margin-top: 15px; }
21
+ button { background: var(--primary); color: white; border: none; padding: 12px 20px; border-radius: 8px; font-weight: 600; cursor: pointer; transition: 0.2s; }
22
+ button:hover { opacity: 0.9; transform: translateY(-1px); }
23
+ button:active { transform: translateY(0); }
24
+ button.secondary { background: #e8e8ed; color: #1d1d1f; }
18
25
 
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; }
26
+ #output { background: #1d1d1f; color: #00ff00; padding: 15px; border-radius: 8px; font-family: 'Courier New', monospace; font-size: 13px; min-height: 100px; white-space: pre-wrap; margin-top: 15px; overflow-x: auto; }
27
+ .badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; background: #eee; margin-right: 5px; }
28
+ .progress-bar { height: 6px; background: #eee; border-radius: 3px; margin-top: 10px; overflow: hidden; display: none; }
29
+ .progress-inner { height: 100%; background: var(--primary); width: 0%; transition: 0.3s; }
23
30
 
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; }
31
+ /* 预览图 */
32
+ #img-preview { max-width: 100px; border-radius: 8px; margin-top: 10px; display: none; }
42
33
  </style>
43
34
  </head>
44
35
  <body>
45
36
 
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>
37
+ <div class="container">
38
+ <div style="display: flex; justify-content: space-between; align-items: center;">
39
+ <h1>VibeAI v4.0 <small style="font-size: 12px; color: var(--primary);">Orchestrator</small></h1>
40
+ <button id="setup-btn" class="secondary">⚙️ 配置 AI 供应商</button>
41
+ </div>
42
+
43
+ <div class="grid">
44
+ <!-- 左侧:单次对话与视觉 -->
45
+ <div class="card">
46
+ <h3>单次对话 (支持 Vision)</h3>
47
+ <label>选择已绑定的实例</label>
48
+ <select id="instance-select-1"></select>
49
+
50
+ <div style="margin-top: 15px;">
51
+ <label>输入提示词</label>
52
+ <textarea id="chat-input" rows="3" placeholder="你好,请介绍一下你自己..."></textarea>
58
53
  </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>
54
+
55
+ <div style="margin-top: 15px;">
56
+ <label>多模态图片 (可选)</label>
57
+ <input type="file" id="image-input" accept="image/*">
58
+ <img id="img-preview" src="">
72
59
  </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>
60
+
61
+ <div class="btn-group">
62
+ <button id="send-btn">发送请求</button>
63
+ <button id="stream-btn" class="secondary">流式输出</button>
64
+ <button id="abort-btn" class="secondary" style="color: red;">取消</button>
82
65
  </div>
83
66
  </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>
67
+
68
+ <!-- 右侧:批处理任务 -->
69
+ <div class="card">
70
+ <h3>批处理引擎 (Batch)</h3>
71
+ <label>选择执行实例</label>
72
+ <select id="instance-select-2"></select>
73
+
74
+ <div style="margin-top: 15px;">
75
+ <label>并发数设置</label>
76
+ <select id="concurrency-select">
77
+ <option value="1">1 (串行)</option>
78
+ <option value="3" selected>3 (推荐并发)</option>
79
+ <option value="10">10 (高并发)</option>
80
+ </select>
96
81
  </div>
82
+
83
+ <p style="font-size: 12px; color: #666;">演示:将同时发送 5 个不同的“翻译”任务。</p>
84
+
85
+ <button id="batch-btn" style="width: 100%; background: #34C759;">启动批量翻译任务</button>
86
+
87
+ <div class="progress-bar" id="batch-progress">
88
+ <div class="progress-inner" id="batch-progress-inner"></div>
89
+ </div>
90
+ <div id="batch-status" style="font-size: 12px; margin-top: 5px; color: #666;"></div>
97
91
  </div>
98
- </section>
99
- </main>
92
+ </div>
93
+
94
+ <div class="card">
95
+ <label>控制台输出 (Console)</label>
96
+ <div id="output">等待操作...</div>
97
+ </div>
98
+ </div>
100
99
 
100
+ <!-- 引入 VibeAI 4.0 代码 (此处为 ESM 模块) -->
101
101
  <script type="module">
102
- import { vibeAI } from './vibe-ai.js';
102
+ // 直接导入或定义 VibeAI
103
+ // 为方便 Demo 运行,这里粘贴了你之前确认的 v4.0 完整逻辑
104
+ import { vibeAI } from './vibe-ai.js'; // 假设你已保存为 vibeAI.js
105
+
106
+ // --- Demo 页面逻辑 ---
107
+
108
+ const output = document.getElementById('output');
109
+ const log = (msg, append = false) => {
110
+ if (append) output.innerText += msg;
111
+ else output.innerText = msg;
112
+ output.scrollTop = output.scrollHeight;
113
+ };
103
114
 
104
115
  // 1. 初始化
105
- vibeAI.init({ setupBtnId: 'setup-btn' });
116
+ await vibeAI.init({ setupBtnId: 'setup-btn' });
117
+ vibeAI.bindModelSelect('instance-select-1');
118
+ vibeAI.bindModelSelect('instance-select-2');
119
+
120
+ // 2. 图片预览预览逻辑
121
+ let base64Image = null;
122
+ document.getElementById('image-input').onchange = async (e) => {
123
+ const file = e.target.files[0];
124
+ if (file) {
125
+ const preview = document.getElementById('img-preview');
126
+ // 使用 SDK 内置的辅助方法
127
+ const payload = await vibeAI.constructor.fileToVisionPayload(file);
128
+ base64Image = payload; // 存起来
129
+ preview.src = payload.image_url.url;
130
+ preview.style.display = 'block';
131
+ }
132
+ };
106
133
 
107
- // 2. 绑定多个模型选择器(实现双屏独立选择)
108
- vibeAI.bindModelSelect('select-left');
109
- vibeAI.bindModelSelect('select-right');
134
+ // 3. 单次对话逻辑
135
+ document.getElementById('send-btn').onclick = async () => {
136
+ const insId = 'instance-select-1';
137
+ const content = document.getElementById('chat-input').value;
138
+ if (!content) return alert("请输入内容");
110
139
 
111
- // 存储当前选中的图片
112
- const pendingFiles = { left: null, right: null };
140
+ try {
141
+ log("正在请求...");
142
+ const ins = vibeAI.getInstance(insId);
143
+
144
+ // 构建消息,如果有图片则使用 Vision 格式
145
+ const messages = [{
146
+ role: 'user',
147
+ content: base64Image ? [{ type: 'text', text: content }, base64Image] : content
148
+ }];
149
+
150
+ const result = await ins.chat({ messages });
151
+ log(`[DONE] ${result.choices[0].message.content}`);
152
+ } catch (e) {
153
+ log(`[ERROR] ${e.message}`);
154
+ }
155
+ };
113
156
 
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">`;
157
+ // 4. 流式输出逻辑
158
+ document.getElementById('stream-btn').onclick = async () => {
159
+ const insId = 'instance-select-1';
160
+ const content = document.getElementById('chat-input').value;
161
+ try {
162
+ log("流式输出开始:\n\n");
163
+ const ins = vibeAI.getInstance(insId);
164
+ const stream = await ins.chat({ messages: [{ role: 'user', content }] }, { stream: true });
165
+
166
+ for await (const chunk of stream) {
167
+ log(chunk, true);
168
+ }
169
+ } catch (e) {
170
+ log(`\n[STREAM ERROR] ${e.message}`, true);
171
+ }
172
+ };
173
+
174
+ // 5. 取消请求
175
+ document.getElementById('abort-btn').onclick = () => {
176
+ vibeAI.abort('instance-select-1');
177
+ log("\n[USER ABORT] 已手动中断请求", true);
122
178
  };
123
179
 
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();
180
+ // 6. 批处理引擎演示
181
+ document.getElementById('batch-btn').onclick = async () => {
182
+ const insId = 'instance-select-2';
183
+ const concurrency = parseInt(document.getElementById('concurrency-select').value);
130
184
 
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] } }
185
+ try {
186
+ const batch = vibeAI.createBatch(insId, { concurrency, retry: 1 });
187
+ const tasks = [
188
+ "将 'Hello World' 翻译成中文",
189
+ "将 'Apple' 翻译成中文",
190
+ "将 'Artificial Intelligence' 翻译成中文",
191
+ " 'Open Source' 翻译成中文",
192
+ "将 'JavaScript' 翻译成中文"
139
193
  ];
140
- }
141
194
 
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;
195
+ tasks.forEach(t => batch.add(t));
196
+
197
+ // UI 更新
198
+ log("批量任务启动...");
199
+ const progress = document.getElementById('batch-progress');
200
+ const inner = document.getElementById('batch-progress-inner');
201
+ const status = document.getElementById('batch-status');
202
+ progress.style.display = 'block';
203
+
204
+ const results = await batch.run((done, total, last) => {
205
+ const percent = (done / total) * 100;
206
+ inner.style.width = `${percent}%`;
207
+ status.innerText = `进度: ${done}/${total} - ${last.success ? '✅' : '❌'}`;
208
+ log(`[BATCH] 已完成 ${done}/${total}\n`, true);
209
+ });
154
210
 
155
- try {
156
- const stream = await vibeAI.chat({
157
- instanceId: `select-${side}`, // 动态对应绑定的 select ID
158
- messages: [{ role: 'user', content: content }],
159
- stream: true
211
+ log("\n--- 批量任务全结果 ---\n");
212
+ results.forEach((r, i) => {
213
+ const text = r.success ? r.data.choices[0].message.content : r.error;
214
+ log(`任务 ${i+1}: ${text}\n`, true);
160
215
  });
161
216
 
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;
217
+ } catch (e) {
218
+ log(`[BATCH ERROR] ${e.message}`);
170
219
  }
171
-
172
- // 清理
173
- inputEl.value = '';
174
- pendingFiles[side] = null;
175
- document.getElementById(`preview-${side}`).innerHTML = '';
176
220
  };
221
+
177
222
  </script>
178
223
 
179
224
  </body>
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "vibe-ai-c",
3
- "version": "3.3.2",
3
+ "version": "4.0.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
  }