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