add
This commit is contained in:
parent
8500f0752a
commit
0e0ae91473
|
|
@ -1,3 +1,4 @@
|
||||||
|
import './styles/scrollbar.css'
|
||||||
import { initPreferences } from '@vben/preferences';
|
import { initPreferences } from '@vben/preferences';
|
||||||
import { unmountGlobalLoading } from '@vben/utils';
|
import { unmountGlobalLoading } from '@vben/utils';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
name: 'Settings',
|
||||||
|
path: '/settings',
|
||||||
|
component: () => import('#/views/settings/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'carbon:settings',
|
||||||
|
title: '设置',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
/* 亮色 */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 4px solid #f5f5f5;
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a0a0a0;
|
||||||
|
border: 4px solid #f5f5f5;
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗色 */
|
||||||
|
html.dark ::-webkit-scrollbar-track {
|
||||||
|
background: #1e1e1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark ::-webkit-scrollbar-thumb {
|
||||||
|
background: #4a4a4a;
|
||||||
|
border: 4px solid #1e1e1e;
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark ::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #666666;
|
||||||
|
border: 4px solid #1e1e1e;
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import SourceStep from './steps/SourceStep.vue';
|
||||||
import SegmentConfig from './steps/SegmentConfig.vue';
|
import SegmentConfig from './steps/SegmentConfig.vue';
|
||||||
import SegmentPreview from './steps/SegmentPreview.vue';
|
import SegmentPreview from './steps/SegmentPreview.vue';
|
||||||
import type { SegmentConfig as SegmentConfigType } from './steps/SegmentConfig.vue';
|
import type { SegmentConfig as SegmentConfigType } from './steps/SegmentConfig.vue';
|
||||||
|
import Page from '../../../../../../packages/effects/common-ui/src/components/page/page.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -50,37 +51,48 @@ function goBack() {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NFlex vertical style="height: 100%; overflow: hidden">
|
<Page auto-content-height>
|
||||||
<!-- 顶部导航栏 -->
|
<NFlex vertical style="width: 100%; height: 100%">
|
||||||
<NFlex
|
<!-- 顶部导航栏 -->
|
||||||
align="center"
|
<NFlex
|
||||||
style="
|
align="center"
|
||||||
padding: 0 24px;
|
style="
|
||||||
height: 56px;
|
padding: 0 24px;
|
||||||
border-bottom: 1px solid var(--n-border-color);
|
height: 56px;
|
||||||
flex-shrink: 0;
|
border-bottom: 1px solid var(--n-border-color);
|
||||||
"
|
flex-shrink: 0;
|
||||||
>
|
"
|
||||||
<NButton text @click="goBack">← {{ datasetName }}</NButton>
|
>
|
||||||
<NSteps :current="currentStep" size="small" style="flex: 1; margin: 0 auto; max-width: 560px">
|
<NButton text @click="goBack">← {{ datasetName }}</NButton>
|
||||||
<NStep title="选择数据源" />
|
<NSteps
|
||||||
<NStep title="文本分段与清洗" />
|
:current="currentStep"
|
||||||
<NStep title="处理并完成" />
|
size="small"
|
||||||
</NSteps>
|
style="flex: 1; margin: 0 auto; max-width: 560px"
|
||||||
</NFlex>
|
>
|
||||||
|
<NStep title="选择数据源" />
|
||||||
|
<NStep title="文本分段与清洗" />
|
||||||
|
<NStep title="处理并完成" />
|
||||||
|
</NSteps>
|
||||||
|
</NFlex>
|
||||||
|
|
||||||
<!-- Step 1: 选择数据源 -->
|
<!-- Step 1: 选择数据源 -->
|
||||||
<SourceStep
|
<SourceStep
|
||||||
v-if="currentStep === 1"
|
v-if="currentStep === 1"
|
||||||
v-model:source="selectedSource"
|
v-model:source="selectedSource"
|
||||||
v-model:file-list="fileList"
|
v-model:file-list="fileList"
|
||||||
@next="handleSourceNext"
|
@next="handleSourceNext"
|
||||||
/>
|
style="flex: 1; overflow-y: auto"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Step 2: 文本分段与清洗 -->
|
<!-- Step 2: 文本分段与清洗 -->
|
||||||
<NFlex v-else-if="currentStep === 2">
|
<NFlex v-else-if="currentStep === 2" style="flex: 1; min-height: 0; overflow: hidden">
|
||||||
<SegmentConfig @prev="handlePrev" @save="handleSave" style="width: 50%; overflow: hidden" />
|
<SegmentConfig
|
||||||
<SegmentPreview style="flex: 1; overflow: hidden" />
|
@prev="handlePrev"
|
||||||
|
@save="handleSave"
|
||||||
|
style="width: 50%; height: 100%; overflow-y: auto"
|
||||||
|
/>
|
||||||
|
<SegmentPreview style="width: 50%; height: 100%; overflow-y: auto" />
|
||||||
|
</NFlex>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</NFlex>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -69,18 +69,16 @@ function handleSave() {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height style="width: 100%">
|
<NFlex
|
||||||
<NFlex
|
vertical
|
||||||
vertical
|
style="
|
||||||
:gap="0"
|
height: 100%;
|
||||||
style="
|
flex-shrink: 0;
|
||||||
height: 100%;
|
border-right: 1px solid var(--n-border-color);
|
||||||
flex-shrink: 0;
|
overflow-y: auto;
|
||||||
border-right: 1px solid var(--n-border-color);
|
"
|
||||||
overflow-y: auto;
|
>
|
||||||
padding: 20px 0;
|
<NFlex vertical style="padding: 0 10px">
|
||||||
"
|
|
||||||
>
|
|
||||||
<!-- 分段设置 -->
|
<!-- 分段设置 -->
|
||||||
<NText strong style="margin-bottom: 12px">分段设置</NText>
|
<NText strong style="margin-bottom: 12px">分段设置</NText>
|
||||||
|
|
||||||
|
|
@ -223,10 +221,10 @@ function handleSave() {
|
||||||
</NFlex>
|
</NFlex>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<NFlex justify="space-between">
|
<NFlex justify="space-between" style="padding-bottom: 24px">
|
||||||
<NButton secondary @click="emit('prev')">← 上一步</NButton>
|
<NButton secondary @click="emit('prev')">← 上一步</NButton>
|
||||||
<NButton type="primary" @click="handleSave">保存并处理</NButton>
|
<NButton type="primary" @click="handleSave">保存并处理</NButton>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</Page>
|
</NFlex>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -45,35 +45,85 @@ const props = withDefaults(defineProps<{ file?: PreviewFile }>(), {
|
||||||
content:
|
content:
|
||||||
'二、过去(Past)这类时态都用来描述"过去"发生的动作或状态,有明确时间段,也有在另一个过去之事发生的动作。',
|
'二、过去(Past)这类时态都用来描述"过去"发生的动作或状态,有明确时间段,也有在另一个过去之事发生的动作。',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
chars: 89,
|
||||||
|
content:
|
||||||
|
'好的,这种分类方式更直观,面事朋友从"时可"这个维度来理解和记忆时态。我们可以把16种时态归纳为四大类:**现在**、**过去**、**将来**和**过去的将来**。',
|
||||||
|
},
|
||||||
|
{ id: 2, chars: 3, content: '---' },
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
chars: 62,
|
||||||
|
content:
|
||||||
|
'一、现在(Present)这类时态着重与**现在**这个时间点相关,包括可能性动作、正在发生的动作、以及对现在有影响的动作。',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
chars: 638,
|
||||||
|
content:
|
||||||
|
'**一般现在时(Simple Present Tense)** **用法:** 描述习惯、事实、真理。 **例句:** I **go** to the gym every morning.(我每天早上都去健身房。)**现在进行时(Present Continuous Tense)** **用法:** 描述正在发生、或最近一段时间正在进行的动作。 **例句:** He **is studying** for his exam right now.(他现在正在准备考试。)**现在完成时(Present Perfect Tense)** **用法:** 描述过去发生但对现在有影响的动作。 **例句:** I **have lost** my phone.(我把手机弄丢了,所以现在没有手机。)**现在完成进行时(Present Perfect Continuous Tense)** **用法:** 描述从过去开始持续到现在,且可能还在继续的动作。 **例句:** It **has been raining** for two hours.(雨已经下了两个小时了,且目还在下。)',
|
||||||
|
},
|
||||||
|
{ id: 5, chars: 3, content: '---' },
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
chars: 60,
|
||||||
|
content:
|
||||||
|
'二、过去(Past)这类时态都用来描述"过去"发生的动作或状态,有明确时间段,也有在另一个过去之事发生的动作。',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
chars: 89,
|
||||||
|
content:
|
||||||
|
'好的,这种分类方式更直观,面事朋友从"时可"这个维度来理解和记忆时态。我们可以把16种时态归纳为四大类:**现在**、**过去**、**将来**和**过去的将来**。',
|
||||||
|
},
|
||||||
|
{ id: 2, chars: 3, content: '---' },
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
chars: 62,
|
||||||
|
content:
|
||||||
|
'一、现在(Present)这类时态着重与**现在**这个时间点相关,包括可能性动作、正在发生的动作、以及对现在有影响的动作。',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
chars: 638,
|
||||||
|
content:
|
||||||
|
'**一般现在时(Simple Present Tense)** **用法:** 描述习惯、事实、真理。 **例句:** I **go** to the gym every morning.(我每天早上都去健身房。)**现在进行时(Present Continuous Tense)** **用法:** 描述正在发生、或最近一段时间正在进行的动作。 **例句:** He **is studying** for his exam right now.(他现在正在准备考试。)**现在完成时(Present Perfect Tense)** **用法:** 描述过去发生但对现在有影响的动作。 **例句:** I **have lost** my phone.(我把手机弄丢了,所以现在没有手机。)**现在完成进行时(Present Perfect Continuous Tense)** **用法:** 描述从过去开始持续到现在,且可能还在继续的动作。 **例句:** It **has been raining** for two hours.(雨已经下了两个小时了,且目还在下。)',
|
||||||
|
},
|
||||||
|
{ id: 5, chars: 3, content: '---' },
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
chars: 60,
|
||||||
|
content:
|
||||||
|
'二、过去(Past)这类时态都用来描述"过去"发生的动作或状态,有明确时间段,也有在另一个过去之事发生的动作。',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<NFlex vertical style="flex: 1; height: 100%">
|
||||||
<NFlex vertical style="flex: 1; overflow-y: auto; padding: 20px 24px" :gap="0">
|
<!-- 文件信息 -->
|
||||||
<!-- 文件信息 -->
|
<NFlex align="center" :gap="8" style="margin-bottom: 16px">
|
||||||
<NFlex align="center" :gap="8" style="margin-bottom: 16px">
|
<NTag size="small" type="info">预览</NTag>
|
||||||
<NTag size="small" type="info">预览</NTag>
|
<NText style="font-size: 13px">{{ props.file.name }}</NText>
|
||||||
<NText style="font-size: 13px">{{ props.file.name }}</NText>
|
<NText depth="3" style="font-size: 12px">{{ props.file.timeAgo }}</NText>
|
||||||
<NText depth="3" style="font-size: 12px">{{ props.file.timeAgo }}</NText>
|
|
||||||
</NFlex>
|
|
||||||
|
|
||||||
<!-- 分块列表 -->
|
|
||||||
<NList :show-divider="false">
|
|
||||||
<NListItem v-for="chunk in props.file.chunks" :key="chunk.id" style="padding: 0 0 16px">
|
|
||||||
<NFlex vertical :gap="4">
|
|
||||||
<NFlex align="center" :gap="8">
|
|
||||||
<NTag size="tiny" type="default">Chunk-{{ chunk.id }}</NTag>
|
|
||||||
<NText depth="3" style="font-size: 11px">{{ chunk.chars }} characters</NText>
|
|
||||||
</NFlex>
|
|
||||||
<NText style="font-size: 13px; line-height: 1.7; white-space: pre-wrap">
|
|
||||||
{{ chunk.content }}
|
|
||||||
</NText>
|
|
||||||
</NFlex>
|
|
||||||
</NListItem>
|
|
||||||
</NList>
|
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</Page>
|
|
||||||
|
<!-- 分块列表 -->
|
||||||
|
<NList :show-divider="false" style="flex: 1; overflow-y: auto">
|
||||||
|
<NListItem v-for="chunk in props.file.chunks" :key="chunk.id">
|
||||||
|
<NFlex vertical>
|
||||||
|
<NFlex align="center">
|
||||||
|
<NTag size="tiny" type="default">Chunk-{{ chunk.id }}</NTag>
|
||||||
|
<NText depth="3" style="font-size: 11px">{{ chunk.chars }} characters</NText>
|
||||||
|
</NFlex>
|
||||||
|
<NText style="font-size: 13px; line-height: 1.7; white-space: pre-wrap">
|
||||||
|
{{ chunk.content }}
|
||||||
|
</NText>
|
||||||
|
</NFlex>
|
||||||
|
</NListItem>
|
||||||
|
</NList>
|
||||||
|
</NFlex>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ function handleNext() {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NFlex justify="center" align="center">
|
<NFlex justify="center" align="flex-start">
|
||||||
<NFlex vertical>
|
<NFlex vertical>
|
||||||
<NText strong style="font-size: 15px">选择数据源</NText>
|
<NText strong style="font-size: 15px">选择数据源</NText>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, shallowRef, defineAsyncComponent } from 'vue';
|
||||||
|
import { NFlex, NLayout, NLayoutContent, NLayoutSider, NText } from 'naive-ui';
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const ApiExtension = defineAsyncComponent(() => import('./panels/ApiExtension.vue'));
|
||||||
|
const ModelProviders = defineAsyncComponent(() => import('./panels/ModelProviders.vue'));
|
||||||
|
const Members = defineAsyncComponent(() => import('./panels/Members.vue'));
|
||||||
|
const DataSources = defineAsyncComponent(() => import('./panels/DataSources.vue'));
|
||||||
|
const Language = defineAsyncComponent(() => import('./panels/Language.vue'));
|
||||||
|
|
||||||
|
type PanelKey = 'model-providers' | 'members' | 'data-sources' | 'api-extension' | 'language';
|
||||||
|
|
||||||
|
const activePanel = ref<PanelKey>('model-providers');
|
||||||
|
|
||||||
|
const panelMap: Record<PanelKey, ReturnType<typeof defineAsyncComponent>> = {
|
||||||
|
'model-providers': ModelProviders,
|
||||||
|
members: Members,
|
||||||
|
'data-sources': DataSources,
|
||||||
|
'api-extension': ApiExtension,
|
||||||
|
language: Language,
|
||||||
|
};
|
||||||
|
|
||||||
|
const workspaceItems: { key: PanelKey; label: string; icon: string }[] = [
|
||||||
|
{ key: 'model-providers', label: '模型供应商', icon: '🤖' },
|
||||||
|
{ key: 'members', label: '成员', icon: '👥' },
|
||||||
|
{ key: 'data-sources', label: '数据来源', icon: '🗃️' },
|
||||||
|
{ key: 'api-extension', label: 'API 扩展', icon: '🔌' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const generalItems: { key: PanelKey; label: string; icon: string }[] = [
|
||||||
|
{ key: 'language', label: '语言', icon: '🌐' },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<NLayout has-sider style="height: 100%">
|
||||||
|
<NLayoutSider
|
||||||
|
:width="220"
|
||||||
|
:native-scrollbar="false"
|
||||||
|
content-style="padding: 24px 12px;"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<NText strong tag="div" style="font-size: 18px; padding: 0 12px; margin-bottom: 20px">
|
||||||
|
设置
|
||||||
|
</NText>
|
||||||
|
|
||||||
|
<!-- 工作空间 -->
|
||||||
|
<NText
|
||||||
|
depth="3"
|
||||||
|
tag="div"
|
||||||
|
style="
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 0 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
工作空间
|
||||||
|
</NText>
|
||||||
|
<NFlex vertical :gap="2" style="margin-bottom: 16px">
|
||||||
|
<NFlex
|
||||||
|
v-for="item in workspaceItems"
|
||||||
|
:key="item.key"
|
||||||
|
align="center"
|
||||||
|
:gap="10"
|
||||||
|
style="
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
"
|
||||||
|
:style="activePanel === item.key ? 'background: var(--n-color-embedded);' : ''"
|
||||||
|
@click="activePanel = item.key"
|
||||||
|
>
|
||||||
|
<span style="font-size: 15px; line-height: 1">{{ item.icon }}</span>
|
||||||
|
<NText :style="activePanel === item.key ? 'font-weight: 500;' : ''">
|
||||||
|
{{ item.label }}
|
||||||
|
</NText>
|
||||||
|
</NFlex>
|
||||||
|
</NFlex>
|
||||||
|
|
||||||
|
<!-- 通用 -->
|
||||||
|
<NText
|
||||||
|
depth="3"
|
||||||
|
tag="div"
|
||||||
|
style="
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 0 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
通用
|
||||||
|
</NText>
|
||||||
|
<NFlex vertical :gap="2">
|
||||||
|
<NFlex
|
||||||
|
v-for="item in generalItems"
|
||||||
|
:key="item.key"
|
||||||
|
align="center"
|
||||||
|
:gap="10"
|
||||||
|
style="
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
"
|
||||||
|
:style="activePanel === item.key ? 'background: var(--n-color-embedded);' : ''"
|
||||||
|
@click="activePanel = item.key"
|
||||||
|
>
|
||||||
|
<span style="font-size: 15px; line-height: 1">{{ item.icon }}</span>
|
||||||
|
<NText :style="activePanel === item.key ? 'font-weight: 500;' : ''">
|
||||||
|
{{ item.label }}
|
||||||
|
</NText>
|
||||||
|
</NFlex>
|
||||||
|
</NFlex>
|
||||||
|
</NLayoutSider>
|
||||||
|
|
||||||
|
<NLayoutContent :native-scrollbar="false" content-style="padding: 32px 40px;">
|
||||||
|
<component :is="panelMap[activePanel]" />
|
||||||
|
</NLayoutContent>
|
||||||
|
</NLayout>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { NButton, NCard, NFlex, NTag, NText, NA } from 'naive-ui';
|
||||||
|
import { useMessage } from 'naive-ui';
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
|
||||||
|
function handleAdd() {
|
||||||
|
message.info('新增 API 扩展');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NFlex vertical :gap="20">
|
||||||
|
<NText tag="h2" style="font-size: 20px; font-weight: 600; margin: 0">API 扩展</NText>
|
||||||
|
|
||||||
|
<NCard :bordered="true" style="max-width: 760px">
|
||||||
|
<NFlex :gap="16" align="flex-start">
|
||||||
|
<NFlex
|
||||||
|
align="center"
|
||||||
|
justify="center"
|
||||||
|
style="
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--n-color-embedded);
|
||||||
|
flex-shrink: 0;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span style="font-size: 24px">📤</span>
|
||||||
|
</NFlex>
|
||||||
|
<NFlex vertical :gap="6">
|
||||||
|
<NText style="font-size: 14px; line-height: 1.6">
|
||||||
|
API 扩展提供了一个集中式的 API 管理,在此统一添加 API 配置后,方便在 Dify
|
||||||
|
上的各类应用中直接使用。
|
||||||
|
</NText>
|
||||||
|
<NA href="#" style="font-size: 13px"> 了解如何开发您自己的 API 扩展。↗ </NA>
|
||||||
|
</NFlex>
|
||||||
|
</NFlex>
|
||||||
|
</NCard>
|
||||||
|
|
||||||
|
<NButton
|
||||||
|
secondary
|
||||||
|
style="max-width: 760px; width: 100%; justify-content: center"
|
||||||
|
@click="handleAdd"
|
||||||
|
>
|
||||||
|
+ 新增 API 扩展
|
||||||
|
</NButton>
|
||||||
|
</NFlex>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { NFlex, NText, NCard, NButton, NIcon, NSpin } from 'naive-ui';
|
||||||
|
import { ChevronDownOutline, ChevronForwardOutline } from '@vicons/ionicons5';
|
||||||
|
|
||||||
|
const installExpanded = ref(true);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NFlex vertical :gap="24">
|
||||||
|
<NText tag="h2" style="font-size: 20px; font-weight: 600; margin: 0">数据来源</NText>
|
||||||
|
|
||||||
|
<!-- Google Drive Card -->
|
||||||
|
<NCard :bordered="true" size="small" style="border-radius: 10px">
|
||||||
|
<!-- 顶部:logo + 名称 + 副标题 + 配置按钮 -->
|
||||||
|
<NFlex align="center" justify="space-between">
|
||||||
|
<NFlex align="center" :gap="12">
|
||||||
|
<!-- Google Drive logo -->
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 87.3 78"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
style="width: 36px; height: 32px"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m6.6 66.85 3.85 6.65c.8 1.4 1.95 2.5 3.3 3.3l13.75-23.8h-27.5c0 1.55.4 3.1 1.2 4.5z"
|
||||||
|
fill="#0066da"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m43.65 25-13.75-23.8c-1.35.8-2.5 1.9-3.3 3.3l-25.4 44a9.06 9.06 0 0 0 -1.2 4.5h27.5z"
|
||||||
|
fill="#00ac47"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m73.55 76.8c1.35-.8 2.5-1.9 3.3-3.3l1.6-2.75 7.65-13.25c.8-1.4 1.2-2.95 1.2-4.5h-27.502l5.852 11.5z"
|
||||||
|
fill="#ea4335"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m43.65 25 13.75-23.8c-1.35-.8-2.9-1.2-4.5-1.2h-18.5c-1.6 0-3.15.45-4.5 1.2z"
|
||||||
|
fill="#00832d"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m59.8 53h-32.3l-13.75 23.8c1.35.8 2.9 1.2 4.5 1.2h50.8c1.6 0 3.15-.45 4.5-1.2z"
|
||||||
|
fill="#2684fc"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m73.4 26.5-12.7-22c-.8-1.4-1.95-2.5-3.3-3.3l-13.75 23.8 16.15 28h27.45c0-1.55-.4-3.1-1.2-4.5z"
|
||||||
|
fill="#ffba00"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<NFlex vertical :gap="3">
|
||||||
|
<NText style="font-size: 14px; font-weight: 600; line-height: 1.4">Google Drive</NText>
|
||||||
|
<NText depth="3" style="font-size: 12px">langgenius/google_drive</NText>
|
||||||
|
</NFlex>
|
||||||
|
</NFlex>
|
||||||
|
<NButton size="small" secondary>+ 配置</NButton>
|
||||||
|
</NFlex>
|
||||||
|
|
||||||
|
<!-- 已连接的工作区 sub-section -->
|
||||||
|
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--n-border-color)">
|
||||||
|
<NText depth="3" style="font-size: 12px; display: block; margin-bottom: 10px"
|
||||||
|
>已连接的工作区</NText
|
||||||
|
>
|
||||||
|
<NText depth="3" style="font-size: 13px">请配置凭据</NText>
|
||||||
|
</div>
|
||||||
|
</NCard>
|
||||||
|
|
||||||
|
<!-- 安装数据源供应商 -->
|
||||||
|
<div>
|
||||||
|
<NFlex
|
||||||
|
align="center"
|
||||||
|
justify="space-between"
|
||||||
|
style="cursor: pointer; user-select: none; padding: 4px 0"
|
||||||
|
@click="installExpanded = !installExpanded"
|
||||||
|
>
|
||||||
|
<NFlex align="center" :gap="6">
|
||||||
|
<NIcon
|
||||||
|
:component="installExpanded ? ChevronDownOutline : ChevronForwardOutline"
|
||||||
|
size="16"
|
||||||
|
style="color: #6b7280"
|
||||||
|
/>
|
||||||
|
<NText style="font-size: 14px; font-weight: 500">安装数据源供应商</NText>
|
||||||
|
</NFlex>
|
||||||
|
<a
|
||||||
|
href="javascript:void(0)"
|
||||||
|
style="font-size: 13px; color: #3b82f6; text-decoration: none"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
发现更多就在 Dify 市场 ↗
|
||||||
|
</a>
|
||||||
|
</NFlex>
|
||||||
|
<div v-if="installExpanded" style="padding: 20px 0; text-align: center">
|
||||||
|
<NSpin size="small" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NFlex>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { NFlex, NText, NSelect } from 'naive-ui';
|
||||||
|
|
||||||
|
const language = ref('zh-CN');
|
||||||
|
const langOptions = [
|
||||||
|
{ label: '简体中文', value: 'zh-CN' },
|
||||||
|
{ label: 'English', value: 'en-US' },
|
||||||
|
{ label: '日本語', value: 'ja-JP' },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NFlex vertical :gap="20">
|
||||||
|
<NText tag="h2" style="font-size: 20px; font-weight: 600; margin: 0">语言</NText>
|
||||||
|
<NFlex align="center" :gap="16">
|
||||||
|
<NText style="font-size: 14px; white-space: nowrap">界面语言</NText>
|
||||||
|
<NSelect v-model:value="language" :options="langOptions" style="width: 200px" />
|
||||||
|
</NFlex>
|
||||||
|
</NFlex>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { NFlex, NText, NButton, NDataTable, NAvatar } from 'naive-ui';
|
||||||
|
import { useMessage } from 'naive-ui';
|
||||||
|
import type { DataTableColumns } from 'naive-ui';
|
||||||
|
import { h, ref } from 'vue';
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
|
||||||
|
interface Member {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = ref<Member[]>([
|
||||||
|
{ id: 1, name: 'godboy', email: 'godboy@example.com', role: '管理员' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const columns: DataTableColumns<Member> = [
|
||||||
|
{ title: '姓名', key: 'name' },
|
||||||
|
{ title: '邮箱', key: 'email' },
|
||||||
|
{ title: '角色', key: 'role' },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
render: (row) =>
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
text: true,
|
||||||
|
type: 'error',
|
||||||
|
onClick: () => message.info(`移除 ${row.name}`),
|
||||||
|
},
|
||||||
|
{ default: () => '移除' },
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NFlex vertical :gap="20">
|
||||||
|
<NFlex align="center" justify="space-between">
|
||||||
|
<NText tag="h2" style="font-size: 20px; font-weight: 600; margin: 0">成员</NText>
|
||||||
|
<NButton type="primary" size="small" @click="message.info('邀请成员')">+ 邀请成员</NButton>
|
||||||
|
</NFlex>
|
||||||
|
<NDataTable :columns="columns" :data="data" :bordered="false" />
|
||||||
|
</NFlex>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import {
|
||||||
|
NFlex,
|
||||||
|
NText,
|
||||||
|
NCard,
|
||||||
|
NButton,
|
||||||
|
NInput,
|
||||||
|
NIcon,
|
||||||
|
NAvatar,
|
||||||
|
NCollapse,
|
||||||
|
NCollapseItem,
|
||||||
|
NDivider,
|
||||||
|
} from 'naive-ui';
|
||||||
|
import { SearchOutline, SettingsOutline } from '@vicons/ionicons5';
|
||||||
|
import { useVbenForm, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { ModelCertificateFrom } from './schema';
|
||||||
|
|
||||||
|
const searchText = ref('');
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
title: '编辑模型凭据',
|
||||||
|
});
|
||||||
|
const manageModels = (id: string) => {
|
||||||
|
modalApi.open();
|
||||||
|
};
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
showDefaultActions: false,
|
||||||
|
...ModelCertificateFrom,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NFlex vertical :gap="24">
|
||||||
|
<!-- 页面标题 + 搜索 -->
|
||||||
|
<NFlex align="center" justify="space-between">
|
||||||
|
<NText tag="h2" style="font-size: 20px; font-weight: 600; margin: 0">模型供应商</NText>
|
||||||
|
<NInput
|
||||||
|
v-model:value="searchText"
|
||||||
|
placeholder="搜索"
|
||||||
|
size="small"
|
||||||
|
style="width: 200px"
|
||||||
|
clearable
|
||||||
|
round
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<NIcon :component="SearchOutline" />
|
||||||
|
</template>
|
||||||
|
</NInput>
|
||||||
|
</NFlex>
|
||||||
|
|
||||||
|
<!-- 模型列表 -->
|
||||||
|
<div>
|
||||||
|
<NFlex align="center" justify="space-between" style="margin-bottom: 12px">
|
||||||
|
<NText style="font-size: 14px; font-weight: 500">模型列表</NText>
|
||||||
|
<NButton text size="small" type="primary">
|
||||||
|
<template #icon>
|
||||||
|
<NIcon :component="SettingsOutline" />
|
||||||
|
</template>
|
||||||
|
默认模型设置
|
||||||
|
</NButton>
|
||||||
|
</NFlex>
|
||||||
|
|
||||||
|
<!-- Ollama Card (已配置) -->
|
||||||
|
<NFlex>
|
||||||
|
<NCard :bordered="true" size="small" style="border-radius: 10px">
|
||||||
|
<NFlex justify="space-between" align="center">
|
||||||
|
<NFlex align="center" :gap="12">
|
||||||
|
<!-- Ollama Logo -->
|
||||||
|
<NAvatar
|
||||||
|
style="width: 40px; height: 40px"
|
||||||
|
size="small"
|
||||||
|
src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp"
|
||||||
|
/>
|
||||||
|
<NText style="font-size: 14px; font-weight: 600; line-height: 1">Ollama</NText>
|
||||||
|
</NFlex>
|
||||||
|
<NFlex :gap="8">
|
||||||
|
<NButton size="small" secondary @click="manageModels('ollama')">管理凭据</NButton>
|
||||||
|
</NFlex>
|
||||||
|
</NFlex>
|
||||||
|
<NDivider />
|
||||||
|
<NCollapse>
|
||||||
|
<NCollapseItem>
|
||||||
|
<template #header>
|
||||||
|
<n-text type="primary"> 已连接的模型 </n-text>
|
||||||
|
</template>
|
||||||
|
</NCollapseItem>
|
||||||
|
</NCollapse>
|
||||||
|
</NCard>
|
||||||
|
</NFlex>
|
||||||
|
</div>
|
||||||
|
</NFlex>
|
||||||
|
<Modal>
|
||||||
|
<Form />
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.n-divider:not(.n-divider--vertical) {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import type { VbenFormProps } from "@vben/common-ui";
|
||||||
|
|
||||||
|
export const ModelCertificateFrom: VbenFormProps = {
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
fieldName: 'CertificateType',
|
||||||
|
label: '凭证类型',
|
||||||
|
component: "Select",
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: 'API密钥', value: 'apiKey' },
|
||||||
|
{ label: '其他', value: 'other' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: "modelName",
|
||||||
|
label: "模型名称",
|
||||||
|
component: "Input",
|
||||||
|
rules: "required",
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ["CertificateType"],
|
||||||
|
show: (values) => values.CertificateType === 'other',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: "modelType",
|
||||||
|
label: "模型类型",
|
||||||
|
component: "Select",
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: 'LLM', value: 'llm' },
|
||||||
|
{ label: 'Text Embedding', value: 'textEmbedding' },
|
||||||
|
{ label: 'Rerank', value: 'rerank' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ["CertificateType"],
|
||||||
|
show: (values) => values.CertificateType === 'other',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue