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/README.md +152 -53
- package/demo.html +183 -138
- package/package.json +7 -4
- package/vibe-ai.js +311 -174
- package/vibe-ai.min.js +0 -1
package/README.md
CHANGED
|
@@ -1,76 +1,175 @@
|
|
|
1
|
-
# VibeAI
|
|
1
|
+
# # VibeAI v4.0 - Orchestrator SDK
|
|
2
2
|
|
|
3
|
-
VibeAI
|
|
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
|
-
|
|
8
|
-
- **增强安全性**:可选的 **AES-GCM (Web Crypto API)** 加密,使用主密码保护本地存储的 API Key。
|
|
9
|
-
- **多实例绑定**:支持将多个 `<select>` 元素绑定到不同的模型实例,并独立持久化其选择状态。
|
|
10
|
-
- **智能模型管理**:
|
|
11
|
-
- **上下文搜索**:针对拥有大量模型(>10个)的供应商提供实时搜索过滤。
|
|
12
|
-
- **批量操作**:支持对搜索结果进行“全选/清空”,不影响隐藏的模型。
|
|
13
|
-
- **自动排序**:模型列表按字母顺序排列。
|
|
14
|
-
- **高稳定性 UI**:采用颗粒度 DOM 更新策略,在刷新列表或搜索时保持输入框焦点与滚动位置。
|
|
15
|
-
- **智能路径逻辑**:自动处理 Base URL,如果请求失败会智能尝试补全 `/v1` 后缀。
|
|
7
|
+
For the most stable and optimized experience, always import from **unpkg**:
|
|
16
8
|
|
|
17
|
-
## 快速开始
|
|
18
|
-
|
|
19
|
-
### 1. 引入与初始化
|
|
20
9
|
```javascript
|
|
21
|
-
import { vibeAI } from '
|
|
10
|
+
import { vibeAI } from 'https://unpkg.com/vibe-ai-c@4.0.0';
|
|
11
|
+
```
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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.
|
|
22
|
+
|
|
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.
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<!-- HTML -->
|
|
33
|
+
<button id="settings-btn">Open AI Settings</button>
|
|
34
|
+
<select id="chat-model-selector"></select>
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
vibeAI.
|
|
36
|
+
<script type="module">
|
|
37
|
+
import { vibeAI } from 'https://unpkg.com/vibe-ai-c@4.0.0';
|
|
38
|
+
|
|
39
|
+
await vibeAI.init({ setupBtnId: 'settings-btn' });
|
|
40
|
+
// Bind a selector to manage model choices
|
|
41
|
+
vibeAI.bindModelSelect('chat-model-selector');
|
|
42
|
+
</script>
|
|
30
43
|
```
|
|
31
44
|
|
|
32
|
-
### 2.
|
|
45
|
+
### 2. Single Chat (Simple vs Stream)
|
|
46
|
+
|
|
47
|
+
Use an **Instance** to perform chat operations.
|
|
48
|
+
|
|
33
49
|
```javascript
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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 });
|
|
40
60
|
|
|
41
61
|
for await (const chunk of stream) {
|
|
42
|
-
|
|
62
|
+
process.stdout.write(chunk); // UI: update your text container here
|
|
43
63
|
}
|
|
44
64
|
```
|
|
45
65
|
|
|
46
|
-
|
|
66
|
+
### 3. Concurrent Batch Processing (Orchestrator)
|
|
47
67
|
|
|
48
|
-
|
|
49
|
-
- 初始化 SDK。自动执行从 `v2` 到 `v3` 的配置迁移。
|
|
50
|
-
- `setupBtnId`: (可选) 绑定一个 HTML 按钮用于打开管理界面。
|
|
68
|
+
Process hundreds of tasks with built-in concurrency limits and retries.
|
|
51
69
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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));
|
|
56
78
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
79
|
+
const results = await batch.run((done, total, last) => {
|
|
80
|
+
console.log(`Progress: ${done}/${total} | Success: ${last.success}`);
|
|
81
|
+
});
|
|
82
|
+
```
|
|
61
83
|
|
|
62
|
-
|
|
63
|
-
- 编程方式打开供应商管理弹窗。
|
|
84
|
+
---
|
|
64
85
|
|
|
65
|
-
##
|
|
86
|
+
## 🖼 Vision Support (Multimodal)
|
|
66
87
|
|
|
67
|
-
|
|
68
|
-
- **加密模式**:开启加密后,API Key 以 `ENC:JSON_STR` 格式存储。主密码仅存在于内存会话中(SessionKey)。
|
|
69
|
-
- **智能补全**:如果填写的 Base URL 请求失败,SDK 会检查是否缺少 `/v1`。例如:输入 `https://api.example.com` 失败后,会自动尝试 `https://api.example.com/v1`。
|
|
88
|
+
VibeAI provides a helper to convert `File` objects into LLM-compatible vision payloads.
|
|
70
89
|
|
|
71
|
-
|
|
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
|
+
---
|
|
107
|
+
|
|
108
|
+
## 📖 API Reference for LLMs
|
|
109
|
+
|
|
110
|
+
### `vibeAI` Methods
|
|
111
|
+
|
|
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.
|
|
117
|
+
|
|
118
|
+
### `VibeInstance` Methods
|
|
119
|
+
|
|
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.
|
|
123
|
+
|
|
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
|
+
```
|
|
72
174
|
|
|
73
|
-
|
|
74
|
-
2. **智能选择**:模型少于 10 个时默认全选;超过 10 个时需用户手动勾选常用模型。
|
|
75
|
-
3. **搜索过滤**:在模型列表搜索框输入时,非匹配项会通过 CSS 隐藏,确保不破坏 DOM 树。
|
|
76
|
-
4. **加密切换**:点击顶部“锁定”状态可切换明文/加密模式。
|
|
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
|
|
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);
|
|
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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
.
|
|
21
|
-
.
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
47
|
-
<div
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<div class="
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
</
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
126
|
-
const
|
|
127
|
-
const
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibe-ai-c",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "一个极简的、面向浏览器的多供应商 AI SDK 与 UI 管理工具。支持 OpenAI 兼容协议。",
|
|
5
5
|
"main": "vibe-ai.js",
|
|
6
6
|
"type": "module",
|
|
@@ -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": "
|
|
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
|
}
|