@opentiny/next-sdk 0.1.3 → 0.1.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.
Files changed (58) hide show
  1. package/McpSdk.ts +14 -0
  2. package/WebAgent.ts +5 -0
  3. package/WebMcp.ts +18 -0
  4. package/Zod.ts +1 -0
  5. package/agent/AgentModelProvider.ts +150 -36
  6. package/agent/type.ts +1 -1
  7. package/agent/utils/getAISDKTools.ts +35 -0
  8. package/dist/agent/AgentModelProvider.d.ts +43 -4
  9. package/dist/agent/AgentModelProvider.js +145 -24
  10. package/dist/agent/type.d.ts +1 -1
  11. package/dist/agent/utils/getAISDKTools.d.ts +8 -0
  12. package/dist/agent/utils/getAISDKTools.js +36 -0
  13. package/dist/index.d.ts +5 -4
  14. package/dist/index.es.dev.js +42947 -0
  15. package/dist/index.es.js +18006 -17750
  16. package/dist/index.js +10 -4
  17. package/dist/index.umd.dev.js +43341 -0
  18. package/dist/index.umd.js +285 -114
  19. package/dist/mcpsdk@1.17.0.dev.js +21391 -0
  20. package/dist/mcpsdk@1.17.0.es.dev.js +21389 -0
  21. package/dist/mcpsdk@1.17.0.es.js +14525 -0
  22. package/dist/mcpsdk@1.17.0.js +16 -0
  23. package/dist/remoter/createRemoter.d.ts +10 -2
  24. package/dist/remoter/createRemoter.js +312 -55
  25. package/dist/webagent.dev.js +27509 -0
  26. package/dist/webagent.es.dev.js +27115 -0
  27. package/dist/webagent.es.js +21448 -0
  28. package/dist/webagent.js +529 -0
  29. package/dist/webmcp-full.dev.js +22879 -0
  30. package/dist/webmcp-full.es.dev.js +22875 -0
  31. package/dist/webmcp-full.es.js +15821 -0
  32. package/dist/webmcp-full.js +16 -0
  33. package/dist/webmcp.dev.js +1373 -0
  34. package/dist/webmcp.es.dev.js +1366 -0
  35. package/dist/webmcp.es.js +1232 -0
  36. package/dist/webmcp.js +1 -0
  37. package/dist/zod@3.25.76.dev.js +4039 -0
  38. package/dist/zod@3.25.76.es.dev.js +4035 -0
  39. package/dist/zod@3.25.76.es.js +2947 -0
  40. package/dist/zod@3.25.76.js +1 -0
  41. package/index.ts +16 -4
  42. package/package.json +16 -4
  43. package/remoter/createRemoter.ts +327 -62
  44. package/runtime.html +98 -0
  45. package/script/utils.ts +26 -0
  46. package/vite.config.mcpSdk.ts +28 -0
  47. package/vite.config.ts +13 -12
  48. package/vite.config.webAgent.ts +19 -0
  49. package/vite.config.webMcp.ts +40 -0
  50. package/vite.config.webMcpFull.ts +19 -0
  51. package/vite.config.zod.ts +23 -0
  52. package/agent/utils/aiProviderFactories.ts +0 -7
  53. package/agent/utils/index.ts +0 -79
  54. package/dist/agent/utils/aiProviderFactories.d.ts +0 -6
  55. package/dist/agent/utils/aiProviderFactories.js +0 -6
  56. package/dist/agent/utils/index.d.ts +0 -14
  57. package/dist/agent/utils/index.js +0 -72
  58. package/dist/index.cjs.js +0 -365
@@ -3,13 +3,17 @@ import { QrCode } from './QrCode'
3
3
  /** 菜单项配置接口 */
4
4
  interface MenuItemConfig {
5
5
  /** 菜单项标识 */
6
- action: 'qr-code' | 'ai-chat' | 'remote-control'
6
+ action: 'qr-code' | 'ai-chat' | 'remote-control' | 'remote-url'
7
7
  /** 是否显示该菜单项 */
8
8
  show?: boolean
9
9
  /** 菜单项文本 */
10
10
  text?: string
11
+ /** 菜单项提示 */
12
+ tip?: string
11
13
  /** 菜单项图标SVG */
12
14
  icon?: string
15
+ /** 是否显示复制图标 */
16
+ showCopyIcon?: boolean
13
17
  }
14
18
 
15
19
  /** 配置选项接口 */
@@ -26,48 +30,66 @@ interface FloatingBlockOptions {
26
30
  }
27
31
 
28
32
  // 动作类型
29
- type ActionType = 'qr-code' | 'ai-chat' | 'remote-control'
30
-
31
- // 默认菜单项配置
32
- const DEFAULT_MENU_ITEMS: MenuItemConfig[] = [
33
- {
34
- action: 'qr-code',
35
- show: true,
36
- text: '弹出二维码',
37
- icon: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
38
- <path d="M3 9H6V21H3C2.45 21 2 20.55 2 20V10C2 9.45 2.45 9 3 9Z" fill="currentColor"/>
39
- <path d="M12 2H20C21.1 2 22 2.9 22 4V20C22 21.1 21.1 22 20 22H12C10.9 22 10 21.1 10 20V4C10 2.9 10.9 2 12 2ZM12 20H20V4H12V20Z" fill="currentColor"/>
40
- <path d="M15 7H17V9H15V7Z" fill="currentColor"/>
41
- <path d="M15 11H17V13H15V11Z" fill="currentColor"/>
42
- <path d="M15 15H17V17H15V15Z" fill="currentColor"/>
43
- <path d="M19 7H21V9H19V7Z" fill="currentColor"/>
44
- <path d="M19 11H21V13H19V11Z" fill="currentColor"/>
45
- <path d="M19 15H21V17H19V15Z" fill="currentColor"/>
33
+ type ActionType = 'qr-code' | 'ai-chat' | 'remote-control' | 'remote-url'
34
+
35
+ const getDefaultMenuItems = (options: FloatingBlockOptions): MenuItemConfig[] => {
36
+ return [
37
+ {
38
+ action: 'qr-code',
39
+ show: true,
40
+ text: '弹出二维码',
41
+ icon: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
42
+ <rect x="3" y="3" width="6" height="6" rx="1" stroke="currentColor" stroke-width="2" fill="none"/>
43
+ <rect x="15" y="3" width="6" height="6" rx="1" stroke="currentColor" stroke-width="2" fill="none"/>
44
+ <rect x="3" y="15" width="6" height="6" rx="1" stroke="currentColor" stroke-width="2" fill="none"/>
45
+ <line x1="9" y1="6" x2="9" y2="18" stroke="currentColor" stroke-width="1.5"/>
46
+ <line x1="15" y1="6" x2="15" y2="18" stroke="currentColor" stroke-width="1.5"/>
47
+ <line x1="6" y1="9" x2="18" y2="9" stroke="currentColor" stroke-width="1.5"/>
48
+ <line x1="6" y1="15" x2="18" y2="15" stroke="currentColor" stroke-width="1.5"/>
49
+ <circle cx="12" cy="12" r="1" fill="currentColor"/>
46
50
  </svg>`
47
- },
48
- {
49
- action: 'ai-chat',
50
- show: true,
51
- text: '弹出AI对话框',
52
- icon: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
51
+ },
52
+ {
53
+ action: 'ai-chat',
54
+ show: true,
55
+ text: '弹出 AI 对话框',
56
+ icon: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
53
57
  <path d="M20 2H4C2.9 2 2 2.9 2 4V22L6 18H20C21.1 18 22 17.1 22 16V4C22 2.9 21.1 2 20 2ZM20 16H6L4 18V4H20V16Z" fill="currentColor"/>
54
58
  <path d="M7 9H17V11H7V9Z" fill="currentColor"/>
55
59
  <path d="M7 12H14V14H7V12Z" fill="currentColor"/>
56
60
  </svg>`
57
- },
58
- {
59
- action: 'remote-control',
60
- show: true,
61
- text: '发送遥控指令',
62
- icon: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
63
- <path d="M7 2H17C18.1 2 19 2.9 19 4V20C19 21.1 18.1 22 17 22H7C5.9 22 5 21.1 5 20V4C5 2.9 5.9 2 7 2ZM7 4V20H17V4H7Z" fill="currentColor"/>
64
- <path d="M9 6H15V8H9V6Z" fill="currentColor"/>
65
- <path d="M9 10H15V12H9V10Z" fill="currentColor"/>
66
- <path d="M9 14H15V16H9V14Z" fill="currentColor"/>
67
- <path d="M9 18H15V20H9V18Z" fill="currentColor"/>
61
+ },
62
+ {
63
+ action: 'remote-control',
64
+ show: true,
65
+ text: `识别码:${options.sessionId.slice(-6)}`,
66
+ showCopyIcon: true,
67
+ icon: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
68
+ <rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/>
69
+ <rect x="6" y="6" width="3" height="2" rx="0.5" fill="currentColor"/>
70
+ <rect x="10" y="6" width="3" height="2" rx="0.5" fill="currentColor"/>
71
+ <rect x="14" y="6" width="3" height="2" rx="0.5" fill="currentColor"/>
72
+ <rect x="6" y="9" width="3" height="2" rx="0.5" fill="currentColor"/>
73
+ <rect x="10" y="9" width="3" height="2" rx="0.5" fill="currentColor"/>
74
+ <rect x="14" y="9" width="3" height="2" rx="0.5" fill="currentColor"/>
75
+ <rect x="6" y="12" width="3" height="2" rx="0.5" fill="currentColor"/>
76
+ <rect x="10" y="12" width="3" height="2" rx="0.5" fill="currentColor"/>
77
+ <rect x="14" y="12" width="3" height="2" rx="0.5" fill="currentColor"/>
68
78
  </svg>`
69
- }
70
- ]
79
+ },
80
+ {
81
+ action: 'remote-url',
82
+ show: true,
83
+ text: `${options.qrCodeUrl}`,
84
+ tip: options.qrCodeUrl,
85
+ showCopyIcon: true,
86
+ icon: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
87
+ <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
88
+ <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
89
+ </svg>`
90
+ }
91
+ ]
92
+ }
71
93
 
72
94
  class FloatingBlock {
73
95
  private options: FloatingBlockOptions
@@ -76,6 +98,11 @@ class FloatingBlock {
76
98
  private dropdownMenu!: HTMLDivElement
77
99
  private menuItems: MenuItemConfig[]
78
100
 
101
+ // 计算 sessionPrefix 属性
102
+ private get sessionPrefix(): string {
103
+ return this.options.qrCodeUrl?.includes('?') ? '&sessionId=' : '?sessionId='
104
+ }
105
+
79
106
  constructor(options: FloatingBlockOptions) {
80
107
  if (!options.sessionId) {
81
108
  throw new Error('sessionId is required')
@@ -99,10 +126,10 @@ class FloatingBlock {
99
126
  */
100
127
  private mergeMenuItems(userMenuItems?: MenuItemConfig[]): MenuItemConfig[] {
101
128
  if (!userMenuItems) {
102
- return DEFAULT_MENU_ITEMS
129
+ return getDefaultMenuItems(this.options)
103
130
  }
104
131
 
105
- return DEFAULT_MENU_ITEMS.map((defaultItem) => {
132
+ return getDefaultMenuItems(this.options).map((defaultItem) => {
106
133
  const userItem = userMenuItems.find((item) => item.action === defaultItem.action)
107
134
  if (userItem) {
108
135
  return {
@@ -150,7 +177,18 @@ class FloatingBlock {
150
177
  <div class="tiny-remoter-dropdown-item__icon">
151
178
  ${item.icon}
152
179
  </div>
153
- <span>${item.text}</span>
180
+ <span title="${item.tip}">${item.text}</span>
181
+ ${
182
+ item.showCopyIcon
183
+ ? `
184
+ <div class="tiny-remoter-copy-icon" data-action="${item.action}">
185
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
186
+ <path d="M16 1H4C2.9 1 2 1.9 2 3V17H4V3H16V1ZM19 5H8C6.9 5 6 5.9 6 7V21C6 22.1 6.9 23 8 23H19C20.1 23 21 22.1 21 21V7C21 5.9 20.1 5 19 5ZM19 21H8V7H19V21Z" fill="currentColor"/>
187
+ </svg>
188
+ </div>
189
+ `
190
+ : ''
191
+ }
154
192
  </div>
155
193
  `
156
194
  )
@@ -170,6 +208,19 @@ class FloatingBlock {
170
208
  // 绑定菜单项点击事件
171
209
  this.dropdownMenu.addEventListener('click', (e: Event) => {
172
210
  const target = e.target as HTMLElement
211
+
212
+ // 检查是否点击了复制图标
213
+ const copyIcon = target.closest('.tiny-remoter-copy-icon') as HTMLElement
214
+ if (copyIcon) {
215
+ e.stopPropagation() // 阻止事件冒泡,避免触发菜单项点击
216
+ const action = copyIcon.dataset.action as ActionType
217
+ if (action) {
218
+ this.handleAction(action)
219
+ }
220
+ return
221
+ }
222
+
223
+ // 处理普通菜单项点击
173
224
  const actionItem = target.closest('.tiny-remoter-dropdown-item') as HTMLElement
174
225
  const action = actionItem?.dataset.action as ActionType
175
226
  if (action) {
@@ -222,24 +273,165 @@ class FloatingBlock {
222
273
  this.showAIChat()
223
274
  break
224
275
  case 'remote-control':
225
- this.showRemoteControl()
276
+ this.copyRemoteControl()
277
+ break
278
+ case 'remote-url':
279
+ this.copyRemoteURL()
226
280
  break
227
281
  }
228
282
  this.closeDropdown()
229
283
  }
230
284
 
285
+ private copyRemoteControl(): void {
286
+ this.copyToClipboard(this.options.sessionId.slice(-6))
287
+ }
288
+
289
+ private copyRemoteURL(): void {
290
+ this.copyToClipboard((this.options.qrCodeUrl || '') + this.sessionPrefix + this.options.sessionId)
291
+ }
292
+
293
+ // 实现复制到剪贴板功能
294
+ private async copyToClipboard(text: string): Promise<void> {
295
+ try {
296
+ // 优先使用现代浏览器的 Clipboard API
297
+ if (navigator.clipboard && window.isSecureContext) {
298
+ await navigator.clipboard.writeText(text)
299
+ this.showCopyFeedback(true)
300
+ } else {
301
+ // 降级方案:使用传统的 document.execCommand
302
+ const textArea = document.createElement('textarea')
303
+ textArea.value = text
304
+ textArea.style.position = 'fixed'
305
+ textArea.style.left = '-999999px'
306
+ textArea.style.top = '-999999px'
307
+ document.body.appendChild(textArea)
308
+ textArea.focus()
309
+ textArea.select()
310
+
311
+ const successful = document.execCommand('copy')
312
+ document.body.removeChild(textArea)
313
+
314
+ if (successful) {
315
+ this.showCopyFeedback(true)
316
+ } else {
317
+ this.showCopyFeedback(false)
318
+ }
319
+ }
320
+ } catch (error) {
321
+ console.error('复制失败:', error)
322
+ this.showCopyFeedback(false)
323
+ }
324
+ }
325
+
326
+ // 显示复制反馈提示
327
+ private showCopyFeedback(success: boolean): void {
328
+ const message = success ? '复制成功!' : '复制失败,请手动复制'
329
+ const feedback = document.createElement('div')
330
+ feedback.className = `tiny-remoter-copy-feedback ${success ? 'success' : 'error'}`
331
+ feedback.textContent = message
332
+
333
+ document.body.appendChild(feedback)
334
+
335
+ // 添加显示动画
336
+ setTimeout(() => feedback.classList.add('show'), 10)
337
+
338
+ // 3秒后自动移除
339
+ setTimeout(() => {
340
+ feedback.classList.remove('show')
341
+ setTimeout(() => {
342
+ if (feedback.parentNode) {
343
+ feedback.parentNode.removeChild(feedback)
344
+ }
345
+ }, 300)
346
+ }, 1500)
347
+ }
348
+
231
349
  // 创建二维码弹窗
232
350
  private async showQRCode(): Promise<void> {
233
- const qrCode = new QrCode(this.options.qrCodeUrl + '?sessionId=' + this.options.sessionId, {})
351
+ const qrCode = new QrCode((this.options.qrCodeUrl || '') + this.sessionPrefix + this.options.sessionId, {})
234
352
  const base64 = await qrCode.toDataURL()
235
353
  const modal = this.createModal(
236
354
  '扫码前往智能遥控器',
237
355
  `
238
- <div style="text-align: center; padding: 20px;">
239
- <div style="width: 200px; height: 200px; background: #f0f0f0; margin: 0 auto 20px; display: flex; align-items: center; justify-content: center; border-radius: 8px;">
240
- <img src="${base64}" alt="二维码" style="width: 100%; height: 100%; object-fit: contain;">
356
+ <div style="text-align: center; padding: 32px;">
357
+ <!-- 二维码容器 - 添加渐变背景和阴影效果 -->
358
+ <div style="
359
+ width: 240px;
360
+ height: 240px;
361
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
362
+ margin: 0 auto 24px;
363
+ display: flex;
364
+ align-items: center;
365
+ justify-content: center;
366
+ border-radius: 20px;
367
+ box-shadow: 0 20px 40px rgba(102, 126, 234, 0.3);
368
+ position: relative;
369
+ overflow: hidden;
370
+ ">
371
+ <!-- 装饰性背景元素 -->
372
+ <div style="
373
+ position: absolute;
374
+ top: -50%;
375
+ left: -50%;
376
+ width: 200%;
377
+ height: 200%;
378
+ background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
379
+ animation: rotate 20s linear infinite;
380
+ "></div>
381
+
382
+ <!-- 二维码图片容器 -->
383
+ <div style="
384
+ width: 200px;
385
+ height: 200px;
386
+ background: white;
387
+ border-radius: 16px;
388
+ padding: 16px;
389
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
390
+ position: relative;
391
+ z-index: 1;
392
+ ">
393
+ <img src="${base64}" alt="二维码" style="
394
+ width: 100%;
395
+ height: 100%;
396
+ object-fit: contain;
397
+ border-radius: 8px;
398
+ ">
399
+ </div>
400
+ </div>
401
+
402
+ <!-- 标题文字 -->
403
+ <h3 style="
404
+ color: #333;
405
+ margin: 0 0 12px 0;
406
+ font-size: 20px;
407
+ font-weight: 600;
408
+ letter-spacing: 0.5px;
409
+ ">扫描二维码</h3>
410
+
411
+ <!-- 描述文字 -->
412
+ <p style="
413
+ color: #666;
414
+ margin: 0 auto;
415
+ margin-bottom: 20px;
416
+ font-size: 14px;
417
+ line-height: 1.6;
418
+ max-width: 280px;
419
+ ">请使用手机微信或者浏览器扫描二维码跳转到智能遥控器</p>
420
+
421
+ <!-- 提示图标和文字 -->
422
+ <div style="
423
+ display: flex;
424
+ align-items: center;
425
+ justify-content: center;
426
+ gap: 8px;
427
+ color: #999;
428
+ font-size: 12px;
429
+ ">
430
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="opacity: 0.7;">
431
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="currentColor"/>
432
+ </svg>
433
+ <span>支持微信、浏览器等多种方式</span>
241
434
  </div>
242
- <p style="color: #666; margin: 0;">请使用手机微信或者浏览器扫描二维码跳转到智能遥控器</p>
243
435
  </div>
244
436
  `
245
437
  )
@@ -251,22 +443,6 @@ class FloatingBlock {
251
443
  this.options.onShowAIChat?.()
252
444
  }
253
445
 
254
- // 创建遥控指令弹窗
255
- private showRemoteControl(): void {
256
- const modal = this.createModal(
257
- '输入需要发送的用户名',
258
- `
259
- <div style="padding: 20px;">
260
- <div style="display: flex; gap: 10px;">
261
- <input type="text" placeholder="输入用户名" style="flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px;">
262
- <button style="padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;">发送</button>
263
- </div>
264
- </div>
265
- `
266
- )
267
- this.showModal(modal)
268
- }
269
-
270
446
  private createModal(title: string, content: string): HTMLDivElement {
271
447
  const modal = document.createElement('div')
272
448
  modal.className = 'tiny-remoter-floating-modal'
@@ -377,6 +553,14 @@ class FloatingBlock {
377
553
  color: #333;
378
554
  }
379
555
 
556
+ .tiny-remoter-dropdown-item > span {
557
+ flex: 1;
558
+ overflow: hidden;
559
+ max-width: 120px;
560
+ text-overflow: ellipsis;
561
+ white-space: nowrap;
562
+ }
563
+
380
564
  .tiny-remoter-dropdown-item:hover {
381
565
  background: #f8f9fa;
382
566
  transform: translateX(4px);
@@ -393,6 +577,33 @@ class FloatingBlock {
393
577
  color: #667eea;
394
578
  }
395
579
 
580
+ /* 复制图标样式 */
581
+ .tiny-remoter-copy-icon {
582
+ display: flex;
583
+ align-items: center;
584
+ justify-content: center;
585
+ width: 24px;
586
+ height: 24px;
587
+ background: #f0f0f0;
588
+ border-radius: 6px;
589
+ color: #666;
590
+ cursor: pointer;
591
+ transition: all 0.2s ease;
592
+ opacity: 0.7;
593
+ margin-left: auto;
594
+ }
595
+
596
+ .tiny-remoter-copy-icon:hover {
597
+ background: #e0e0e0;
598
+ color: #333;
599
+ opacity: 1;
600
+ transform: scale(1.1);
601
+ }
602
+
603
+ .tiny-remoter-copy-icon:active {
604
+ transform: scale(0.95);
605
+ }
606
+
396
607
  /* 弹窗样式 */
397
608
  .tiny-remoter-floating-modal {
398
609
  position: fixed;
@@ -478,6 +689,16 @@ class FloatingBlock {
478
689
  padding: 24px;
479
690
  }
480
691
 
692
+ /* 二维码弹窗动画 */
693
+ @keyframes rotate {
694
+ from {
695
+ transform: rotate(0deg);
696
+ }
697
+ to {
698
+ transform: rotate(360deg);
699
+ }
700
+ }
701
+
481
702
  /* 响应式设计 */
482
703
  @media (max-width: 768px) {
483
704
  .tiny-remoter-floating-block {
@@ -499,6 +720,40 @@ class FloatingBlock {
499
720
  }
500
721
  }
501
722
 
723
+ /* 复制反馈提示样式 */
724
+ .tiny-remoter-copy-feedback {
725
+ position: fixed;
726
+ top: 50%;
727
+ left: 50%;
728
+ transform: translate(-50%, -50%);
729
+ background: rgba(0, 0, 0, 0.8);
730
+ color: white;
731
+ padding: 12px 24px;
732
+ border-radius: 8px;
733
+ font-size: 14px;
734
+ font-weight: 500;
735
+ z-index: 10000;
736
+ opacity: 0;
737
+ visibility: hidden;
738
+ transition: all 0.3s ease;
739
+ backdrop-filter: blur(4px);
740
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
741
+ }
742
+
743
+ .tiny-remoter-copy-feedback.show {
744
+ opacity: 1;
745
+ visibility: visible;
746
+ transform: translate(-50%, -50%) scale(1);
747
+ }
748
+
749
+ .tiny-remoter-copy-feedback.success {
750
+ background: rgba(34, 197, 94, 0.9);
751
+ }
752
+
753
+ .tiny-remoter-copy-feedback.error {
754
+ background: rgba(239, 68, 68, 0.9);
755
+ }
756
+
502
757
  /* 深色主题支持 */
503
758
  @media (prefers-color-scheme: dark) {
504
759
  .tiny-remoter-floating-dropdown {
@@ -518,6 +773,16 @@ class FloatingBlock {
518
773
  background: #2a2a2a;
519
774
  }
520
775
 
776
+ .tiny-remoter-copy-icon {
777
+ background: #2a2a2a;
778
+ color: #ccc;
779
+ }
780
+
781
+ .tiny-remoter-copy-icon:hover {
782
+ background: #3a3a3a;
783
+ color: white;
784
+ }
785
+
521
786
  .tiny-remoter-modal-content {
522
787
  background: #1a1a1a;
523
788
  color: white;
package/runtime.html ADDED
@@ -0,0 +1,98 @@
1
+ <html>
2
+ <head>
3
+ <!-- 导入 NEXT SDK -->
4
+ <script src="./dist/mcpsdk@1.17.0.js"></script>
5
+ <script src="./dist/zod@3.25.76.js"></script>
6
+ <script src="./dist/webmcp.js"></script>
7
+ <script src="./dist/webagent.js"></script>
8
+ <meta charset="UTF-8" />
9
+ <title>WebMCP 示例</title>
10
+ <style>
11
+ body {
12
+ font-family: Arial, sans-serif;
13
+ margin: 0;
14
+ padding: 0;
15
+ text-align: center;
16
+ }
17
+ h1 {
18
+ text-align: center;
19
+ margin-top: 100px;
20
+ }
21
+ </style>
22
+ <script>
23
+ const { createMessageChannelPairTransport, WebMcpServer, WebMcpClient, ResourceTemplate, z } = WebMCP
24
+ const { createRemoter } = WebAgent
25
+
26
+ async function connect() {
27
+ // Create pair MCP transports
28
+ const [serverTransport, clientTransport] = createMessageChannelPairTransport()
29
+ // Create an MCP server
30
+ const server = new WebMcpServer({
31
+ name: 'demo-server',
32
+ version: '1.0.0'
33
+ })
34
+
35
+ // Add an addition tool
36
+ server.registerTool(
37
+ '根据用户的心情生成页面的背景颜色',
38
+ {
39
+ title: '生成页面背景颜色',
40
+ description:
41
+ '根据用户的心情或者情绪生成页面的背景颜色,要求:传入的color参数格式为十六进制颜色值,比如 #000000',
42
+ inputSchema: { color: z.string() }
43
+ },
44
+ async ({ color }) => {
45
+ document.body.style.backgroundColor = color
46
+ return {
47
+ content: [{ type: 'text', text: String(color) }]
48
+ }
49
+ }
50
+ )
51
+
52
+ // Create an MCP Client
53
+ const client = new WebMcpClient({
54
+ name: 'demo-client',
55
+ version: '1.0.0'
56
+ })
57
+
58
+ // Connect the client and server
59
+ await server.connect(serverTransport)
60
+ await client.connect(clientTransport)
61
+
62
+ // Connect to the Web Agent server
63
+ const { transport, sessionId } = await client.connect({
64
+ url: 'https://agent.opentiny.design/api/v1/webmcp-trial/mcp',
65
+ agent: true
66
+ })
67
+
68
+ document.getElementById('sessionId').innerHTML = sessionId
69
+
70
+ createRemoter({
71
+ sessionId,
72
+ qrCodeUrl:
73
+ 'https://ai.opentiny.design/next-remoter?title=遥控器&welcome-title=背景变变变&welcome-desc=请说一句能表达情绪的话,页面会自动更新一个适合的颜色&suggestion=很高兴&suggestion=有点兴奋&suggestion=风和日丽',
74
+ menuItems: [
75
+ {
76
+ action: 'ai-chat',
77
+ show: false
78
+ },
79
+ {
80
+ action: 'remote-control',
81
+ show: false
82
+ }
83
+ ]
84
+ })
85
+
86
+ window.addEventListener('pagehide', async () => {
87
+ await transport.terminateSession()
88
+ })
89
+ }
90
+
91
+ connect()
92
+ </script>
93
+ </head>
94
+ <body>
95
+ <h1>根据用户的心情生成页面的背景颜色</h1>
96
+ <div id="sessionId"></div>
97
+ </body>
98
+ </html>
@@ -0,0 +1,26 @@
1
+ import { readFileSync } from 'fs'
2
+ import { resolve } from 'path'
3
+ import { fileURLToPath } from 'url'
4
+
5
+ // 获取 __dirname 的替代方案
6
+ const __filename = fileURLToPath(import.meta.url)
7
+ const __dirname = resolve(__filename, '..')
8
+
9
+ // 读取目标包的版本号
10
+ export function getPackageVersion(packageName: string): string {
11
+ try {
12
+ // 直接读取 node_modules 中目标包的 package.json 获取实际版本号
13
+ const packagePath = resolve(__dirname, `../node_modules/${packageName}/package.json`)
14
+ const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'))
15
+
16
+ if (packageJson.version) {
17
+ return packageJson.version
18
+ }
19
+
20
+ // 如果没有找到版本号,返回默认版本
21
+ return 'latest'
22
+ } catch (error) {
23
+ console.warn(`无法读取 node_modules/${packageName} 版本号,使用默认命名:`, error)
24
+ return 'latest'
25
+ }
26
+ }
@@ -0,0 +1,28 @@
1
+ import { defineConfig } from 'vite'
2
+ import { getPackageVersion } from './script/utils'
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig(({ mode }) => {
6
+ // 根据构建模式决定是否启用代码压缩
7
+ // development 模式:不压缩,便于调试
8
+ // production 模式:启用压缩,优化性能
9
+ const shouldMinify = mode !== 'dev'
10
+
11
+ console.log('mode', mode)
12
+
13
+ return {
14
+ build: {
15
+ emptyOutDir: false,
16
+ minify: shouldMinify, // 动态设置压缩配置
17
+ lib: {
18
+ entry: 'McpSdk.ts',
19
+ name: 'MCPSDK',
20
+ formats: ['es', 'umd'],
21
+ fileName: (format) => {
22
+ const version = getPackageVersion('@modelcontextprotocol/sdk')
23
+ return `mcpsdk@${version}${format === 'es' ? '.es' : ''}${shouldMinify ? '' : '.dev'}.js`
24
+ }
25
+ }
26
+ }
27
+ }
28
+ })