mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-04-03 19:06:10 +00:00
add llm config
This commit is contained in:
@@ -2,6 +2,7 @@ package com.zl.mjga.controller;
|
|||||||
|
|
||||||
import com.zl.mjga.dto.PageRequestDto;
|
import com.zl.mjga.dto.PageRequestDto;
|
||||||
import com.zl.mjga.dto.PageResponseDto;
|
import com.zl.mjga.dto.PageResponseDto;
|
||||||
|
import com.zl.mjga.dto.ai.LlmQueryDto;
|
||||||
import com.zl.mjga.dto.ai.LlmVm;
|
import com.zl.mjga.dto.ai.LlmVm;
|
||||||
import com.zl.mjga.service.AiChatService;
|
import com.zl.mjga.service.AiChatService;
|
||||||
import com.zl.mjga.service.LlmService;
|
import com.zl.mjga.service.LlmService;
|
||||||
@@ -52,7 +53,8 @@ public class AiController {
|
|||||||
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).READ_LLM_CONFIG_PERMISSION)")
|
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).READ_LLM_CONFIG_PERMISSION)")
|
||||||
@GetMapping("/llm/page-query")
|
@GetMapping("/llm/page-query")
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public PageResponseDto<List<LlmVm>> pageQueryLlm(@ModelAttribute PageRequestDto pageRequestDto) {
|
public PageResponseDto<List<LlmVm>> pageQueryLlm(
|
||||||
return llmService.pageQueryLlm(pageRequestDto);
|
@ModelAttribute PageRequestDto pageRequestDto, @ModelAttribute LlmQueryDto llmQueryDto) {
|
||||||
|
return llmService.pageQueryLlm(pageRequestDto, llmQueryDto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package com.zl.mjga.dto.ai;
|
||||||
|
|
||||||
|
public record LlmQueryDto(String name) {}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
package com.zl.mjga.repository;
|
package com.zl.mjga.repository;
|
||||||
|
|
||||||
import static org.jooq.generated.mjga.Tables.AI_LLM_CONFIG;
|
import static org.jooq.generated.mjga.Tables.AI_LLM_CONFIG;
|
||||||
|
import static org.jooq.impl.DSL.noCondition;
|
||||||
|
|
||||||
import com.zl.mjga.dto.PageRequestDto;
|
import com.zl.mjga.dto.PageRequestDto;
|
||||||
|
import com.zl.mjga.dto.ai.LlmQueryDto;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.jooq.Configuration;
|
import org.jooq.Configuration;
|
||||||
import org.jooq.Record;
|
import org.jooq.Record;
|
||||||
import org.jooq.Result;
|
import org.jooq.Result;
|
||||||
@@ -19,11 +22,15 @@ public class LlmRepository extends AiLlmConfigDao {
|
|||||||
super(configuration);
|
super(configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<Record> pageFetchBy(PageRequestDto pageRequestDto) {
|
public Result<Record> pageFetchBy(PageRequestDto pageRequestDto, LlmQueryDto llmQueryDto) {
|
||||||
return ctx()
|
return ctx()
|
||||||
.select(
|
.select(
|
||||||
AI_LLM_CONFIG.asterisk(), DSL.count().over().as("total_llm").convertFrom(Long::valueOf))
|
AI_LLM_CONFIG.asterisk(), DSL.count().over().as("total_llm").convertFrom(Long::valueOf))
|
||||||
.from(AI_LLM_CONFIG)
|
.from(AI_LLM_CONFIG)
|
||||||
|
.where(
|
||||||
|
StringUtils.isNotEmpty(llmQueryDto.name())
|
||||||
|
? AI_LLM_CONFIG.NAME.eq(llmQueryDto.name())
|
||||||
|
: noCondition())
|
||||||
.orderBy(pageRequestDto.getSortFields())
|
.orderBy(pageRequestDto.getSortFields())
|
||||||
.limit(pageRequestDto.getSize())
|
.limit(pageRequestDto.getSize())
|
||||||
.offset(pageRequestDto.getOffset())
|
.offset(pageRequestDto.getOffset())
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.zl.mjga.service;
|
package com.zl.mjga.service;
|
||||||
|
|
||||||
import com.zl.mjga.config.ai.AiChatAssistant;
|
import com.zl.mjga.config.ai.AiChatAssistant;
|
||||||
|
import com.zl.mjga.exception.BusinessException;
|
||||||
import dev.langchain4j.service.TokenStream;
|
import dev.langchain4j.service.TokenStream;
|
||||||
|
import java.util.Optional;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jooq.generated.mjga.enums.LlmCodeEnum;
|
import org.jooq.generated.mjga.enums.LlmCodeEnum;
|
||||||
@@ -26,8 +28,9 @@ public class AiChatService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TokenStream chatPrecedenceLlmWith(String sessionIdentifier, String userMessage) {
|
public TokenStream chatPrecedenceLlmWith(String sessionIdentifier, String userMessage) {
|
||||||
AiLlmConfig precedenceLlmBy = llmService.getPrecedenceLlmBy(true);
|
Optional<AiLlmConfig> precedenceLlmBy = llmService.getPrecedenceLlmBy(true);
|
||||||
LlmCodeEnum code = precedenceLlmBy.getCode();
|
AiLlmConfig aiLlmConfig = precedenceLlmBy.orElseThrow(() -> new BusinessException("没有开启的大模型"));
|
||||||
|
LlmCodeEnum code = aiLlmConfig.getCode();
|
||||||
return switch (code) {
|
return switch (code) {
|
||||||
case ZHI_PU -> zhiPuChatAssistant.chat(sessionIdentifier, userMessage);
|
case ZHI_PU -> zhiPuChatAssistant.chat(sessionIdentifier, userMessage);
|
||||||
case DEEP_SEEK -> deepSeekChatAssistant.chat(sessionIdentifier, userMessage);
|
case DEEP_SEEK -> deepSeekChatAssistant.chat(sessionIdentifier, userMessage);
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package com.zl.mjga.service;
|
|||||||
|
|
||||||
import com.zl.mjga.dto.PageRequestDto;
|
import com.zl.mjga.dto.PageRequestDto;
|
||||||
import com.zl.mjga.dto.PageResponseDto;
|
import com.zl.mjga.dto.PageResponseDto;
|
||||||
|
import com.zl.mjga.dto.ai.LlmQueryDto;
|
||||||
import com.zl.mjga.dto.ai.LlmVm;
|
import com.zl.mjga.dto.ai.LlmVm;
|
||||||
import com.zl.mjga.repository.LlmRepository;
|
import com.zl.mjga.repository.LlmRepository;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jooq.Record;
|
import org.jooq.Record;
|
||||||
@@ -26,16 +28,14 @@ public class LlmService {
|
|||||||
return llmRepository.fetchOneByCode(llmCodeEnum);
|
return llmRepository.fetchOneByCode(llmCodeEnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AiLlmConfig getPrecedenceLlmBy(Boolean enable) {
|
public Optional<AiLlmConfig> getPrecedenceLlmBy(Boolean enable) {
|
||||||
List<AiLlmConfig> aiLlmConfigs = llmRepository.fetchByEnable(enable);
|
List<AiLlmConfig> aiLlmConfigs = llmRepository.fetchByEnable(enable);
|
||||||
//noinspection OptionalGetWithoutIsPresent
|
return aiLlmConfigs.stream().max((o1, o2) -> o2.getPriority().compareTo(o1.getPriority()));
|
||||||
return aiLlmConfigs.stream()
|
|
||||||
.max((o1, o2) -> o2.getPriority().compareTo(o1.getPriority()))
|
|
||||||
.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PageResponseDto<List<LlmVm>> pageQueryLlm(PageRequestDto pageRequestDto) {
|
public PageResponseDto<List<LlmVm>> pageQueryLlm(
|
||||||
Result<Record> records = llmRepository.pageFetchBy(pageRequestDto);
|
PageRequestDto pageRequestDto, LlmQueryDto llmQueryDto) {
|
||||||
|
Result<Record> records = llmRepository.pageFetchBy(pageRequestDto, llmQueryDto);
|
||||||
if (records.isEmpty()) {
|
if (records.isEmpty()) {
|
||||||
return PageResponseDto.empty();
|
return PageResponseDto.empty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,8 +57,6 @@
|
|||||||
"vue-tsc": "^2.2.8"
|
"vue-tsc": "^2.2.8"
|
||||||
},
|
},
|
||||||
"msw": {
|
"msw": {
|
||||||
"workerDirectory": [
|
"workerDirectory": ["public"]
|
||||||
"public"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,26 @@ export default [
|
|||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
}),
|
}),
|
||||||
|
http.get("/ai/llm/page-query", () => {
|
||||||
|
const generateLlm = () => ({
|
||||||
|
id: faker.number.int({ min: 1, max: 100 }),
|
||||||
|
name: faker.lorem.word(),
|
||||||
|
modelName: faker.lorem.word(),
|
||||||
|
apiKey: faker.string.uuid(),
|
||||||
|
url: faker.internet.url(),
|
||||||
|
enable: faker.datatype.boolean(),
|
||||||
|
priority: faker.number.int({ min: 1, max: 10 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockData = {
|
||||||
|
data: faker.helpers.multiple(generateLlm, { count: 10 }),
|
||||||
|
total: 30,
|
||||||
|
};
|
||||||
|
return HttpResponse.json(mockData);
|
||||||
|
}),
|
||||||
|
http.put("/ai/llm", () => {
|
||||||
|
return HttpResponse.json({
|
||||||
|
message: "Llm updated successfully",
|
||||||
|
});
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -44,6 +44,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/ai/llm": {
|
||||||
|
"put": {
|
||||||
|
"tags": [
|
||||||
|
"ai-controller"
|
||||||
|
],
|
||||||
|
"operationId": "updateLlm",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/LlmVm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/scheduler/trigger/resume": {
|
"/scheduler/trigger/resume": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -1008,6 +1031,44 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/ai/llm/page-query": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"ai-controller"
|
||||||
|
],
|
||||||
|
"operationId": "pageQueryLlm",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "pageRequestDto",
|
||||||
|
"in": "query",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PageRequestDto"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "llmQueryDto",
|
||||||
|
"in": "query",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/LlmQueryDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"*/*": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PageResponseDtoListLlmVm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1027,6 +1088,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"LlmVm": {
|
||||||
|
"required": [
|
||||||
|
"apiKey",
|
||||||
|
"enable",
|
||||||
|
"id",
|
||||||
|
"modelName",
|
||||||
|
"name",
|
||||||
|
"priority",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"modelName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"apiKey": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enable": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"priority": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"JobKeyDto": {
|
"JobKeyDto": {
|
||||||
"required": [
|
"required": [
|
||||||
"group",
|
"group",
|
||||||
@@ -1684,6 +1782,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"LlmQueryDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PageResponseDtoListLlmVm": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"total": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/LlmVm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
96
frontend/src/api/types/schema.d.ts
vendored
96
frontend/src/api/types/schema.d.ts
vendored
@@ -20,6 +20,22 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/ai/llm": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put: operations["updateLlm"];
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/scheduler/trigger/resume": {
|
"/scheduler/trigger/resume": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -484,6 +500,22 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/ai/llm/page-query": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["pageQueryLlm"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export type webhooks = Record<string, never>;
|
export type webhooks = Record<string, never>;
|
||||||
export interface components {
|
export interface components {
|
||||||
@@ -492,6 +524,17 @@ export interface components {
|
|||||||
name: string;
|
name: string;
|
||||||
group: string;
|
group: string;
|
||||||
};
|
};
|
||||||
|
LlmVm: {
|
||||||
|
/** Format: int64 */
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
modelName: string;
|
||||||
|
apiKey: string;
|
||||||
|
url: string;
|
||||||
|
enable: boolean;
|
||||||
|
/** Format: int32 */
|
||||||
|
priority: number;
|
||||||
|
};
|
||||||
JobKeyDto: {
|
JobKeyDto: {
|
||||||
name: string;
|
name: string;
|
||||||
group: string;
|
group: string;
|
||||||
@@ -714,6 +757,14 @@ export interface components {
|
|||||||
total?: number;
|
total?: number;
|
||||||
data?: components["schemas"]["DepartmentRespDto"][];
|
data?: components["schemas"]["DepartmentRespDto"][];
|
||||||
};
|
};
|
||||||
|
LlmQueryDto: {
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
PageResponseDtoListLlmVm: {
|
||||||
|
/** Format: int64 */
|
||||||
|
total?: number;
|
||||||
|
data?: components["schemas"]["LlmVm"][];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
responses: never;
|
responses: never;
|
||||||
parameters: never;
|
parameters: never;
|
||||||
@@ -747,6 +798,28 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
updateLlm: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["LlmVm"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
resumeTrigger: {
|
resumeTrigger: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -1553,4 +1626,27 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
pageQueryLlm: {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
pageRequestDto: components["schemas"]["PageRequestDto"];
|
||||||
|
llmQueryDto: components["schemas"]["LlmQueryDto"];
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"*/*": components["schemas"]["PageResponseDtoListLlmVm"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
142
frontend/src/components/LlmUpdateModal.vue
Normal file
142
frontend/src/components/LlmUpdateModal.vue
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<template>
|
||||||
|
<!-- Main modal -->
|
||||||
|
<div id="user-upsert-modal" tabindex="-1" aria-hidden="true"
|
||||||
|
class="bg-gray-900/50 /80 hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
||||||
|
<div class="relative p-4 w-full max-w-md max-h-full">
|
||||||
|
<!-- Modal content -->
|
||||||
|
<div class="relative bg-white rounded-lg shadow-sm ">
|
||||||
|
<!-- Modal header -->
|
||||||
|
<div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t border-gray-200">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 ">
|
||||||
|
大模型管理
|
||||||
|
</h3>
|
||||||
|
<button type="button" @click="closeModal"
|
||||||
|
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center ">
|
||||||
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Close modal</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Modal body -->
|
||||||
|
<form class="p-4 md:p-5">
|
||||||
|
<div class="grid gap-4 mb-4 grid-cols-2">
|
||||||
|
<div class="col-span-2">
|
||||||
|
<label for="name" class="block mb-2 text-sm font-medium text-gray-900 ">名称</label>
|
||||||
|
<input type="text" name="名称" id="name" v-model="formData.name"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 "
|
||||||
|
required="true">
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2">
|
||||||
|
<label for="modelName" class="block mb-2 text-sm font-medium autocomplete text-gray-900 ">模型名称</label>
|
||||||
|
<input type="text" id="modelName" autocomplete="new-password" v-model="formData.modelName"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 "
|
||||||
|
required />
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2">
|
||||||
|
<label for="apiKey" class="block mb-2 text-sm font-medium autocomplete text-gray-900 ">apiKey</label>
|
||||||
|
<input type="text" id="apiKey" autocomplete="new-password" v-model="formData.apiKey"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||||
|
required />
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2">
|
||||||
|
<label for="url" class="block mb-2 text-sm font-medium text-gray-900 ">url</label>
|
||||||
|
<input type="text" id="url" autocomplete="new-password" v-model="formData.url"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 "
|
||||||
|
required />
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2 sm:col-span-1">
|
||||||
|
<label for="status" class="block mb-2 text-sm font-medium text-gray-900 ">状态</label>
|
||||||
|
<select id="status" v-model="formData.enable"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5">
|
||||||
|
<option :value=true>启用</option>
|
||||||
|
<option :value=false>禁用</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2">
|
||||||
|
<label for="priority" class="block mb-2 text-sm font-medium autocomplete text-gray-900 ">优先级</label>
|
||||||
|
<input type="text" id="priority" autocomplete="new-password" v-model="formData.priority"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||||
|
required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" @click.prevent="handleSubmit"
|
||||||
|
class="text-white flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center self-start mt-5">
|
||||||
|
保存
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import useAlertStore from "@/composables/store/useAlertStore";
|
||||||
|
import { initFlowbite } from "flowbite";
|
||||||
|
import { onMounted, ref, watch } from "vue";
|
||||||
|
import { z } from "zod";
|
||||||
|
import type { components } from "../api/types/schema";
|
||||||
|
|
||||||
|
const alertStore = useAlertStore();
|
||||||
|
const { llm, onSubmit } = defineProps<{
|
||||||
|
llm?: components["schemas"]["LlmVm"];
|
||||||
|
closeModal: () => void;
|
||||||
|
onSubmit: (data: components["schemas"]["LlmVm"]) => Promise<void>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const formData = ref();
|
||||||
|
|
||||||
|
const updateFormData = (newLlm: typeof llm) => {
|
||||||
|
formData.value = {
|
||||||
|
...newLlm,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => llm, updateFormData, {
|
||||||
|
immediate: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const llmSchema = z.object({
|
||||||
|
id: z.number({
|
||||||
|
message: "id不能为空",
|
||||||
|
}),
|
||||||
|
name: z.string({
|
||||||
|
message: "名称不能为空",
|
||||||
|
}),
|
||||||
|
modelName: z.string({
|
||||||
|
message: "模型名称不能为空",
|
||||||
|
}),
|
||||||
|
apiKey: z.string({
|
||||||
|
message: "apiKey不能为空",
|
||||||
|
}),
|
||||||
|
url: z.string({
|
||||||
|
message: "url不能为空",
|
||||||
|
}),
|
||||||
|
enable: z.boolean({
|
||||||
|
message: "状态不能为空",
|
||||||
|
}),
|
||||||
|
priority: z.number({
|
||||||
|
message: "优先级必须为数字",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const validatedData = llmSchema.parse(formData.value);
|
||||||
|
await onSubmit(validatedData);
|
||||||
|
updateFormData(undefined);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof z.ZodError) {
|
||||||
|
alertStore.showAlert({
|
||||||
|
level: "error",
|
||||||
|
content: error.errors[0].message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initFlowbite();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -32,6 +32,7 @@ import SchedulerIcon from "./icons/SchedulerIcon.vue";
|
|||||||
import SettingsIcon from "./icons/SettingsIcon.vue";
|
import SettingsIcon from "./icons/SettingsIcon.vue";
|
||||||
import UsersIcon from "./icons/UsersIcon.vue";
|
import UsersIcon from "./icons/UsersIcon.vue";
|
||||||
import AiChatIcon from "./icons/AiChatIcon.vue";
|
import AiChatIcon from "./icons/AiChatIcon.vue";
|
||||||
|
import LlmConfigIcon from "./icons/LlmConfigIcon.vue";
|
||||||
|
|
||||||
// 菜单配置
|
// 菜单配置
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
@@ -75,6 +76,11 @@ const menuItems = [
|
|||||||
path: `${RoutePath.DASHBOARD}/${RoutePath.AICHATVIEW}`,
|
path: `${RoutePath.DASHBOARD}/${RoutePath.AICHATVIEW}`,
|
||||||
icon: AiChatIcon,
|
icon: AiChatIcon,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "大模型管理",
|
||||||
|
path: `${RoutePath.DASHBOARD}/${RoutePath.LLMCONFIGVIEW}`,
|
||||||
|
icon: LlmConfigIcon,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|||||||
@@ -42,8 +42,8 @@
|
|||||||
required placeholder="编辑时非必填" />
|
required placeholder="编辑时非必填" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-2 sm:col-span-1">
|
<div class="col-span-2 sm:col-span-1">
|
||||||
<label for="category" class="block mb-2 text-sm font-medium text-gray-900 ">状态</label>
|
<label for="status" class="block mb-2 text-sm font-medium text-gray-900 ">状态</label>
|
||||||
<select id="category" v-model="formData.enable"
|
<select id="status" v-model="formData.enable"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5">
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5">
|
||||||
<option :value=true>启用</option>
|
<option :value=true>启用</option>
|
||||||
<option :value=false>禁用</option>
|
<option :value=false>禁用</option>
|
||||||
|
|||||||
15
frontend/src/components/icons/LlmConfigIcon.vue
Normal file
15
frontend/src/components/icons/LlmConfigIcon.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||||
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-brain-icon lucide-brain">
|
||||||
|
<path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z" />
|
||||||
|
<path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z" />
|
||||||
|
<path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4" />
|
||||||
|
<path d="M17.599 6.5a3 3 0 0 0 .399-1.375" />
|
||||||
|
<path d="M6.003 5.125A3 3 0 0 0 6.401 6.5" />
|
||||||
|
<path d="M3.477 10.896a4 4 0 0 1 .585-.396" />
|
||||||
|
<path d="M19.938 10.5a4 4 0 0 1 .585.396" />
|
||||||
|
<path d="M6 18a4 4 0 0 1-1.967-.516" />
|
||||||
|
<path d="M19.967 17.484A4 4 0 0 1 18 18" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { fetchEventSource } from "@microsoft/fetch-event-source";
|
import { fetchEventSource } from "@microsoft/fetch-event-source";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import useAuthStore from "../store/useAuthStore";
|
import useAuthStore from "../store/useAuthStore";
|
||||||
|
import useAlertStore from "../store/useAlertStore";
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
|||||||
32
frontend/src/composables/ai/useLlmQuery.ts
Normal file
32
frontend/src/composables/ai/useLlmQuery.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { ref } from "vue";
|
||||||
|
import type { components } from "../../api/types/schema";
|
||||||
|
import client from "../../api/client";
|
||||||
|
|
||||||
|
export const useLlmQuery = () => {
|
||||||
|
const total = ref<number>(0);
|
||||||
|
const llms = ref<components["schemas"]["LlmVm"][]>([]);
|
||||||
|
|
||||||
|
const fetchLlmConfigs = async (page = 1, size = 10, name?: string) => {
|
||||||
|
const { data } = await client.GET("/ai/llm/page-query", {
|
||||||
|
params: {
|
||||||
|
query: {
|
||||||
|
pageRequestDto: {
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
},
|
||||||
|
llmQueryDto: {
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
llms.value = data?.data ?? [];
|
||||||
|
total.value = !data || !data.total ? 0 : data.total;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
llms,
|
||||||
|
total,
|
||||||
|
fetchLlmConfigs,
|
||||||
|
};
|
||||||
|
};
|
||||||
13
frontend/src/composables/ai/useLlmUpdate.ts
Normal file
13
frontend/src/composables/ai/useLlmUpdate.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { components } from "../../api/types/schema";
|
||||||
|
import client from "../../api/client";
|
||||||
|
|
||||||
|
export const useLlmUpdate = () => {
|
||||||
|
const updateLlmConfig = async (llm: components["schemas"]["LlmVm"]) => {
|
||||||
|
await client.PUT("/ai/llm", {
|
||||||
|
body: llm,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
updateLlmConfig,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -16,6 +16,7 @@ export enum RoutePath {
|
|||||||
POSITIONVIEW = "positions",
|
POSITIONVIEW = "positions",
|
||||||
CREATEUSERVIEW = "create-user",
|
CREATEUSERVIEW = "create-user",
|
||||||
AICHATVIEW = "ai/chat",
|
AICHATVIEW = "ai/chat",
|
||||||
|
LLMCONFIGVIEW = "llm/config",
|
||||||
SCHEDULERVIEW = "scheduler",
|
SCHEDULERVIEW = "scheduler",
|
||||||
UPSERTUSERVIEW = "upsert-user",
|
UPSERTUSERVIEW = "upsert-user",
|
||||||
UPSERTROLEVIEW = "upsert-role",
|
UPSERTROLEVIEW = "upsert-role",
|
||||||
@@ -41,6 +42,7 @@ export enum RouteName {
|
|||||||
POSITIONVIEW = "positions",
|
POSITIONVIEW = "positions",
|
||||||
CREATEUSERVIEW = "create-user",
|
CREATEUSERVIEW = "create-user",
|
||||||
AICHATVIEW = "ai/chat",
|
AICHATVIEW = "ai/chat",
|
||||||
|
LLMCONFIGVIEW = "llm/config",
|
||||||
SCHEDULERVIEW = "scheduler",
|
SCHEDULERVIEW = "scheduler",
|
||||||
UPSERTUSERVIEW = "upsert-user",
|
UPSERTUSERVIEW = "upsert-user",
|
||||||
UPSERTROLEVIEW = "upsert-role",
|
UPSERTROLEVIEW = "upsert-role",
|
||||||
@@ -67,4 +69,6 @@ export enum EPermission {
|
|||||||
WRITE_USER_ROLE_PERMISSION = "WRITE_USER_ROLE_PERMISSION",
|
WRITE_USER_ROLE_PERMISSION = "WRITE_USER_ROLE_PERMISSION",
|
||||||
DELETE_USER_ROLE_PERMISSION = "DELETE_USER_ROLE_PERMISSION",
|
DELETE_USER_ROLE_PERMISSION = "DELETE_USER_ROLE_PERMISSION",
|
||||||
READ_USER_ROLE_PERMISSION = "READ_USER_ROLE_PERMISSION",
|
READ_USER_ROLE_PERMISSION = "READ_USER_ROLE_PERMISSION",
|
||||||
|
READ_LLM_CONFIG_PERMISSION = "READ_LLM_CONFIG_PERMISSION",
|
||||||
|
WRITE_LLM_CONFIG_PERMISSION = "WRITE_LLM_CONFIG_PERMISSION",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,15 @@ const aiRoutes: RouteRecordRaw[] = [
|
|||||||
component: () => import("@/views/AiChatView.vue"),
|
component: () => import("@/views/AiChatView.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
// hasPermission: EPermission.READ_USER_ROLE_PERMISSION,
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: RoutePath.LLMCONFIGVIEW,
|
||||||
|
name: RouteName.LLMCONFIGVIEW,
|
||||||
|
component: () => import("@/views/LlmConfigView.vue"),
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
hasPermission: EPermission.READ_LLM_CONFIG_PERMISSION,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const makeErrorHandler =
|
|||||||
}) => void,
|
}) => void,
|
||||||
) =>
|
) =>
|
||||||
(err: unknown, instance: ComponentPublicInstance | null, info: string) => {
|
(err: unknown, instance: ComponentPublicInstance | null, info: string) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
if (err instanceof UnAuthError) {
|
if (err instanceof UnAuthError) {
|
||||||
signOut();
|
signOut();
|
||||||
router.push(RoutePath.LOGIN);
|
router.push(RoutePath.LOGIN);
|
||||||
|
|||||||
@@ -57,8 +57,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LoadingIcon from "@/components/icons/LoadingIcon.vue";
|
import LoadingIcon from "@/components/icons/LoadingIcon.vue";
|
||||||
import useAlertStore from "@/composables/store/useAlertStore";
|
import useAlertStore from "@/composables/store/useAlertStore";
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from "dompurify";
|
||||||
import { marked } from 'marked';
|
import { marked } from "marked";
|
||||||
import { computed, nextTick, onUnmounted, ref, watch } from "vue";
|
import { computed, nextTick, onUnmounted, ref, watch } from "vue";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import Button from "../components/Button.vue";
|
import Button from "../components/Button.vue";
|
||||||
@@ -72,26 +72,24 @@ const chatContainer = ref<HTMLElement | null>(null);
|
|||||||
const alertStore = useAlertStore();
|
const alertStore = useAlertStore();
|
||||||
|
|
||||||
marked.setOptions({
|
marked.setOptions({
|
||||||
gfm: true,
|
gfm: true,
|
||||||
breaks: true,
|
breaks: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const renderMarkdown = (content: string) => {
|
const renderMarkdown = (content: string) => {
|
||||||
if (!content) return '';
|
if (!content) return "";
|
||||||
|
|
||||||
const restoredContent = content
|
const restoredContent = content
|
||||||
.replace(/␣/g, ' ')
|
.replace(/␣/g, " ")
|
||||||
.replace(/⇥/g, '\t')
|
.replace(/⇥/g, "\t")
|
||||||
.replace(//g, '\n');
|
.replace(//g, "\n");
|
||||||
|
|
||||||
|
const processedContent = restoredContent
|
||||||
|
.replace(/^(\s*)(`{3,})/gm, "$1$2")
|
||||||
|
.replace(/(\s+)`/g, "$1`");
|
||||||
|
|
||||||
const processedContent = restoredContent
|
const rawHtml = marked(processedContent);
|
||||||
.replace(/^(\s*)(`{3,})/gm, '$1$2')
|
return DOMPurify.sanitize(rawHtml as string);
|
||||||
.replace(/(\s+)`/g, '$1`');
|
|
||||||
|
|
||||||
const rawHtml = marked(processedContent);
|
|
||||||
return DOMPurify.sanitize(rawHtml as string);
|
|
||||||
};
|
};
|
||||||
const chatElements = computed(() => {
|
const chatElements = computed(() => {
|
||||||
return messages.value.map((message, index) => {
|
return messages.value.map((message, index) => {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
</form>
|
</form>
|
||||||
<!-- Create Modal toggle -->
|
<!-- Create Modal toggle -->
|
||||||
<button @click="handleUpsertDepartmentClick()"
|
<button @click="handleUpsertDepartmentClick()"
|
||||||
class="flex items-center block text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center absolute right-5 bottom-2"
|
class="flex items-center text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center absolute right-5 bottom-2"
|
||||||
type="button">
|
type="button">
|
||||||
新增部门
|
新增部门
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
166
frontend/src/views/LlmConfigView.vue
Normal file
166
frontend/src/views/LlmConfigView.vue
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<div class="px-4 pt-6 xl:grid-cols-3 xl:gap-4 sm:rounded-lg mt-14">
|
||||||
|
<div class="mb-4 col-span-full">
|
||||||
|
<Breadcrumbs :names="['大模型管理']" />
|
||||||
|
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl ">大模型管理</h1>
|
||||||
|
</div>
|
||||||
|
<div class="relative">
|
||||||
|
<form class="max-w-xs mb-4 ">
|
||||||
|
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only ">Search</label>
|
||||||
|
<div class="relative">
|
||||||
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
|
||||||
|
<svg class="w-4 h-4 text-gray-500 " aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||||
|
viewBox="0 0 20 20">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<input type="search" id="default-search" v-model="name"
|
||||||
|
class="block w-full p-4 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 "
|
||||||
|
placeholder="模型名称" required />
|
||||||
|
<button type="submit"
|
||||||
|
class="text-white absolute end-2.5 bottom-2.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 "
|
||||||
|
@click.prevent="handleSearch">搜索</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<!-- Create Modal toggle -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative overflow-x-auto">
|
||||||
|
<table
|
||||||
|
class="w-full whitespace-nowrap text-sm text-left rtl:text-right shadow-lg rounded-lg text-gray-500 overflow-x-auto">
|
||||||
|
<thead class="text-xs uppercase bg-gray-50 ">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="p-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input id="checkbox-all-search" disabled type="checkbox"
|
||||||
|
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 focus:ring-2 ">
|
||||||
|
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="px-6 py-3">名称</th>
|
||||||
|
<th scope="col" class="px-6 py-3">模型名称</th>
|
||||||
|
<th scope="col" class="px-6 py-3">apiKey</th>
|
||||||
|
<th scope="col" class="px-6 py-3">url</th>
|
||||||
|
<th scope="col" class="px-6 py-3">状态</th>
|
||||||
|
<th scope="col" class="px-6 py-3">优先级</th>
|
||||||
|
<th scope="col" class="px-6 py-3">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="llm in llms" :key="llm.id" class="bg-white border-b border-gray-200 hover:bg-gray-50 ">
|
||||||
|
<td class="w-4 p-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input :id="'checkbox-table-search-' + llm.id" type="checkbox" disabled
|
||||||
|
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 focus:ring-2 ">
|
||||||
|
<label :for="'checkbox-table-search-' + llm.id" class="sr-only">checkbox</label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis font-medium text-gray-900">
|
||||||
|
{{
|
||||||
|
`${llm.name}` }}</td>
|
||||||
|
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{
|
||||||
|
`${llm.modelName}` }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{
|
||||||
|
llm.apiKey }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{ llm.url }}</td>
|
||||||
|
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="h-2.5 w-2.5 rounded-full me-2" :class="llm.enable ? 'bg-blue-500' : 'bg-red-500'"></div> {{
|
||||||
|
llm.enable === true ? "启用" : "禁用" }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{ llm.priority }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 ">
|
||||||
|
<div class="flex items-center gap-x-2">
|
||||||
|
<button @click="handleLlmUpdateClick(llm)"
|
||||||
|
:class="['flex items-center justify-center gap-x-1 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 ']"
|
||||||
|
type="button">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||||
|
clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
<span>编辑</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TablePagination :pageChange="handlePageChange" :total="total" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LlmUpdateModal :llm="selectedLlm" :id="'llm-update-modal'" :closeModal="() => {
|
||||||
|
llmUpdateModal!.hide();
|
||||||
|
}" :onSubmit="handleUpdateModalSubmit"></LlmUpdateModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||||
|
import TablePagination from "@/components/TablePagination.vue";
|
||||||
|
import useAlertStore from "@/composables/store/useAlertStore";
|
||||||
|
import { Modal, type ModalInterface, initFlowbite } from "flowbite";
|
||||||
|
import { nextTick, onMounted, ref } from "vue";
|
||||||
|
import type { components } from "../api/types/schema";
|
||||||
|
import { useLlmQuery } from "@/composables/ai/useLlmQuery";
|
||||||
|
import { useLlmUpdate } from "@/composables/ai/useLlmUpdate";
|
||||||
|
import LlmUpdateModal from "@/components/LlmUpdateModal.vue";
|
||||||
|
|
||||||
|
const llmUpdateModal = ref<ModalInterface>();
|
||||||
|
const selectedLlm = ref<components["schemas"]["LlmVm"]>();
|
||||||
|
const name = ref<string>("");
|
||||||
|
|
||||||
|
const { llms, fetchLlmConfigs, total } = useLlmQuery();
|
||||||
|
const { updateLlmConfig } = useLlmUpdate();
|
||||||
|
|
||||||
|
const alertStore = useAlertStore();
|
||||||
|
|
||||||
|
const handleUpdateModalSubmit = async (llm: components["schemas"]["LlmVm"]) => {
|
||||||
|
await updateLlmConfig(llm);
|
||||||
|
llmUpdateModal.value?.hide();
|
||||||
|
alertStore.showAlert({
|
||||||
|
level: "success",
|
||||||
|
content: "操作成功",
|
||||||
|
});
|
||||||
|
await fetchLlmConfigs();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = async () => {
|
||||||
|
await fetchLlmConfigs();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = async (page: number, pageSize: number) => {
|
||||||
|
await fetchLlmConfigs(page, pageSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLlmUpdateClick = async (llm: components["schemas"]["LlmVm"]) => {
|
||||||
|
selectedLlm.value = llm;
|
||||||
|
await nextTick(() => {
|
||||||
|
llmUpdateModal.value?.show();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchLlmConfigs();
|
||||||
|
initFlowbite();
|
||||||
|
const $llmUpdateModalElement: HTMLElement | null =
|
||||||
|
document.querySelector("#llm-update-modal");
|
||||||
|
|
||||||
|
llmUpdateModal.value = new Modal(
|
||||||
|
$llmUpdateModalElement,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
id: "llm-update-modal",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
Reference in New Issue
Block a user