@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.
- package/McpSdk.ts +14 -0
- package/WebAgent.ts +5 -0
- package/WebMcp.ts +18 -0
- package/Zod.ts +1 -0
- package/agent/AgentModelProvider.ts +150 -36
- package/agent/type.ts +1 -1
- package/agent/utils/getAISDKTools.ts +35 -0
- package/dist/agent/AgentModelProvider.d.ts +43 -4
- package/dist/agent/AgentModelProvider.js +145 -24
- package/dist/agent/type.d.ts +1 -1
- package/dist/agent/utils/getAISDKTools.d.ts +8 -0
- package/dist/agent/utils/getAISDKTools.js +36 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.es.dev.js +42947 -0
- package/dist/index.es.js +18006 -17750
- package/dist/index.js +10 -4
- package/dist/index.umd.dev.js +43341 -0
- package/dist/index.umd.js +285 -114
- package/dist/mcpsdk@1.17.0.dev.js +21391 -0
- package/dist/mcpsdk@1.17.0.es.dev.js +21389 -0
- package/dist/mcpsdk@1.17.0.es.js +14525 -0
- package/dist/mcpsdk@1.17.0.js +16 -0
- package/dist/remoter/createRemoter.d.ts +10 -2
- package/dist/remoter/createRemoter.js +312 -55
- package/dist/webagent.dev.js +27509 -0
- package/dist/webagent.es.dev.js +27115 -0
- package/dist/webagent.es.js +21448 -0
- package/dist/webagent.js +529 -0
- package/dist/webmcp-full.dev.js +22879 -0
- package/dist/webmcp-full.es.dev.js +22875 -0
- package/dist/webmcp-full.es.js +15821 -0
- package/dist/webmcp-full.js +16 -0
- package/dist/webmcp.dev.js +1373 -0
- package/dist/webmcp.es.dev.js +1366 -0
- package/dist/webmcp.es.js +1232 -0
- package/dist/webmcp.js +1 -0
- package/dist/zod@3.25.76.dev.js +4039 -0
- package/dist/zod@3.25.76.es.dev.js +4035 -0
- package/dist/zod@3.25.76.es.js +2947 -0
- package/dist/zod@3.25.76.js +1 -0
- package/index.ts +16 -4
- package/package.json +16 -4
- package/remoter/createRemoter.ts +327 -62
- package/runtime.html +98 -0
- package/script/utils.ts +26 -0
- package/vite.config.mcpSdk.ts +28 -0
- package/vite.config.ts +13 -12
- package/vite.config.webAgent.ts +19 -0
- package/vite.config.webMcp.ts +40 -0
- package/vite.config.webMcpFull.ts +19 -0
- package/vite.config.zod.ts +23 -0
- package/agent/utils/aiProviderFactories.ts +0 -7
- package/agent/utils/index.ts +0 -79
- package/dist/agent/utils/aiProviderFactories.d.ts +0 -6
- package/dist/agent/utils/aiProviderFactories.js +0 -6
- package/dist/agent/utils/index.d.ts +0 -14
- package/dist/agent/utils/index.js +0 -72
- package/dist/index.cjs.js +0 -365
package/remoter/createRemoter.ts
CHANGED
|
@@ -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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
<
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
<
|
|
66
|
-
<
|
|
67
|
-
<
|
|
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
|
|
129
|
+
return getDefaultMenuItems(this.options)
|
|
103
130
|
}
|
|
104
131
|
|
|
105
|
-
return
|
|
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.
|
|
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
|
|
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:
|
|
239
|
-
|
|
240
|
-
|
|
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>
|
package/script/utils.ts
ADDED
|
@@ -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
|
+
})
|