feat: 病历模块优化、AI辅助问诊、接入deepseek
This commit is contained in:
@@ -0,0 +1,601 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 系统配置 - AI:二级 Tab
|
||||
* 1) 模型配置:选用平台 / 密钥 / 模型
|
||||
* 2) 病历配置:勾选 AI 写病历需要生成的字段
|
||||
* 3) 提醒文案:VIP 关联的醒目提示与知情同意
|
||||
*/
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { Button, Checkbox, Input, Spin, Tabs, Tag, message } from 'ant-design-vue';
|
||||
import { CheckCircleFilled, KeyOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
import {
|
||||
getAiFeatureCopyList,
|
||||
getAiPlatformConfigOptions,
|
||||
saveAiFeatureCopy,
|
||||
} from '#/views/system/ai/platform/api';
|
||||
import { saveSystemConfig } from '#/views/system/system-config/api';
|
||||
|
||||
/** 二级 Tab 持久化 */
|
||||
const SUB_TAB_STORAGE_KEY = 'system_config_ai_sub_tab';
|
||||
|
||||
interface PlatformCard {
|
||||
id: number;
|
||||
code: string;
|
||||
name: string;
|
||||
logo: string;
|
||||
description: string;
|
||||
default_model: string;
|
||||
api_url: string;
|
||||
models: {
|
||||
id: number;
|
||||
label: string;
|
||||
value: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
is_default: number;
|
||||
}[];
|
||||
keys: {
|
||||
id?: number;
|
||||
key_name?: string;
|
||||
name?: string;
|
||||
label?: string;
|
||||
value: number;
|
||||
is_default?: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface FeatureCopyRow {
|
||||
id: number;
|
||||
feature_code: string;
|
||||
feature_name: string;
|
||||
record_label: string;
|
||||
disclaimer_text: string;
|
||||
consent_text: string;
|
||||
}
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const copySaving = ref(false);
|
||||
const platforms = ref<PlatformCard[]>([]);
|
||||
const activeProvider = ref('');
|
||||
const activeApiKeyId = ref<number | undefined>(undefined);
|
||||
const activeModel = ref('');
|
||||
const featureCopies = ref<FeatureCopyRow[]>([]);
|
||||
const subTab = ref('model');
|
||||
/** 病历配置:可选字段与已勾选编码 */
|
||||
const mrFieldOptions = ref<{ label: string; value: string }[]>([]);
|
||||
const mrSelectedFields = ref<string[]>([]);
|
||||
const mrSaving = ref(false);
|
||||
|
||||
const currentPlatform = computed(() =>
|
||||
platforms.value.find((p) => p.code === activeProvider.value),
|
||||
);
|
||||
|
||||
const keyOptions = computed(() => currentPlatform.value?.keys || []);
|
||||
const modelOptions = computed(() => currentPlatform.value?.models || []);
|
||||
|
||||
/** 密钥展示名:key_name → name → label → 兜底 */
|
||||
function keyDisplayName(k: {
|
||||
key_name?: string;
|
||||
name?: string;
|
||||
label?: string;
|
||||
value: number;
|
||||
id?: number;
|
||||
}): string {
|
||||
const candidates = [k.key_name, k.name, k.label];
|
||||
for (const c of candidates) {
|
||||
const s = String(c || '').trim();
|
||||
if (s) return s;
|
||||
}
|
||||
return `密钥#${k.id || k.value}`;
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const [optRes, copyRes] = await Promise.all([
|
||||
getAiPlatformConfigOptions(),
|
||||
getAiFeatureCopyList(),
|
||||
]);
|
||||
const data = optRes?.data || optRes || {};
|
||||
platforms.value = Array.isArray(data.platforms) ? data.platforms : [];
|
||||
const active = data.active || {};
|
||||
activeProvider.value = String(active.provider || platforms.value[0]?.code || '');
|
||||
activeApiKeyId.value = active.api_key_id ? Number(active.api_key_id) : undefined;
|
||||
activeModel.value = String(active.model || '');
|
||||
syncSelectionForPlatform();
|
||||
const mr = data.medical_record_fields || {};
|
||||
mrFieldOptions.value = Array.isArray(mr.options)
|
||||
? mr.options.map((o: any) => ({
|
||||
label: String(o.label || o.value || ''),
|
||||
value: String(o.value || ''),
|
||||
}))
|
||||
: [];
|
||||
mrSelectedFields.value = Array.isArray(mr.selected)
|
||||
? mr.selected.map((c: any) => String(c))
|
||||
: mrFieldOptions.value.map((o) => o.value);
|
||||
const copies = copyRes?.data || copyRes || [];
|
||||
featureCopies.value = (Array.isArray(copies) ? copies : []).map((r: any) => ({
|
||||
id: Number(r.id || 0),
|
||||
feature_code: String(r.feature_code || ''),
|
||||
feature_name: String(r.feature_name || r.feature_code || ''),
|
||||
record_label: String(r.record_label || ''),
|
||||
disclaimer_text: String(r.disclaimer_text || ''),
|
||||
consent_text: String(r.consent_text || ''),
|
||||
}));
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 切换平台后:密钥/模型回落到该平台默认或首个 */
|
||||
function syncSelectionForPlatform() {
|
||||
const keys = keyOptions.value;
|
||||
if (activeApiKeyId.value && !keys.some((k) => k.value === activeApiKeyId.value)) {
|
||||
const def = keys.find((k) => Number(k.is_default) === 1);
|
||||
activeApiKeyId.value = def?.value ?? keys[0]?.value;
|
||||
} else if (!activeApiKeyId.value && keys[0]) {
|
||||
const def = keys.find((k) => Number(k.is_default) === 1);
|
||||
activeApiKeyId.value = def?.value ?? keys[0].value;
|
||||
}
|
||||
const models = modelOptions.value;
|
||||
if (activeModel.value && !models.some((m) => m.value === activeModel.value)) {
|
||||
const def = models.find((m) => Number(m.is_default) === 1);
|
||||
activeModel.value =
|
||||
def?.value || currentPlatform.value?.default_model || models[0]?.value || '';
|
||||
} else if (!activeModel.value) {
|
||||
const def = models.find((m) => Number(m.is_default) === 1);
|
||||
activeModel.value =
|
||||
def?.value || currentPlatform.value?.default_model || models[0]?.value || '';
|
||||
}
|
||||
}
|
||||
|
||||
watch(activeProvider, () => {
|
||||
syncSelectionForPlatform();
|
||||
});
|
||||
|
||||
function onSubTabChange(key: string | number) {
|
||||
subTab.value = String(key);
|
||||
localStorage.setItem(SUB_TAB_STORAGE_KEY, String(key));
|
||||
}
|
||||
|
||||
/** 病历字段全选 / 清空 */
|
||||
function selectAllMrFields() {
|
||||
mrSelectedFields.value = mrFieldOptions.value.map((o) => o.value);
|
||||
}
|
||||
|
||||
function clearMrFields() {
|
||||
mrSelectedFields.value = [];
|
||||
}
|
||||
|
||||
/** 保存 AI 写病历输出字段勾选 */
|
||||
async function handleSaveMrFields() {
|
||||
if (!mrSelectedFields.value.length) {
|
||||
message.warning('请至少勾选一个病历字段');
|
||||
return;
|
||||
}
|
||||
// 按 options 顺序保存,保证提示词字段顺序稳定
|
||||
const selectedSet = new Set(mrSelectedFields.value);
|
||||
const ordered = mrFieldOptions.value
|
||||
.map((o) => o.value)
|
||||
.filter((v) => selectedSet.has(v));
|
||||
mrSaving.value = true;
|
||||
try {
|
||||
await saveSystemConfig([
|
||||
{
|
||||
config_key: 'ai_medical_record_fields',
|
||||
config_value: JSON.stringify(ordered),
|
||||
value_type: 'json',
|
||||
config_group: 'ai',
|
||||
description: 'AI写病历需生成的字段编码列表(JSON数组,不含主诉)',
|
||||
sort: 410,
|
||||
} as any,
|
||||
]);
|
||||
mrSelectedFields.value = ordered;
|
||||
message.success('病历字段配置已保存');
|
||||
} finally {
|
||||
mrSaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function selectProvider(code: string) {
|
||||
activeProvider.value = code;
|
||||
}
|
||||
|
||||
function selectKey(id: number) {
|
||||
activeApiKeyId.value = id;
|
||||
}
|
||||
|
||||
function selectModel(code: string) {
|
||||
activeModel.value = code;
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!activeProvider.value) {
|
||||
message.warning('请选择 AI 平台');
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
await saveSystemConfig([
|
||||
{
|
||||
config_key: 'ai_active_provider',
|
||||
config_value: activeProvider.value,
|
||||
value_type: 'string',
|
||||
config_group: 'ai',
|
||||
description: '当前 AI 平台编码(spark/deepseek)',
|
||||
sort: 400,
|
||||
},
|
||||
{
|
||||
config_key: 'ai_active_api_key_id',
|
||||
config_value: String(activeApiKeyId.value || 0),
|
||||
value_type: 'string',
|
||||
config_group: 'ai',
|
||||
description: '当前选用的 AI API Key ID',
|
||||
sort: 401,
|
||||
},
|
||||
{
|
||||
config_key: 'ai_active_model',
|
||||
config_value: String(activeModel.value || ''),
|
||||
value_type: 'string',
|
||||
config_group: 'ai',
|
||||
description: '当前 AI 模型名(空则用平台默认)',
|
||||
sort: 402,
|
||||
},
|
||||
]);
|
||||
message.success('AI 配置已保存');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存 VIP 关联的 AI 醒目提示 / 知情同意文案 */
|
||||
async function handleSaveCopies() {
|
||||
if (!featureCopies.value.length) {
|
||||
message.warning('暂无文案可保存,请先执行 SQL 初始化');
|
||||
return;
|
||||
}
|
||||
copySaving.value = true;
|
||||
try {
|
||||
await saveAiFeatureCopy({
|
||||
items: featureCopies.value.map((r) => ({
|
||||
id: r.id,
|
||||
feature_code: r.feature_code,
|
||||
record_label: r.record_label,
|
||||
disclaimer_text: r.disclaimer_text,
|
||||
consent_text: r.consent_text,
|
||||
})),
|
||||
});
|
||||
message.success('AI 功能文案已保存');
|
||||
} finally {
|
||||
copySaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const stored = localStorage.getItem(SUB_TAB_STORAGE_KEY);
|
||||
if (stored === 'model' || stored === 'copy' || stored === 'mr') {
|
||||
subTab.value = stored;
|
||||
}
|
||||
load();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Spin :spinning="loading">
|
||||
<div class="ai-config-panel py-2">
|
||||
<Tabs :active-key="subTab" @change="onSubTabChange">
|
||||
<Tabs.TabPane key="model" tab="模型配置">
|
||||
<div class="py-2">
|
||||
<div class="mb-2 font-medium text-[hsl(var(--foreground))]">选用 AI 平台</div>
|
||||
<div class="mb-3 text-sm text-[hsl(var(--muted-foreground))]">
|
||||
平台 / 模型 / 密钥请在「AI平台管理」维护;此处点选当前运行时组合。
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
<div
|
||||
v-for="p in platforms"
|
||||
:key="p.code"
|
||||
class="ai-pick-card"
|
||||
:class="{ 'ai-pick-card--on': activeProvider === p.code }"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="selectProvider(p.code)"
|
||||
@keydown.enter="selectProvider(p.code)"
|
||||
>
|
||||
<CheckCircleFilled
|
||||
v-if="activeProvider === p.code"
|
||||
class="ai-pick-check"
|
||||
/>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="ai-pick-logo">
|
||||
<img
|
||||
v-if="p.logo"
|
||||
:src="p.logo"
|
||||
:alt="p.name"
|
||||
class="h-full w-full object-contain"
|
||||
/>
|
||||
<span v-else class="ai-pick-logo-fallback">
|
||||
{{ (p.name || p.code || '?').slice(0, 1) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1 pr-5">
|
||||
<div class="font-medium text-[hsl(var(--foreground))]">
|
||||
{{ p.name }}
|
||||
</div>
|
||||
<div class="mt-0.5 text-xs text-[hsl(var(--muted-foreground))]">
|
||||
{{ p.code }} · {{ p.default_model || '—' }}
|
||||
</div>
|
||||
<div
|
||||
class="mt-1 line-clamp-2 text-xs text-[hsl(var(--muted-foreground))]"
|
||||
>
|
||||
{{ p.description || p.api_url }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 mb-2 font-medium text-[hsl(var(--foreground))]">API Key</div>
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
<div
|
||||
v-for="k in keyOptions"
|
||||
:key="k.value"
|
||||
class="ai-pick-card"
|
||||
:class="{ 'ai-pick-card--on': activeApiKeyId === k.value }"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="selectKey(k.value)"
|
||||
@keydown.enter="selectKey(k.value)"
|
||||
>
|
||||
<CheckCircleFilled
|
||||
v-if="activeApiKeyId === k.value"
|
||||
class="ai-pick-check"
|
||||
/>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="ai-pick-icon">
|
||||
<KeyOutlined />
|
||||
</div>
|
||||
<div class="min-w-0 flex-1 pr-5">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="font-medium text-[hsl(var(--foreground))]">
|
||||
{{ keyDisplayName(k) }}
|
||||
</span>
|
||||
<Tag v-if="k.is_default === 1" color="gold">默认</Tag>
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-[hsl(var(--muted-foreground))]">
|
||||
ID {{ k.value }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!keyOptions.length"
|
||||
class="mt-1 text-xs text-amber-600 dark:text-amber-400"
|
||||
>
|
||||
当前平台暂无启用密钥,请先到「AI平台管理」添加
|
||||
</div>
|
||||
|
||||
<div class="mt-6 mb-2 font-medium text-[hsl(var(--foreground))]">模型</div>
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
<div
|
||||
v-for="m in modelOptions"
|
||||
:key="m.value"
|
||||
class="ai-pick-card"
|
||||
:class="{ 'ai-pick-card--on': activeModel === m.value }"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="selectModel(m.value)"
|
||||
@keydown.enter="selectModel(m.value)"
|
||||
>
|
||||
<CheckCircleFilled
|
||||
v-if="activeModel === m.value"
|
||||
class="ai-pick-check"
|
||||
/>
|
||||
<div class="min-w-0 pr-5">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="font-medium text-[hsl(var(--foreground))]">
|
||||
{{ m.name || m.label || m.code }}
|
||||
</span>
|
||||
<Tag v-if="m.is_default === 1" color="gold">默认</Tag>
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-[hsl(var(--muted-foreground))]">
|
||||
{{ m.code }}
|
||||
</div>
|
||||
<div
|
||||
v-if="m.description"
|
||||
class="mt-1 line-clamp-2 text-xs text-[hsl(var(--muted-foreground))]"
|
||||
>
|
||||
{{ m.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!modelOptions.length"
|
||||
class="mt-1 text-xs text-amber-600 dark:text-amber-400"
|
||||
>
|
||||
当前平台暂无启用模型,请先到「AI平台管理」添加
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<Button type="primary" :loading="saving" @click="handleSave">
|
||||
保存 AI 配置
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane key="mr" tab="病历配置">
|
||||
<div class="py-2">
|
||||
<div class="mb-3 text-sm text-[hsl(var(--muted-foreground))]">
|
||||
勾选后,AI 写病历只会返回这些字段(主诉仍由医生填写,不在此配置)。未勾选的字段不会生成。
|
||||
</div>
|
||||
<div class="mb-3 flex flex-wrap gap-2">
|
||||
<Button size="small" @click="selectAllMrFields">全选</Button>
|
||||
<Button size="small" @click="clearMrFields">清空</Button>
|
||||
<span class="text-xs text-[hsl(var(--muted-foreground))] self-center">
|
||||
已选 {{ mrSelectedFields.length }} / {{ mrFieldOptions.length }}
|
||||
</span>
|
||||
</div>
|
||||
<Checkbox.Group
|
||||
v-model:value="mrSelectedFields"
|
||||
class="ai-mr-fields grid grid-cols-2 gap-x-4 gap-y-2 md:grid-cols-3 xl:grid-cols-4"
|
||||
>
|
||||
<Checkbox
|
||||
v-for="opt in mrFieldOptions"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</Checkbox>
|
||||
</Checkbox.Group>
|
||||
<div class="mt-6">
|
||||
<Button type="primary" :loading="mrSaving" @click="handleSaveMrFields">
|
||||
保存病历配置
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane key="copy" tab="提醒文案">
|
||||
<div class="py-2">
|
||||
<div class="mb-3 text-sm text-[hsl(var(--muted-foreground))]">
|
||||
按 VIP 功能码维护醒目提示与首次知情同意全文;医生签署后仅该功能无需再次确认。
|
||||
</div>
|
||||
<div
|
||||
v-if="!featureCopies.length"
|
||||
class="rounded-lg border border-dashed border-[hsl(var(--border))] p-4 text-sm text-[hsl(var(--muted-foreground))]"
|
||||
>
|
||||
暂无文案数据,请执行 sql/20260806/xk_ai_disclaimer_consent.sql
|
||||
</div>
|
||||
<div
|
||||
v-for="row in featureCopies"
|
||||
:key="row.feature_code"
|
||||
class="ai-copy-card mb-4"
|
||||
>
|
||||
<div class="mb-2 flex flex-wrap items-center gap-2">
|
||||
<span class="font-medium text-[hsl(var(--foreground))]">
|
||||
{{ row.feature_name || row.feature_code }}
|
||||
</span>
|
||||
<span class="text-xs text-[hsl(var(--muted-foreground))]">
|
||||
{{ row.feature_code }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mb-1 text-xs text-[hsl(var(--muted-foreground))]">
|
||||
记录称谓(用于「本{称谓}由 AI…」)
|
||||
</div>
|
||||
<Input
|
||||
v-model:value="row.record_label"
|
||||
class="mb-3 max-w-xs"
|
||||
placeholder="病历 / 处方"
|
||||
/>
|
||||
<div class="mb-1 text-xs text-[hsl(var(--muted-foreground))]">
|
||||
醒目提示(生成记录 / 模态框顶部)
|
||||
</div>
|
||||
<Input.TextArea
|
||||
v-model:value="row.disclaimer_text"
|
||||
:rows="4"
|
||||
class="mb-3"
|
||||
placeholder="本病历/处方由 AI…"
|
||||
/>
|
||||
<div class="mb-1 text-xs text-[hsl(var(--muted-foreground))]">
|
||||
首次知情同意全文
|
||||
</div>
|
||||
<Input.TextArea
|
||||
v-model:value="row.consent_text"
|
||||
:rows="6"
|
||||
placeholder="我已知悉:…"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="featureCopies.length" class="mt-2">
|
||||
<Button type="primary" :loading="copySaving" @click="handleSaveCopies">
|
||||
保存 AI 功能文案
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Spin>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ai-pick-card {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
background: hsl(var(--card));
|
||||
color: hsl(var(--foreground));
|
||||
outline: none;
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
box-shadow 0.15s ease,
|
||||
background-color 0.15s ease,
|
||||
transform 0.15s ease;
|
||||
}
|
||||
.ai-pick-card:hover {
|
||||
border-color: hsl(var(--primary) / 0.45);
|
||||
box-shadow: 0 4px 14px hsl(var(--foreground) / 0.06);
|
||||
}
|
||||
.ai-pick-card--on {
|
||||
border-color: hsl(var(--primary));
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
hsl(var(--primary) / 0.14),
|
||||
hsl(var(--primary) / 0.04)
|
||||
);
|
||||
box-shadow:
|
||||
0 0 0 1px hsl(var(--primary) / 0.35),
|
||||
0 8px 20px hsl(var(--primary) / 0.12);
|
||||
}
|
||||
.ai-pick-check {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
font-size: 18px;
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
.ai-pick-logo,
|
||||
.ai-pick-icon {
|
||||
display: flex;
|
||||
height: 44px;
|
||||
width: 44px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
border: 1px solid hsl(var(--border));
|
||||
background: hsl(var(--muted) / 0.45);
|
||||
}
|
||||
.ai-pick-logo {
|
||||
padding: 5px;
|
||||
}
|
||||
.ai-pick-icon {
|
||||
font-size: 18px;
|
||||
color: hsl(var(--primary));
|
||||
background: hsl(var(--primary) / 0.1);
|
||||
border-color: hsl(var(--primary) / 0.25);
|
||||
}
|
||||
.ai-pick-logo-fallback {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
.ai-copy-card {
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 10px;
|
||||
padding: 14px 16px;
|
||||
background: hsl(var(--card));
|
||||
}
|
||||
.ai-mr-fields :deep(.ant-checkbox-wrapper) {
|
||||
margin-inline-start: 0;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user