# TinyBrowserTabs - 浏览器标签页组件

TinyBrowserTabs 是一个基于 TinyVue 组件库、遵循 OpenTiny 设计规范的 Vue3 浏览器标签页组件。它易于使用且功能强大，支持标签页的添加、删除、拖拽排序等操作，提供接近真实浏览器的多标签页体验。

## 特性与优势

- 支持多标签页的添加、删除、切换和拖拽排序，交互接近主流浏览器，易于使用。
- 提供 tab-title、tab-icon 和 default 等多个插槽，开发者可以自定义每个标签页的标题、图标和内容区域，满足各种业务需求。
- 通过 `tabs` 属性传入标签页数据，支持动态添加、删除和切换，便于与后端或业务逻辑集成。
- 提供 add、close、go-back、go-forward、refresh 等事件，方便父组件监听和处理用户操作。
- 支持嵌入网页/iframe，每个标签页可以加载不同的 URL，适用于 WebView、页面预览、低代码平台等场景。
- 作为 Vue3 组件开发，API 简单，易于集成到任何 Vue 项目中。

## 快速上手

安装 TinyBrowserTabs

```shell
npm i @opentiny/vue-browser-tabs
```

导入 TinyBrowserTabs ：

```javascript
import TinyBrowserTabs from '@opentiny/vue-browser-tabs'
```

在模板中使用：

```html
<script setup>
  import { ref } from 'vue'
  import TinyBrowserTabs from '@opentiny/vue-browser-tabs'

  const tabs = ref([
    { title: '百度', name: 'baidu', url: 'https://www.baidu.com', icon: 'https://www.baidu.com/favicon.ico' },
    { title: '必应', name: 'bing', url: 'https://www.bing.com', icon: 'https://www.bing.com/favicon.ico' }
  ])
  const activeName = ref('baidu')

  const handleClose = (name) => {
    tabs.value = tabs.value.filter((tab) => tab.name !== name)
  }
  const handleAdd = () => {
    const idx = tabs.value.length + 1
    tabs.value.push({
      title: `新标签页${idx}`,
      name: `tab${idx}`,
      url: '',
      icon: ''
    })
  }
</script>

<template>
  <TinyBrowserTabs v-model="activeName" :items="tabs" @close="handleClose" @add="handleAdd" />
</template>
```

## 本地开发

```shell
git clone git@github.com:opentiny/mcp.git
cd package/tiny-browser-tabs
pnpm i
pnpm dev
```

打开浏览器访问：[http://localhost:5173/tiny-browser-tabs/](http://localhost:5173/tiny-browser-tabs/)

## API

### Props

| 属性名          | 类型    | 默认值 | 说明                 |
| --------------- | ------- | ------ | -------------------- |
| items           | items[] | []     | 标签页列表数据       |
| modelValue      | string  | -      | 当前激活的标签页名称 |
| draggable       | boolean | false  | 标签页是否可拖拽     |
| backward        | boolean | true   | 是否启用后退功能     |
| forward         | boolean | true   | 是否启用前进功能     |
| addressEditable | boolean | false  | 地址栏是否可编辑     |

## 事件

| 事件          | 参数          | 说明                |
| ------------- | ------------- | ------------------- |
| change        | Item          | 标签页已更改        |
| close         | Item          | 标签页已关闭        |
| add           | -             | 添加新标签页        |
| address-enter | string, Item  | 地址栏回车          |
| go-back       | Item          | 后退                |
| go-forward    | Item          | 前进                |
| refresh       | Item          | 刷新                |
| minimize      | -             | 最小化窗口          |
| maximize      | -             | 最大化窗口          |
| window-close  | -             | 关闭窗口            |
| star          | Item, boolean | 收藏/取消收藏标签页 |

### 插槽 (Slots)

| 插槽      | 属性                        | 说明             |
| --------- | --------------------------- | ---------------- |
| tab-icon  | { item, index, isActive }   | 自定义标签页图标 |
| tab-title | { item, index, isActive }   | 自定义标签页标题 |
| default   | { item, items, activeName } | 自定义标签页内容 |

### 类型定义

````typescript
interface Tab {
  title: string
  name: string
  url?: string
  icon?: string
}

## MCP 集成

### 在 iframe 页面中使用 McpProxyClient

```javascript
import { McpProxyClient } from '@opentiny/vue-browser-tabs'

// 创建一个新的 MCP 代理客户端实例
const mcpProxyClient = new McpProxyClient()

// 注册一个工具
await mcpProxyClient.registerTool('my-tool', {
  description: '一个示例工具',
  parameters: {
    type: 'object',
    properties: {
      message: {
        type: 'string',
        description: '要处理的消息'
      }
    },
    required: ['message']
  },
  implementation: async (args) => {
    // 工具实现在 iframe 上下文中运行
    return {
      success: true,
      result: `已处理: ${args.message}`
    }
  }
})

// 调用一个工具
const result = await mcpProxyClient.callTool('another-tool', {
  param1: 'value1'
})

// 列出可用的工具
const tools = await mcpProxyClient.listTools()
````

### 设置与父组件的通信

在你的 iframe 页面中，你需要为 MCP 代理客户端设置目标源：

```javascript
// 等待父组件发送配置
window.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'MCP_CONFIG') {
    // 设置通信的目标源
    mcpProxyClient.setTargetOrigin(event.origin)
  }
})
```

### iframe 应用的 MCP 代理

组件还提供了一个 MCP 代理机制，允许 iframe 应用通过 postMessage 通信使用父应用的 MCP 服务。这使得单个 web agent 能够控制多个 iframe 应用界面。

#### 工作原理

1. 当 iframe 加载时，它会向父应用发送 `LOAD_COMPLETE` 消息
2. 父应用响应并发送 MCP 配置给 iframe
3. iframe 使用 `mcpProxyClient` 与父应用的 MCP 服务通信
4. 父应用处理 MCP 操作并将结果返回给 iframe

#### 在 iframe 应用中使用 mcpProxyClient

```javascript
// 在 iframe 应用组件中
import { inject } from 'vue'

// 获取父应用提供的 MCP 代理客户端
const mcpProxyClient = inject('mcpProxyClient')

// 注册工具
await mcpProxyClient.registerTool('my-tool', {
  description: '我的自定义工具',
  implementation: async (args) => {
    return {
      success: true,
      result: '工具执行成功'
    }
  }
})

// 调用工具
const result = await mcpProxyClient.callTool('existing-tool', {
  param: 'value'
})

// 列出所有工具
const tools = await mcpProxyClient.listTools()
```

这种机制使单个 AI agent 能够：

1. 控制多个 iframe 应用
2. 从不同的 iframe 应用注册工具
3. 协调跨多个应用的复杂任务
4. 维护统一的控制界面

## 贡献者 ✨

Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
  <tbody>
    <tr>
      <td align="center" valign="top" width="12.5%"><a href="https://kagol.github.io/blogs"><img src="https://avatars.githubusercontent.com/u/9566362?v=4?s=100" width="100px;" alt="Kagol"/><br /><sub><b>Kagol</b></sub></a><br /><a href="https://github.com/opentiny/tiny-browser-tabs/commits?author=kagol" title="Code">💻</a></td>
      <td align="center" valign="top" width="12.5%"><a href="https://github.com/zzcr"><img src="https://avatars.githubusercontent.com/u/18521562?v=4?s=100" width="100px;" alt="ajaxzheng"/><br /><sub><b>ajaxzheng</b></sub></a><br /><a href="https://github.com/opentiny/tiny-browser-tabs/commits?author=zzcr" title="Code">💻</a></td>
      <td align="center" valign="top" width="12.5%"><a href="https://github.com/discreted66"><img src="https://avatars.githubusercontent.com/u/190872652?v=4?s=100" width="100px;" alt="liukun"/><br /><sub><b>liukun</b></sub></a><br /><a href="https://github.com/opentiny/tiny-vue/commits?author=discreted66" title="Code">💻</a></td>
    </tr>
  </tbody>
</table>

<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
