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.esm.js
CHANGED
|
@@ -380,8 +380,25 @@ class AIChatClient {
|
|
|
380
380
|
if (conversationId) payload.conversationId = conversationId;
|
|
381
381
|
const attachmentIds = options.attachmentIds || this.normalizeAttachmentIds(options.attachments);
|
|
382
382
|
if (attachmentIds.length > 0) payload.attachmentIds = attachmentIds;
|
|
383
|
-
|
|
384
|
-
if (bizParams)
|
|
383
|
+
let bizParams = options.bizParams || options.extendInfo || this.config.extendInfo;
|
|
384
|
+
if (bizParams) {
|
|
385
|
+
if (typeof bizParams === 'string') {
|
|
386
|
+
try {
|
|
387
|
+
bizParams = JSON.parse(bizParams);
|
|
388
|
+
} catch (e) {
|
|
389
|
+
bizParams = {};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
bizParams = {};
|
|
394
|
+
}
|
|
395
|
+
// P3: 将页面上下文合并到 bizParams(contextMode='bizParams' 时由 dialog 传入)
|
|
396
|
+
if (options.context) {
|
|
397
|
+
Object.assign(bizParams, options.context);
|
|
398
|
+
}
|
|
399
|
+
if (Object.keys(bizParams).length > 0) {
|
|
400
|
+
payload.bizParams = JSON.stringify(bizParams);
|
|
401
|
+
}
|
|
385
402
|
return payload;
|
|
386
403
|
}
|
|
387
404
|
normalizeAttachmentIds(attachments) {
|
|
@@ -3564,7 +3581,7 @@ const PRESET_THEMES = {
|
|
|
3564
3581
|
* 样式与功能完全对齐 Vue 版本(1:1 复刻)
|
|
3565
3582
|
*
|
|
3566
3583
|
* @author IBC AI Team
|
|
3567
|
-
* @version 2.
|
|
3584
|
+
* @version 2.0.5
|
|
3568
3585
|
*/
|
|
3569
3586
|
|
|
3570
3587
|
|
|
@@ -3655,6 +3672,8 @@ class AIChatDialog extends HTMLElement {
|
|
|
3655
3672
|
this._runtimeEventsContainerEl = null;
|
|
3656
3673
|
this._runtimeStatusEl = null;
|
|
3657
3674
|
this._runtimeCountEl = null;
|
|
3675
|
+
// P3: 页面上下文数据(宿主页面选中的数据)
|
|
3676
|
+
this._context = {};
|
|
3658
3677
|
|
|
3659
3678
|
// ====== 默认主题配置(与 themes.js DEFAULT_THEME 完全一致) ======
|
|
3660
3679
|
this._themeVars = {
|
|
@@ -3741,6 +3760,8 @@ class AIChatDialog extends HTMLElement {
|
|
|
3741
3760
|
if (this._handleOffline) window.removeEventListener('offline', this._handleOffline);
|
|
3742
3761
|
// P2-17: 清理resize监听
|
|
3743
3762
|
if (this._handleResize) window.removeEventListener('resize', this._handleResize);
|
|
3763
|
+
// P0: 清理拖拽事件监听
|
|
3764
|
+
this._cleanupDragListeners();
|
|
3744
3765
|
// 暂停后台Token刷新定时器(detach时停止,re-attach时由connectedCallback恢复)
|
|
3745
3766
|
if (this._chatClient) {
|
|
3746
3767
|
this._chatClient.stopBackgroundRefresh();
|
|
@@ -3892,6 +3913,14 @@ class AIChatDialog extends HTMLElement {
|
|
|
3892
3913
|
authFn: null,
|
|
3893
3914
|
onLoad: null,
|
|
3894
3915
|
onError: null,
|
|
3916
|
+
// P3: 发送前回调 - 每发消息前调用,可返回最新页面上下文(与 setContext 合并,此回调结果优先)
|
|
3917
|
+
onBeforeSend: null,
|
|
3918
|
+
// P3: 上下文注入方式 — 'prompt'=拼到用户消息前面(默认), 'bizParams'=注入到 bizParams JSON 中
|
|
3919
|
+
contextMode: 'prompt',
|
|
3920
|
+
// P4: 调试模式 - 开启后 console 打印请求/响应/SSE/上下文合并全过程
|
|
3921
|
+
debug: false,
|
|
3922
|
+
// P4: 自定义消息操作按钮 — 返回数组 [{label, className?, onClick}] 在 AI 消息下方渲染
|
|
3923
|
+
onMessageActions: null,
|
|
3895
3924
|
suggestions: [{
|
|
3896
3925
|
label: '帮我购买一台轻量应用服务器用于部署应用',
|
|
3897
3926
|
value: '帮我购买一台轻量应用服务器用于部署应用'
|
|
@@ -3905,6 +3934,11 @@ class AIChatDialog extends HTMLElement {
|
|
|
3905
3934
|
this._chatClient = new AIChatClient(this._config);
|
|
3906
3935
|
if (this._conversationId) this._chatClient.setConversationId(this._conversationId);
|
|
3907
3936
|
this.applyConfig();
|
|
3937
|
+
// P1-2 fix: 在 _config 就绪后初始化拖拽,以正确读取 enableDrag
|
|
3938
|
+
if (this._config?.enableDrag !== false && this.getAttribute('mode') !== 'inline' && !this._dragInitialized) {
|
|
3939
|
+
this.initDraggable();
|
|
3940
|
+
}
|
|
3941
|
+
this._debugLog('初始化完成', 'appId=' + this._config.appId, 'userId=' + this._config.userId, 'baseUrl=' + this._config.apiBaseUrl, 'debug=' + !!this._config.debug, 'contextMode=' + (this._config.contextMode || 'prompt'));
|
|
3908
3942
|
this.loadAppDetail();
|
|
3909
3943
|
// 触发 onLoad 回调(与Vue版一致)
|
|
3910
3944
|
if (this._config?.onLoad) this._config.onLoad(this);
|
|
@@ -3991,6 +4025,44 @@ class AIChatDialog extends HTMLElement {
|
|
|
3991
4025
|
return this;
|
|
3992
4026
|
}
|
|
3993
4027
|
|
|
4028
|
+
/**
|
|
4029
|
+
* 设置页面上下文数据(宿主页面选中的数据,随每次请求发送)
|
|
4030
|
+
* @param {Object} context - 上下文数据对象,传 null 清空
|
|
4031
|
+
* @returns {this}
|
|
4032
|
+
*/
|
|
4033
|
+
setContext(context) {
|
|
4034
|
+
this._context = context && typeof context === 'object' ? {
|
|
4035
|
+
...context
|
|
4036
|
+
} : {};
|
|
4037
|
+
return this;
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
/**
|
|
4041
|
+
* 获取当前上下文数据
|
|
4042
|
+
* @returns {Object}
|
|
4043
|
+
*/
|
|
4044
|
+
getContext() {
|
|
4045
|
+
return {
|
|
4046
|
+
...this._context
|
|
4047
|
+
};
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
/**
|
|
4051
|
+
* 外部触发表态发送(业务层主动调用,如点击外部按钮触发 AI 请求)
|
|
4052
|
+
* @param {string} text - 要发送的消息内容
|
|
4053
|
+
* @returns {this}
|
|
4054
|
+
*/
|
|
4055
|
+
send(text) {
|
|
4056
|
+
const content = String(text || '').trim();
|
|
4057
|
+
if (!content) return this;
|
|
4058
|
+
if (this._input) this._input.value = content;
|
|
4059
|
+
this._autoResizeInput();
|
|
4060
|
+
this._updateSendButtonState();
|
|
4061
|
+
this._lastSendTime = 0; // 跳过防抖,外部触发视为独立操作
|
|
4062
|
+
this.handleSend();
|
|
4063
|
+
return this;
|
|
4064
|
+
}
|
|
4065
|
+
|
|
3994
4066
|
// ==================== 显示/隐藏 ====================
|
|
3995
4067
|
|
|
3996
4068
|
showDialog() {
|
|
@@ -4626,6 +4698,19 @@ class AIChatDialog extends HTMLElement {
|
|
|
4626
4698
|
color: var(--ai-success);
|
|
4627
4699
|
background: rgba(16, 185, 129, 0.08);
|
|
4628
4700
|
}
|
|
4701
|
+
/* P4: 自定义操作按钮(文本标签型,宽度自适应) */
|
|
4702
|
+
.action-icon.custom-action-btn {
|
|
4703
|
+
width: auto;
|
|
4704
|
+
padding: 4px 10px;
|
|
4705
|
+
font-size: 12px;
|
|
4706
|
+
font-weight: 500;
|
|
4707
|
+
white-space: nowrap;
|
|
4708
|
+
}
|
|
4709
|
+
.action-icon.custom-action-btn:hover {
|
|
4710
|
+
color: #fff;
|
|
4711
|
+
background: var(--ai-primary);
|
|
4712
|
+
transform: translateY(-1px);
|
|
4713
|
+
}
|
|
4629
4714
|
|
|
4630
4715
|
/* ========== 底部输入框(与Vue .dialog-footer 一致) ========== */
|
|
4631
4716
|
.dialog-footer {
|
|
@@ -5206,8 +5291,8 @@ class AIChatDialog extends HTMLElement {
|
|
|
5206
5291
|
});
|
|
5207
5292
|
}
|
|
5208
5293
|
|
|
5209
|
-
//
|
|
5210
|
-
|
|
5294
|
+
// 拖拽(内嵌模式禁用)— 由 init() 在 _config 就绪后触发
|
|
5295
|
+
// initDraggable 延迟到 init() 中执行以免 _config 为空
|
|
5211
5296
|
});
|
|
5212
5297
|
}
|
|
5213
5298
|
|
|
@@ -5260,59 +5345,97 @@ class AIChatDialog extends HTMLElement {
|
|
|
5260
5345
|
initDraggable() {
|
|
5261
5346
|
const header = this.shadowRoot?.querySelector('.dialog-header');
|
|
5262
5347
|
if (!header) return;
|
|
5348
|
+
|
|
5349
|
+
// 清理上一次遗留的 document 级监听(SPA 路由切换场景)
|
|
5350
|
+
this._cleanupDragListeners();
|
|
5351
|
+
const self = this;
|
|
5263
5352
|
const startDrag = (clientX, clientY) => {
|
|
5264
5353
|
// P2-17: 移动端禁用拖拽(与Vue版 startTouchDrag: if(isExpanded || isMobile) return 一致)
|
|
5265
|
-
if (
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
const rect =
|
|
5269
|
-
|
|
5270
|
-
|
|
5354
|
+
if (self._isExpanded || self._isMobile) return;
|
|
5355
|
+
self._isDragging = true;
|
|
5356
|
+
self._dialog.classList.add('dragging');
|
|
5357
|
+
const rect = self._dialog.getBoundingClientRect();
|
|
5358
|
+
self._dragStartX = clientX - rect.left;
|
|
5359
|
+
self._dragStartY = clientY - rect.top;
|
|
5271
5360
|
};
|
|
5272
5361
|
const onDrag = (clientX, clientY) => {
|
|
5273
|
-
if (!
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5362
|
+
if (!self._isDragging) return;
|
|
5363
|
+
self._dialogX = clientX - self._dragStartX;
|
|
5364
|
+
self._dialogY = clientY - self._dragStartY;
|
|
5365
|
+
self._dialog.style.left = self._dialogX + 'px';
|
|
5366
|
+
self._dialog.style.right = 'auto';
|
|
5367
|
+
self._dialog.style.top = self._dialogY + 'px';
|
|
5368
|
+
self._dialog.style.bottom = 'auto';
|
|
5280
5369
|
};
|
|
5281
5370
|
const stopDrag = () => {
|
|
5282
|
-
if (
|
|
5283
|
-
|
|
5284
|
-
|
|
5371
|
+
if (self._isDragging) {
|
|
5372
|
+
self._isDragging = false;
|
|
5373
|
+
self._dialog.classList.remove('dragging');
|
|
5285
5374
|
}
|
|
5286
5375
|
};
|
|
5287
5376
|
|
|
5288
|
-
//
|
|
5289
|
-
|
|
5377
|
+
// 鼠标事件(桌面端)— 保存引用以便清理
|
|
5378
|
+
this._dragMouseDownHandler = e => {
|
|
5290
5379
|
if (e.target.closest('button, .header-icon')) return;
|
|
5291
5380
|
startDrag(e.clientX, e.clientY);
|
|
5292
5381
|
e.preventDefault();
|
|
5293
|
-
}
|
|
5294
|
-
|
|
5382
|
+
};
|
|
5383
|
+
this._dragMouseMoveHandler = e => {
|
|
5295
5384
|
onDrag(e.clientX, e.clientY);
|
|
5296
|
-
}
|
|
5297
|
-
|
|
5385
|
+
};
|
|
5386
|
+
this._dragMouseUpHandler = stopDrag;
|
|
5387
|
+
header.addEventListener('mousedown', this._dragMouseDownHandler);
|
|
5388
|
+
document.addEventListener('mousemove', this._dragMouseMoveHandler);
|
|
5389
|
+
document.addEventListener('mouseup', this._dragMouseUpHandler);
|
|
5298
5390
|
|
|
5299
5391
|
// P1-7: 触摸事件(移动端,与Vue版一致)
|
|
5300
|
-
|
|
5301
|
-
if (
|
|
5392
|
+
this._dragTouchStartHandler = e => {
|
|
5393
|
+
if (self._isExpanded) return;
|
|
5302
5394
|
if (e.target.closest('button, .header-icon')) return;
|
|
5303
5395
|
const touch = e.touches[0];
|
|
5304
5396
|
startDrag(touch.clientX, touch.clientY);
|
|
5305
|
-
}
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
document.addEventListener('touchmove', e => {
|
|
5309
|
-
if (!this._isDragging) return;
|
|
5397
|
+
};
|
|
5398
|
+
this._dragTouchMoveHandler = e => {
|
|
5399
|
+
if (!self._isDragging) return;
|
|
5310
5400
|
const touch = e.touches[0];
|
|
5311
5401
|
onDrag(touch.clientX, touch.clientY);
|
|
5312
|
-
}
|
|
5402
|
+
};
|
|
5403
|
+
this._dragTouchEndHandler = stopDrag;
|
|
5404
|
+
header.addEventListener('touchstart', this._dragTouchStartHandler, {
|
|
5313
5405
|
passive: true
|
|
5314
5406
|
});
|
|
5315
|
-
document.addEventListener('
|
|
5407
|
+
document.addEventListener('touchmove', this._dragTouchMoveHandler, {
|
|
5408
|
+
passive: true
|
|
5409
|
+
});
|
|
5410
|
+
document.addEventListener('touchend', this._dragTouchEndHandler);
|
|
5411
|
+
this._dragInitialized = true;
|
|
5412
|
+
}
|
|
5413
|
+
|
|
5414
|
+
// P0: 清理拖拽事件监听(SPA disconnectedCallback / re-init 调用)
|
|
5415
|
+
_cleanupDragListeners() {
|
|
5416
|
+
if (this._dragMouseDownHandler) {
|
|
5417
|
+
const header = this.shadowRoot?.querySelector('.dialog-header');
|
|
5418
|
+
if (header) {
|
|
5419
|
+
header.removeEventListener('mousedown', this._dragMouseDownHandler);
|
|
5420
|
+
header.removeEventListener('touchstart', this._dragTouchStartHandler, {
|
|
5421
|
+
passive: true
|
|
5422
|
+
});
|
|
5423
|
+
}
|
|
5424
|
+
this._dragMouseDownHandler = null;
|
|
5425
|
+
this._dragTouchStartHandler = null;
|
|
5426
|
+
}
|
|
5427
|
+
if (this._dragMouseMoveHandler) {
|
|
5428
|
+
document.removeEventListener('mousemove', this._dragMouseMoveHandler);
|
|
5429
|
+
document.removeEventListener('touchmove', this._dragTouchMoveHandler, {
|
|
5430
|
+
passive: true
|
|
5431
|
+
});
|
|
5432
|
+
document.removeEventListener('mouseup', this._dragMouseUpHandler);
|
|
5433
|
+
document.removeEventListener('touchend', this._dragTouchEndHandler);
|
|
5434
|
+
this._dragMouseMoveHandler = null;
|
|
5435
|
+
this._dragTouchMoveHandler = null;
|
|
5436
|
+
this._dragMouseUpHandler = null;
|
|
5437
|
+
this._dragTouchEndHandler = null;
|
|
5438
|
+
}
|
|
5316
5439
|
}
|
|
5317
5440
|
applyConfig() {
|
|
5318
5441
|
if (!this._config) return;
|
|
@@ -5477,11 +5600,12 @@ class AIChatDialog extends HTMLElement {
|
|
|
5477
5600
|
<div class="message-content">
|
|
5478
5601
|
${hasRuntimeEvents ? this._buildRuntimePanel(msg, idx) : ''}
|
|
5479
5602
|
${bubbleContent ? `<div class="${bubbleClass}">${bubbleContent}</div>` : ''}
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5603
|
+
${!msg._isStreaming ? `<div class="message-actions-bar">
|
|
5604
|
+
${msg.type === 'ai' ? `<div class="action-icons">
|
|
5605
|
+
<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>
|
|
5606
|
+
<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>
|
|
5607
|
+
${this._buildCustomActions(msg, idx)}
|
|
5608
|
+
</div>` : ''}
|
|
5485
5609
|
</div>` : ''}
|
|
5486
5610
|
</div>
|
|
5487
5611
|
</div>`;
|
|
@@ -5511,6 +5635,24 @@ class AIChatDialog extends HTMLElement {
|
|
|
5511
5635
|
});
|
|
5512
5636
|
});
|
|
5513
5637
|
|
|
5638
|
+
// P4: 绑定自定义操作按钮
|
|
5639
|
+
this._body.querySelectorAll('.custom-action-btn').forEach(btn => {
|
|
5640
|
+
btn.addEventListener('click', () => {
|
|
5641
|
+
const idx = parseInt(btn.dataset.idx);
|
|
5642
|
+
const actionIdx = parseInt(btn.dataset.action);
|
|
5643
|
+
const msg = this._messages[idx];
|
|
5644
|
+
if (!msg) return;
|
|
5645
|
+
try {
|
|
5646
|
+
const actions = this._getCustomActions(msg, idx);
|
|
5647
|
+
if (actions && actions[actionIdx]) {
|
|
5648
|
+
actions[actionIdx].onClick(msg.content);
|
|
5649
|
+
}
|
|
5650
|
+
} catch (e) {
|
|
5651
|
+
console.warn('[AIChatDialog] 自定义操作按钮执行失败:', e);
|
|
5652
|
+
}
|
|
5653
|
+
});
|
|
5654
|
+
});
|
|
5655
|
+
|
|
5514
5656
|
// 绑定执行过程面板折叠/展开(Shadow DOM 下 inline onclick 不可靠)
|
|
5515
5657
|
this._body.querySelectorAll('.runtime-header').forEach(header => {
|
|
5516
5658
|
header.addEventListener('click', () => {
|
|
@@ -5763,7 +5905,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
5763
5905
|
this.showToast(this._config?.emptyMessageError || '请输入消息内容');
|
|
5764
5906
|
return;
|
|
5765
5907
|
}
|
|
5766
|
-
if (this._isLoading) return;
|
|
5908
|
+
if (this._isLoading || this._streaming) return;
|
|
5767
5909
|
|
|
5768
5910
|
// P1-11: 字数限制检查
|
|
5769
5911
|
const maxLen = this._config?.maxLength ?? 500;
|
|
@@ -5819,8 +5961,47 @@ class AIChatDialog extends HTMLElement {
|
|
|
5819
5961
|
attachmentIds.push(res.data.attachmentId);
|
|
5820
5962
|
}
|
|
5821
5963
|
}
|
|
5822
|
-
|
|
5823
|
-
|
|
5964
|
+
|
|
5965
|
+
// P3: 收集页面上下文(setContext + onBeforeSend)
|
|
5966
|
+
let context = {
|
|
5967
|
+
...(this._context || {})
|
|
5968
|
+
};
|
|
5969
|
+
if (typeof this._config?.onBeforeSend === 'function') {
|
|
5970
|
+
try {
|
|
5971
|
+
const dynamicCtx = await this._config.onBeforeSend();
|
|
5972
|
+
if (dynamicCtx && typeof dynamicCtx === 'object') {
|
|
5973
|
+
context = {
|
|
5974
|
+
...context,
|
|
5975
|
+
...dynamicCtx
|
|
5976
|
+
};
|
|
5977
|
+
}
|
|
5978
|
+
} catch (e) {
|
|
5979
|
+
console.warn('[AIChatDialog] onBeforeSend 执行失败:', e);
|
|
5980
|
+
}
|
|
5981
|
+
}
|
|
5982
|
+
const hasContext = context && Object.keys(context).length > 0;
|
|
5983
|
+
const rawMode = this._config?.contextMode;
|
|
5984
|
+
const VALID_CONTEXT_MODES = ['prompt', 'bizParams'];
|
|
5985
|
+
const contextMode = VALID_CONTEXT_MODES.includes(rawMode) ? rawMode : rawMode ? (console.warn('[AIChatDialog] 无效的 contextMode: "' + rawMode + '",已回退为 "prompt"(有效值: ' + VALID_CONTEXT_MODES.join(', ') + ')'), 'prompt') : 'prompt';
|
|
5986
|
+
let finalPrompt = content || attachmentText;
|
|
5987
|
+
let contextForBizParams = null;
|
|
5988
|
+
if (hasContext) {
|
|
5989
|
+
this._debugLog('上下文合并', 'keys=', Object.keys(context), 'mode=', contextMode);
|
|
5990
|
+
if (contextMode === 'bizParams') {
|
|
5991
|
+
// 注入到 bizParams
|
|
5992
|
+
contextForBizParams = context;
|
|
5993
|
+
} else {
|
|
5994
|
+
// 默认:拼到 prompt 前面
|
|
5995
|
+
const ctxText = this._formatContextText(context);
|
|
5996
|
+
if (ctxText) {
|
|
5997
|
+
finalPrompt = ctxText + '\n---\n' + finalPrompt;
|
|
5998
|
+
}
|
|
5999
|
+
}
|
|
6000
|
+
}
|
|
6001
|
+
this._debugLog('发送', 'prompt=' + (finalPrompt || '').substring(0, 150) + (finalPrompt && finalPrompt.length > 150 ? '...' : ''), 'ctxKeys=' + Object.keys(contextForBizParams || context || {}).join(','), 'mode=' + contextMode);
|
|
6002
|
+
await this.sendMessageToAI(finalPrompt, {
|
|
6003
|
+
attachmentIds,
|
|
6004
|
+
context: contextForBizParams
|
|
5824
6005
|
});
|
|
5825
6006
|
} catch (err) {
|
|
5826
6007
|
console.error('[AIChatDialog] Error:', err);
|
|
@@ -5854,6 +6035,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
5854
6035
|
...(options.streamOptions || {})
|
|
5855
6036
|
};
|
|
5856
6037
|
if (options.attachmentIds?.length) streamOpts.attachmentIds = options.attachmentIds;
|
|
6038
|
+
if (options.context) streamOpts.context = options.context;
|
|
5857
6039
|
await this._directApiCallStream(msgId, content, streamOpts);
|
|
5858
6040
|
}
|
|
5859
6041
|
}
|
|
@@ -5911,6 +6093,10 @@ class AIChatDialog extends HTMLElement {
|
|
|
5911
6093
|
if (!this._chatClient) {
|
|
5912
6094
|
this._chatClient = new AIChatClient(this._config || {});
|
|
5913
6095
|
}
|
|
6096
|
+
if (this._config?.debug) {
|
|
6097
|
+
this._chatClient.config.debug = true;
|
|
6098
|
+
}
|
|
6099
|
+
this._debugLog('Stream开始', 'msgId=' + msgId, 'content=' + (content || '').substring(0, 80), 'opts=' + JSON.stringify(requestOptions));
|
|
5914
6100
|
if (Object.prototype.hasOwnProperty.call(requestOptions, 'conversationId')) {
|
|
5915
6101
|
this._chatClient.setConversationId(requestOptions.conversationId || null);
|
|
5916
6102
|
} else if (this._conversationId) {
|
|
@@ -6029,6 +6215,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
6029
6215
|
}
|
|
6030
6216
|
}, requestOptions);
|
|
6031
6217
|
if (response?.success) {
|
|
6218
|
+
this._debugLog('Stream完成', 'ok, conversationId=' + (response.data?.conversationId || this._conversationId), 'textLen=' + streamText.length);
|
|
6032
6219
|
if (response.data?.conversationId) {
|
|
6033
6220
|
this._conversationId = response.data.conversationId;
|
|
6034
6221
|
} else if (this._chatClient.conversationId) {
|
|
@@ -6041,6 +6228,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
6041
6228
|
streamText = result;
|
|
6042
6229
|
}
|
|
6043
6230
|
} else {
|
|
6231
|
+
this._finalizeMsg(msgId, streamText || '(无回复)');
|
|
6044
6232
|
return;
|
|
6045
6233
|
}
|
|
6046
6234
|
this._finalizeMsg(msgId, streamText || '(无回复)');
|
|
@@ -6051,12 +6239,14 @@ class AIChatDialog extends HTMLElement {
|
|
|
6051
6239
|
}));
|
|
6052
6240
|
if (this._config?.onMessageReceived) this._config.onMessageReceived(streamText);
|
|
6053
6241
|
} catch (e) {
|
|
6242
|
+
this._debugWarn('Stream异常', e.name + ': ' + (e.message || ''));
|
|
6054
6243
|
if (e.name === 'AbortError') {
|
|
6055
6244
|
this._updateMsg(msgId, '(请求超时或已取消)');
|
|
6056
6245
|
} else {
|
|
6057
6246
|
console.error('[AIChatDialog] 流式失败:', e);
|
|
6058
6247
|
this._updateMsg(msgId, '网络错误: ' + (e.message || '请检查连接'));
|
|
6059
6248
|
}
|
|
6249
|
+
this._finalizeMsg(msgId, this._messages.find(m => m.id === msgId)?.content || '(无回复)');
|
|
6060
6250
|
}
|
|
6061
6251
|
}
|
|
6062
6252
|
_getStreamTextEl() {
|
|
@@ -6180,6 +6370,27 @@ class AIChatDialog extends HTMLElement {
|
|
|
6180
6370
|
return this._ERROR_CONTENT_PATTERNS.some(p => content.startsWith(p));
|
|
6181
6371
|
}
|
|
6182
6372
|
|
|
6373
|
+
// P3: 将上下文对象格式化为可读文本(拼入 prompt 前缀)
|
|
6374
|
+
_formatContextText(context) {
|
|
6375
|
+
if (!context || typeof context !== 'object') return '';
|
|
6376
|
+
const lines = ['[页面上下文]'];
|
|
6377
|
+
for (const key in context) {
|
|
6378
|
+
if (!Object.prototype.hasOwnProperty.call(context, key)) continue;
|
|
6379
|
+
const val = context[key];
|
|
6380
|
+
if (Array.isArray(val) && val.length > 0 && typeof val[0] === 'object') {
|
|
6381
|
+
lines.push(key + ':');
|
|
6382
|
+
for (const item of val) {
|
|
6383
|
+
lines.push(' - ' + Object.values(item).join(' | '));
|
|
6384
|
+
}
|
|
6385
|
+
} else if (typeof val === 'object' && val !== null) {
|
|
6386
|
+
lines.push(key + ': ' + JSON.stringify(val));
|
|
6387
|
+
} else {
|
|
6388
|
+
lines.push(key + ': ' + val);
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
return lines.join('\n');
|
|
6392
|
+
}
|
|
6393
|
+
|
|
6183
6394
|
// 运行时事件管理 – incremental DOM, no renderMessages
|
|
6184
6395
|
_addRuntimeEvent(msgId, event) {
|
|
6185
6396
|
const idx = this._messages.findIndex(m => m.id === msgId);
|
|
@@ -6274,6 +6485,30 @@ class AIChatDialog extends HTMLElement {
|
|
|
6274
6485
|
panel.appendChild(eventsContainer);
|
|
6275
6486
|
return panel;
|
|
6276
6487
|
}
|
|
6488
|
+
|
|
6489
|
+
// P4: 获取自定义操作按钮列表(缓存结果避免重复调用 onMessageActions)
|
|
6490
|
+
_getCustomActions(msg, idx) {
|
|
6491
|
+
const key = '_customActions_' + idx;
|
|
6492
|
+
if (!msg[key] && typeof this._config?.onMessageActions === 'function') {
|
|
6493
|
+
try {
|
|
6494
|
+
msg[key] = this._config.onMessageActions(msg, idx) || [];
|
|
6495
|
+
} catch (e) {
|
|
6496
|
+
console.warn('[AIChatDialog] onMessageActions 执行失败:', e);
|
|
6497
|
+
msg[key] = [];
|
|
6498
|
+
}
|
|
6499
|
+
}
|
|
6500
|
+
return msg[key] || [];
|
|
6501
|
+
}
|
|
6502
|
+
|
|
6503
|
+
// P4: 生成自定义操作按钮 HTML
|
|
6504
|
+
_buildCustomActions(msg, idx) {
|
|
6505
|
+
const actions = this._getCustomActions(msg, idx);
|
|
6506
|
+
if (!actions.length) return '';
|
|
6507
|
+
return actions.map((a, i) => {
|
|
6508
|
+
const cls = a.className || '';
|
|
6509
|
+
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>';
|
|
6510
|
+
}).join('');
|
|
6511
|
+
}
|
|
6277
6512
|
_buildRuntimePanel(msg, msgIdx) {
|
|
6278
6513
|
const events = msg._runtimeEvents || [];
|
|
6279
6514
|
if (events.length === 0) return '';
|
|
@@ -6310,7 +6545,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
6310
6545
|
// ==================== 重新生成(与Vue regenerate 一致) ====================
|
|
6311
6546
|
|
|
6312
6547
|
async regenerate(index) {
|
|
6313
|
-
if (this._isLoading) return;
|
|
6548
|
+
if (this._isLoading || this._streaming) return;
|
|
6314
6549
|
const aiMessage = this._messages[index];
|
|
6315
6550
|
if (!aiMessage || aiMessage.type !== 'ai') return;
|
|
6316
6551
|
let userQuestion = '';
|
|
@@ -6498,6 +6733,20 @@ class AIChatDialog extends HTMLElement {
|
|
|
6498
6733
|
const n = new Date();
|
|
6499
6734
|
return `${String(n.getHours()).padStart(2, '0')}:${String(n.getMinutes()).padStart(2, '0')}`;
|
|
6500
6735
|
}
|
|
6736
|
+
|
|
6737
|
+
/**
|
|
6738
|
+
* 调试日志(仅在 config.debug === true 时输出)
|
|
6739
|
+
*/
|
|
6740
|
+
_debugLog(tag, ...args) {
|
|
6741
|
+
if (this._config?.debug) {
|
|
6742
|
+
console.log('%c[AI-SDK]%c ' + tag, 'color:#2563eb;font-weight:600', 'color:inherit', ...args);
|
|
6743
|
+
}
|
|
6744
|
+
}
|
|
6745
|
+
_debugWarn(tag, ...args) {
|
|
6746
|
+
if (this._config?.debug) {
|
|
6747
|
+
console.warn('%c[AI-SDK]%c ' + tag, 'color:#f59e0b;font-weight:600', 'color:inherit', ...args);
|
|
6748
|
+
}
|
|
6749
|
+
}
|
|
6501
6750
|
escapeHtml(s) {
|
|
6502
6751
|
if (!s) return '';
|
|
6503
6752
|
const d = document.createElement('div');
|
|
@@ -6588,7 +6837,7 @@ if (typeof window !== 'undefined') {
|
|
|
6588
6837
|
* 支持任意前端框架:Vue, React, Angular, 原生HTML等
|
|
6589
6838
|
*
|
|
6590
6839
|
* @author IBC AI Team
|
|
6591
|
-
* @version 2.0.
|
|
6840
|
+
* @version 2.0.5
|
|
6592
6841
|
*/
|
|
6593
6842
|
|
|
6594
6843
|
|
|
@@ -6805,12 +7054,10 @@ function useAIChat(options = {}) {
|
|
|
6805
7054
|
},
|
|
6806
7055
|
sendMessage: content => {
|
|
6807
7056
|
if (dialogRef.current) {
|
|
6808
|
-
const input = dialogRef.current.shadowRoot?.querySelector('.
|
|
7057
|
+
const input = dialogRef.current.shadowRoot?.querySelector('.message-input');
|
|
6809
7058
|
if (input) {
|
|
6810
|
-
input.value = content;
|
|
6811
|
-
input.dispatchEvent(new Event('input'));
|
|
7059
|
+
dialogRef.current.send?.(content) || (input.value = content, input.dispatchEvent(new Event('input')), dialogRef.current.shadowRoot?.querySelector('.send-btn')?.click());
|
|
6812
7060
|
}
|
|
6813
|
-
dialogRef.current.handleSend?.() || dialogRef.current.shadowRoot?.querySelector('.ai-send-btn')?.click();
|
|
6814
7061
|
}
|
|
6815
7062
|
},
|
|
6816
7063
|
clearMessages: () => {
|
|
@@ -6831,14 +7078,14 @@ var index = {
|
|
|
6831
7078
|
createAIChatDialog,
|
|
6832
7079
|
installVuePlugin,
|
|
6833
7080
|
useAIChat,
|
|
6834
|
-
version: '2.0.
|
|
7081
|
+
version: '2.0.5'
|
|
6835
7082
|
};
|
|
6836
7083
|
|
|
6837
7084
|
// 全局暴露(UMD/浏览器环境)
|
|
6838
7085
|
if (typeof window !== 'undefined') {
|
|
6839
7086
|
window.AICreateChatDialog = createAIChatDialog;
|
|
6840
7087
|
window.AIWebSDK = {
|
|
6841
|
-
version: '2.0.
|
|
7088
|
+
version: '2.0.5',
|
|
6842
7089
|
create: createAIChatDialog,
|
|
6843
7090
|
AIChatDialog,
|
|
6844
7091
|
AIChatClient,
|