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" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"[typescript]": { "[typescript]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"[typescriptreact]": { "[typescriptreact]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint" "editor.defaultFormatter": "rvest.vs-code-prettier-eslint"

View File

@ -20,41 +20,41 @@ import request from '@/config/axios'
export const AppApi = { export const AppApi = {
// 获取应用列表 // 获取应用列表
getAppList: async (params: any) => { 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) => { 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) => { getApp: async (id: string) => {
return await request.get({ url: `/aigc/app/${id}` }) return await request.get({ url: `/chat/aigc/app/${id}` })
}, },
// 根据模型ID获取应用 // 根据模型ID获取应用
getAppByModelId: async (id: string) => { getAppByModelId: async (id: string) => {
return await request.get({ url: `/aigc/app/byModelId/${id}` }) return await request.get({ url: `/chat/aigc/app/byModelId/${id}` })
}, },
// 获取应用API通道 // 获取应用API通道
getAppApiChannel: async (appId: string) => { 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) => { 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) => { 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) => { 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 = { export const AppApiManagement = {
// 获取API列表 // 获取API列表
getApiList: async (params: any) => { 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分页数据 // 获取API分页数据
getApiPage: async (params: any) => { 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详情 // 获取API详情
getApi: async (id: string) => { getApi: async (id: string) => {
return await request.get({ url: `/aigc/app/api/${id}` }) return await request.get({ url: `/chat/aigc/app/api/${id}` })
}, },
// 新增API // 新增API
createApi: async (data: any) => { createApi: async (data: any) => {
return await request.post({ url: '/aigc/app/api', data }) return await request.post({ url: '/chat/aigc/app/api', data })
}, },
// 更新API // 更新API
updateApi: async (data: any) => { updateApi: async (data: any) => {
return await request.put({ url: '/aigc/app/api', data }) return await request.put({ url: '/chat/aigc/app/api', data })
}, },
// 删除API // 删除API
deleteApi: async (id: string) => { deleteApi: async (id: string) => {
return await request.delete({ url: `/aigc/app/api/${id}` }) return await request.delete({ url: `/chat/aigc/app/api/${id}` })
}, },
// 发布API // 发布API
publishApi: async (id: string) => { 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 // 下线API
offlineApi: async (id: string) => { 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 onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void
) { ) {
return request.post({ return request.post({
url: '/aigc/chat/completions', url: '/chat/aigc/chat/completions',
data, data,
signal: controller.signal, signal: controller.signal,
onDownloadProgress, onDownloadProgress,
@ -33,26 +33,26 @@ export function chat(
export function clean(conversationId: string | null) { export function clean(conversationId: string | null) {
return request.delete({ return request.delete({
url: `/aigc/chat/messages/clean/${conversationId}` url: `/chat/aigc/chat/messages/clean/${conversationId}`
}) })
} }
export function getMessages(conversationId?: string | number) { export function getMessages(conversationId?: string | number) {
return request.get({ return request.get({
url: `/aigc/chat/messages/${conversationId}` url: `/chat/aigc/chat/messages/${conversationId}`
}) })
} }
export function getAppInfo(params: any) { export function getAppInfo(params: any) {
return request.get({ return request.get({
url: `/aigc/app/info`, url: `/chat/aigc/app/info`,
params params
}) })
} }
export function getImageModels() { export function getImageModels() {
return request.get({ return request.get({
url: '/aigc/chat/getImageModels' url: '/chat/aigc/chat/getImageModels'
}) })
} }
@ -61,7 +61,7 @@ export function getImageModels() {
*/ */
export function genImage(data: any) { export function genImage(data: any) {
return request.post({ return request.post({
url: '/aigc/chat/image', url: '/chat/aigc/chat/image',
data data
}) })
} }
@ -71,7 +71,7 @@ export function genImage(data: any) {
*/ */
export function genMindMap(data: any) { export function genMindMap(data: any) {
return request.post({ return request.post({
url: '/aigc/chat/mindmap', url: '/chat/aigc/chat/mindmap',
data data
}) })
} }

View File

@ -25,7 +25,7 @@ export class OssApi {
*/ */
static policy() { static policy() {
return request.get({ return request.get({
url: '/oss/policy' url: '/chat/oss/policy'
}) })
} }
@ -34,7 +34,7 @@ export class OssApi {
*/ */
static list(params: any) { static list(params: any) {
return request.get({ return request.get({
url: '/oss/list', url: '/chat/oss/list',
params params
}) })
} }
@ -44,19 +44,18 @@ export class OssApi {
*/ */
static del(objectName: string) { static del(objectName: string) {
return request.delete({ return request.delete({
url: `/oss/${objectName}` url: `/chat/oss/${objectName}`
}) })
} }
/** /**
* *
*/ */
static upload(data: FormData) { static uploadUrl = '/chat/aigc/oss/upload'
return request.post({
url: '/aigc/oss/upload', static upload(data: any) {
headers: { return request.upload({
'Content-Type': 'multipart/form-data', url: '/chat/aigc/oss/upload',
},
data data
}) })
} }
@ -66,7 +65,7 @@ export class OssApi {
*/ */
static getUrl(objectName: string) { static getUrl(objectName: string) {
return request.get({ return request.get({
url: `/oss/url/${objectName}` 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> => { const getElFormRef = (): ComponentRef<typeof ElForm> => {
return unref(elFormRef) as ComponentRef<typeof ElForm> return unref(elFormRef) as ComponentRef<typeof ElForm>
} }
@ -129,7 +131,8 @@ export default defineComponent({
addSchema, addSchema,
setSchema, setSchema,
getElFormRef, getElFormRef,
clearForm clearForm,
setValue
}) })
// formModel // formModel

View File

@ -79,7 +79,9 @@ const props = defineProps({
width: propTypes.string.def('150px'), // ==> 150px width: propTypes.string.def('150px'), // ==> 150px
borderradius: propTypes.string.def('8px'), // ==> 8px borderradius: propTypes.string.def('8px'), // ==> 8px
showDelete: propTypes.bool.def(true), // 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 { t } = useI18n() //
const message = useMessage() // const message = useMessage() //
@ -99,7 +101,7 @@ const deleteImg = () => {
emit('update:modelValue', '') emit('update:modelValue', '')
} }
const { uploadUrl, httpRequest } = useUpload() const { uploadUrl, httpRequest } = useUpload({ customApi: props.customApi })
const editImg = () => { const editImg = () => {
const dom = document.querySelector(`#${uuid.value} .el-upload__input`) 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 => { const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
let params = res
if (props.successBefore) {
params = props.successBefore(res)
}
message.success('上传成功') 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' 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() const uploadUrl = getUploadUrl()
// 是否使用前端直连上传 // 是否使用前端直连上传
@ -40,7 +40,8 @@ export const useUpload = () => {
// 模式二:后端上传 // 模式二:后端上传
// 重写 el-upload httpRequest 文件上传成功会走成功的钩子,失败走失败的钩子 // 重写 el-upload httpRequest 文件上传成功会走成功的钩子,失败走失败的钩子
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
FileApi.updateFile({ file: options.file }) const Api = defaultParams?.customApi || FileApi.updateFile
Api({ file: options.file })
.then((res) => { .then((res) => {
if (res.code === 0) { if (res.code === 0) {
resolve(res) resolve(res)

View File

@ -149,7 +149,7 @@ service.interceptors.response.use(
}) })
} }
} else if (code === 500) { } else if (code === 500) {
ElMessage.error(t('sys.api.errMsg500')) ElMessage.error(msg || t('sys.api.errMsg500'))
return Promise.reject(new Error(msg)) return Promise.reject(new Error(msg))
} else if (code === 901) { } else if (code === 901) {
ElMessage.error({ 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 './FormCreate/index.scss';
@use './theme.scss'; @use './theme.scss';
@use 'element-plus/theme-chalk/dark/css-vars.css'; @use 'element-plus/theme-chalk/dark/css-vars.css';
@import './highligt.css';
.reset-margin [class*='el-icon'] + span { .reset-margin [class*='el-icon'] + span {
margin-left: 2px !important; margin-left: 2px !important;
} }

View File

@ -43,7 +43,6 @@ async function fetchData() {
try { try {
const data = await getAppInfo({ const data = await getAppInfo({
appId: id, appId: id,
conversationId: null
}) })
form.value = data form.value = data
appStore.info = 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> <script lang="ts" setup>
import { useAppStore } from '@/views/ai/app/store'; import { useAppStore } from '@/views/ai/app/store';
@ -30,7 +16,7 @@
<div class="p-2 flex justify-between items-center"> <div class="p-2 flex justify-between items-center">
<div class="text-md font-bold">Prompt 提示词</div> <div class="text-md font-bold">Prompt 提示词</div>
</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 <el-input
type="textarea" type="textarea"
v-model="appStore.info.prompt" v-model="appStore.info.prompt"

View File

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

View File

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

View File

@ -15,13 +15,13 @@
--> -->
<script lang="ts" setup> <script lang="ts" setup>
import hljs from 'highlight.js'; import hljs from 'highlight.js';
import javascript from 'highlight.js/lib/languages/javascript'; import javascript from 'highlight.js/lib/languages/javascript';
import { onMounted } from 'vue';
hljs.registerLanguage('javascript', javascript); hljs.registerLanguage('javascript', javascript);
const url = `http://langchat.cn`;
const url = `http://langchat.cn`; const request = `
const request = `
POST /v1/chat/completions HTTP/1.1 POST /v1/chat/completions HTTP/1.1
Content-Type: application/json Content-Type: application/json
Authorization: 'Bearer YOUR_ACCESS_TOKEN' Authorization: 'Bearer YOUR_ACCESS_TOKEN'
@ -31,9 +31,9 @@ Body:
{ "role": "user", "content": "你好" } { "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}
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": {"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} 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 url = 'http://langchat.cn/v1/chat/completions';
const data = { const data = {
"messages": [ "messages": [
@ -73,41 +73,76 @@ fetch(url, {
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
}); });
`; `;
onMounted(() => {
//
document.querySelectorAll('pre code').forEach((el) => {
hljs.highlightElement(el as HTMLElement);
});
});
</script> </script>
<template> <template>
<div class="p-4 bg-white h-full overflow-auto rounded"> <el-scrollbar class="p-4 bg-white h-full overflow-auto rounded">
<n-config-provider :hljs="hljs" class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div> <div>
<n-alert title="API URLAPI接口格式遵循OpenAI格式" type="info" /> <el-alert
<div class="bg-[#18181c] mt-2 py-2 px-4 overflow-x-auto rounded"> title="API URLAPI接口格式遵循OpenAI格式"
<n-code :code="url" class="text-white" language="JavaScript" /> 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> </div>
<div> <div>
<n-alert title="Request" type="info" /> <el-alert
<div class="bg-[#18181c] mt-2 py-2 px-4 overflow-x-auto rounded"> title="Request"
<n-code :code="request" class="text-white" language="JavaScript" /> type="info"
</div> :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>
<div> <div>
<n-alert title="ResponseStream" type="info" /> <el-alert
<div class="bg-[#18181c] py-2 mt-2 px-4 overflow-x-auto rounded"> title="ResponseStream"
<n-code :code="response" class="text-white" language="JavaScript" /> type="info"
</div> :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>
<div> <div>
<n-alert title="API请求示例" type="info" /> <el-alert
<div class="bg-[#18181c] mt-2 py-2 px-4 overflow-x-auto rounded"> title="API请求示例"
<n-code :code="demo" class="text-white" language="javascript" /> 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> </div>
</div> </div>
</n-config-provider> </el-scrollbar>
</div>
</template> </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 { FormSchema } from '@/types/form'
import { AppApi } from '@/api/new-ai/app' import { AppApi } from '@/api/new-ai/app'
import { ElMessage } from 'element-plus' 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 emit = defineEmits(['reload'])
const visible = ref(false) const visible = ref(false)
const formData = ref({}) const formData = ref({})
const formRef = ref() const formRef = ref()
const schemas = ref<FormSchema[]>([ const schemas = ref<Array<FormSchema>>([
{ {
field: 'name', field: 'name',
label: '应用名称', label: '应用名称',
@ -23,8 +26,26 @@ const schemas = ref<FormSchema[]>([
}, },
componentProps: { componentProps: {
placeholder: '请输入应用名称' placeholder: '请输入应用名称'
}
}, },
{
field: 'modelId',
label: '关联模型',
component: 'Input',
formItemProps: {
rules: [{ required: true, message: '请选择关联模型' }]
}
},
{
field: 'cover',
label: '应用图标',
component: 'Input',
formItemProps: {
required: false
},
componentProps: {
placeholder: '请输入应用图标'
}
}, },
{ {
field: 'description', field: 'description',
@ -41,29 +62,6 @@ const schemas = ref<FormSchema[]>([
type: 'textarea', type: 'textarea',
rows: 4, rows: 4,
placeholder: '请输入应用描述' 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 () => { const handleSubmit = async () => {
try {
const form = formRef.value.getElFormRef() const form = formRef.value.getElFormRef()
await form.validate() await form.validate()
const values = formRef.value.formModel const values = formRef.value.formModel
const Api = isEdit.value ? AppApi.updateApp : AppApi.createApp
loading.value = true loading.value = true
values.modelId = values.modelId?.[1]
if (isEdit.value) { await Api(values).finally(() => (loading.value = false))
await AppApi.updateApp(values) ElMessage.success(isEdit.value ? '更新应用成功' : '创建应用成功')
ElMessage.success('更新应用成功')
} else {
await AppApi.createApp(values)
ElMessage.success('创建应用成功')
}
close() close()
emit('reload') emit('reload')
} catch (error) { }
console.error('Failed to save app:', error) const handleImport = (params: Record<any, any>) => {
ElMessage.error(isEdit.value ? '更新应用失败' : '创建应用失败') return {
} finally { data: params.data.url
loading.value = false
} }
} }
defineExpose({ defineExpose({
show, show,
close close
@ -134,12 +125,14 @@ defineExpose({
width="500px" width="500px"
@close="close" @close="close"
> >
<Form <Form ref="formRef" :model="formData" :schema="schemas" v-loading="loading">
ref="formRef" <template #modelId="scope">
:model="formData" <ModelSelect :id="scope.modelId" class="w-full" v-model="scope.modelId" />
:schema="schemas" </template>
v-loading="loading" <template #cover="scoped">
/> <UploadImg v-model="scoped.cover" :custom-api="OssApi.upload" :success-before="handleImport"/>
</template>
</Form>
<template #footer> <template #footer>
<el-button type="primary" @click="handleSubmit" :loading="loading">确认</el-button> <el-button type="primary" @click="handleSubmit" :loading="loading">确认</el-button>
<el-button @click="close" :loading="loading">取消</el-button> <el-button @click="close" :loading="loading">取消</el-button>
@ -148,4 +141,24 @@ defineExpose({
</template> </template>
<style scoped lang="scss"> <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> </style>

View File

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

View File

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

View File

@ -25,7 +25,7 @@
</script> </script>
<template> <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" /> <Header title="AI聊天助手" @reload="fetch" />
<main ref="contentRef" class="flex-1 overflow-hidden overflow-y-auto"> <main ref="contentRef" class="flex-1 overflow-hidden overflow-y-auto">
<Chat /> <Chat />
@ -34,12 +34,10 @@
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
:v-deep(.n-tabs.n-tabs--top .n-tab-pane) {
padding: 0 !important;
}
.chat-card { .chat-card {
box-sizing: border-box; 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 { main {
height: calc(100% - 60px); 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> <script lang="ts" setup>
import { onMounted, ref, toRaw } from 'vue' import { onMounted, ref, toRaw } from 'vue'
@ -69,12 +55,12 @@ onMounted(async () => {
}) })
function onUpdate(val: any) { function onUpdate(val: any) {
console.log(val, 'val')
const group = options.value.find(g => g.children.some(c => c.value === val)) const group = options.value.find(g => g.children.some(c => c.value === val))
if (!group) return if (!group) return
console.log(group, 'group')
const option = group.children.find(c => c.value === val) const option = group.children.find(c => c.value === val)
if (!option) return if (!option) return
const obj = option.raw const obj = option.raw
emit('update', { emit('update', {
id: obj.id, id: obj.id,
@ -88,16 +74,19 @@ function onUpdate(val: any) {
</script> </script>
<template> <template>
<el-cascader <el-select
v-model="modelId" v-model="modelId"
:options="options"
:size="size" :size="size"
:props="{ :props="{
expandTrigger: 'hover' expandTrigger: 'hover'
}" }"
placeholder="请选择关联模型" placeholder="请选择关联模型"
@change="onUpdate" @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> </template>
<style lang="scss" scoped> <style lang="scss" scoped>