ibc-ai-web-sdk 2.0.4 → 2.0.5
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/index.cjs.js +299 -52
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +299 -52
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +299 -52
- package/dist/index.umd.js.map +1 -1
- package/dist/index.umd.min.js +1 -1
- package/index.d.ts +35 -1
- package/package.json +2 -2
package/dist/index.cjs.js
CHANGED
|
@@ -384,8 +384,25 @@ class AIChatClient {
|
|
|
384
384
|
if (conversationId) payload.conversationId = conversationId;
|
|
385
385
|
const attachmentIds = options.attachmentIds || this.normalizeAttachmentIds(options.attachments);
|
|
386
386
|
if (attachmentIds.length > 0) payload.attachmentIds = attachmentIds;
|
|
387
|
-
|
|
388
|
-
if (bizParams)
|
|
387
|
+
let bizParams = options.bizParams || options.extendInfo || this.config.extendInfo;
|
|
388
|
+
if (bizParams) {
|
|
389
|
+
if (typeof bizParams === 'string') {
|
|
390
|
+
try {
|
|
391
|
+
bizParams = JSON.parse(bizParams);
|
|
392
|
+
} catch (e) {
|
|
393
|
+
bizParams = {};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
bizParams = {};
|
|
398
|
+
}
|
|
399
|
+
// P3: 将页面上下文合并到 bizParams(contextMode='bizParams' 时由 dialog 传入)
|
|
400
|
+
if (options.context) {
|
|
401
|
+
Object.assign(bizParams, options.context);
|
|
402
|
+
}
|
|
403
|
+
if (Object.keys(bizParams).length > 0) {
|
|
404
|
+
payload.bizParams = JSON.stringify(bizParams);
|
|
405
|
+
}
|
|
389
406
|
return payload;
|
|
390
407
|
}
|
|
391
408
|
normalizeAttachmentIds(attachments) {
|
|
@@ -3568,7 +3585,7 @@ const PRESET_THEMES = {
|
|
|
3568
3585
|
* 样式与功能完全对齐 Vue 版本(1:1 复刻)
|
|
3569
3586
|
*
|
|
3570
3587
|
* @author IBC AI Team
|
|
3571
|
-
* @version 2.
|
|
3588
|
+
* @version 2.0.5
|
|
3572
3589
|
*/
|
|
3573
3590
|
|
|
3574
3591
|
|
|
@@ -3659,6 +3676,8 @@ class AIChatDialog extends HTMLElement {
|
|
|
3659
3676
|
this._runtimeEventsContainerEl = null;
|
|
3660
3677
|
this._runtimeStatusEl = null;
|
|
3661
3678
|
this._runtimeCountEl = null;
|
|
3679
|
+
// P3: 页面上下文数据(宿主页面选中的数据)
|
|
3680
|
+
this._context = {};
|
|
3662
3681
|
|
|
3663
3682
|
// ====== 默认主题配置(与 themes.js DEFAULT_THEME 完全一致) ======
|
|
3664
3683
|
this._themeVars = {
|
|
@@ -3745,6 +3764,8 @@ class AIChatDialog extends HTMLElement {
|
|
|
3745
3764
|
if (this._handleOffline) window.removeEventListener('offline', this._handleOffline);
|
|
3746
3765
|
// P2-17: 清理resize监听
|
|
3747
3766
|
if (this._handleResize) window.removeEventListener('resize', this._handleResize);
|
|
3767
|
+
// P0: 清理拖拽事件监听
|
|
3768
|
+
this._cleanupDragListeners();
|
|
3748
3769
|
// 暂停后台Token刷新定时器(detach时停止,re-attach时由connectedCallback恢复)
|
|
3749
3770
|
if (this._chatClient) {
|
|
3750
3771
|
this._chatClient.stopBackgroundRefresh();
|
|
@@ -3896,6 +3917,14 @@ class AIChatDialog extends HTMLElement {
|
|
|
3896
3917
|
authFn: null,
|
|
3897
3918
|
onLoad: null,
|
|
3898
3919
|
onError: null,
|
|
3920
|
+
// P3: 发送前回调 - 每发消息前调用,可返回最新页面上下文(与 setContext 合并,此回调结果优先)
|
|
3921
|
+
onBeforeSend: null,
|
|
3922
|
+
// P3: 上下文注入方式 — 'prompt'=拼到用户消息前面(默认), 'bizParams'=注入到 bizParams JSON 中
|
|
3923
|
+
contextMode: 'prompt',
|
|
3924
|
+
// P4: 调试模式 - 开启后 console 打印请求/响应/SSE/上下文合并全过程
|
|
3925
|
+
debug: false,
|
|
3926
|
+
// P4: 自定义消息操作按钮 — 返回数组 [{label, className?, onClick}] 在 AI 消息下方渲染
|
|
3927
|
+
onMessageActions: null,
|
|
3899
3928
|
suggestions: [{
|
|
3900
3929
|
label: '帮我购买一台轻量应用服务器用于部署应用',
|
|
3901
3930
|
value: '帮我购买一台轻量应用服务器用于部署应用'
|
|
@@ -3909,6 +3938,11 @@ class AIChatDialog extends HTMLElement {
|
|
|
3909
3938
|
this._chatClient = new AIChatClient(this._config);
|
|
3910
3939
|
if (this._conversationId) this._chatClient.setConversationId(this._conversationId);
|
|
3911
3940
|
this.applyConfig();
|
|
3941
|
+
// P1-2 fix: 在 _config 就绪后初始化拖拽,以正确读取 enableDrag
|
|
3942
|
+
if (this._config?.enableDrag !== false && this.getAttribute('mode') !== 'inline' && !this._dragInitialized) {
|
|
3943
|
+
this.initDraggable();
|
|
3944
|
+
}
|
|
3945
|
+
this._debugLog('初始化完成', 'appId=' + this._config.appId, 'userId=' + this._config.userId, 'baseUrl=' + this._config.apiBaseUrl, 'debug=' + !!this._config.debug, 'contextMode=' + (this._config.contextMode || 'prompt'));
|
|
3912
3946
|
this.loadAppDetail();
|
|
3913
3947
|
// 触发 onLoad 回调(与Vue版一致)
|
|
3914
3948
|
if (this._config?.onLoad) this._config.onLoad(this);
|
|
@@ -3995,6 +4029,44 @@ class AIChatDialog extends HTMLElement {
|
|
|
3995
4029
|
return this;
|
|
3996
4030
|
}
|
|
3997
4031
|
|
|
4032
|
+
/**
|
|
4033
|
+
* 设置页面上下文数据(宿主页面选中的数据,随每次请求发送)
|
|
4034
|
+
* @param {Object} context - 上下文数据对象,传 null 清空
|
|
4035
|
+
* @returns {this}
|
|
4036
|
+
*/
|
|
4037
|
+
setContext(context) {
|
|
4038
|
+
this._context = context && typeof context === 'object' ? {
|
|
4039
|
+
...context
|
|
4040
|
+
} : {};
|
|
4041
|
+
return this;
|
|
4042
|
+
}
|
|
4043
|
+
|
|
4044
|
+
/**
|
|
4045
|
+
* 获取当前上下文数据
|
|
4046
|
+
* @returns {Object}
|
|
4047
|
+
*/
|
|
4048
|
+
getContext() {
|
|
4049
|
+
return {
|
|
4050
|
+
...this._context
|
|
4051
|
+
};
|
|
4052
|
+
}
|
|
4053
|
+
|
|
4054
|
+
/**
|
|
4055
|
+
* 外部触发表态发送(业务层主动调用,如点击外部按钮触发 AI 请求)
|
|
4056
|
+
* @param {string} text - 要发送的消息内容
|
|
4057
|
+
* @returns {this}
|
|
4058
|
+
*/
|
|
4059
|
+
send(text) {
|
|
4060
|
+
const content = String(text || '').trim();
|
|
4061
|
+
if (!content) return this;
|
|
4062
|
+
if (this._input) this._input.value = content;
|
|
4063
|
+
this._autoResizeInput();
|
|
4064
|
+
this._updateSendButtonState();
|
|
4065
|
+
this._lastSendTime = 0; // 跳过防抖,外部触发视为独立操作
|
|
4066
|
+
this.handleSend();
|
|
4067
|
+
return this;
|
|
4068
|
+
}
|
|
4069
|
+
|
|
3998
4070
|
// ==================== 显示/隐藏 ====================
|
|
3999
4071
|
|
|
4000
4072
|
showDialog() {
|
|
@@ -4630,6 +4702,19 @@ class AIChatDialog extends HTMLElement {
|
|
|
4630
4702
|
color: var(--ai-success);
|
|
4631
4703
|
background: rgba(16, 185, 129, 0.08);
|
|
4632
4704
|
}
|
|
4705
|
+
/* P4: 自定义操作按钮(文本标签型,宽度自适应) */
|
|
4706
|
+
.action-icon.custom-action-btn {
|
|
4707
|
+
width: auto;
|
|
4708
|
+
padding: 4px 10px;
|
|
4709
|
+
font-size: 12px;
|
|
4710
|
+
font-weight: 500;
|
|
4711
|
+
white-space: nowrap;
|
|
4712
|
+
}
|
|
4713
|
+
.action-icon.custom-action-btn:hover {
|
|
4714
|
+
color: #fff;
|
|
4715
|
+
background: var(--ai-primary);
|
|
4716
|
+
transform: translateY(-1px);
|
|
4717
|
+
}
|
|
4633
4718
|
|
|
4634
4719
|
/* ========== 底部输入框(与Vue .dialog-footer 一致) ========== */
|
|
4635
4720
|
.dialog-footer {
|
|
@@ -5210,8 +5295,8 @@ class AIChatDialog extends HTMLElement {
|
|
|
5210
5295
|
});
|
|
5211
5296
|
}
|
|
5212
5297
|
|
|
5213
|
-
//
|
|
5214
|
-
|
|
5298
|
+
// 拖拽(内嵌模式禁用)— 由 init() 在 _config 就绪后触发
|
|
5299
|
+
// initDraggable 延迟到 init() 中执行以免 _config 为空
|
|
5215
5300
|
});
|
|
5216
5301
|
}
|
|
5217
5302
|
|
|
@@ -5264,59 +5349,97 @@ class AIChatDialog extends HTMLElement {
|
|
|
5264
5349
|
initDraggable() {
|
|
5265
5350
|
const header = this.shadowRoot?.querySelector('.dialog-header');
|
|
5266
5351
|
if (!header) return;
|
|
5352
|
+
|
|
5353
|
+
// 清理上一次遗留的 document 级监听(SPA 路由切换场景)
|
|
5354
|
+
this._cleanupDragListeners();
|
|
5355
|
+
const self = this;
|
|
5267
5356
|
const startDrag = (clientX, clientY) => {
|
|
5268
5357
|
// P2-17: 移动端禁用拖拽(与Vue版 startTouchDrag: if(isExpanded || isMobile) return 一致)
|
|
5269
|
-
if (
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
const rect =
|
|
5273
|
-
|
|
5274
|
-
|
|
5358
|
+
if (self._isExpanded || self._isMobile) return;
|
|
5359
|
+
self._isDragging = true;
|
|
5360
|
+
self._dialog.classList.add('dragging');
|
|
5361
|
+
const rect = self._dialog.getBoundingClientRect();
|
|
5362
|
+
self._dragStartX = clientX - rect.left;
|
|
5363
|
+
self._dragStartY = clientY - rect.top;
|
|
5275
5364
|
};
|
|
5276
5365
|
const onDrag = (clientX, clientY) => {
|
|
5277
|
-
if (!
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5366
|
+
if (!self._isDragging) return;
|
|
5367
|
+
self._dialogX = clientX - self._dragStartX;
|
|
5368
|
+
self._dialogY = clientY - self._dragStartY;
|
|
5369
|
+
self._dialog.style.left = self._dialogX + 'px';
|
|
5370
|
+
self._dialog.style.right = 'auto';
|
|
5371
|
+
self._dialog.style.top = self._dialogY + 'px';
|
|
5372
|
+
self._dialog.style.bottom = 'auto';
|
|
5284
5373
|
};
|
|
5285
5374
|
const stopDrag = () => {
|
|
5286
|
-
if (
|
|
5287
|
-
|
|
5288
|
-
|
|
5375
|
+
if (self._isDragging) {
|
|
5376
|
+
self._isDragging = false;
|
|
5377
|
+
self._dialog.classList.remove('dragging');
|
|
5289
5378
|
}
|
|
5290
5379
|
};
|
|
5291
5380
|
|
|
5292
|
-
//
|
|
5293
|
-
|
|
5381
|
+
// 鼠标事件(桌面端)— 保存引用以便清理
|
|
5382
|
+
this._dragMouseDownHandler = e => {
|
|
5294
5383
|
if (e.target.closest('button, .header-icon')) return;
|
|
5295
5384
|
startDrag(e.clientX, e.clientY);
|
|
5296
5385
|
e.preventDefault();
|
|
5297
|
-
}
|
|
5298
|
-
|
|
5386
|
+
};
|
|
5387
|
+
this._dragMouseMoveHandler = e => {
|
|
5299
5388
|
onDrag(e.clientX, e.clientY);
|
|
5300
|
-
}
|
|
5301
|
-
|
|
5389
|
+
};
|
|
5390
|
+
this._dragMouseUpHandler = stopDrag;
|
|
5391
|
+
header.addEventListener('mousedown', this._dragMouseDownHandler);
|
|
5392
|
+
document.addEventListener('mousemove', this._dragMouseMoveHandler);
|
|
5393
|
+
document.addEventListener('mouseup', this._dragMouseUpHandler);
|
|
5302
5394
|
|
|
5303
5395
|
// P1-7: 触摸事件(移动端,与Vue版一致)
|
|
5304
|
-
|
|
5305
|
-
if (
|
|
5396
|
+
this._dragTouchStartHandler = e => {
|
|
5397
|
+
if (self._isExpanded) return;
|
|
5306
5398
|
if (e.target.closest('button, .header-icon')) return;
|
|
5307
5399
|
const touch = e.touches[0];
|
|
5308
5400
|
startDrag(touch.clientX, touch.clientY);
|
|
5309
|
-
}
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
document.addEventListener('touchmove', e => {
|
|
5313
|
-
if (!this._isDragging) return;
|
|
5401
|
+
};
|
|
5402
|
+
this._dragTouchMoveHandler = e => {
|
|
5403
|
+
if (!self._isDragging) return;
|
|
5314
5404
|
const touch = e.touches[0];
|
|
5315
5405
|
onDrag(touch.clientX, touch.clientY);
|
|
5316
|
-
}
|
|
5406
|
+
};
|
|
5407
|
+
this._dragTouchEndHandler = stopDrag;
|
|
5408
|
+
header.addEventListener('touchstart', this._dragTouchStartHandler, {
|
|
5317
5409
|
passive: true
|
|
5318
5410
|
});
|
|
5319
|
-
document.addEventListener('
|
|
5411
|
+
document.addEventListener('touchmove', this._dragTouchMoveHandler, {
|
|
5412
|
+
passive: true
|
|
5413
|
+
});
|
|
5414
|
+
document.addEventListener('touchend', this._dragTouchEndHandler);
|
|
5415
|
+
this._dragInitialized = true;
|
|
5416
|
+
}
|
|
5417
|
+
|
|
5418
|
+
// P0: 清理拖拽事件监听(SPA disconnectedCallback / re-init 调用)
|
|
5419
|
+
_cleanupDragListeners() {
|
|
5420
|
+
if (this._dragMouseDownHandler) {
|
|
5421
|
+
const header = this.shadowRoot?.querySelector('.dialog-header');
|
|
5422
|
+
if (header) {
|
|
5423
|
+
header.removeEventListener('mousedown', this._dragMouseDownHandler);
|
|
5424
|
+
header.removeEventListener('touchstart', this._dragTouchStartHandler, {
|
|
5425
|
+
passive: true
|
|
5426
|
+
});
|
|
5427
|
+
}
|
|
5428
|
+
this._dragMouseDownHandler = null;
|
|
5429
|
+
this._dragTouchStartHandler = null;
|
|
5430
|
+
}
|
|
5431
|
+
if (this._dragMouseMoveHandler) {
|
|
5432
|
+
document.removeEventListener('mousemove', this._dragMouseMoveHandler);
|
|
5433
|
+
document.removeEventListener('touchmove', this._dragTouchMoveHandler, {
|
|
5434
|
+
passive: true
|
|
5435
|
+
});
|
|
5436
|
+
document.removeEventListener('mouseup', this._dragMouseUpHandler);
|
|
5437
|
+
document.removeEventListener('touchend', this._dragTouchEndHandler);
|
|
5438
|
+
this._dragMouseMoveHandler = null;
|
|
5439
|
+
this._dragTouchMoveHandler = null;
|
|
5440
|
+
this._dragMouseUpHandler = null;
|
|
5441
|
+
this._dragTouchEndHandler = null;
|
|
5442
|
+
}
|
|
5320
5443
|
}
|
|
5321
5444
|
applyConfig() {
|
|
5322
5445
|
if (!this._config) return;
|
|
@@ -5481,11 +5604,12 @@ class AIChatDialog extends HTMLElement {
|
|
|
5481
5604
|
<div class="message-content">
|
|
5482
5605
|
${hasRuntimeEvents ? this._buildRuntimePanel(msg, idx) : ''}
|
|
5483
5606
|
${bubbleContent ? `<div class="${bubbleClass}">${bubbleContent}</div>` : ''}
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5607
|
+
${!msg._isStreaming ? `<div class="message-actions-bar">
|
|
5608
|
+
${msg.type === 'ai' ? `<div class="action-icons">
|
|
5609
|
+
<span class="action-icon copy-btn${msg.copied ? ' copied' : ''}" data-idx="${idx}" title="${msg.copied ? '已复制' : '复制'}">${msg.copied ? '<svg viewBox="0 0 24 24" fill="none" stroke-width="2"><path d="m20 6-11 11-5-5"/></svg>' : '<svg viewBox="0 0 24 24" fill="none" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>'}</span>
|
|
5610
|
+
<span class="action-icon regenerate-btn" data-idx="${idx}" title="重新回答"><svg viewBox="0 0 24 24" fill="none" stroke-width="2"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/></svg></span>
|
|
5611
|
+
${this._buildCustomActions(msg, idx)}
|
|
5612
|
+
</div>` : ''}
|
|
5489
5613
|
</div>` : ''}
|
|
5490
5614
|
</div>
|
|
5491
5615
|
</div>`;
|
|
@@ -5515,6 +5639,24 @@ class AIChatDialog extends HTMLElement {
|
|
|
5515
5639
|
});
|
|
5516
5640
|
});
|
|
5517
5641
|
|
|
5642
|
+
// P4: 绑定自定义操作按钮
|
|
5643
|
+
this._body.querySelectorAll('.custom-action-btn').forEach(btn => {
|
|
5644
|
+
btn.addEventListener('click', () => {
|
|
5645
|
+
const idx = parseInt(btn.dataset.idx);
|
|
5646
|
+
const actionIdx = parseInt(btn.dataset.action);
|
|
5647
|
+
const msg = this._messages[idx];
|
|
5648
|
+
if (!msg) return;
|
|
5649
|
+
try {
|
|
5650
|
+
const actions = this._getCustomActions(msg, idx);
|
|
5651
|
+
if (actions && actions[actionIdx]) {
|
|
5652
|
+
actions[actionIdx].onClick(msg.content);
|
|
5653
|
+
}
|
|
5654
|
+
} catch (e) {
|
|
5655
|
+
console.warn('[AIChatDialog] 自定义操作按钮执行失败:', e);
|
|
5656
|
+
}
|
|
5657
|
+
});
|
|
5658
|
+
});
|
|
5659
|
+
|
|
5518
5660
|
// 绑定执行过程面板折叠/展开(Shadow DOM 下 inline onclick 不可靠)
|
|
5519
5661
|
this._body.querySelectorAll('.runtime-header').forEach(header => {
|
|
5520
5662
|
header.addEventListener('click', () => {
|
|
@@ -5767,7 +5909,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
5767
5909
|
this.showToast(this._config?.emptyMessageError || '请输入消息内容');
|
|
5768
5910
|
return;
|
|
5769
5911
|
}
|
|
5770
|
-
if (this._isLoading) return;
|
|
5912
|
+
if (this._isLoading || this._streaming) return;
|
|
5771
5913
|
|
|
5772
5914
|
// P1-11: 字数限制检查
|
|
5773
5915
|
const maxLen = this._config?.maxLength ?? 500;
|
|
@@ -5823,8 +5965,47 @@ class AIChatDialog extends HTMLElement {
|
|
|
5823
5965
|
attachmentIds.push(res.data.attachmentId);
|
|
5824
5966
|
}
|
|
5825
5967
|
}
|
|
5826
|
-
|
|
5827
|
-
|
|
5968
|
+
|
|
5969
|
+
// P3: 收集页面上下文(setContext + onBeforeSend)
|
|
5970
|
+
let context = {
|
|
5971
|
+
...(this._context || {})
|
|
5972
|
+
};
|
|
5973
|
+
if (typeof this._config?.onBeforeSend === 'function') {
|
|
5974
|
+
try {
|
|
5975
|
+
const dynamicCtx = await this._config.onBeforeSend();
|
|
5976
|
+
if (dynamicCtx && typeof dynamicCtx === 'object') {
|
|
5977
|
+
context = {
|
|
5978
|
+
...context,
|
|
5979
|
+
...dynamicCtx
|
|
5980
|
+
};
|
|
5981
|
+
}
|
|
5982
|
+
} catch (e) {
|
|
5983
|
+
console.warn('[AIChatDialog] onBeforeSend 执行失败:', e);
|
|
5984
|
+
}
|
|
5985
|
+
}
|
|
5986
|
+
const hasContext = context && Object.keys(context).length > 0;
|
|
5987
|
+
const rawMode = this._config?.contextMode;
|
|
5988
|
+
const VALID_CONTEXT_MODES = ['prompt', 'bizParams'];
|
|
5989
|
+
const contextMode = VALID_CONTEXT_MODES.includes(rawMode) ? rawMode : rawMode ? (console.warn('[AIChatDialog] 无效的 contextMode: "' + rawMode + '",已回退为 "prompt"(有效值: ' + VALID_CONTEXT_MODES.join(', ') + ')'), 'prompt') : 'prompt';
|
|
5990
|
+
let finalPrompt = content || attachmentText;
|
|
5991
|
+
let contextForBizParams = null;
|
|
5992
|
+
if (hasContext) {
|
|
5993
|
+
this._debugLog('上下文合并', 'keys=', Object.keys(context), 'mode=', contextMode);
|
|
5994
|
+
if (contextMode === 'bizParams') {
|
|
5995
|
+
// 注入到 bizParams
|
|
5996
|
+
contextForBizParams = context;
|
|
5997
|
+
} else {
|
|
5998
|
+
// 默认:拼到 prompt 前面
|
|
5999
|
+
const ctxText = this._formatContextText(context);
|
|
6000
|
+
if (ctxText) {
|
|
6001
|
+
finalPrompt = ctxText + '\n---\n' + finalPrompt;
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
6004
|
+
}
|
|
6005
|
+
this._debugLog('发送', 'prompt=' + (finalPrompt || '').substring(0, 150) + (finalPrompt && finalPrompt.length > 150 ? '...' : ''), 'ctxKeys=' + Object.keys(contextForBizParams || context || {}).join(','), 'mode=' + contextMode);
|
|
6006
|
+
await this.sendMessageToAI(finalPrompt, {
|
|
6007
|
+
attachmentIds,
|
|
6008
|
+
context: contextForBizParams
|
|
5828
6009
|
});
|
|
5829
6010
|
} catch (err) {
|
|
5830
6011
|
console.error('[AIChatDialog] Error:', err);
|
|
@@ -5858,6 +6039,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
5858
6039
|
...(options.streamOptions || {})
|
|
5859
6040
|
};
|
|
5860
6041
|
if (options.attachmentIds?.length) streamOpts.attachmentIds = options.attachmentIds;
|
|
6042
|
+
if (options.context) streamOpts.context = options.context;
|
|
5861
6043
|
await this._directApiCallStream(msgId, content, streamOpts);
|
|
5862
6044
|
}
|
|
5863
6045
|
}
|
|
@@ -5915,6 +6097,10 @@ class AIChatDialog extends HTMLElement {
|
|
|
5915
6097
|
if (!this._chatClient) {
|
|
5916
6098
|
this._chatClient = new AIChatClient(this._config || {});
|
|
5917
6099
|
}
|
|
6100
|
+
if (this._config?.debug) {
|
|
6101
|
+
this._chatClient.config.debug = true;
|
|
6102
|
+
}
|
|
6103
|
+
this._debugLog('Stream开始', 'msgId=' + msgId, 'content=' + (content || '').substring(0, 80), 'opts=' + JSON.stringify(requestOptions));
|
|
5918
6104
|
if (Object.prototype.hasOwnProperty.call(requestOptions, 'conversationId')) {
|
|
5919
6105
|
this._chatClient.setConversationId(requestOptions.conversationId || null);
|
|
5920
6106
|
} else if (this._conversationId) {
|
|
@@ -6033,6 +6219,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
6033
6219
|
}
|
|
6034
6220
|
}, requestOptions);
|
|
6035
6221
|
if (response?.success) {
|
|
6222
|
+
this._debugLog('Stream完成', 'ok, conversationId=' + (response.data?.conversationId || this._conversationId), 'textLen=' + streamText.length);
|
|
6036
6223
|
if (response.data?.conversationId) {
|
|
6037
6224
|
this._conversationId = response.data.conversationId;
|
|
6038
6225
|
} else if (this._chatClient.conversationId) {
|
|
@@ -6045,6 +6232,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
6045
6232
|
streamText = result;
|
|
6046
6233
|
}
|
|
6047
6234
|
} else {
|
|
6235
|
+
this._finalizeMsg(msgId, streamText || '(无回复)');
|
|
6048
6236
|
return;
|
|
6049
6237
|
}
|
|
6050
6238
|
this._finalizeMsg(msgId, streamText || '(无回复)');
|
|
@@ -6055,12 +6243,14 @@ class AIChatDialog extends HTMLElement {
|
|
|
6055
6243
|
}));
|
|
6056
6244
|
if (this._config?.onMessageReceived) this._config.onMessageReceived(streamText);
|
|
6057
6245
|
} catch (e) {
|
|
6246
|
+
this._debugWarn('Stream异常', e.name + ': ' + (e.message || ''));
|
|
6058
6247
|
if (e.name === 'AbortError') {
|
|
6059
6248
|
this._updateMsg(msgId, '(请求超时或已取消)');
|
|
6060
6249
|
} else {
|
|
6061
6250
|
console.error('[AIChatDialog] 流式失败:', e);
|
|
6062
6251
|
this._updateMsg(msgId, '网络错误: ' + (e.message || '请检查连接'));
|
|
6063
6252
|
}
|
|
6253
|
+
this._finalizeMsg(msgId, this._messages.find(m => m.id === msgId)?.content || '(无回复)');
|
|
6064
6254
|
}
|
|
6065
6255
|
}
|
|
6066
6256
|
_getStreamTextEl() {
|
|
@@ -6184,6 +6374,27 @@ class AIChatDialog extends HTMLElement {
|
|
|
6184
6374
|
return this._ERROR_CONTENT_PATTERNS.some(p => content.startsWith(p));
|
|
6185
6375
|
}
|
|
6186
6376
|
|
|
6377
|
+
// P3: 将上下文对象格式化为可读文本(拼入 prompt 前缀)
|
|
6378
|
+
_formatContextText(context) {
|
|
6379
|
+
if (!context || typeof context !== 'object') return '';
|
|
6380
|
+
const lines = ['[页面上下文]'];
|
|
6381
|
+
for (const key in context) {
|
|
6382
|
+
if (!Object.prototype.hasOwnProperty.call(context, key)) continue;
|
|
6383
|
+
const val = context[key];
|
|
6384
|
+
if (Array.isArray(val) && val.length > 0 && typeof val[0] === 'object') {
|
|
6385
|
+
lines.push(key + ':');
|
|
6386
|
+
for (const item of val) {
|
|
6387
|
+
lines.push(' - ' + Object.values(item).join(' | '));
|
|
6388
|
+
}
|
|
6389
|
+
} else if (typeof val === 'object' && val !== null) {
|
|
6390
|
+
lines.push(key + ': ' + JSON.stringify(val));
|
|
6391
|
+
} else {
|
|
6392
|
+
lines.push(key + ': ' + val);
|
|
6393
|
+
}
|
|
6394
|
+
}
|
|
6395
|
+
return lines.join('\n');
|
|
6396
|
+
}
|
|
6397
|
+
|
|
6187
6398
|
// 运行时事件管理 – incremental DOM, no renderMessages
|
|
6188
6399
|
_addRuntimeEvent(msgId, event) {
|
|
6189
6400
|
const idx = this._messages.findIndex(m => m.id === msgId);
|
|
@@ -6278,6 +6489,30 @@ class AIChatDialog extends HTMLElement {
|
|
|
6278
6489
|
panel.appendChild(eventsContainer);
|
|
6279
6490
|
return panel;
|
|
6280
6491
|
}
|
|
6492
|
+
|
|
6493
|
+
// P4: 获取自定义操作按钮列表(缓存结果避免重复调用 onMessageActions)
|
|
6494
|
+
_getCustomActions(msg, idx) {
|
|
6495
|
+
const key = '_customActions_' + idx;
|
|
6496
|
+
if (!msg[key] && typeof this._config?.onMessageActions === 'function') {
|
|
6497
|
+
try {
|
|
6498
|
+
msg[key] = this._config.onMessageActions(msg, idx) || [];
|
|
6499
|
+
} catch (e) {
|
|
6500
|
+
console.warn('[AIChatDialog] onMessageActions 执行失败:', e);
|
|
6501
|
+
msg[key] = [];
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
return msg[key] || [];
|
|
6505
|
+
}
|
|
6506
|
+
|
|
6507
|
+
// P4: 生成自定义操作按钮 HTML
|
|
6508
|
+
_buildCustomActions(msg, idx) {
|
|
6509
|
+
const actions = this._getCustomActions(msg, idx);
|
|
6510
|
+
if (!actions.length) return '';
|
|
6511
|
+
return actions.map((a, i) => {
|
|
6512
|
+
const cls = a.className || '';
|
|
6513
|
+
return '<span class="action-icon custom-action-btn' + (cls ? ' ' + this.escapeAttr(cls) : '') + '" data-idx="' + idx + '" data-action="' + i + '" title="' + this.escapeAttr(a.label) + '">' + this.escapeHtml(a.label) + '</span>';
|
|
6514
|
+
}).join('');
|
|
6515
|
+
}
|
|
6281
6516
|
_buildRuntimePanel(msg, msgIdx) {
|
|
6282
6517
|
const events = msg._runtimeEvents || [];
|
|
6283
6518
|
if (events.length === 0) return '';
|
|
@@ -6314,7 +6549,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
6314
6549
|
// ==================== 重新生成(与Vue regenerate 一致) ====================
|
|
6315
6550
|
|
|
6316
6551
|
async regenerate(index) {
|
|
6317
|
-
if (this._isLoading) return;
|
|
6552
|
+
if (this._isLoading || this._streaming) return;
|
|
6318
6553
|
const aiMessage = this._messages[index];
|
|
6319
6554
|
if (!aiMessage || aiMessage.type !== 'ai') return;
|
|
6320
6555
|
let userQuestion = '';
|
|
@@ -6502,6 +6737,20 @@ class AIChatDialog extends HTMLElement {
|
|
|
6502
6737
|
const n = new Date();
|
|
6503
6738
|
return `${String(n.getHours()).padStart(2, '0')}:${String(n.getMinutes()).padStart(2, '0')}`;
|
|
6504
6739
|
}
|
|
6740
|
+
|
|
6741
|
+
/**
|
|
6742
|
+
* 调试日志(仅在 config.debug === true 时输出)
|
|
6743
|
+
*/
|
|
6744
|
+
_debugLog(tag, ...args) {
|
|
6745
|
+
if (this._config?.debug) {
|
|
6746
|
+
console.log('%c[AI-SDK]%c ' + tag, 'color:#2563eb;font-weight:600', 'color:inherit', ...args);
|
|
6747
|
+
}
|
|
6748
|
+
}
|
|
6749
|
+
_debugWarn(tag, ...args) {
|
|
6750
|
+
if (this._config?.debug) {
|
|
6751
|
+
console.warn('%c[AI-SDK]%c ' + tag, 'color:#f59e0b;font-weight:600', 'color:inherit', ...args);
|
|
6752
|
+
}
|
|
6753
|
+
}
|
|
6505
6754
|
escapeHtml(s) {
|
|
6506
6755
|
if (!s) return '';
|
|
6507
6756
|
const d = document.createElement('div');
|
|
@@ -6592,7 +6841,7 @@ if (typeof window !== 'undefined') {
|
|
|
6592
6841
|
* 支持任意前端框架:Vue, React, Angular, 原生HTML等
|
|
6593
6842
|
*
|
|
6594
6843
|
* @author IBC AI Team
|
|
6595
|
-
* @version 2.0.
|
|
6844
|
+
* @version 2.0.5
|
|
6596
6845
|
*/
|
|
6597
6846
|
|
|
6598
6847
|
|
|
@@ -6809,12 +7058,10 @@ function useAIChat(options = {}) {
|
|
|
6809
7058
|
},
|
|
6810
7059
|
sendMessage: content => {
|
|
6811
7060
|
if (dialogRef.current) {
|
|
6812
|
-
const input = dialogRef.current.shadowRoot?.querySelector('.
|
|
7061
|
+
const input = dialogRef.current.shadowRoot?.querySelector('.message-input');
|
|
6813
7062
|
if (input) {
|
|
6814
|
-
input.value = content;
|
|
6815
|
-
input.dispatchEvent(new Event('input'));
|
|
7063
|
+
dialogRef.current.send?.(content) || (input.value = content, input.dispatchEvent(new Event('input')), dialogRef.current.shadowRoot?.querySelector('.send-btn')?.click());
|
|
6816
7064
|
}
|
|
6817
|
-
dialogRef.current.handleSend?.() || dialogRef.current.shadowRoot?.querySelector('.ai-send-btn')?.click();
|
|
6818
7065
|
}
|
|
6819
7066
|
},
|
|
6820
7067
|
clearMessages: () => {
|
|
@@ -6835,14 +7082,14 @@ var index = {
|
|
|
6835
7082
|
createAIChatDialog,
|
|
6836
7083
|
installVuePlugin,
|
|
6837
7084
|
useAIChat,
|
|
6838
|
-
version: '2.0.
|
|
7085
|
+
version: '2.0.5'
|
|
6839
7086
|
};
|
|
6840
7087
|
|
|
6841
7088
|
// 全局暴露(UMD/浏览器环境)
|
|
6842
7089
|
if (typeof window !== 'undefined') {
|
|
6843
7090
|
window.AICreateChatDialog = createAIChatDialog;
|
|
6844
7091
|
window.AIWebSDK = {
|
|
6845
|
-
version: '2.0.
|
|
7092
|
+
version: '2.0.5',
|
|
6846
7093
|
create: createAIChatDialog,
|
|
6847
7094
|
AIChatDialog,
|
|
6848
7095
|
AIChatClient,
|