|
|
@@ -1,49 +1,95 @@
|
|
|
<template>
|
|
|
<div class="ai-page ai-plugin">
|
|
|
- <a-tabs v-model:active-key="activeTab" class="ai-plugin__tabs" size="small" destroy-inactive-tab-pane>
|
|
|
- <template #rightExtra>
|
|
|
- <div class="ai-topbar__actions">
|
|
|
- <span class="ai-topbar__meta ai-plugin__summary">{{ summary }}</span>
|
|
|
- <a-button :loading="loading" @click="reload">
|
|
|
- <template #icon><Icon icon="mdi:refresh" :size="14" /></template>
|
|
|
- 刷新
|
|
|
- </a-button>
|
|
|
- <!-- 顶栏主操作按页签提供(与模型页同构):技能=新增技能 + 上传 zip 安装,工具包=新建工具包;工具只读无顶栏操作 -->
|
|
|
- <a-button v-if="activeTab === 'skill'" :disabled="installing" @click="openSkillCreator">
|
|
|
+ <!-- 顶部工具卡:页签切换 + 全局摘要 + 当前页签操作(视觉对齐自主模式「持续联系」页) -->
|
|
|
+ <section class="ai-plugin__toolbar">
|
|
|
+ <div class="ai-plugin__card-title">插件与技能</div>
|
|
|
+ <div class="ai-plugin__tabs" role="tablist" aria-label="插件与技能分类">
|
|
|
+ <button
|
|
|
+ v-for="tab in TABS"
|
|
|
+ :key="tab.key"
|
|
|
+ type="button"
|
|
|
+ role="tab"
|
|
|
+ class="ai-plugin__tab"
|
|
|
+ :class="{ 'is-on': activeTab === tab.key }"
|
|
|
+ :aria-selected="activeTab === tab.key"
|
|
|
+ @click="activeTab = tab.key"
|
|
|
+ >
|
|
|
+ <Icon :icon="tab.icon" :size="14" />
|
|
|
+ <span>{{ tab.label }}</span>
|
|
|
+ <span class="ai-plugin__tab-count">{{ tabCount(tab.key) }}</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <span class="ai-plugin__summary">{{ summary }}</span>
|
|
|
+ <span class="ai-plugin__spacer"></span>
|
|
|
+
|
|
|
+ <div class="ai-plugin__actions">
|
|
|
+ <a-button :loading="loading" @click="reload">
|
|
|
+ <template #icon><Icon icon="mdi:refresh" :size="14" /></template>
|
|
|
+ 刷新
|
|
|
+ </a-button>
|
|
|
+ <!-- 主操作按页签提供:技能=新增/上传 zip,工具包=新建;工具只读无操作 -->
|
|
|
+ <template v-if="activeTab === 'skill'">
|
|
|
+ <a-button :disabled="installing" @click="openSkillCreator">
|
|
|
<template #icon><Icon icon="mdi:plus" :size="14" /></template>
|
|
|
新增技能
|
|
|
</a-button>
|
|
|
- <a-button
|
|
|
- v-if="activeTab === 'skill'"
|
|
|
- type="primary"
|
|
|
- :disabled="installing"
|
|
|
- :loading="installing"
|
|
|
- @click="pickZip"
|
|
|
- >
|
|
|
+ <a-button type="primary" :disabled="installing" :loading="installing" @click="pickZip">
|
|
|
<template #icon><Icon icon="mdi:upload" :size="14" /></template>
|
|
|
上传 zip 安装
|
|
|
</a-button>
|
|
|
- <a-button v-else-if="activeTab === 'pack'" type="primary" @click="openPackEditor(null)">
|
|
|
- <template #icon><Icon icon="mdi:plus" :size="14" /></template>
|
|
|
- 新建工具包
|
|
|
- </a-button>
|
|
|
- <input ref="fileRef" class="ai-plugin__file" type="file" accept=".zip" @change="onZipPicked" />
|
|
|
- </div>
|
|
|
- </template>
|
|
|
-
|
|
|
- <!-- ── ① 技能(SKILL.md 包) ──────────────────────────────── -->
|
|
|
- <a-tab-pane key="skill">
|
|
|
- <template #tab>
|
|
|
- <span>技能</span>
|
|
|
- <span class="ai-plugin__tab-count">{{ skills.length }}</span>
|
|
|
</template>
|
|
|
+ <a-button v-else-if="activeTab === 'pack'" type="primary" @click="openPackEditor(null)">
|
|
|
+ <template #icon><Icon icon="mdi:plus" :size="14" /></template>
|
|
|
+ 新建工具包
|
|
|
+ </a-button>
|
|
|
+ </div>
|
|
|
+ <input ref="fileRef" class="ai-plugin__file" type="file" accept=".zip" @change="onZipPicked" />
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- 内容卡:标题/说明 + 页签内筛选 + 卡片网格 -->
|
|
|
+ <section class="ai-plugin__panel">
|
|
|
+ <div class="ai-plugin__panel-head">
|
|
|
+ <span class="ai-plugin__card-title">{{ activeTabMeta.label }}</span>
|
|
|
+ <span class="ai-plugin__panel-desc">{{ activeTabMeta.desc }}</span>
|
|
|
+ <span class="ai-plugin__spacer"></span>
|
|
|
+
|
|
|
+ <div v-if="activeTab === 'tool'" class="ai-plugin__filters">
|
|
|
+ <a-select
|
|
|
+ v-model:value="groupFilter"
|
|
|
+ class="ai-plugin__select"
|
|
|
+ :options="groupOptions"
|
|
|
+ placeholder="全部分类"
|
|
|
+ />
|
|
|
+ <a-input
|
|
|
+ v-model:value="toolKeyword"
|
|
|
+ class="ai-plugin__search"
|
|
|
+ allow-clear
|
|
|
+ placeholder="搜索工具,如 夜间通话"
|
|
|
+ >
|
|
|
+ <template #prefix><Icon icon="mdi:magnify" :size="14" /></template>
|
|
|
+ </a-input>
|
|
|
+ <span class="ai-plugin__filter-count">
|
|
|
+ {{
|
|
|
+ filteredTools.length === tools.length
|
|
|
+ ? `共 ${tools.length} 个`
|
|
|
+ : `匹配 ${filteredTools.length} / ${tools.length} 个`
|
|
|
+ }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <span v-else class="ai-plugin__panel-count">{{ activeTabMeta.countText }}</span>
|
|
|
+ </div>
|
|
|
|
|
|
- <a-spin :spinning="loading">
|
|
|
+ <div class="ai-plugin__panel-body">
|
|
|
+ <!-- ── ① 技能(SKILL.md 包) ──────────────────────────────── -->
|
|
|
+ <a-spin v-if="activeTab === 'skill'" :spinning="loading">
|
|
|
<div v-if="skills.length" class="ai-plugin__grid">
|
|
|
<article v-for="skill in skills" :key="skill.path || skill.name" class="ai-plugin__card">
|
|
|
<div class="ai-plugin__card-head">
|
|
|
- <div class="ai-plugin__name">
|
|
|
- <span class="ai-plugin__ellipsis" :title="skill.name">{{ skill.name }}</span>
|
|
|
+ <span class="ai-plugin__card-icon">
|
|
|
+ <Icon icon="mdi:script-text-outline" :size="18" />
|
|
|
+ </span>
|
|
|
+ <div class="ai-plugin__card-heading">
|
|
|
+ <span class="ai-plugin__name" :title="skill.name">{{ skill.name }}</span>
|
|
|
<span class="ai-plugin__chip" :class="sourceMeta(skill).cls">{{ sourceMeta(skill).label }}</span>
|
|
|
</div>
|
|
|
<a-switch
|
|
|
@@ -52,187 +98,89 @@
|
|
|
size="small"
|
|
|
@change="(checked) => onToggle(skill, checked === true)"
|
|
|
/>
|
|
|
- <span v-else class="ai-plugin__status is-off">只读</span>
|
|
|
+ <span v-else class="ai-plugin__chip">只读</span>
|
|
|
</div>
|
|
|
|
|
|
<p class="ai-plugin__card-desc">{{ skill.description || '(SKILL.md 里没有 description)' }}</p>
|
|
|
|
|
|
<div class="ai-plugin__card-ops">
|
|
|
- <a-button type="link" size="small" @click="openSkill(skill)">查看详情</a-button>
|
|
|
- <a-button v-if="skill.managed" type="link" size="small" @click="openSkillEditor(skill)">
|
|
|
+ <button type="button" class="ai-plugin__op" @click="openSkill(skill)">查看详情</button>
|
|
|
+ <button v-if="skill.managed" type="button" class="ai-plugin__op" @click="openSkillEditor(skill)">
|
|
|
编辑
|
|
|
- </a-button>
|
|
|
- <a-button v-if="skill.managed" type="link" size="small" danger @click="confirmRemove(skill)">
|
|
|
- 卸载
|
|
|
- </a-button>
|
|
|
- </div>
|
|
|
- </article>
|
|
|
- </div>
|
|
|
- <div v-else class="ai-plugin__empty">
|
|
|
- 还没有技能。点右上角「新增技能」在页面上直接创建,或上传一个包含 <b>SKILL.md</b> 的 zip
|
|
|
- 安装(包里只有一层技能目录时最省事)。
|
|
|
- </div>
|
|
|
- </a-spin>
|
|
|
- </a-tab-pane>
|
|
|
-
|
|
|
- <!-- ── ② 工具包(用户自定义工具组合,新建研判方案时勾选) ───────── -->
|
|
|
- <a-tab-pane key="pack">
|
|
|
- <template #tab>
|
|
|
- <span>工具包</span>
|
|
|
- <span class="ai-plugin__tab-count">{{ packs.length }}</span>
|
|
|
- </template>
|
|
|
-
|
|
|
- <!-- 工具包编辑器:新建/修改共用,默认窗口弹框(可右上角全屏);工具按业务组以卡片勾选(基础工具始终可用、不进包) -->
|
|
|
- <BasicModal
|
|
|
- :open="packEditor.open"
|
|
|
- :title="packEditor.editing ? `修改工具包:${packEditor.name}` : '新建工具包'"
|
|
|
- :width="1100"
|
|
|
- wrap-class-name="ai-pack-modal"
|
|
|
- :confirm-loading="packEditor.saving"
|
|
|
- ok-text="保存"
|
|
|
- cancel-text="取消"
|
|
|
- @ok="savePack"
|
|
|
- @cancel="closePackEditor"
|
|
|
- >
|
|
|
- <div class="ai-plugin__pack-form">
|
|
|
- <div class="ai-plugin__pack-row">
|
|
|
- <label class="ai-plugin__pack-label"><i class="ai-plugin__req">*</i>工具包名</label>
|
|
|
- <input
|
|
|
- v-model="packEditor.name"
|
|
|
- class="ai-plugin__pack-input"
|
|
|
- :maxlength="100"
|
|
|
- placeholder="工具包名(如:影子公司穿透)"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div class="ai-plugin__pack-row ai-plugin__pack-row--top">
|
|
|
- <label class="ai-plugin__pack-label">描述</label>
|
|
|
- <textarea
|
|
|
- v-model="packEditor.description"
|
|
|
- class="ai-plugin__pack-input ai-plugin__pack-textarea"
|
|
|
- :maxlength="500"
|
|
|
- placeholder="一句话描述(可选,会出现在新建研判方案的勾选卡片上)"
|
|
|
- ></textarea>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="ai-plugin__pack-section">
|
|
|
- <div class="ai-plugin__pack-section-head">
|
|
|
- <span class="ai-plugin__pack-section-title">选择工具</span>
|
|
|
- <span class="ai-plugin__pack-count">{{ packEditorCount ? `已选 ${packEditorCount} 个` : '未选择' }}</span>
|
|
|
- <span class="ai-plugin__pack-section-sp"></span>
|
|
|
- <label class="ai-plugin__search">
|
|
|
- <Icon icon="mdi:magnify" :size="14" />
|
|
|
- <input v-model="packEditor.keyword" type="text" placeholder="搜索工具,如 大额交易" />
|
|
|
- </label>
|
|
|
- </div>
|
|
|
- <div class="ai-plugin__pack-hint"
|
|
|
- >点卡片勾选工具;基础工具(如「执行 SQL 查询」)始终可用、不进工具包。</div
|
|
|
- >
|
|
|
-
|
|
|
- <div v-for="g in packToolGroups" :key="g.label" class="ai-plugin__pack-group">
|
|
|
- <div class="ai-plugin__pack-group-title">
|
|
|
- {{ g.label }}
|
|
|
- <span class="ai-plugin__chip">{{ g.tools.length }} 个</span>
|
|
|
- <span class="ai-plugin__pack-group-sp"></span>
|
|
|
+ </button>
|
|
|
+ <span class="ai-plugin__ops-sp"></span>
|
|
|
<button
|
|
|
+ v-if="skill.managed"
|
|
|
type="button"
|
|
|
- class="ai-plugin__pack-all"
|
|
|
- :class="{ 'is-on': isGroupAllOn(g) }"
|
|
|
- @click="toggleGroupAll(g)"
|
|
|
+ class="ai-plugin__op is-danger"
|
|
|
+ @click="confirmRemove(skill)"
|
|
|
>
|
|
|
- <span class="ai-plugin__pack-ck"><Icon icon="mdi:check" :size="11" /></span>
|
|
|
- 全选
|
|
|
+ 卸载
|
|
|
</button>
|
|
|
</div>
|
|
|
- <div class="ai-plugin__pack-cards">
|
|
|
- <div
|
|
|
- v-for="t in g.tools"
|
|
|
- :key="t.toolName"
|
|
|
- class="ai-plugin__pack-card"
|
|
|
- :class="{ 'is-on': packEditor.checked[t.toolName] }"
|
|
|
- @click="packEditor.checked[t.toolName] = !packEditor.checked[t.toolName]"
|
|
|
- >
|
|
|
- <span class="ai-plugin__pack-ck"><Icon icon="mdi:check" :size="11" /></span>
|
|
|
- <div class="ai-plugin__pack-card-name">{{ toolLabel(t.toolName) }}</div>
|
|
|
- <div class="ai-plugin__pack-card-desc">{{ t.description || '(无说明)' }}</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div v-if="!packToolGroups.length" class="ai-plugin__empty">没有匹配的工具。</div>
|
|
|
+ </article>
|
|
|
</div>
|
|
|
- </BasicModal>
|
|
|
+ <EmptyState
|
|
|
+ v-else
|
|
|
+ title="还没有技能"
|
|
|
+ description="点右上角「新增技能」在页面上直接创建,或上传一个包含 SKILL.md 的 zip 安装。"
|
|
|
+ :min-height="300"
|
|
|
+ description-width="min(100%, 520px)"
|
|
|
+ />
|
|
|
+ </a-spin>
|
|
|
|
|
|
- <a-spin :spinning="packsLoading">
|
|
|
+ <!-- ── ② 工具包(用户自定义工具组合,新建研判方案时勾选) ───────── -->
|
|
|
+ <a-spin v-else-if="activeTab === 'pack'" :spinning="packsLoading">
|
|
|
<div v-if="packs.length" class="ai-plugin__grid">
|
|
|
<article v-for="p in packs" :key="p.id" class="ai-plugin__card is-clickable" @click="openPack(p)">
|
|
|
- <!-- 右上角来源角标(与研判方案卡同构);旧后端无 builtin 字段时不显示,避免误标 -->
|
|
|
- <span
|
|
|
- v-if="p.builtin !== undefined"
|
|
|
- class="ai-plugin__pack-badge"
|
|
|
- :class="p.builtin ? 'is-builtin' : 'is-custom'"
|
|
|
- >
|
|
|
- {{ p.builtin ? '内置' : '自定义' }}
|
|
|
- </span>
|
|
|
- <div class="ai-plugin__card-head ai-plugin__pack-head">
|
|
|
- <div class="ai-plugin__name">
|
|
|
- <span class="ai-plugin__ellipsis" :title="p.name">{{ p.name }}</span>
|
|
|
- <span class="ai-plugin__chip">{{ p.toolCount }} 个工具</span>
|
|
|
+ <div class="ai-plugin__card-head">
|
|
|
+ <span class="ai-plugin__card-icon is-pack">
|
|
|
+ <Icon icon="mdi:package-variant-closed" :size="18" />
|
|
|
+ </span>
|
|
|
+ <div class="ai-plugin__card-heading">
|
|
|
+ <div class="ai-plugin__name-row">
|
|
|
+ <span class="ai-plugin__name" :title="p.name">{{ p.name }}</span>
|
|
|
+ <!-- 旧后端无 builtin 字段时不显示,避免误标 -->
|
|
|
+ <span v-if="p.builtin !== undefined" class="ai-plugin__chip" :class="p.builtin ? '' : 'is-primary'">
|
|
|
+ {{ p.builtin ? '内置' : '自定义' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <span class="ai-plugin__card-sub">{{ p.toolCount }} 个工具</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
<p class="ai-plugin__card-desc">{{ p.description || '(无描述)' }}</p>
|
|
|
+
|
|
|
<div class="ai-plugin__card-tools">
|
|
|
- <span v-for="n in p.toolNames.slice(0, 4)" :key="n" class="ai-plugin__chip">{{ toolLabel(n) }}</span>
|
|
|
- <span v-if="p.toolNames.length > 4" class="ai-plugin__chip">+{{ p.toolNames.length - 4 }}</span>
|
|
|
+ <span v-for="n in p.toolNames.slice(0, 4)" :key="n" class="ai-plugin__chip is-soft">
|
|
|
+ {{ toolLabel(n) }}
|
|
|
+ </span>
|
|
|
+ <span v-if="p.toolNames.length > 4" class="ai-plugin__chip is-soft">
|
|
|
+ +{{ p.toolNames.length - 4 }}
|
|
|
+ </span>
|
|
|
</div>
|
|
|
+
|
|
|
<div class="ai-plugin__card-ops" @click.stop>
|
|
|
- <a-button type="link" size="small" @click="openPack(p)">查看工具</a-button>
|
|
|
- <span class="ai-plugin__ops-sp"></span>
|
|
|
- <!-- 内置工具包不可编辑/删除(入口不渲染,后端另有双重校验) -->
|
|
|
+ <button type="button" class="ai-plugin__op" @click="openPack(p)">查看工具</button>
|
|
|
<template v-if="p.mine">
|
|
|
- <a-button type="link" size="small" @click="openPackEditor(p)">编辑</a-button>
|
|
|
- <a-button type="link" size="small" danger @click="confirmDeletePack(p)">删除</a-button>
|
|
|
+ <button type="button" class="ai-plugin__op" @click="openPackEditor(p)">编辑</button>
|
|
|
+ <span class="ai-plugin__ops-sp"></span>
|
|
|
+ <button type="button" class="ai-plugin__op is-danger" @click="confirmDeletePack(p)"> 删除 </button>
|
|
|
</template>
|
|
|
</div>
|
|
|
</article>
|
|
|
</div>
|
|
|
- <div v-else class="ai-plugin__empty">
|
|
|
- 还没有工具包。点右上角「新建工具包」,把任意工具组合成可复用的工具包,新建研判方案时可整体勾选。
|
|
|
- </div>
|
|
|
- </a-spin>
|
|
|
- </a-tab-pane>
|
|
|
-
|
|
|
- <!-- ── ③ 工具(只读,数据来自 agent_tool 表) ─────────────── -->
|
|
|
- <a-tab-pane key="tool">
|
|
|
- <template #tab>
|
|
|
- <span>工具</span>
|
|
|
- <span class="ai-plugin__tab-count">{{ tools.length }}</span>
|
|
|
- </template>
|
|
|
-
|
|
|
- <div class="ai-plugin__ops">
|
|
|
- <a-select
|
|
|
- v-model:value="groupFilter"
|
|
|
- class="ai-plugin__select"
|
|
|
- :options="groupOptions"
|
|
|
- placeholder="全部分类"
|
|
|
+ <EmptyState
|
|
|
+ v-else
|
|
|
+ title="还没有工具包"
|
|
|
+ description="点右上角「新建工具包」,把任意工具组合成可复用的工具包,新建研判方案时可整体勾选。"
|
|
|
+ :min-height="300"
|
|
|
+ description-width="min(100%, 560px)"
|
|
|
/>
|
|
|
- <a-input
|
|
|
- v-model:value="toolKeyword"
|
|
|
- class="ai-plugin__search"
|
|
|
- allow-clear
|
|
|
- placeholder="搜索工具,如 夜间通话"
|
|
|
- >
|
|
|
- <template #prefix><Icon icon="mdi:magnify" :size="14" /></template>
|
|
|
- </a-input>
|
|
|
- <span class="ai-plugin__filter-count">
|
|
|
- {{
|
|
|
- filteredTools.length === tools.length
|
|
|
- ? `共 ${tools.length} 个`
|
|
|
- : `匹配 ${filteredTools.length} / ${tools.length} 个`
|
|
|
- }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
+ </a-spin>
|
|
|
|
|
|
- <a-spin :spinning="loading">
|
|
|
+ <!-- ── ③ 工具(只读,数据来自 agent_tool 表) ─────────────── -->
|
|
|
+ <a-spin v-else :spinning="loading">
|
|
|
<div v-if="filteredTools.length" class="ai-plugin__grid">
|
|
|
<article
|
|
|
v-for="tool in filteredTools"
|
|
|
@@ -241,25 +189,118 @@
|
|
|
@click="openTool(tool)"
|
|
|
>
|
|
|
<div class="ai-plugin__card-head">
|
|
|
- <span class="ai-plugin__tool-name ai-plugin__ellipsis">{{ toolLabel(tool.toolName) }}</span>
|
|
|
+ <span class="ai-plugin__card-icon is-tool">
|
|
|
+ <Icon icon="mdi:tools" :size="18" />
|
|
|
+ </span>
|
|
|
+ <div class="ai-plugin__card-heading">
|
|
|
+ <span class="ai-plugin__name" :title="tool.toolName">{{ toolLabel(tool.toolName) }}</span>
|
|
|
+ <span class="ai-plugin__card-sub">{{ tool.toolName }}</span>
|
|
|
+ </div>
|
|
|
<span v-if="tool.readOnly" class="ai-plugin__chip">内置</span>
|
|
|
</div>
|
|
|
|
|
|
<div class="ai-plugin__card-meta">
|
|
|
- <span class="ai-plugin__chip">{{ tool.groupLabel || tool.groupCode || '未分组' }}</span>
|
|
|
+ <span class="ai-plugin__chip is-soft">{{ tool.groupLabel || tool.groupCode || '未分组' }}</span>
|
|
|
</div>
|
|
|
|
|
|
<p class="ai-plugin__card-desc">{{ tool.description || '(无说明)' }}</p>
|
|
|
+
|
|
|
+ <div class="ai-plugin__card-ops">
|
|
|
+ <span class="ai-plugin__link">
|
|
|
+ 查看详情
|
|
|
+ <Icon icon="mdi:chevron-right" :size="14" />
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
</article>
|
|
|
</div>
|
|
|
- <div v-else class="ai-plugin__empty">
|
|
|
- {{
|
|
|
- tools.length ? '没有匹配的工具' : '还没有同步到工具清单(服务端启动时会自动同步,需先建 agent_tool 表)'
|
|
|
- }}
|
|
|
- </div>
|
|
|
+ <EmptyState
|
|
|
+ v-else
|
|
|
+ :title="tools.length ? '没有匹配的工具' : '还没有同步到工具清单'"
|
|
|
+ :description="tools.length ? '换个分类或关键字再试试。' : '服务端启动时会自动同步,需先建 agent_tool 表。'"
|
|
|
+ :min-height="300"
|
|
|
+ description-width="min(100%, 520px)"
|
|
|
+ />
|
|
|
</a-spin>
|
|
|
- </a-tab-pane>
|
|
|
- </a-tabs>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- 弹框:工具包编辑器(新建/修改共用;工具按业务组以卡片勾选) -->
|
|
|
+ <BasicModal
|
|
|
+ :open="packEditor.open"
|
|
|
+ :title="packEditor.editing ? `修改工具包:${packEditor.name}` : '新建工具包'"
|
|
|
+ :width="1100"
|
|
|
+ wrap-class-name="ai-pack-modal"
|
|
|
+ :confirm-loading="packEditor.saving"
|
|
|
+ ok-text="保存"
|
|
|
+ cancel-text="取消"
|
|
|
+ @ok="savePack"
|
|
|
+ @cancel="closePackEditor"
|
|
|
+ >
|
|
|
+ <div class="ai-plugin__pack-form">
|
|
|
+ <div class="ai-plugin__pack-row">
|
|
|
+ <label class="ai-plugin__pack-label"><i class="ai-plugin__req">*</i>工具包名</label>
|
|
|
+ <input
|
|
|
+ v-model="packEditor.name"
|
|
|
+ class="ai-plugin__pack-input"
|
|
|
+ :maxlength="100"
|
|
|
+ placeholder="工具包名(如:影子公司穿透)"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="ai-plugin__pack-row ai-plugin__pack-row--top">
|
|
|
+ <label class="ai-plugin__pack-label">描述</label>
|
|
|
+ <textarea
|
|
|
+ v-model="packEditor.description"
|
|
|
+ class="ai-plugin__pack-input ai-plugin__pack-textarea"
|
|
|
+ :maxlength="500"
|
|
|
+ placeholder="一句话描述(可选,会出现在新建研判方案的勾选卡片上)"
|
|
|
+ ></textarea>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="ai-plugin__pack-section">
|
|
|
+ <div class="ai-plugin__pack-section-head">
|
|
|
+ <span class="ai-plugin__pack-section-title">选择工具</span>
|
|
|
+ <span class="ai-plugin__pack-count">{{ packEditorCount ? `已选 ${packEditorCount} 个` : '未选择' }}</span>
|
|
|
+ <span class="ai-plugin__pack-section-sp"></span>
|
|
|
+ <label class="ai-plugin__pack-search">
|
|
|
+ <Icon icon="mdi:magnify" :size="14" />
|
|
|
+ <input v-model="packEditor.keyword" type="text" placeholder="搜索工具,如 大额交易" />
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+ <div class="ai-plugin__pack-hint">点卡片勾选工具;基础工具(如「执行 SQL 查询」)始终可用、不进工具包。</div>
|
|
|
+
|
|
|
+ <div v-for="g in packToolGroups" :key="g.label" class="ai-plugin__pack-group">
|
|
|
+ <div class="ai-plugin__pack-group-title">
|
|
|
+ {{ g.label }}
|
|
|
+ <span class="ai-plugin__chip">{{ g.tools.length }} 个</span>
|
|
|
+ <span class="ai-plugin__pack-group-sp"></span>
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="ai-plugin__pack-all"
|
|
|
+ :class="{ 'is-on': isGroupAllOn(g) }"
|
|
|
+ @click="toggleGroupAll(g)"
|
|
|
+ >
|
|
|
+ <span class="ai-plugin__pack-ck"><Icon icon="mdi:check" :size="11" /></span>
|
|
|
+ 全选
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="ai-plugin__pack-cards">
|
|
|
+ <div
|
|
|
+ v-for="t in g.tools"
|
|
|
+ :key="t.toolName"
|
|
|
+ class="ai-plugin__pack-card"
|
|
|
+ :class="{ 'is-on': packEditor.checked[t.toolName] }"
|
|
|
+ @click="packEditor.checked[t.toolName] = !packEditor.checked[t.toolName]"
|
|
|
+ >
|
|
|
+ <span class="ai-plugin__pack-ck"><Icon icon="mdi:check" :size="11" /></span>
|
|
|
+ <div class="ai-plugin__pack-card-name">{{ toolLabel(t.toolName) }}</div>
|
|
|
+ <div class="ai-plugin__pack-card-desc">{{ t.description || '(无说明)' }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="!packToolGroups.length" class="ai-plugin__pack-empty">没有匹配的工具。</div>
|
|
|
+ </div>
|
|
|
+ </BasicModal>
|
|
|
|
|
|
<!-- 弹框:工具详情(说明 + 所属工具组说明) -->
|
|
|
<a-modal
|
|
|
@@ -350,8 +391,9 @@
|
|
|
* 原「MCP 接入」页签已按需求移除(服务端 MCP Server 能力本身保留,仅不再在页面展示)。
|
|
|
*/
|
|
|
import { computed, onMounted, reactive, ref } from 'vue';
|
|
|
- import { Button, Checkbox, Input, Modal, Select, Spin, Switch, Tabs } from 'ant-design-vue';
|
|
|
+ import { Button, Input, Modal, Select, Spin, Switch } from 'ant-design-vue';
|
|
|
import { BasicModal } from '@/core/components/Modal';
|
|
|
+ import { EmptyState } from '@/core/components/EmptyState';
|
|
|
import Icon from '@/core/components/Icon/src/Icon.vue';
|
|
|
import { useMessage } from '@/core/hooks/web/useMessage';
|
|
|
import { useUserStore } from '@/core/store/modules/case';
|
|
|
@@ -363,10 +405,7 @@
|
|
|
import SkillEditorModal from '../../components/SkillEditorModal.vue';
|
|
|
import type { AgentTool, SaveToolPackParams, Skill, ToolPack } from '../../api/types';
|
|
|
|
|
|
- const ATabs = Tabs;
|
|
|
- const ATabPane = Tabs.TabPane;
|
|
|
const AButton = Button;
|
|
|
- const ACheckbox = Checkbox;
|
|
|
const AInput = Input;
|
|
|
const AModal = Modal;
|
|
|
const ASelect = Select;
|
|
|
@@ -407,6 +446,41 @@
|
|
|
() => `${skills.value.length} 个技能 · ${packs.value.length} 个工具包 · ${tools.value.length} 个工具`,
|
|
|
);
|
|
|
|
|
|
+ /** 页签元数据:顶部切换按钮与内容卡标题/说明共用一份,避免两处文案漂移 */
|
|
|
+ const TABS: Array<{ key: PluginTab; label: string; icon: string; desc: string }> = [
|
|
|
+ {
|
|
|
+ key: 'skill',
|
|
|
+ label: '技能',
|
|
|
+ icon: 'mdi:script-text-outline',
|
|
|
+ desc: '用 SKILL.md 描述任务步骤,AI 在对话中按需加载。',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'pack',
|
|
|
+ label: '工具包',
|
|
|
+ icon: 'mdi:package-variant-closed',
|
|
|
+ desc: '把常用工具组合成可复用的工具包,新建研判方案时整体勾选。',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'tool',
|
|
|
+ label: '工具',
|
|
|
+ icon: 'mdi:tools',
|
|
|
+ desc: '服务端注册的可用工具清单,按分类筛选查看工具说明。',
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ const activeTabMeta = computed(() => {
|
|
|
+ const meta = TABS.find((t) => t.key === activeTab.value) ?? TABS[0];
|
|
|
+ if (meta.key === 'skill') return { ...meta, countText: `共 ${skills.value.length} 个技能` };
|
|
|
+ if (meta.key === 'pack') return { ...meta, countText: `共 ${packs.value.length} 个工具包` };
|
|
|
+ return { ...meta, countText: `共 ${tools.value.length} 个工具` };
|
|
|
+ });
|
|
|
+
|
|
|
+ function tabCount(key: PluginTab) {
|
|
|
+ if (key === 'skill') return skills.value.length;
|
|
|
+ if (key === 'pack') return packs.value.length;
|
|
|
+ return tools.value.length;
|
|
|
+ }
|
|
|
+
|
|
|
/** 已安装技能名:新增技能弹框创建前做同名预判(服务端仍会再校验一次) */
|
|
|
const skillNames = computed(() => skills.value.map((s) => s.name));
|
|
|
|
|
|
@@ -1355,9 +1429,632 @@
|
|
|
-webkit-box-orient: vertical;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
+
|
|
|
+ /* ======================================================================
|
|
|
+ * 卡片化改版:对齐自主模式「持续联系」页的视觉规范
|
|
|
+ * 页面底 #f4f6fa · 白卡圆角 14 · 描边 #e7ebf3 · 标题蓝条 · 主按钮 #2a50ec
|
|
|
+ * ==================================================================== */
|
|
|
+ .ai-page.ai-plugin {
|
|
|
+ gap: 8px;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ min-height: 0;
|
|
|
+ margin: 0;
|
|
|
+ padding: 8px 10px;
|
|
|
+ background: #f4f6fa;
|
|
|
+ border: 0;
|
|
|
+ border-radius: 0;
|
|
|
+ box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-plugin {
|
|
|
+ &__toolbar {
|
|
|
+ display: flex;
|
|
|
+ flex: 0 0 auto;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ min-height: 52px;
|
|
|
+ padding: 10px 14px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #e7ebf3;
|
|
|
+ border-radius: 14px;
|
|
|
+ box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__card-title {
|
|
|
+ position: relative;
|
|
|
+ flex: 0 0 auto;
|
|
|
+ padding-left: 11px;
|
|
|
+ color: #111827;
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 22px;
|
|
|
+ white-space: nowrap;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 4px;
|
|
|
+ bottom: 4px;
|
|
|
+ left: 0;
|
|
|
+ width: 3px;
|
|
|
+ background: #2a50ec;
|
|
|
+ border-radius: 2px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &__tabs {
|
|
|
+ display: inline-flex;
|
|
|
+ flex: 0 0 auto;
|
|
|
+ align-items: center;
|
|
|
+ gap: 2px;
|
|
|
+ padding: 3px;
|
|
|
+ background: #f4f6fa;
|
|
|
+ border: 1px solid #e7ebf3;
|
|
|
+ border-radius: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__tab {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ height: 28px;
|
|
|
+ padding: 0 10px;
|
|
|
+ color: #5b6577;
|
|
|
+ font-size: 12.5px;
|
|
|
+ font-weight: 500;
|
|
|
+ font-family: inherit;
|
|
|
+ line-height: 1;
|
|
|
+ white-space: nowrap;
|
|
|
+ background: transparent;
|
|
|
+ border: 0;
|
|
|
+ border-radius: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition:
|
|
|
+ color 0.15s ease,
|
|
|
+ background-color 0.15s ease,
|
|
|
+ box-shadow 0.15s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #2a50ec;
|
|
|
+ background: rgb(42 80 236 / 6%);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-on {
|
|
|
+ color: #2a50ec;
|
|
|
+ font-weight: 600;
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 1px 3px rgb(16 24 40 / 10%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &__tab-count {
|
|
|
+ display: inline-block;
|
|
|
+ min-width: 18px;
|
|
|
+ padding: 0 5px;
|
|
|
+ font-size: 11px;
|
|
|
+ line-height: 16px;
|
|
|
+ color: inherit;
|
|
|
+ text-align: center;
|
|
|
+ background: rgb(42 80 236 / 8%);
|
|
|
+ border-radius: 999px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__summary {
|
|
|
+ max-width: 220px;
|
|
|
+ overflow: hidden;
|
|
|
+ color: #98a2b3;
|
|
|
+ font-size: 11.5px;
|
|
|
+ white-space: nowrap;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__spacer {
|
|
|
+ flex: 1 1 auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__actions {
|
|
|
+ display: flex;
|
|
|
+ flex: 0 0 auto;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ :deep(.ant-btn) {
|
|
|
+ height: 30px;
|
|
|
+ padding: 0 14px;
|
|
|
+ color: #5b6577;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #e7ebf3;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: none;
|
|
|
+
|
|
|
+ &:hover,
|
|
|
+ &:focus {
|
|
|
+ color: #2a50ec;
|
|
|
+ background: #f8faff;
|
|
|
+ border-color: #bfcdfb;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.ant-btn-primary) {
|
|
|
+ color: #fff;
|
|
|
+ background: #2a50ec;
|
|
|
+ border-color: #2a50ec;
|
|
|
+
|
|
|
+ &:hover,
|
|
|
+ &:focus {
|
|
|
+ color: #fff;
|
|
|
+ background: #4b6bf0;
|
|
|
+ border-color: #4b6bf0;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ background: #1e3ed0;
|
|
|
+ border-color: #1e3ed0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.ant-btn[disabled]) {
|
|
|
+ color: rgb(0 0 0 / 25%);
|
|
|
+ background: #f5f5f5;
|
|
|
+ border-color: #e7ebf3;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &__panel {
|
|
|
+ display: flex;
|
|
|
+ flex: 1 1 auto;
|
|
|
+ flex-direction: column;
|
|
|
+ min-height: 0;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #e7ebf3;
|
|
|
+ border-radius: 14px;
|
|
|
+ box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__panel-head {
|
|
|
+ display: flex;
|
|
|
+ flex: 0 0 auto;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ min-height: 48px;
|
|
|
+ padding: 8px 14px;
|
|
|
+ border-bottom: 1px solid #eff2f7;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__panel-desc {
|
|
|
+ min-width: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ color: #98a2b3;
|
|
|
+ font-size: 11.5px;
|
|
|
+ white-space: nowrap;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__panel-count {
|
|
|
+ color: #98a2b3;
|
|
|
+ font-size: 11.5px;
|
|
|
+ white-space: nowrap;
|
|
|
+ font-variant-numeric: tabular-nums;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__panel-body {
|
|
|
+ flex: 1 1 auto;
|
|
|
+ min-height: 0;
|
|
|
+ padding: 14px;
|
|
|
+ overflow-y: auto;
|
|
|
+ overscroll-behavior: contain;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__filters {
|
|
|
+ display: flex;
|
|
|
+ flex: 0 0 auto;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__select {
|
|
|
+ width: 180px;
|
|
|
+
|
|
|
+ :deep(.ant-select-selector) {
|
|
|
+ height: 32px !important;
|
|
|
+ padding: 0 10px;
|
|
|
+ background: #fff !important;
|
|
|
+ border: 1px solid #e7ebf3 !important;
|
|
|
+ border-radius: 8px !important;
|
|
|
+ box-shadow: none !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.ant-select-selection-item),
|
|
|
+ :deep(.ant-select-selection-placeholder) {
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 30px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &__search {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ width: 240px;
|
|
|
+ height: 32px;
|
|
|
+ padding: 0 10px;
|
|
|
+ font-size: 12px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #e7ebf3;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: none;
|
|
|
+ transition:
|
|
|
+ border-color 0.2s ease,
|
|
|
+ box-shadow 0.2s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: #bfcdfb;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:focus,
|
|
|
+ &.ant-input-affix-wrapper-focused {
|
|
|
+ border-color: #2a50ec;
|
|
|
+ box-shadow: 0 0 0 2px rgb(42 80 236 / 12%);
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.ant-input) {
|
|
|
+ height: 100%;
|
|
|
+ padding: 0;
|
|
|
+ font-size: 12px;
|
|
|
+ background: transparent;
|
|
|
+ border: 0;
|
|
|
+ box-shadow: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.ant-input-clear-icon) {
|
|
|
+ color: #98a2b3;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &__filter-count {
|
|
|
+ margin-left: 2px;
|
|
|
+ color: #98a2b3;
|
|
|
+ font-size: 11.5px;
|
|
|
+ white-space: nowrap;
|
|
|
+ font-variant-numeric: tabular-nums;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__card {
|
|
|
+ gap: 10px;
|
|
|
+ padding: 14px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #e7ebf3;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ transition:
|
|
|
+ border-color 0.15s ease,
|
|
|
+ box-shadow 0.15s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: #bfcdfb;
|
|
|
+ box-shadow: 0 4px 14px rgb(16 24 40 / 8%);
|
|
|
+ transform: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-clickable {
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-clickable:hover {
|
|
|
+ border-color: #bfcdfb;
|
|
|
+ box-shadow: 0 4px 14px rgb(16 24 40 / 8%);
|
|
|
+ transform: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &__card-head {
|
|
|
+ align-items: flex-start;
|
|
|
+ justify-content: flex-start;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__card-icon {
|
|
|
+ display: inline-flex;
|
|
|
+ flex: 0 0 auto;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 34px;
|
|
|
+ height: 34px;
|
|
|
+ color: #2a50ec;
|
|
|
+ background: rgb(42 80 236 / 8%);
|
|
|
+ border-radius: 9px;
|
|
|
+
|
|
|
+ &.is-pack {
|
|
|
+ color: #7c3aed;
|
|
|
+ background: rgb(124 58 237 / 8%);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-tool {
|
|
|
+ color: #0d9488;
|
|
|
+ background: rgb(13 148 136 / 8%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &__card-heading {
|
|
|
+ display: flex;
|
|
|
+ flex: 1 1 auto;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 4px;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__name-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__name {
|
|
|
+ display: block;
|
|
|
+ min-width: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ color: #111827;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ white-space: nowrap;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__card-sub {
|
|
|
+ overflow: hidden;
|
|
|
+ color: #98a2b3;
|
|
|
+ font-size: 11.5px;
|
|
|
+ white-space: nowrap;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ font-variant-numeric: tabular-nums;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__card-head :deep(.ant-switch) {
|
|
|
+ flex: 0 0 auto;
|
|
|
+ margin-top: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 技能卡里 chip 是纵向 flex 的直接子项,不收回内容宽度会被拉满 */
|
|
|
+ &__card-heading > .ai-plugin__chip {
|
|
|
+ align-self: flex-start;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__chip {
|
|
|
+ max-width: 220px;
|
|
|
+ height: 20px;
|
|
|
+ padding: 0 7px;
|
|
|
+ overflow: hidden;
|
|
|
+ color: #5b6577;
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 500;
|
|
|
+ line-height: 18px;
|
|
|
+ white-space: nowrap;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ background: #f4f6fa;
|
|
|
+ border: 1px solid #e7ebf3;
|
|
|
+ border-radius: 6px;
|
|
|
+
|
|
|
+ &.is-soft {
|
|
|
+ color: #4b5a7a;
|
|
|
+ background: #f7f9ff;
|
|
|
+ border-color: #e3e9fb;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-primary {
|
|
|
+ color: #2a50ec;
|
|
|
+ background: rgb(42 80 236 / 7%);
|
|
|
+ border-color: rgb(42 80 236 / 18%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &__card-desc {
|
|
|
+ min-height: calc(12.5px * 1.7 * 2);
|
|
|
+ margin: 0;
|
|
|
+ color: #5b6577;
|
|
|
+ font-size: 12.5px;
|
|
|
+ line-height: 1.7;
|
|
|
+ -webkit-line-clamp: 3;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__card-meta,
|
|
|
+ &__card-tools {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__card-ops {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ margin-top: auto;
|
|
|
+ padding-top: 10px;
|
|
|
+ border-top: 1px solid #eff2f7;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__ops-sp {
|
|
|
+ flex: 1 1 auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__op {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 26px;
|
|
|
+ padding: 0 10px;
|
|
|
+ color: #5b6577;
|
|
|
+ font-size: 11.5px;
|
|
|
+ font-weight: 500;
|
|
|
+ font-family: inherit;
|
|
|
+ white-space: nowrap;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #e7ebf3;
|
|
|
+ border-radius: 6px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition:
|
|
|
+ color 0.15s ease,
|
|
|
+ background-color 0.15s ease,
|
|
|
+ border-color 0.15s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #2a50ec;
|
|
|
+ background: #f8faff;
|
|
|
+ border-color: #bfcdfb;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-danger:hover {
|
|
|
+ color: #e5484d;
|
|
|
+ background: #fff5f5;
|
|
|
+ border-color: rgb(229 72 77 / 35%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &__link {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 2px;
|
|
|
+ color: #2a50ec;
|
|
|
+ font-size: 11.5px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 弹框内搜索框(工具包编辑器)沿用 32px 白底控件 */
|
|
|
+ .ai-plugin__pack-section-head .ai-plugin__pack-search {
|
|
|
+ display: flex;
|
|
|
+ flex: 0 0 auto;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ width: 240px;
|
|
|
+ height: 32px;
|
|
|
+ padding: 0 10px;
|
|
|
+ color: #98a2b3;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #e7ebf3;
|
|
|
+ border-radius: 8px;
|
|
|
+ cursor: text;
|
|
|
+
|
|
|
+ input {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ color: #111827;
|
|
|
+ font-size: 12px;
|
|
|
+ font-family: inherit;
|
|
|
+ background: transparent;
|
|
|
+ border: 0;
|
|
|
+ outline: none;
|
|
|
+
|
|
|
+ &::placeholder {
|
|
|
+ color: #98a2b3;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-plugin__pack-input {
|
|
|
+ border-color: #e7ebf3;
|
|
|
+
|
|
|
+ &:focus {
|
|
|
+ border-color: #2a50ec;
|
|
|
+ box-shadow: 0 0 0 2px rgb(42 80 236 / 12%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-plugin__pack-count {
|
|
|
+ color: #5b6577;
|
|
|
+ background: #f4f6fa;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-plugin__pack-card {
|
|
|
+ border-color: #e7ebf3;
|
|
|
+ border-radius: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-plugin__pack-card.is-on {
|
|
|
+ border-color: #2a50ec;
|
|
|
+ background: rgb(42 80 236 / 3%);
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-plugin__pack-empty {
|
|
|
+ padding: 18px;
|
|
|
+ color: #98a2b3;
|
|
|
+ font-size: 12px;
|
|
|
+ text-align: center;
|
|
|
+ background: #fafbfe;
|
|
|
+ border: 1px dashed #dbe2ef;
|
|
|
+ border-radius: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 1440px) {
|
|
|
+ .ai-plugin__summary {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 1180px) {
|
|
|
+ .ai-plugin__panel-desc {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 窄窗口先收起页签图标,给右侧操作按钮让位(页签文字与计数仍完整) */
|
|
|
+ @media (max-width: 1120px) {
|
|
|
+ .ai-plugin__tab .jeesite-icon {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
</style>
|
|
|
|
|
|
<style lang="less">
|
|
|
+ /*
|
|
|
+ * 窗口态观感与页面卡片统一:14px 圆角、白底标题条、30px 操作按钮。
|
|
|
+ * 项目全局对 .ant-modal 有过高优先级覆盖(:not(...) 链),这里必须用 !important。
|
|
|
+ */
|
|
|
+ .ai-pack-modal .ant-modal-content {
|
|
|
+ overflow: hidden;
|
|
|
+ border-radius: 14px !important;
|
|
|
+ padding: 0 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-pack-modal .ant-modal-header {
|
|
|
+ margin: 0 !important;
|
|
|
+ padding: 14px 20px !important;
|
|
|
+ background: #fff !important;
|
|
|
+ border-bottom: 1px solid #eff2f7 !important;
|
|
|
+ border-radius: 14px 14px 0 0 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-pack-modal .ant-modal-title {
|
|
|
+ color: #111827 !important;
|
|
|
+ font-size: 15px !important;
|
|
|
+ font-weight: 600 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-pack-modal .ant-modal-close {
|
|
|
+ top: 14px;
|
|
|
+ right: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-pack-modal .ant-modal-body {
|
|
|
+ padding: 16px 20px 8px !important;
|
|
|
+ }
|
|
|
+
|
|
|
/*
|
|
|
* 全屏态补齐(弹框挂到 body 下,只能用全局样式):antd 弹框是 position:relative,
|
|
|
* fullscreen 规则的 top/bottom 拉不开高度——这里显式补高度,正文区 flex 占满并内部滚动;
|