fetch: 页面移植

AI应用接口对接
This commit is contained in:
杨谢雨 2025-03-05 17:44:31 +08:00
parent 1bc33d2784
commit ac2d981a23
21 changed files with 374 additions and 224 deletions

View File

@ -62,7 +62,7 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"

View File

@ -20,41 +20,41 @@ import request from '@/config/axios'
export const AppApi = {
// 获取应用列表
getAppList: async (params: any) => {
return await request.get({ url: '/aigc/app/list', params })
return await request.get({ url: '/chat/aigc/app/list', params })
},
// 获取应用分页数据
getAppPage: async (params: any) => {
return await request.get({ url: '/aigc/app/page', params })
return await request.get({ url: '/chat/aigc/app/page', params })
},
// 获取应用详情
getApp: async (id: string) => {
return await request.get({ url: `/aigc/app/${id}` })
return await request.get({ url: `/chat/aigc/app/${id}` })
},
// 根据模型ID获取应用
getAppByModelId: async (id: string) => {
return await request.get({ url: `/aigc/app/byModelId/${id}` })
return await request.get({ url: `/chat/aigc/app/byModelId/${id}` })
},
// 获取应用API通道
getAppApiChannel: async (appId: string) => {
return await request.get({ url: `/aigc/app/channel/api/${appId}` })
return await request.get({ url: `/chat/aigc/app/channel/api/${appId}` })
},
// 新增应用
createApp: async (data: any) => {
return await request.post({ url: '/aigc/app', data })
return await request.post({ url: '/chat/aigc/app', data })
},
// 更新应用
updateApp: async (data: any) => {
return await request.put({ url: '/aigc/app', data })
return await request.put({ url: '/chat/aigc/app', data })
},
// 删除应用
deleteApp: async (id: string) => {
return await request.delete({ url: `/aigc/app/${id}` })
return await request.delete({ url: `/chat/aigc/app/${id}` })
}
}

View File

@ -20,41 +20,41 @@ import request from '@/config/axios'
export const AppApiManagement = {
// 获取API列表
getApiList: async (params: any) => {
return await request.get({ url: '/aigc/app/api/list', params })
return await request.get({ url: '/chat/aigc/app/api/list', params })
},
// 获取API分页数据
getApiPage: async (params: any) => {
return await request.get({ url: '/aigc/app/api/page', params })
return await request.get({ url: '/chat/aigc/app/api/page', params })
},
// 获取API详情
getApi: async (id: string) => {
return await request.get({ url: `/aigc/app/api/${id}` })
return await request.get({ url: `/chat/aigc/app/api/${id}` })
},
// 新增API
createApi: async (data: any) => {
return await request.post({ url: '/aigc/app/api', data })
return await request.post({ url: '/chat/aigc/app/api', data })
},
// 更新API
updateApi: async (data: any) => {
return await request.put({ url: '/aigc/app/api', data })
return await request.put({ url: '/chat/aigc/app/api', data })
},
// 删除API
deleteApi: async (id: string) => {
return await request.delete({ url: `/aigc/app/api/${id}` })
return await request.delete({ url: `/chat/aigc/app/api/${id}` })
},
// 发布API
publishApi: async (id: string) => {
return await request.put({ url: `/aigc/app/api/publish/${id}` })
return await request.put({ url: `/chat/aigc/app/api/publish/${id}` })
},
// 下线API
offlineApi: async (id: string) => {
return await request.put({ url: `/aigc/app/api/offline/${id}` })
return await request.put({ url: `/chat/aigc/app/api/offline/${id}` })
}
}

View File

@ -23,7 +23,7 @@ export function chat(
onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void
) {
return request.post({
url: '/aigc/chat/completions',
url: '/chat/aigc/chat/completions',
data,
signal: controller.signal,
onDownloadProgress,
@ -33,26 +33,26 @@ export function chat(
export function clean(conversationId: string | null) {
return request.delete({
url: `/aigc/chat/messages/clean/${conversationId}`
url: `/chat/aigc/chat/messages/clean/${conversationId}`
})
}
export function getMessages(conversationId?: string | number) {
return request.get({
url: `/aigc/chat/messages/${conversationId}`
url: `/chat/aigc/chat/messages/${conversationId}`
})
}
export function getAppInfo(params: any) {
return request.get({
url: `/aigc/app/info`,
url: `/chat/aigc/app/info`,
params
})
}
export function getImageModels() {
return request.get({
url: '/aigc/chat/getImageModels'
url: '/chat/aigc/chat/getImageModels'
})
}
@ -61,7 +61,7 @@ export function getImageModels() {
*/
export function genImage(data: any) {
return request.post({
url: '/aigc/chat/image',
url: '/chat/aigc/chat/image',
data
})
}
@ -71,7 +71,7 @@ export function genImage(data: any) {
*/
export function genMindMap(data: any) {
return request.post({
url: '/aigc/chat/mindmap',
url: '/chat/aigc/chat/mindmap',
data
})
}

View File

@ -24,8 +24,8 @@ export class OssApi {
* OSS上传策略
*/
static policy() {
return request.get({
url: '/oss/policy'
return request.get({
url: '/chat/oss/policy'
})
}
@ -33,9 +33,9 @@ export class OssApi {
*
*/
static list(params: any) {
return request.get({
url: '/oss/list',
params
return request.get({
url: '/chat/oss/list',
params
})
}
@ -43,20 +43,19 @@ export class OssApi {
*
*/
static del(objectName: string) {
return request.delete({
url: `/oss/${objectName}`
return request.delete({
url: `/chat/oss/${objectName}`
})
}
/**
*
*/
static upload(data: FormData) {
return request.post({
url: '/aigc/oss/upload',
headers: {
'Content-Type': 'multipart/form-data',
},
static uploadUrl = '/chat/aigc/oss/upload'
static upload(data: any) {
return request.upload({
url: '/chat/aigc/oss/upload',
data
})
}
@ -65,8 +64,8 @@ export class OssApi {
* 访URL
*/
static getUrl(objectName: string) {
return request.get({
url: `/oss/url/${objectName}`
return request.get({
url: `/chat/oss/url/${objectName}`
})
}
}

View File

@ -113,7 +113,9 @@ export default defineComponent({
}
}
}
const setValue = (key: string, value: any) => {
formModel.value[key] = value
}
const getElFormRef = (): ComponentRef<typeof ElForm> => {
return unref(elFormRef) as ComponentRef<typeof ElForm>
}
@ -129,7 +131,8 @@ export default defineComponent({
addSchema,
setSchema,
getElFormRef,
clearForm
clearForm,
setValue
})
// formModel

View File

@ -79,7 +79,9 @@ const props = defineProps({
width: propTypes.string.def('150px'), // ==> 150px
borderradius: propTypes.string.def('8px'), // ==> 8px
showDelete: propTypes.bool.def(true), //
showBtnText: propTypes.bool.def(true) //
showBtnText: propTypes.bool.def(true), //
customApi: propTypes.func.def(() => undefined), // ==> '')
successBefore: propTypes.func.def(() => undefined), // ==> undefined
})
const { t } = useI18n() //
const message = useMessage() //
@ -99,7 +101,7 @@ const deleteImg = () => {
emit('update:modelValue', '')
}
const { uploadUrl, httpRequest } = useUpload()
const { uploadUrl, httpRequest } = useUpload({ customApi: props.customApi })
const editImg = () => {
const dom = document.querySelector(`#${uuid.value} .el-upload__input`)
@ -117,8 +119,12 @@ const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
//
const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
let params = res
if (props.successBefore) {
params = props.successBefore(res)
}
message.success('上传成功')
emit('update:modelValue', res.data)
emit('update:modelValue', params.data)
}
//

View File

@ -10,7 +10,7 @@ export const getUploadUrl = (): string => {
return import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/infra/file/upload'
}
export const useUpload = () => {
export const useUpload = (defaultParams?: { customApi: any }) => {
// 后端上传地址
const uploadUrl = getUploadUrl()
// 是否使用前端直连上传
@ -40,7 +40,8 @@ export const useUpload = () => {
// 模式二:后端上传
// 重写 el-upload httpRequest 文件上传成功会走成功的钩子,失败走失败的钩子
return new Promise((resolve, reject) => {
FileApi.updateFile({ file: options.file })
const Api = defaultParams?.customApi || FileApi.updateFile
Api({ file: options.file })
.then((res) => {
if (res.code === 0) {
resolve(res)

View File

@ -149,7 +149,7 @@ service.interceptors.response.use(
})
}
} else if (code === 500) {
ElMessage.error(t('sys.api.errMsg500'))
ElMessage.error(msg || t('sys.api.errMsg500'))
return Promise.reject(new Error(msg))
} else if (code === 901) {
ElMessage.error({

142
src/styles/highligt.css Normal file
View File

@ -0,0 +1,142 @@
/* VSCode Dark+ Theme for highlight.js */
.hljs {
background: #1e1e1e !important;
color: #d4d4d4 !important;
--el-scrollbar-opacity: 0.3;
--el-scrollbar-bg-color: var(--el-text-color-secondary);
--el-scrollbar-hover-opacity: 0.5;
--el-scrollbar-hover-bg-color: var(--el-text-color-secondary);
}
/* 滚动条样式 */
.hljs::-webkit-scrollbar {
width: 6px !important;
}
.hljs::-webkit-scrollbar-thumb {
background-color: var(--el-scrollbar-bg-color) !important;
opacity: var(--el-scrollbar-opacity) !important;
border-radius: 3px !important;
}
.hljs::-webkit-scrollbar-thumb:hover {
background-color: var(--el-scrollbar-hover-bg-color) !important;
opacity: var(--el-scrollbar-hover-opacity) !important;
}
.hljs::-webkit-scrollbar-track {
background-color: transparent !important;
}
.hljs-keyword {
color: #569cd6 !important;
}
.hljs-built_in {
color: #4ec9b0 !important;
}
.hljs-type {
color: #4ec9b0 !important;
}
.hljs-literal {
color: #569cd6 !important;
}
.hljs-number {
color: #b5cea8 !important;
}
.hljs-regexp {
color: #d16969 !important;
}
.hljs-string {
color: #ce9178 !important;
}
.hljs-subst {
color: #d4d4d4 !important;
}
.hljs-symbol {
color: #d4d4d4 !important;
}
.hljs-class {
color: #4ec9b0 !important;
}
.hljs-function {
color: #dcdcaa !important;
}
.hljs-title {
color: #dcdcaa !important;
}
.hljs-params {
color: #d4d4d4 !important;
}
.hljs-comment {
color: #6a9955 !important;
}
.hljs-doctag {
color: #608b4e !important;
}
.hljs-meta,
.hljs-meta .hljs-keyword {
color: #9b9b9b !important;
}
.hljs-meta .hljs-string {
color: #ce9178 !important;
}
.hljs-attr {
color: #9cdcfe !important;
}
.hljs-attribute {
color: #9cdcfe !important;
}
.hljs-name {
color: #569cd6 !important;
}
.hljs-section {
color: #d4d4d4 !important;
}
.hljs-tag {
color: #569cd6 !important;
}
.hljs-variable {
color: #9cdcfe !important;
}
.hljs-template-variable {
color: #9cdcfe !important;
}
.hljs-template-tag {
color: #569cd6 !important;
}
/* 添加代码块样式 */
pre code.hljs {
display: block !important;
padding: 1em !important;
overflow-x: auto !important;
border-radius: 6px !important;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace !important;
font-size: 14px !important;
line-height: 1.5 !important;
}

View File

@ -2,7 +2,7 @@
@use './FormCreate/index.scss';
@use './theme.scss';
@use 'element-plus/theme-chalk/dark/css-vars.css';
@import './highligt.css';
.reset-margin [class*='el-icon'] + span {
margin-left: 2px !important;
}

View File

@ -43,7 +43,6 @@ async function fetchData() {
try {
const data = await getAppInfo({
appId: id,
conversationId: null
})
form.value = data
appStore.info = data

View File

@ -1,18 +1,4 @@
<!--
- Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
-
- Licensed under the GNU Affero General Public License, Version 3 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- https://www.gnu.org/licenses/agpl-3.0.html
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-->
<script lang="ts" setup>
import { useAppStore } from '@/views/ai/app/store';
@ -30,7 +16,7 @@
<div class="p-2 flex justify-between items-center">
<div class="text-md font-bold">Prompt 提示词</div>
</div>
<div class="p-4 pt-0 h-full mb-10">
<div class="p-4 pt-0 h-full mb-10" v-if="appStore.info">
<el-input
type="textarea"
v-model="appStore.info.prompt"

View File

@ -18,7 +18,7 @@ async function show() {
function onAdd(item) {
appStore.addKnowledge(item)
ElMessage.success('关联成功')
// ElMessage.success('')
}
function onRemove(item) {

View File

@ -11,6 +11,7 @@ const knowledgeRef = ref()
async function onSaveModel(val) {
appStore.modelId = val.id
console.log(val)
emit('update')
}

View File

@ -15,13 +15,13 @@
-->
<script lang="ts" setup>
import hljs from 'highlight.js';
import javascript from 'highlight.js/lib/languages/javascript';
import hljs from 'highlight.js';
import javascript from 'highlight.js/lib/languages/javascript';
import { onMounted } from 'vue';
hljs.registerLanguage('javascript', javascript);
const url = `http://langchat.cn`;
const request = `
hljs.registerLanguage('javascript', javascript);
const url = `http://langchat.cn`;
const request = `
POST /v1/chat/completions HTTP/1.1
Content-Type: application/json
Authorization: 'Bearer YOUR_ACCESS_TOKEN'
@ -31,9 +31,9 @@ Body:
{ "role": "user", "content": "你好" }
]
}
`;
`;
const response = `
const response = `
data: {"choices": [{"index": 0, "delta": {"content": "你好!"}, "finish_reason": null}], "session_id": null}
data: {"choices": [{"index": 0, "delta": {"content": "我能"}, "finish_reason": null}], "session_id": null}
@ -43,9 +43,9 @@ data: {"choices": [{"index": 0, "delta": {"content": "为你"}, "finish_reason":
data: {"choices": [{"index": 0, "delta": {"content": "做些什么?"}, "finish_reason": null}], "session_id": null}
data: {"choices": [{"index": 0, "delta": {}, "finish_reason": "stop", "usage": {"prompt_tokens": 9, "completion_tokens": 6, "total_tokens": 15}}], "session_id": null}
`;
`;
const demo = `
const demo = `
const url = 'http://langchat.cn/v1/chat/completions';
const data = {
"messages": [
@ -73,41 +73,76 @@ fetch(url, {
.catch(error => {
console.error('Error:', error);
});
`;
`;
onMounted(() => {
//
document.querySelectorAll('pre code').forEach((el) => {
hljs.highlightElement(el as HTMLElement);
});
});
</script>
<template>
<div class="p-4 bg-white h-full overflow-auto rounded">
<n-config-provider :hljs="hljs" class="flex flex-col gap-4">
<el-scrollbar class="p-4 bg-white h-full overflow-auto rounded">
<div class="flex flex-col gap-4">
<div>
<n-alert title="API URLAPI接口格式遵循OpenAI格式" type="info" />
<div class="bg-[#18181c] mt-2 py-2 px-4 overflow-x-auto rounded">
<n-code :code="url" class="text-white" language="JavaScript" />
<el-alert
title="API URLAPI接口格式遵循OpenAI格式"
type="info"
:closable="false"
show-icon
/>
<div class="bg-[#18181c] mt-10px py-2 px-4 overflow-x-auto rounded">
<pre><code class="javascript">{{ url }}</code></pre>
</div>
</div>
<div>
<n-alert title="Request" type="info" />
<div class="bg-[#18181c] mt-2 py-2 px-4 overflow-x-auto rounded">
<n-code :code="request" class="text-white" language="JavaScript" />
</div>
<el-alert
title="Request"
type="info"
:closable="false"
show-icon
/>
<el-scrollbar class="bg-[#18181c] mt-10px py-2 px-4 overflow-x-auto rounded">
<pre><code class="javascript">{{ request }}</code></pre>
</el-scrollbar>
</div>
<div>
<n-alert title="ResponseStream" type="info" />
<div class="bg-[#18181c] py-2 mt-2 px-4 overflow-x-auto rounded">
<n-code :code="response" class="text-white" language="JavaScript" />
</div>
<el-alert
title="ResponseStream"
type="info"
:closable="false"
show-icon
/>
<el-scrollbar class="mt-10px bg-[#18181c] py-2 px-4 rounded">
<pre><code class="javascript">{{ response }}</code></pre>
</el-scrollbar>
</div>
<div>
<n-alert title="API请求示例" type="info" />
<div class="bg-[#18181c] mt-2 py-2 px-4 overflow-x-auto rounded">
<n-code :code="demo" class="text-white" language="javascript" />
</div>
<el-alert
title="API请求示例"
type="info"
:closable="false"
show-icon
/>
<el-scrollbar class="mt-10px bg-[#18181c] py-2 px-4 rounded">
<pre><code class="javascript">{{ demo }}</code></pre>
</el-scrollbar>
</div>
</n-config-provider>
</div>
</div>
</el-scrollbar>
</template>
<style lang="less" scoped></style>
<style lang="scss" scoped>
:deep(pre) {
margin: 0;
code {
color: white;
}
}
</style>

View File

@ -4,12 +4,15 @@ import { nextTick, ref } from 'vue'
import { FormSchema } from '@/types/form'
import { AppApi } from '@/api/new-ai/app'
import { ElMessage } from 'element-plus'
import { OssApi } from '@/api/new-ai/oss'
import type { UploadFile, UploadRequestOptions } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import ModelSelect from '@/views/common/ModelSelect.vue'
const emit = defineEmits(['reload'])
const visible = ref(false)
const formData = ref({})
const formRef = ref()
const schemas = ref<FormSchema[]>([
const schemas = ref<Array<FormSchema>>([
{
field: 'name',
label: '应用名称',
@ -17,14 +20,32 @@ const schemas = ref<FormSchema[]>([
formItemProps: {
required: true,
rules: [
{ required: true, message: '请输入应用名称' },
{ min: 2, max: 20, message: '长度在2-20个字符' }
]
{ required: true, message: '请输入应用名称' },
{ min: 2, max: 20, message: '长度在2-20个字符' }
]
},
componentProps: {
placeholder: '请输入应用名称'
}
},
{
field: 'modelId',
label: '关联模型',
component: 'Input',
formItemProps: {
rules: [{ required: true, message: '请选择关联模型' }]
}
},
{
field: 'cover',
label: '应用图标',
component: 'Input',
formItemProps: {
required: false
},
componentProps: {
placeholder: '请输入应用图标'
}
},
{
field: 'description',
@ -33,37 +54,14 @@ const schemas = ref<FormSchema[]>([
formItemProps: {
required: true,
rules: [
{ required: true, message: '请输入应用描述' },
{ min: 2, max: 200, message: '长度在2-200个字符' }
]
{ required: true, message: '请输入应用描述' },
{ min: 2, max: 200, message: '长度在2-200个字符' }
]
},
componentProps: {
type: 'textarea',
rows: 4,
placeholder: '请输入应用描述'
},
},
{
field: 'icon',
label: '应用图标',
component: 'Input',
formItemProps: {
required: false,
},
componentProps: {
placeholder: '请输入应用图标'
}
},
{
field: 'modelId',
label: '关联模型',
component: 'Input',
formItemProps: {
required: false,
},
componentProps: {
placeholder: '请选择关联模型'
}
}
])
@ -95,29 +93,22 @@ const show = async (data: any = {}) => {
}
const handleSubmit = async () => {
try {
const form = formRef.value.getElFormRef()
await form.validate()
const values = formRef.value.formModel
loading.value = true
if (isEdit.value) {
await AppApi.updateApp(values)
ElMessage.success('更新应用成功')
} else {
await AppApi.createApp(values)
ElMessage.success('创建应用成功')
}
close()
emit('reload')
} catch (error) {
console.error('Failed to save app:', error)
ElMessage.error(isEdit.value ? '更新应用失败' : '创建应用失败')
} finally {
loading.value = false
const form = formRef.value.getElFormRef()
await form.validate()
const values = formRef.value.formModel
const Api = isEdit.value ? AppApi.updateApp : AppApi.createApp
loading.value = true
values.modelId = values.modelId?.[1]
await Api(values).finally(() => (loading.value = false))
ElMessage.success(isEdit.value ? '更新应用成功' : '创建应用成功')
close()
emit('reload')
}
const handleImport = (params: Record<any, any>) => {
return {
data: params.data.url
}
}
defineExpose({
show,
close
@ -125,21 +116,23 @@ defineExpose({
</script>
<template>
<el-dialog
v-model="visible"
:close-on-click-modal="!loading"
:close-on-press-escape="!loading"
<el-dialog
v-model="visible"
:close-on-click-modal="!loading"
:close-on-press-escape="!loading"
draggable
:title="isEdit ? '编辑应用' : '新增应用'"
:title="isEdit ? '编辑应用' : '新增应用'"
width="500px"
@close="close"
>
<Form
ref="formRef"
:model="formData"
:schema="schemas"
v-loading="loading"
/>
<Form ref="formRef" :model="formData" :schema="schemas" v-loading="loading">
<template #modelId="scope">
<ModelSelect :id="scope.modelId" class="w-full" v-model="scope.modelId" />
</template>
<template #cover="scoped">
<UploadImg v-model="scoped.cover" :custom-api="OssApi.upload" :success-before="handleImport"/>
</template>
</Form>
<template #footer>
<el-button type="primary" @click="handleSubmit" :loading="loading">确认</el-button>
<el-button @click="close" :loading="loading">取消</el-button>
@ -148,4 +141,24 @@ defineExpose({
</template>
<style scoped lang="scss">
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
</style>

View File

@ -20,30 +20,15 @@ const router = useRouter()
//
const loading = ref(false)
const tableData = ref<TableDataItem[]>([
{
id: 1,
name: '',
description: '',
apiKey: '',
provider: '',
channel: '',
createTime: ''
}
])
const tableData = ref<TableDataItem[]>([])
//
const loadData = async () => {
try {
loading.value = true
const res = await AppApi.getAppList({})
tableData.value = res.data || []
} catch (error) {
ElMessage.error('获取应用列表失败')
console.error('Failed to fetch app list:', error)
} finally {
loading.value = true
const res = await AppApi.getAppList({}).finally(() => {
loading.value = false
}
})
tableData.value = res || []
}
//
@ -86,7 +71,6 @@ const handleInfo = (record: any) => {
onMounted(() => {
loadData()
})
</script>
<template>
@ -109,15 +93,11 @@ onMounted(() => {
</div>
</el-col>
<el-col
v-for="item in tableData.concat(tableData).concat(tableData).concat(tableData)"
v-for="item in tableData"
:key="item.id"
:span="6"
>
<el-card
class="app-card cursor-pointer w-full"
shadow="hover"
@click="handleInfo(item)"
>
<el-card class="app-card cursor-pointer w-full" shadow="hover" @click="handleInfo(item)">
<template #header>
<div class="flex items-center">
<div class="sm:mx-4">
@ -135,7 +115,7 @@ onMounted(() => {
</div>
</template>
<div class="app-card-content flex justify-between items-center">
<p class="text-gray-600">{{ item.description || '暂无描述' }}</p>
<p class="text-gray-600">{{ item.des || '暂无描述' }}</p>
<el-dropdown trigger="hover" width="500px">
<div
:class="[activeDropdownId === item.id ? 'bg-gray-200' : 'hover:bg-gray-200']"
@ -148,13 +128,10 @@ onMounted(() => {
<el-dropdown-item @click.stop="handleEdit(item)">编辑此应用</el-dropdown-item>
<el-dropdown-item @click.stop="handleDelete(item)">删除此应用</el-dropdown-item>
<el-dropdown-item disabled divided>
<div class='w-150px'>
<div class="w-150px">
<div>信息</div>
<span class='text-xs text-stone-500'>
创建时间{{item.createTime}}
</span>
<span class="text-xs text-stone-500"> 创建时间{{ item.createTime }} </span>
</div>
</el-dropdown-item>
</el-dropdown-menu>
</template>
@ -162,6 +139,9 @@ onMounted(() => {
</div>
</el-card>
</el-col>
<el-col v-if="!tableData.length" :span="18">
<el-empty description="暂无数据"/>
</el-col>
</el-row>
<EditCom ref="editRef" @reload="loadData" />

View File

@ -25,13 +25,11 @@
async function fetchData() {
loading.value = true;
const id = route.params.id;
const data = await getAppInfo({
appId: id,
conversationId: null,
const appId = route.params.id;
const data = await getAppInfo({ appId }).finally(() => {
loading.value = false
});
form.value = data;
loading.value = false;
}
</script>

View File

@ -25,7 +25,7 @@
</script>
<template>
<div class="p-4 pt-1 chat-card w-full h-full bg-white rounded-xl shadow-xl pt-20px">
<div class="p-4 pt-1 chat-card w-full h-full bg-white rounded-xl shadow-md pt-20px">
<Header title="AI聊天助手" @reload="fetch" />
<main ref="contentRef" class="flex-1 overflow-hidden overflow-y-auto">
<Chat />
@ -34,12 +34,10 @@
</template>
<style lang="scss" scoped>
:v-deep(.n-tabs.n-tabs--top .n-tab-pane) {
padding: 0 !important;
}
.chat-card {
box-sizing: border-box;
height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - (var(--app-content-padding) * 3)) !important;
height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - (var(--app-content-padding) * 3) + 20px) !important;
main {
height: calc(100% - 60px);
}

View File

@ -1,18 +1,4 @@
<!--
- Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
-
- Licensed under the GNU Affero General Public License, Version 3 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- https://www.gnu.org/licenses/agpl-3.0.html
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-->
<script lang="ts" setup>
import { onMounted, ref, toRaw } from 'vue'
@ -69,12 +55,12 @@ onMounted(async () => {
})
function onUpdate(val: any) {
console.log(val, 'val')
const group = options.value.find(g => g.children.some(c => c.value === val))
if (!group) return
console.log(group, 'group')
const option = group.children.find(c => c.value === val)
if (!option) return
const obj = option.raw
emit('update', {
id: obj.id,
@ -88,16 +74,19 @@ function onUpdate(val: any) {
</script>
<template>
<el-cascader
<el-select
v-model="modelId"
:options="options"
:size="size"
:props="{
expandTrigger: 'hover'
}"
placeholder="请选择关联模型"
@change="onUpdate"
/>
>
<el-option-group v-for="group in options" :key="group.label" :label="group.label">
<el-option v-for="option in group.children" :key="option.value" :label="option.label" :value="option.value" />
</el-option-group>
</el-select>
</template>
<style lang="scss" scoped>