mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
feat(home): reorganize import options
This commit is contained in:
1 parent
7067c3f1c8
commit
fcf30853e9
12 files changed
+271
-134
No files matched your search
+19
-17
@@ -18,6 +18,24 @@ ChatLab 可以作为 Codex、Claude Code、HermesAgent 等外部 Agent 的本地
|
||||
|
||||
安装:`npm i chatlab-cli -g`(需要 Node.js ≥ 20),并先在 ChatLab 中导入聊天记录。
|
||||
|
||||
## 使用 AI Agent
|
||||
|
||||
推荐通过通用 Agent Skills CLI 安装官方中文 `chatlab-analyze-cn` 技能:
|
||||
|
||||
```bash
|
||||
npx skills add ChatLab/ChatLab --skill chatlab-analyze-cn -g
|
||||
```
|
||||
|
||||
安装后,可以在 Codex、Claude Code、Cursor 等外部 Agent 中直接说:
|
||||
|
||||
```text
|
||||
chatlab-analyze-cn 帮我分析我和小红的聊天记录
|
||||
```
|
||||
|
||||
这个技能会引导 Agent 先执行 `chatlab manifest` 获取机读命令清单,再用安全的 `--format agent/json` 查询聊天记录。
|
||||
|
||||
`chatlab-analyze-cn` 始终只读。如果你希望 Agent 导入新的聊天导出文件,请改用独立的 `chatlab-import-cn` Skill;它会先预览导入计划,再自动新建或增量导入,详见[导入聊天记录指南](./how-to-import.md)。
|
||||
|
||||
## 命令总览
|
||||
|
||||
```bash
|
||||
@@ -77,23 +95,7 @@ agent/json 模式下 stdout 只包含一个 JSON 响应信封,日志一律走
|
||||
- `--raw`(绕过预处理)默认禁用,仅当你显式执行 `chatlab config set cli.allow_raw true` 或设置 `CHATLAB_CLI_ALLOW_RAW=1` 后可用。
|
||||
- 不需要 SQL 兜底能力时,可用 `chatlab config set cli.allow_sql false` 关闭 `sql` 命令。
|
||||
|
||||
## 给 Agent 的使用指南
|
||||
|
||||
推荐通过通用 Agent Skills CLI 安装官方中文 `chatlab-analyze-cn` 技能:
|
||||
|
||||
```bash
|
||||
npx skills add ChatLab/ChatLab --skill chatlab-analyze-cn -g
|
||||
```
|
||||
|
||||
安装后,可以在 Codex、Claude Code、Cursor 等外部 Agent 中直接说:
|
||||
|
||||
```text
|
||||
chatlab-analyze-cn 帮我分析我和小红的聊天记录
|
||||
```
|
||||
|
||||
这个技能会引导 Agent 先执行 `chatlab manifest` 获取机读命令清单,再用安全的 `--format agent/json` 查询聊天记录。
|
||||
|
||||
`chatlab-analyze-cn` 始终只读。如果你希望 Agent 导入新的聊天导出文件,请改用独立的 `chatlab-import-cn` Skill;它会先预览导入计划,再自动新建或增量导入,详见[导入聊天记录指南](./how-to-import.md)。
|
||||
## 查询示例
|
||||
|
||||
典型配方——"谁最早提到某个问题?查看现场":
|
||||
|
||||
|
||||
+19
-17
@@ -18,6 +18,24 @@ The `chatlab` CLI ships a set of query commands optimized for AI agents while st
|
||||
|
||||
Install with `npm i chatlab-cli -g` (Node.js ≥ 20) and import chat records in ChatLab first.
|
||||
|
||||
## Use it with an AI agent
|
||||
|
||||
Install the official `chatlab-analyze` skill through the general Agent Skills CLI:
|
||||
|
||||
```bash
|
||||
npx skills add ChatLab/ChatLab --skill chatlab-analyze -g
|
||||
```
|
||||
|
||||
After installation, ask Codex, Claude Code, Cursor, or another external agent:
|
||||
|
||||
```text
|
||||
chatlab-analyze help me analyze my chat history with Alice
|
||||
```
|
||||
|
||||
The skill tells the agent to run `chatlab manifest` first, then query chat records safely with explicit `--format agent/json` commands.
|
||||
|
||||
`chatlab-analyze` always remains read-only. To let an agent import a new chat export, use the separate `chatlab-import` skill; it previews the import, then automatically creates or incrementally updates a session. See the [Import Chat Records Guide](./how-to-import.md).
|
||||
|
||||
## Command overview
|
||||
|
||||
```bash
|
||||
@@ -77,23 +95,7 @@ Failures return `{ "ok": false, "error": { "code", "message", "hint", "candidate
|
||||
- `--raw` (bypassing preprocessing) is disabled by default; it only works after you explicitly run `chatlab config set cli.allow_raw true` or set `CHATLAB_CLI_ALLOW_RAW=1`.
|
||||
- If you don't need the SQL fallback, disable it with `chatlab config set cli.allow_sql false`.
|
||||
|
||||
## Agent usage guide
|
||||
|
||||
Install the official `chatlab-analyze` skill through the general Agent Skills CLI:
|
||||
|
||||
```bash
|
||||
npx skills add ChatLab/ChatLab --skill chatlab-analyze -g
|
||||
```
|
||||
|
||||
After installation, ask Codex, Claude Code, Cursor, or another external agent:
|
||||
|
||||
```text
|
||||
chatlab-analyze help me analyze my chat history with Alice
|
||||
```
|
||||
|
||||
The skill tells the agent to run `chatlab manifest` first, then query chat records safely with explicit `--format agent/json` commands.
|
||||
|
||||
`chatlab-analyze` always remains read-only. To let an agent import a new chat export, use the separate `chatlab-import` skill; it previews the import, then automatically creates or incrementally updates a session. See the [Import Chat Records Guide](./how-to-import.md).
|
||||
## Query example
|
||||
|
||||
A typical recipe — "who mentioned this first? inspect the surrounding context":
|
||||
|
||||
|
||||
@@ -2,12 +2,20 @@
|
||||
"tagline": "Explore New Chat Memories",
|
||||
"tabs": {
|
||||
"file": "File Import",
|
||||
"api": "Auto Sync",
|
||||
"push": "API Push",
|
||||
"apiDescription": "Connect external platform data sources to automatically sync chat records to ChatLab on a schedule.",
|
||||
"apiButton": "Configure Auto Sync",
|
||||
"pushDescription": "Start the local API server to let third-party tools, plugins, or scripts push chat records directly into ChatLab.",
|
||||
"pushButton": "Configure API Server"
|
||||
"api": "API Import",
|
||||
"cli": "CLI Import",
|
||||
"autoSyncTitle": "Automatic Sync",
|
||||
"autoSyncDescription": "Let ChatLab periodically pull new chat records from an external data source.",
|
||||
"autoSyncButton": "Configure Sync",
|
||||
"apiPushTitle": "API Push",
|
||||
"apiPushDescription": "Let third-party tools, plugins, or scripts write through the local API.",
|
||||
"apiPushButton": "Configure API",
|
||||
"agentImportTitle": "Import with an AI Agent",
|
||||
"agentImportDescription": "Install the official Skill to import chat records automatically.",
|
||||
"manualCliTitle": "Use the CLI Manually",
|
||||
"manualCliDescription": "Ideal for terminal workflows, batch scripts, and repeatable automation.",
|
||||
"copyCommand": "Copy command",
|
||||
"viewDocs": "View documentation"
|
||||
},
|
||||
"import": {
|
||||
"dropHint": "Drop to import files",
|
||||
|
||||
@@ -2,12 +2,20 @@
|
||||
"tagline": "新しいチャットの記憶を探索する",
|
||||
"tabs": {
|
||||
"file": "ファイルインポート",
|
||||
"api": "自動同期",
|
||||
"push": "APIプッシュ",
|
||||
"apiDescription": "外部プラットフォームのデータソースを設定し、チャット履歴をChatLabへ定期的に自動同期します。",
|
||||
"apiButton": "自動同期を設定する",
|
||||
"pushDescription": "ローカルAPIサーバーを起動し、サードパーティのツール・プラグイン・スクリプトからChatLabへチャット履歴をプッシュできます。",
|
||||
"pushButton": "APIサーバーを設定する"
|
||||
"api": "API インポート",
|
||||
"cli": "CLI インポート",
|
||||
"autoSyncTitle": "自動同期",
|
||||
"autoSyncDescription": "ChatLab が外部データソースから新しいチャット履歴を定期的に取得します。",
|
||||
"autoSyncButton": "自動同期を設定",
|
||||
"apiPushTitle": "API プッシュ",
|
||||
"apiPushDescription": "外部ツール、プラグイン、スクリプトからローカル API 経由で書き込みます。",
|
||||
"apiPushButton": "API を設定",
|
||||
"agentImportTitle": "AI Agent でインポート",
|
||||
"agentImportDescription": "公式 Skill をインストールして、チャット履歴を自動的にインポートします。",
|
||||
"manualCliTitle": "CLI を手動で使用",
|
||||
"manualCliDescription": "ターミナル操作、バッチスクリプト、繰り返し可能な自動化に適しています。",
|
||||
"copyCommand": "コマンドをコピー",
|
||||
"viewDocs": "ドキュメントを見る"
|
||||
},
|
||||
"import": {
|
||||
"dropHint": "マウスを離してファイルをインポート",
|
||||
|
||||
@@ -2,12 +2,20 @@
|
||||
"tagline": "探索新的聊天记忆",
|
||||
"tabs": {
|
||||
"file": "文件导入",
|
||||
"api": "自动同步",
|
||||
"push": "API推送",
|
||||
"apiDescription": "配置外部平台的数据源,让聊天记录定期自动同步到ChatLab。",
|
||||
"apiButton": "去配置自动同步",
|
||||
"pushDescription": "开启本地API服务,允许第三方工具/插件或脚本将聊天记录主动推送至ChatLab。",
|
||||
"pushButton": "去开启 API 服务"
|
||||
"api": "API 导入",
|
||||
"cli": "CLI 导入",
|
||||
"autoSyncTitle": "自动同步",
|
||||
"autoSyncDescription": "由 ChatLab 定时从外部数据源拉取新增聊天记录。",
|
||||
"autoSyncButton": "配置自动同步",
|
||||
"apiPushTitle": "API 推送",
|
||||
"apiPushDescription": "让第三方工具、插件或脚本通过本地 API 主动写入。",
|
||||
"apiPushButton": "配置 API 服务",
|
||||
"agentImportTitle": "使用 AI Agent 导入",
|
||||
"agentImportDescription": "安装官方Skill,使用Skill自动导入聊天记录。",
|
||||
"manualCliTitle": "手动使用 CLI",
|
||||
"manualCliDescription": "适合终端操作、批处理脚本和可重复的自动化流程。",
|
||||
"copyCommand": "复制命令",
|
||||
"viewDocs": "查看文档"
|
||||
},
|
||||
"import": {
|
||||
"dropHint": "松开鼠标导入文件",
|
||||
|
||||
@@ -2,12 +2,20 @@
|
||||
"tagline": "探索新的聊天記憶",
|
||||
"tabs": {
|
||||
"file": "檔案匯入",
|
||||
"api": "自動同步",
|
||||
"push": "API推送",
|
||||
"apiDescription": "設定外部平台的資料來源,讓聊天記錄定期自動同步到ChatLab。",
|
||||
"apiButton": "前往設定自動同步",
|
||||
"pushDescription": "開啟本地API服務,允許第三方工具/掛件或腳本將聊天記錄主動推送至ChatLab。",
|
||||
"pushButton": "前往設定 API 服務"
|
||||
"api": "API 匯入",
|
||||
"cli": "命令列匯入",
|
||||
"autoSyncTitle": "自動同步",
|
||||
"autoSyncDescription": "由 ChatLab 定期從外部資料來源拉取新增聊天記錄。",
|
||||
"autoSyncButton": "設定自動同步",
|
||||
"apiPushTitle": "API 推送",
|
||||
"apiPushDescription": "讓第三方工具、外掛或腳本透過本機 API 主動寫入。",
|
||||
"apiPushButton": "設定 API 服務",
|
||||
"agentImportTitle": "使用 AI Agent 匯入",
|
||||
"agentImportDescription": "安裝官方 Skill,使用 Skill 自動匯入聊天記錄。",
|
||||
"manualCliTitle": "手動使用 CLI",
|
||||
"manualCliDescription": "適合終端操作、批次腳本與可重複的自動化流程。",
|
||||
"copyCommand": "複製命令",
|
||||
"viewDocs": "查看文件"
|
||||
},
|
||||
"import": {
|
||||
"dropHint": "鬆開滑鼠匯入檔案",
|
||||
|
||||
@@ -5,34 +5,67 @@ import { useLayoutStore } from '@/stores/layout'
|
||||
const { t } = useI18n()
|
||||
const layoutStore = useLayoutStore()
|
||||
|
||||
// 打开设置弹窗并定位到 API 服务子 Tab
|
||||
// Pull 与 Push 共用“API 导入”入口,但继续进入各自独立的配置子页。
|
||||
function openSyncSettings() {
|
||||
layoutStore.openSettings('api', 'sync')
|
||||
}
|
||||
|
||||
function openApiSettings() {
|
||||
layoutStore.openSettings('api', 'api')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full flex flex-col items-center space-y-6">
|
||||
<div
|
||||
class="flex h-[190px] sm:h-[220px] w-full max-w-md flex-col items-center justify-center rounded-3xl border border-gray-200/50 bg-gray-100/50 px-8 backdrop-blur-md dark:border-white/10 dark:bg-gray-800/40 select-none text-center sm:px-12"
|
||||
>
|
||||
<div class="mb-2 flex h-10 w-10 sm:h-12 sm:w-12 items-center justify-center">
|
||||
<UIcon name="i-heroicons-cpu-chip" class="h-6 w-6 sm:h-8 sm:w-8 text-pink-600 dark:text-pink-400" />
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 leading-relaxed">
|
||||
{{ t('home.tabs.pushDescription') }}
|
||||
</p>
|
||||
<UButton
|
||||
class="mt-3 font-semibold cursor-pointer"
|
||||
color="primary"
|
||||
variant="solid"
|
||||
icon="i-heroicons-cog-6-tooth"
|
||||
@click="openApiSettings"
|
||||
<div class="flex w-full flex-col items-center space-y-6">
|
||||
<div class="grid w-full max-w-2xl gap-3 sm:h-[220px] sm:grid-cols-2">
|
||||
<section
|
||||
class="flex min-h-[210px] flex-col rounded-3xl bg-white/80 p-5 shadow-elevated ring-1 ring-gray-900/[0.04] backdrop-blur-md dark:bg-card-dark dark:ring-white/[0.06]"
|
||||
>
|
||||
{{ t('home.tabs.pushButton') }}
|
||||
</UButton>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex h-10 w-10 items-center justify-center rounded-xl bg-primary-50 dark:bg-primary-500/10">
|
||||
<UIcon name="i-heroicons-cloud-arrow-down" class="h-5 w-5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<span
|
||||
class="rounded-full bg-primary-50 px-2 py-1 text-[10px] font-bold uppercase text-primary-600 dark:bg-primary-500/10 dark:text-primary-400"
|
||||
>
|
||||
Pull
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<h2 class="text-sm font-semibold text-gray-900 dark:text-white">{{ t('home.tabs.autoSyncTitle') }}</h2>
|
||||
<p class="mt-1 text-xs leading-relaxed text-gray-500 dark:text-gray-400">
|
||||
{{ t('home.tabs.autoSyncDescription') }}
|
||||
</p>
|
||||
</div>
|
||||
<UButton class="mt-auto self-start" color="primary" variant="soft" size="sm" @click="openSyncSettings">
|
||||
{{ t('home.tabs.autoSyncButton') }}
|
||||
</UButton>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="flex min-h-[210px] flex-col rounded-3xl bg-white/80 p-5 shadow-elevated ring-1 ring-gray-900/[0.04] backdrop-blur-md dark:bg-card-dark dark:ring-white/[0.06]"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex h-10 w-10 items-center justify-center rounded-xl bg-primary-50 dark:bg-primary-500/10">
|
||||
<UIcon name="i-heroicons-paper-airplane" class="h-5 w-5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<span
|
||||
class="rounded-full bg-primary-50 px-2 py-1 text-[10px] font-bold uppercase text-primary-600 dark:bg-primary-500/10 dark:text-primary-400"
|
||||
>
|
||||
Push
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<h2 class="text-sm font-semibold text-gray-900 dark:text-white">{{ t('home.tabs.apiPushTitle') }}</h2>
|
||||
<p class="mt-1 text-xs leading-relaxed text-gray-500 dark:text-gray-400">
|
||||
{{ t('home.tabs.apiPushDescription') }}
|
||||
</p>
|
||||
</div>
|
||||
<UButton class="mt-auto self-start" color="primary" variant="soft" size="sm" @click="openApiSettings">
|
||||
{{ t('home.tabs.apiPushButton') }}
|
||||
</UButton>
|
||||
</section>
|
||||
</div>
|
||||
<!-- 占位符以对齐文件导入底部的复选框高度,保持 Tab 切换时下方元素位置稳定 -->
|
||||
<div class="h-6 w-full opacity-0 pointer-events-none" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,38 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useLayoutStore } from '@/stores/layout'
|
||||
|
||||
const { t } = useI18n()
|
||||
const layoutStore = useLayoutStore()
|
||||
|
||||
// 打开设置弹窗并定位到自动同步子 Tab
|
||||
function openSyncSettings() {
|
||||
layoutStore.openSettings('api', 'sync')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full flex flex-col items-center space-y-6">
|
||||
<div
|
||||
class="flex h-[190px] sm:h-[220px] w-full max-w-md flex-col items-center justify-center rounded-3xl border border-gray-200/50 bg-gray-100/50 px-8 backdrop-blur-md dark:border-white/10 dark:bg-gray-800/40 select-none text-center sm:px-12"
|
||||
>
|
||||
<div class="mb-2 flex h-10 w-10 sm:h-12 sm:w-12 items-center justify-center">
|
||||
<UIcon name="i-heroicons-cloud-arrow-down" class="h-6 w-6 sm:h-8 sm:w-8 text-pink-600 dark:text-pink-400" />
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 leading-relaxed">
|
||||
{{ t('home.tabs.apiDescription') }}
|
||||
</p>
|
||||
<UButton
|
||||
class="mt-3 font-semibold cursor-pointer"
|
||||
color="primary"
|
||||
variant="solid"
|
||||
icon="i-heroicons-cog-6-tooth"
|
||||
@click="openSyncSettings"
|
||||
>
|
||||
{{ t('home.tabs.apiButton') }}
|
||||
</UButton>
|
||||
</div>
|
||||
<!-- 占位符以对齐文件导入底部的复选框高度,保持 Tab 切换时下方元素位置稳定 -->
|
||||
<div class="h-6 w-full opacity-0 pointer-events-none" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const skillInstallCommand = computed(() => {
|
||||
const skillName = locale.value === 'zh-CN' || locale.value === 'zh-TW' ? 'chatlab-import-cn' : 'chatlab-import'
|
||||
return `npx skills add ChatLab/ChatLab --skill ${skillName} -g`
|
||||
})
|
||||
const cliImportCommand = 'chatlab import ~/Downloads/chat-export.json'
|
||||
const { copy: copySkillCommand, copied: skillCommandCopied } = useClipboard({ copiedDuring: 2000 })
|
||||
const { copy: copyCliCommand, copied: cliCommandCopied } = useClipboard({ copiedDuring: 2000 })
|
||||
|
||||
const importGuideUrl = computed(() => {
|
||||
const prefix = locale.value === 'zh-CN' ? '/cn' : locale.value === 'zh-TW' ? '/tw' : ''
|
||||
return `https://docs.chatlab.fun${prefix}/usage/how-to-import`
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full flex-col items-center space-y-6">
|
||||
<div class="grid w-full max-w-2xl gap-3 sm:h-[220px] sm:grid-cols-2">
|
||||
<section
|
||||
class="flex min-h-[210px] flex-col rounded-3xl bg-white/80 p-5 shadow-elevated ring-1 ring-gray-900/[0.04] backdrop-blur-md dark:bg-card-dark dark:ring-white/[0.06]"
|
||||
>
|
||||
<div class="flex h-10 w-10 items-center justify-center rounded-xl bg-primary-50 dark:bg-primary-500/10">
|
||||
<UIcon name="i-heroicons-sparkles" class="h-5 w-5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<h2 class="text-sm font-semibold text-gray-900 dark:text-white">{{ t('home.tabs.agentImportTitle') }}</h2>
|
||||
<p class="mt-1 text-xs leading-relaxed text-gray-600 dark:text-gray-300">
|
||||
{{ t('home.tabs.agentImportDescription') }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mt-auto flex items-center gap-2 rounded-xl bg-gray-100 px-3 py-2 text-gray-700 ring-1 ring-gray-200/80 dark:bg-gray-900 dark:text-gray-100 dark:ring-white/10"
|
||||
>
|
||||
<code class="min-w-0 flex-1 truncate text-[10px] sm:text-[11px]">{{ skillInstallCommand }}</code>
|
||||
<UButton
|
||||
:icon="skillCommandCopied ? 'i-heroicons-check' : 'i-heroicons-clipboard'"
|
||||
:color="skillCommandCopied ? 'success' : 'neutral'"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
:aria-label="t('home.tabs.copyCommand')"
|
||||
@click="copySkillCommand(skillInstallCommand)"
|
||||
/>
|
||||
</div>
|
||||
<UButton
|
||||
:href="importGuideUrl"
|
||||
target="_blank"
|
||||
color="primary"
|
||||
variant="link"
|
||||
size="xs"
|
||||
icon="i-heroicons-document-text"
|
||||
class="mt-1 self-start px-0"
|
||||
>
|
||||
{{ t('home.tabs.viewDocs') }}
|
||||
</UButton>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="flex min-h-[210px] flex-col rounded-3xl bg-white/80 p-5 shadow-elevated ring-1 ring-gray-900/[0.04] backdrop-blur-md dark:bg-card-dark dark:ring-white/[0.06]"
|
||||
>
|
||||
<div class="flex h-10 w-10 items-center justify-center rounded-xl bg-primary-50 dark:bg-primary-500/10">
|
||||
<UIcon name="i-heroicons-command-line" class="h-5 w-5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<h2 class="text-sm font-semibold text-gray-900 dark:text-white">{{ t('home.tabs.manualCliTitle') }}</h2>
|
||||
<p class="mt-1 text-xs leading-relaxed text-gray-500 dark:text-gray-400">
|
||||
{{ t('home.tabs.manualCliDescription') }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mt-auto flex items-center gap-2 rounded-xl bg-gray-100 px-3 py-2 text-gray-700 ring-1 ring-gray-200/80 dark:bg-gray-900 dark:text-gray-100 dark:ring-white/10"
|
||||
>
|
||||
<code class="min-w-0 flex-1 truncate text-[10px] sm:text-[11px]">{{ cliImportCommand }}</code>
|
||||
<UButton
|
||||
:icon="cliCommandCopied ? 'i-heroicons-check' : 'i-heroicons-clipboard'"
|
||||
:color="cliCommandCopied ? 'success' : 'neutral'"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
:aria-label="t('home.tabs.copyCommand')"
|
||||
@click="copyCliCommand(cliImportCommand)"
|
||||
/>
|
||||
</div>
|
||||
<UButton
|
||||
:href="importGuideUrl"
|
||||
target="_blank"
|
||||
color="primary"
|
||||
variant="link"
|
||||
size="xs"
|
||||
icon="i-heroicons-document-text"
|
||||
class="mt-1 self-start px-0"
|
||||
>
|
||||
{{ t('home.tabs.viewDocs') }}
|
||||
</UButton>
|
||||
</section>
|
||||
</div>
|
||||
<div class="h-6 w-full opacity-0 pointer-events-none" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -878,7 +878,7 @@ const getMergeFileProgressText = (file: MergeFileInfo) =>
|
||||
<!-- 批量导入进度(导入中) -->
|
||||
<div
|
||||
v-if="isBatchImporting && batchFiles.length > 0"
|
||||
class="w-full max-w-4xl rounded-3xl border border-gray-200/50 bg-gray-100/50 px-8 py-6 backdrop-blur-md dark:border-white/10 dark:bg-gray-800/40"
|
||||
class="w-full max-w-4xl rounded-3xl bg-white/80 px-8 py-6 shadow-elevated ring-1 ring-gray-900/[0.04] backdrop-blur-md dark:bg-card-dark dark:ring-white/[0.06]"
|
||||
>
|
||||
<!-- 标题和进度 -->
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
@@ -924,7 +924,7 @@ const getMergeFileProgressText = (file: MergeFileInfo) =>
|
||||
<!-- 合并导入进度 -->
|
||||
<div
|
||||
v-else-if="isMergeImporting && mergeStage !== 'done'"
|
||||
class="w-full max-w-4xl rounded-3xl border border-gray-200/50 bg-gray-100/50 px-8 py-6 backdrop-blur-md dark:border-white/10 dark:bg-gray-800/40"
|
||||
class="w-full max-w-4xl rounded-3xl bg-white/80 px-8 py-6 shadow-elevated ring-1 ring-gray-900/[0.04] backdrop-blur-md dark:bg-card-dark dark:ring-white/[0.06]"
|
||||
>
|
||||
<!-- 标题 -->
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
@@ -977,7 +977,7 @@ const getMergeFileProgressText = (file: MergeFileInfo) =>
|
||||
<!-- 合并导入完成 -->
|
||||
<div
|
||||
v-else-if="isMergeImporting && mergeStage === 'done' && mergeResult"
|
||||
class="w-full max-w-4xl rounded-3xl border border-gray-200/50 bg-gray-100/50 px-8 py-6 backdrop-blur-md dark:border-white/10 dark:bg-gray-800/40"
|
||||
class="w-full max-w-4xl rounded-3xl bg-white/80 px-8 py-6 shadow-elevated ring-1 ring-gray-900/[0.04] backdrop-blur-md dark:bg-card-dark dark:ring-white/[0.06]"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -1005,7 +1005,7 @@ const getMergeFileProgressText = (file: MergeFileInfo) =>
|
||||
<!-- 批量导入结果摘要 -->
|
||||
<div
|
||||
v-else-if="batchImportResult"
|
||||
class="w-full max-w-4xl rounded-3xl border border-gray-200/50 bg-gray-100/50 px-8 py-6 backdrop-blur-md dark:border-white/10 dark:bg-gray-800/40"
|
||||
class="w-full max-w-4xl rounded-3xl bg-white/80 px-8 py-6 shadow-elevated ring-1 ring-gray-900/[0.04] backdrop-blur-md dark:bg-card-dark dark:ring-white/[0.06]"
|
||||
>
|
||||
<!-- 标题 -->
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
@@ -1088,9 +1088,9 @@ const getMergeFileProgressText = (file: MergeFileInfo) =>
|
||||
>
|
||||
<template #default="{ isDragOver }">
|
||||
<div
|
||||
class="group relative flex w-full cursor-pointer flex-col items-center justify-center rounded-3xl border border-gray-200/50 bg-gray-100/50 px-8 h-[190px] sm:h-[220px] backdrop-blur-md transition-all duration-300 hover:border-pink-500/30 hover:bg-gray-100/80 hover:shadow-2xl hover:shadow-pink-500/10 focus:outline-none focus:ring-4 focus:ring-pink-500/20 sm:px-12 dark:border-white/10 dark:bg-gray-800/40 dark:hover:border-pink-500/30 dark:hover:bg-gray-800/60"
|
||||
class="group relative flex w-full cursor-pointer flex-col items-center justify-center rounded-3xl bg-white/80 px-8 h-[190px] sm:h-[220px] shadow-elevated ring-1 ring-gray-900/[0.04] backdrop-blur-md transition-all duration-300 hover:bg-white hover:ring-primary-500/30 hover:shadow-2xl hover:shadow-primary-500/10 focus:outline-none focus:ring-4 focus:ring-primary-500/20 sm:px-12 dark:bg-card-dark dark:ring-white/[0.06] dark:hover:bg-card-dark dark:hover:ring-primary-500/30"
|
||||
:class="{
|
||||
'border-pink-500/50 bg-pink-50/50 dark:border-pink-400/50 dark:bg-pink-500/10':
|
||||
'bg-primary-50/50 ring-primary-500/50 dark:bg-primary-500/10 dark:ring-primary-400/50':
|
||||
isDragOver && !isAnyImporting,
|
||||
'cursor-not-allowed opacity-70': isAnyImporting,
|
||||
}"
|
||||
@@ -1098,15 +1098,19 @@ const getMergeFileProgressText = (file: MergeFileInfo) =>
|
||||
>
|
||||
<!-- 上传图标容器 -->
|
||||
<div
|
||||
class="mb-5 flex h-16 w-16 items-center justify-center rounded-2xl transition-transform duration-300 group-hover:scale-105"
|
||||
class="mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-primary-50 transition-transform duration-300 group-hover:scale-105 dark:bg-primary-500/10"
|
||||
:class="{ 'scale-105': isDragOver && !isAnyImporting, 'animate-pulse': isImporting }"
|
||||
>
|
||||
<UIcon
|
||||
v-if="!isImporting"
|
||||
name="i-heroicons-arrow-up-tray"
|
||||
class="h-8 w-8 text-pink-600 transition-transform duration-200 group-hover:-translate-y-1 dark:text-pink-400"
|
||||
class="h-5 w-5 text-primary-600 dark:text-primary-400"
|
||||
/>
|
||||
<UIcon
|
||||
v-else
|
||||
name="i-heroicons-arrow-path"
|
||||
class="h-5 w-5 animate-spin text-primary-600 dark:text-primary-400"
|
||||
/>
|
||||
<UIcon v-else name="i-heroicons-arrow-path" class="h-8 w-8 animate-spin text-pink-600 dark:text-pink-400" />
|
||||
</div>
|
||||
|
||||
<!-- Text -->
|
||||
|
||||
@@ -3,11 +3,11 @@ import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: 'file' | 'api' | 'push'
|
||||
modelValue: 'file' | 'api' | 'cli'
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: 'file' | 'api' | 'push']
|
||||
'update:modelValue': [value: 'file' | 'api' | 'cli']
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -15,8 +15,8 @@ const { t } = useI18n()
|
||||
// 定义切换栏的三个固定选项
|
||||
const tabs = [
|
||||
{ id: 'file', labelKey: 'home.tabs.file' },
|
||||
{ id: 'cli', labelKey: 'home.tabs.cli' },
|
||||
{ id: 'api', labelKey: 'home.tabs.api' },
|
||||
{ id: 'push', labelKey: 'home.tabs.push' },
|
||||
] as const
|
||||
|
||||
// 缓存按钮引用以计算滑动偏移
|
||||
|
||||
@@ -9,8 +9,8 @@ import AgreementModal from './components/AgreementModal.vue'
|
||||
import MigrationModal from './components/MigrationModal.vue'
|
||||
import ImportArea from './components/import/ImportArea.vue'
|
||||
import ImportTabSelector from './components/import/ImportTabSelector.vue'
|
||||
import AutoSyncCard from './components/import/AutoSyncCard.vue'
|
||||
import ApiImportCard from './components/import/ApiImportCard.vue'
|
||||
import CliImportCard from './components/import/CliImportCard.vue'
|
||||
import ChangelogModal from './components/ChangelogModal.vue'
|
||||
import HomeFooter from './components/HomeFooter.vue'
|
||||
import DemoImportButton from './components/DemoImportButton.vue'
|
||||
@@ -19,7 +19,7 @@ const { t, locale } = useI18n()
|
||||
const sessionStore = useSessionStore()
|
||||
|
||||
// 导入方式选中的 Tab 状态
|
||||
const activeTab = ref<'file' | 'api' | 'push'>('file')
|
||||
const activeTab = ref<'file' | 'api' | 'cli'>('file')
|
||||
|
||||
// 首页入场动效:挂载后通过 requestAnimationFrame 触发,确保初始透明态已完成渲染
|
||||
const isMounted = ref(false)
|
||||
@@ -94,11 +94,11 @@ const tutorialExportUrl = computed(() => {
|
||||
<!-- 文件导入区域 -->
|
||||
<ImportArea v-if="activeTab === 'file'" />
|
||||
|
||||
<!-- 自动同步区域 -->
|
||||
<AutoSyncCard v-else-if="activeTab === 'api'" />
|
||||
<!-- API 导入区域:统一承载自动拉取与 API 推送 -->
|
||||
<ApiImportCard v-else-if="activeTab === 'api'" />
|
||||
|
||||
<!-- API 推送区域 -->
|
||||
<ApiImportCard v-else-if="activeTab === 'push'" />
|
||||
<!-- CLI 导入区域:Agent Skill 与手动 CLI -->
|
||||
<CliImportCard v-else-if="activeTab === 'cli'" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
Reference in new issue
Block a user