npm package: @lcap/spec-ui
Description: Vue3 UI component library
Latest version: 0.0.1

# Spec UI

Vue3 UI 组件库，基于 Vite + TypeScript 构建。

## 特性

- 🚀 基于 Vite + TypeScript
- 📦 按组件目录分 chunk 构建，支持按需加载
- 🧪 完整的测试支持
- 🎨 完整的示例和开发调试环境
- 📄 内置 PDF 预览组件
- 🔍 内置 Diff 查看器组件（基于 @git-diff-view）
- ✍️ 内置 Markdown 编辑器组件（基于 Tiptap）
- 📝 支持多文档编辑器和懒加载
- 💬 完整的聊天界面组件库

## 安装

```bash
pnpm add @lcap/spec-ui
```

**注意：** 如果使用 TypeScript，还需要安装 Vue 3 和 Element Plus 作为 peer dependencies。

## 使用

### PDF 预览组件

```vue
<template>
  <PdfViewer
    :source="pdfSource"
    :page="currentPage"
    :scale="scale"
    :rotation="rotation"
    :show-toolbar="true"
    @page-change="handlePageChange"
    @scale-change="handleScaleChange"
    @rotation-change="handleRotationChange"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { PdfViewer } from '@lcap/spec-ui';

const pdfSource = ref('https://example.com/document.pdf');
const currentPage = ref(1);
const scale = ref(1);
const rotation = ref<0 | 90 | 180 | 270>(0);

const handlePageChange = (page: number) => {
  currentPage.value = page;
};

const handleScaleChange = (newScale: number) => {
  scale.value = newScale;
};

const handleRotationChange = (newRotation: number) => {
  rotation.value = newRotation as 0 | 90 | 180 | 270;
};
</script>
```

### PDF 预览组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| source | `string \| File \| ArrayBuffer` | - | PDF 文件源，可以是 URL、File 对象或 ArrayBuffer |
| page | `number` | `1` | 页面编号（从 1 开始） |
| scale | `number` | `1` | 缩放比例 |
| rotation | `number` | `0` | 旋转角度（0, 90, 180, 270） |
| showToolbar | `boolean` | `true` | 是否显示工具栏 |
| enableTextSelection | `boolean` | `true` | 是否启用文本选择 |
| class | `string` | - | 自定义类名 |
| style | `string \| Record<string, any>` | - | 自定义样式 |

### PDF 预览组件 Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| page-change | `page: number` | 页码改变时触发 |
| scale-change | `scale: number` | 缩放比例改变时触发 |
| rotation-change | `rotation: number` | 旋转角度改变时触发 |

### Diff 查看器组件

`DiffViewer` 组件用于显示代码差异对比，支持分屏和统一两种显示模式。

```vue
<template>
  <DiffViewer
    :diff-data="diffData"
    :diff-view-font-size="14"
    :diff-view-mode="'split'"
    :diff-view-highlight="true"
    :diff-view-add-widget="false"
    :diff-view-wrap="false"
    :diff-view-theme="'light'"
    @add-widget-click="handleAddWidgetClick"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { DiffViewer } from '@lcap/spec-ui';
import type { DiffData, AddWidgetClickEvent } from '@lcap/spec-ui';
import { DiffModeEnum } from '@git-diff-view/vue';

const diffData = ref<DiffData>({
  oldFile: {
    fileName: 'src/index.ts',
    fileLang: 'typescript',
    content: 'export const oldCode = "old";',
  },
  newFile: {
    fileName: 'src/index.ts',
    fileLang: 'typescript',
    content: 'export const newCode = "new";\nexport const addedCode = "added";',
  },
});

const handleAddWidgetClick = (event: AddWidgetClickEvent) => {
  console.log('Add widget clicked:', event);
};
</script>
```

### DiffViewer 组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| diffData | `DiffData` | - | Diff 数据，包含 oldFile 和 newFile |
| diffViewFontSize | `number` | `14` | 字体大小 |
| diffViewMode | `'split' \| 'unified'` | `'split'` | 显示模式，`split` 为分屏模式，`unified` 为统一模式 |
| diffViewHighlight | `boolean` | `true` | 是否高亮显示差异 |
| diffViewAddWidget | `boolean` | `false` | 是否显示添加小部件 |
| diffViewWrap | `boolean` | `false` | 是否自动换行 |
| diffViewTheme | `'light' \| 'dark'` | `'light'` | 主题 |
| extendData | `Record<string, any>` | - | 扩展数据 |
| class | `string` | - | 自定义类名 |
| style | `string \| Record<string, any>` | - | 自定义样式 |

### DiffData 类型

```typescript
interface FileData {
  fileName?: string;      // 文件名
  fileLang?: string;      // 文件语言（用于语法高亮）
  content: string;        // 文件内容
}

interface DiffData {
  oldFile?: FileData;     // 旧文件数据
  newFile?: FileData;     // 新文件数据
}
```

### DiffViewer 组件 Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| add-widget-click | `event: AddWidgetClickEvent` | 点击添加小部件时触发 |

### AddWidgetClickEvent 类型

```typescript
interface AddWidgetClickEvent {
  side: 'old' | 'new';   // 所在侧：old 或 new
  lineNumber: number;     // 行号
}
```

### Markdown 编辑器组件

```vue
<template>
  <div>
    <MarkdownEditorToolbar :editor="editorInstance" />
    <MarkdownEditor
      v-model="markdownContent"
      :marked-options="{ gfm: true, breaks: true }"
      :readonly="false"
      @ready="handleEditorReady"
      @change="handleContentChange"
      @focus="handleFocus"
      @blur="handleBlur"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { MarkdownEditor, MarkdownEditorToolbar } from '@lcap/spec-ui';
import type { Editor } from '@tiptap/core';

const markdownContent = ref('# Hello World\n\nThis is a markdown editor.');
const editorInstance = ref<Editor | null>(null);

const handleEditorReady = (editor: Editor) => {
  editorInstance.value = editor;
};

const handleContentChange = (content: { markdown: string; json: unknown }) => {
  console.log('Content changed:', content.markdown);
};

const handleFocus = () => {
  console.log('Editor focused');
};

const handleBlur = () => {
  console.log('Editor blurred');
};
</script>
```

### Markdown 编辑器组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| modelValue | `string` | - | 初始内容（Markdown 格式），支持 v-model |
| editorOptions | `Partial<EditorOptions>` | - | Tiptap 编辑器配置选项 |
| markedOptions | `MarkedOptions` | - | MarkedJS 配置选项（见下方说明） |
| placeholder | `string` | - | 占位符文本 |
| readonly | `boolean` | `false` | 是否只读 |
| class | `string` | - | 自定义类名 |
| style | `string \| Record<string, string \| number>` | - | 自定义样式 |

### Markdown 编辑器组件 Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| update:modelValue | `value: string` | 内容更新时触发（支持 v-model） |
| ready | `editor: Editor` | 编辑器创建完成时触发 |
| change | `content: { markdown: string; json: unknown }` | 内容变化时触发 |
| focus | - | 编辑器获得焦点时触发 |
| blur | - | 编辑器失去焦点时触发 |

### MarkedOptions 配置

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| breaks | `boolean` | `false` | 将换行符 `\n` 转换为 `<br>` |
| pedantic | `boolean` | `false` | 严格的 Markdown 模式 |
| smartypants | `boolean` | `false` | 智能引号和破折号 |
| gfm | `boolean` | `false` | 启用 GitHub 风格的 Markdown（GFM），支持表格、任务列表等 |

### MarkdownEditorToolbar 组件

工具栏组件提供丰富的格式化工具，包括：

- **文本格式**：粗体、斜体、下划线、删除线
- **标题**：H1-H4 标题
- **列表**：有序列表、无序列表
- **其他**：引用、行内代码、表格、链接、图片、水平线
- **操作**：撤销、重做、清除格式

```vue
<template>
  <MarkdownEditorToolbar :editor="editorInstance" />
</template>

<script setup lang="ts">
import { MarkdownEditorToolbar } from '@lcap/spec-ui';
import type { Editor } from '@tiptap/core';

const editorInstance = ref<Editor | null>(null);
</script>
```

### MultiMarkdownEditor 组件

多文档编辑器组件，支持懒加载和工具栏集成：

```vue
<template>
  <MultiMarkdownEditor
    :documents="documents"
    :min-height="'400px'"
    :marked-options="{ gfm: true }"
    :readonly="false"
    @change="handleDocumentChange"
  >
    <template #header>
      <div class="custom-header">自定义头部内容</div>
    </template>
    <template #placeholder="{ doc }">
      <div class="custom-placeholder">加载中: {{ doc.key }}</div>
    </template>
  </MultiMarkdownEditor>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { MultiMarkdownEditor } from '@lcap/spec-ui';

const documents = ref([
  { key: 'doc-1', content: '# Document 1\n\nContent 1' },
  { key: 'doc-2', content: '# Document 2\n\nContent 2' },
  { key: 'doc-3', content: '# Document 3\n\nContent 3' },
]);

const handleDocumentChange = (event: { key: string; content: string }) => {
  console.log(`Document ${event.key} changed:`, event.content);
};
</script>
```

### MultiMarkdownEditor 组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| documents | `Array<{ key: string; content: string }>` | - | 文档列表 |
| minHeight | `string` | `'400px'` | 每个文档的最小高度（占位符高度） |
| markedOptions | `MarkedOptions` | - | MarkedJS 配置选项 |
| editorOptions | `Partial<EditorOptions>` | - | Tiptap 编辑器配置选项 |
| readonly | `boolean` | `false` | 是否只读 |
| documentClass | `string` | - | 文档容器的自定义类名 |
| documentStyle | `string \| Record<string, string \| number>` | - | 文档容器的自定义样式 |

### MultiMarkdownEditor 组件 Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| change | `event: { key: string; content: string }` | 文档内容变化时触发 |

### MultiMarkdownEditor 组件 Slots

| 插槽名 | 作用域 | 说明 |
|--------|--------|------|
| header | - | 自定义头部内容，显示在工具栏下方 |
| placeholder | `{ doc: { key: string; content: string } }` | 自定义占位符内容，显示在文档加载前 |

## Chat 聊天组件

Chat 组件库提供了一套完整的聊天界面组件，包括聊天容器、消息气泡、输入框、附件管理等。

### Chat 主容器组件

`Chat` 组件是聊天界面的主容器，提供了头部、内容和底部三个区域的插槽。

```vue
<template>
  <Chat title="AI 助手">
    <template #meta>
      <IconAiLogo />
      <span>自定义标题</span>
    </template>
    <template #actions>
      <IconButton>
        <IconMoreList />
      </IconButton>
    </template>
    <template #header>
      <Sessions 
        :items="sessions" 
        :active-id="activeSessionId"
        @select="handleSessionSelect"
        @close="handleSessionClose"
      />
    </template>
    <div class="chat-content">
      <BubbleList :items="messages" />
    </div>
    <template #footer>
      <Sender
        v-model="inputMessage"
        :loading="sending"
        @submit="handleSendMessage"
        @paste-file="handlePasteFile"
      >
        <template #header>
          <Attachments
            v-model:opened="attachmentsOpened"
            :attachments="attachments"
            @remove="handleRemoveAttachment"
          />
        </template>
        <template #actions>
          <Actions :items="actionItems" @click="handleActionClick" />
        </template>
      </Sender>
    </template>
  </Chat>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { 
  Chat, 
  Sessions, 
  BubbleList, 
  Sender, 
  Attachments, 
  Actions,
  IconButton 
} from '@lcap/spec-ui';
import { IconAiLogo, IconMoreList } from '@lcap/spec-ui/icons';

const activeSessionId = ref('session-1');
const inputMessage = ref('');
const sending = ref(false);
const attachmentsOpened = ref(false);
const attachments = ref([]);
const sessions = ref([
  { id: 'session-1', name: '会话 1', status: 'normal' },
  { id: 'session-2', name: '会话 2', status: 'working' },
]);
const messages = ref([
  { role: 'user', id: 'msg-1', content: 'Hello' },
  { role: 'assistant', id: 'msg-2', content: 'Hi there!' },
]);

const handleSendMessage = (message: string) => {
  console.log('发送消息:', message);
};

const handlePasteFile = (firstFile: File, files: FileList) => {
  console.log('粘贴文件:', firstFile, files);
};

const handleSessionSelect = (id: string) => {
  activeSessionId.value = id;
};

const handleSessionClose = (id: string) => {
  console.log('关闭会话:', id);
};

const handleRemoveAttachment = (attachment: any) => {
  console.log('移除附件:', attachment);
};

const handleActionClick = (key: string) => {
  console.log('点击操作:', key);
};

const actionItems = [
  { label: '操作1', key: 'action1' },
  { label: '操作2', key: 'action2' },
];
</script>
```

### Chat 组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| title | `string` | `'CodeWave智能AI助手'` | 聊天标题 |

### Chat 组件 Slots

| 插槽名 | 说明 |
|--------|------|
| meta | 自定义头部元信息（默认显示 AI Logo 和标题） |
| actions | 自定义头部操作按钮 |
| header | 自定义头部内容（通常用于放置 Sessions 组件） |
| default | 聊天内容区域（通常用于放置 BubbleList 组件） |
| footer | 底部内容（通常用于放置 Sender 组件） |

### Sessions 会话列表组件

`Sessions` 组件用于显示和管理聊天会话列表。

```vue
<template>
  <Sessions
    :items="sessions"
    :active-id="activeSessionId"
    @select="handleSelect"
    @close="handleClose"
  />
</template>

<script setup lang="ts">
import { Sessions } from '@lcap/spec-ui';

const sessions = ref([
  { id: 'session-1', name: '会话 1', status: 'normal' },
  { id: 'session-2', name: '会话 2', status: 'working' },
  { id: 'session-3', name: '会话 3', status: 'waiting' },
]);

const activeSessionId = ref('session-1');
</script>
```

### Sessions 组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| items | `Array<{ id: string; name: string; status: 'normal' \| 'working' \| 'waiting' }>` | `[]` | 会话列表 |
| activeId | `string` | - | 当前激活的会话 ID |
| class | `ClassValue` | - | 自定义类名 |
| style | `string \| Record<string, any>` | - | 自定义样式 |

### Sessions 组件 Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| select | `id: string` | 选择会话时触发 |
| close | `id: string` | 关闭会话时触发 |

### Bubble 消息气泡组件

`Bubble` 组件用于显示单条消息，支持用户和助手两种布局。

```vue
<template>
  <!-- 用户消息（右侧） -->
  <Bubble placement="end">
    <template #avatar>
      <IconUser />
    </template>
    <div>这是用户消息</div>
  </Bubble>

  <!-- 助手消息（左侧） -->
  <Bubble placement="start">
    <template #avatar>
      <IconAiLogo />
    </template>
    <template #header>
      <span>AI 助手</span>
    </template>
    <div>这是助手消息</div>
    <template #footer>
      <span>2024-01-01 12:00</span>
    </template>
  </Bubble>
</template>

<script setup lang="ts">
import { Bubble } from '@lcap/spec-ui';
</script>
```

### Bubble 组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| placement | `'start' \| 'end'` | `'start'` | 消息位置，`start` 为左侧（助手），`end` 为右侧（用户） |
| class | `unknown` | - | 自定义类名 |

### Bubble 组件 Slots

| 插槽名 | 说明 |
|--------|------|
| avatar | 头像区域 |
| header | 消息头部（如发送者名称） |
| default | 消息内容 |
| footer | 消息底部（如时间戳） |

### BubbleList 消息列表组件

`BubbleList` 组件用于显示多条消息的列表，支持自动滚动到底部。

```vue
<template>
  <!-- 默认渲染模式 -->
  <BubbleList :items="messages">
    <template #avatar="{ item }">
      <IconAiLogo v-if="item.role === 'assistant'" />
      <IconUser v-else />
    </template>
    <template #default="{ item }">
      <div>{{ item.content }}</div>
    </template>
  </BubbleList>

  <!-- 自定义渲染模式 -->
  <BubbleList :items="messages" :custom-render="true">
    <template #default="{ item, index }">
      <div :class="item.role === 'user' ? 'user-msg' : 'assistant-msg'">
        {{ item.content }}
      </div>
    </template>
    <template #last>
      <div class="loading-indicator">正在输入...</div>
    </template>
  </BubbleList>
</template>

<script setup lang="ts">
import { BubbleList } from '@lcap/spec-ui';

const messages = ref([
  { role: 'user', id: 'msg-1', content: 'Hello' },
  { role: 'assistant', id: 'msg-2', content: 'Hi there!' },
]);
</script>
```

### BubbleList 组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| items | `Array<{ role: 'user' \| 'assistant'; id: string } & Record<string, any>>` | - | 消息列表 |
| customRender | `boolean` | `false` | 是否使用自定义渲染模式，为 `true` 时不会自动渲染 Bubble 组件，而是通过插槽自定义渲染 |

### BubbleList 组件 Slots

| 插槽名 | 作用域 | 说明 |
|--------|--------|------|
| avatar | `{ item: MessageItem }` | 头像区域（仅在默认渲染模式下有效） |
| header | `{ item: MessageItem }` | 消息头部（仅在默认渲染模式下有效） |
| default | `{ item: MessageItem; index: number }` | 消息内容，在自定义渲染模式下用于完全自定义消息渲染 |
| footer | `{ item: MessageItem }` | 消息底部（仅在默认渲染模式下有效） |
| last | - | 列表底部内容，通常用于显示加载指示器等 |

**注意：** 当 `customRender` 为 `true` 时，组件不会自动渲染 `Bubble` 组件，你需要通过 `default` 插槽完全自定义每条消息的渲染。此时 `avatar`、`header`、`footer` 插槽不会生效。

### Sender 消息输入组件

`Sender` 组件提供了消息输入框和发送功能。

```vue
<template>
  <Sender
    v-model="message"
    :loading="sending"
    :disabled="disabled"
    submit-type="enter"
    placeholder="请输入消息"
    @submit="handleSubmit"
    @paste-file="handlePasteFile"
  >
    <template #header>
      <Attachments :attachments="attachments" />
    </template>
    <template #actions>
      <Actions :items="actionItems" @click="handleActionClick" />
    </template>
  </Sender>
</template>

<script setup lang="ts">
import { Sender, Attachments, Actions } from '@lcap/spec-ui';

const message = ref('');
const sending = ref(false);
const disabled = ref(false);
</script>
```

### Sender 组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| modelValue | `string` | - | 输入内容，支持 v-model |
| submitType | `'enter' \| 'shift-enter'` | `'enter'` | 提交方式，`enter` 为回车提交，`shift-enter` 为 Shift+Enter 提交 |
| placeholder | `string` | `'请输入内容'` | 占位符文本 |
| disabled | `boolean` | `false` | 是否禁用 |
| loading | `boolean` | `false` | 是否加载中 |
| rows | `number` | `3` | 文本域行数 |
| autosize | `boolean \| { minRows: number; maxRows: number }` | `{ minRows: 3, maxRows: 6 }` | 是否自动调整高度 |
| class | `unknown` | - | 自定义类名 |
| style | `unknown` | - | 自定义样式 |

### Sender 组件 Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| submit | `message: string` | 提交消息时触发 |
| cancel | - | 取消时触发 |
| change | `message: string` | 内容变化时触发 |
| update:modelValue | `message: string` | 内容更新时触发（支持 v-model） |
| pasteFile | `firstFile: File, files: FileList` | 粘贴文件时触发 |
| focus | `event: FocusEvent` | 获得焦点时触发 |
| blur | `event: FocusEvent` | 失去焦点时触发 |

### Sender 组件 Slots

| 插槽名 | 说明 |
|--------|------|
| header | 输入框上方内容（通常用于放置 Attachments 组件） |
| footer | 输入框下方内容 |
| actions | 操作按钮区域（通常用于放置 Actions 组件） |

### Attachments 附件组件

`Attachments` 组件用于显示和管理附件列表。

```vue
<template>
  <Attachments
    v-model:opened="opened"
    :attachments="attachments"
    @remove="handleRemove"
  />
</template>

<script setup lang="ts">
import { Attachments } from '@lcap/spec-ui';

const opened = ref(false);
const attachments = ref([
  {
    key: '1',
    url: 'https://example.com/image.jpg',
    mimeType: 'image/jpeg',
    name: 'image.jpg',
    size: 1024000,
  },
  {
    key: '2',
    mimeType: 'application/pdf',
    name: 'document.pdf',
    size: 2048000,
  },
]);
</script>
```

### Attachments 组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| opened | `boolean` | `false` | 是否展开，支持 v-model:opened |
| attachments | `Array<Attachment>` | `[]` | 附件列表 |
| containerClass | `ClassValue` | - | 容器自定义类名 |
| class | `ClassValue` | - | 自定义类名 |
| style | `unknown` | - | 自定义样式 |

### Attachment 类型

```typescript
interface Attachment {
  key?: string | number;      // 唯一标识
  url?: string;               // 文件 URL（用于预览）
  mimeType: string;           // MIME 类型
  name: string;               // 文件名
  size: number;               // 文件大小（字节）
}
```

### Attachments 组件 Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| remove | `attachment: Attachment` | 移除附件时触发 |
| update:opened | `opened: boolean` | 展开状态更新时触发（支持 v-model:opened） |

### Actions 操作按钮组件

`Actions` 组件用于显示操作按钮列表。

```vue
<template>
  <Actions
    :items="actionItems"
    direction="horizontal"
    @click="handleClick"
  />
</template>

<script setup lang="ts">
import { Actions } from '@lcap/spec-ui';
import { IconAdd, IconDelete } from '@lcap/spec-ui/icons';

const actionItems = [
  { label: '添加', key: 'add', icon: IconAdd },
  { label: '删除', key: 'delete', icon: IconDelete },
];
</script>
```

### Actions 组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| items | `Array<{ label: string; icon?: Component; key: string }>` | `[]` | 操作项列表 |
| direction | `'vertical' \| 'horizontal'` | `'horizontal'` | 排列方向 |
| class | `ClassValue` | - | 自定义类名 |
| style | `unknown` | - | 自定义样式 |

### Actions 组件 Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| click | `key: string, event: MouseEvent` | 点击操作项时触发 |

### IconButton 图标按钮组件

`IconButton` 组件用于显示图标按钮。

```vue
<template>
  <IconButton
    :size="24"
    :active="isActive"
    :disabled="isDisabled"
    tooltip="提示文本"
    tooltip-placement="top"
  >
    <IconMoreList />
  </IconButton>
</template>

<script setup lang="ts">
import { IconButton } from '@lcap/spec-ui';
import { IconMoreList } from '@lcap/spec-ui/icons';
</script>
```

### IconButton 组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| size | `number` | `24` | 按钮大小 |
| active | `boolean` | `false` | 是否激活/选中状态 |
| disabled | `boolean` | `false` | 是否禁用 |
| tooltip | `string` | - | Tooltip 提示文本 |
| tooltipPlacement | `'top' \| 'bottom' \| 'left' \| 'right' \| 'top-start' \| 'top-end' \| 'bottom-start' \| 'bottom-end' \| 'left-start' \| 'left-end' \| 'right-start' \| 'right-end'` | `'top'` | Tooltip 显示位置 |

### AttachContainer 附件容器组件

`AttachContainer` 组件是附件的容器组件，提供统一的样式和布局。

```vue
<template>
  <AttachContainer :visible="visible" :radius="8">
    <template #content>
      <!-- 附件内容 -->
    </template>
    <!-- 其他内容 -->
  </AttachContainer>
</template>

<script setup lang="ts">
import { AttachContainer } from '@lcap/spec-ui';
</script>
```

### AttachContainer 组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| radius | `number` | - | 圆角半径 |
| containerClass | `ClassValue` | - | 容器自定义类名 |
| visible | `boolean` | - | 是否可见 |

### AttachContainer 组件 Slots

| 插槽名 | 说明 |
|--------|------|
| content | 附件内容区域 |
| default | 其他内容 |

## 开发

### 安装依赖

```bash
pnpm install
```

### 启动开发服务器

```bash
pnpm dev
```

开发服务器会在 `http://localhost:3000` 启动，自动打开浏览器。

### 运行测试

```bash
# 运行所有测试
pnpm test

# 监听模式运行测试
pnpm test:watch
```

### 构建

```bash
pnpm build
```

构建产物会输出到 `dist` 目录，按组件目录分 chunk：

```
dist/
├── index.mjs                # 主入口（ES Module）
├── index.d.ts               # 类型定义
├── index.css                # 样式文件
├── components/
│   ├── pdf-viewer/
│   │   ├── index.mjs        # PDF 预览组件入口
│   │   └── index.d.ts       # 类型定义
│   ├── markdown-editor/
│   │   ├── index.mjs        # Markdown 编辑器组件入口
│   │   └── index.d.ts       # 类型定义
│   └── diff-viewer/
│       ├── index.mjs        # Diff 查看器组件入口
│       └── index.d.ts       # 类型定义
└── chunks/                  # 其他 chunk
```

### 类型检查

```bash
pnpm type-check
```

## 项目结构

```
spec-ui/
├── src/
│   ├── components/          # 组件目录
│   │   ├── pdf-viewer/      # PDF 预览组件
│   │   │   ├── PdfViewer.vue
│   │   │   ├── index.ts
│   │   │   └── types.ts
│   │   ├── markdown-editor/ # Markdown 编辑器组件
│   │   │   ├── MarkdownEditor.vue
│   │   │   ├── MarkdownEditorToolbar.vue
│   │   │   ├── MultiMarkdownEditor.vue
│   │   │   ├── types.ts
│   │   │   ├── tools/       # 工具栏工具组件
│   │   │   └── index.ts
│   │   └── diff-viewer/     # Diff 查看器组件
│   │       ├── DiffViewer.vue
│   │       ├── index.ts
│   │       └── types.ts
│   │   └── chat/            # Chat 聊天组件
│   │       ├── Chat.vue
│   │       ├── Sessions.vue
│   │       ├── Bubble.vue
│   │       ├── BubbleList.vue
│   │       ├── Sender.vue
│   │       ├── Attachments.vue
│   │       ├── AttachContainer.vue
│   │       ├── Actions.vue
│   │       ├── IconButton.vue
│   │       ├── SendButton.vue
│   │       ├── types.ts
│   │       └── index.ts
│   │   └── icons/            # 图标组件
│   │       ├── icon-*.vue     # 各种图标组件
│   │       └── index.ts
│   ├── styles/               # 样式文件
│   │   └── variables.css     # CSS 变量
│   └── index.ts            # 主入口
├── examples/                # 示例和开发调试环境
│   ├── index.html
│   ├── main.ts
│   ├── App.vue
│   └── components/
│       ├── chat/
│       │   └── ChatDemo.vue
│       ├── diff-viewer/
│       │   └── DiffViewerDemo.vue
│       ├── markdown-editor/
│       │   ├── MarkdownEditorDemo.vue
│       │   └── MultiMarkdownEditorDemo.vue
│       └── pdf-viewer/
│           └── PdfViewerDemo.vue
├── tests/                   # 测试文件
│   ├── setup.ts            # 测试设置文件
│   └── components/
│       ├── pdf-viewer.test.ts
│       ├── diff-viewer.test.ts
│       └── multi-markdown-editor.test.ts
├── scripts/                 # 构建脚本
│   └── generate-component-dts.js  # 生成组件类型定义脚本
├── vite.config.ts           # Vite 配置
├── vitest.config.ts         # Vitest 配置
└── package.json
```

## 按需引入

组件库支持按需引入，每个组件都会被打包成独立的 chunk：

```typescript
// 引入整个组件库
import { 
  PdfViewer,
  DiffViewer,
  MarkdownEditor, 
  MarkdownEditorToolbar, 
  MultiMarkdownEditor,
  Chat,
  Sessions,
  Bubble,
  BubbleList,
  Sender,
  Attachments,
  Actions,
  IconButton
} from '@lcap/spec-ui';

// 引入样式文件
import '@lcap/spec-ui/index.css';

// 或者按组件引入
import { PdfViewer } from '@lcap/spec-ui/pdf-viewer';
import { 
  MarkdownEditor, 
  MarkdownEditorToolbar, 
  MultiMarkdownEditor 
} from '@lcap/spec-ui/markdown-editor';
import { DiffViewer } from '@lcap/spec-ui/diff-viewer';

// 引入图标组件
import { IconAiLogo, IconSend, IconAdd } from '@lcap/spec-ui/icons';
```

**注意：** 目前 chat 组件暂不支持单独的导出路径，请从主入口引入。

## License

MIT