feat: 三协议互转网关 + 鉴权修复 + 管理端增强
后端 - 新增 proxy/convert 三协议(chat/messages/responses)请求、响应与 SSE 流式互转, 以 Chat 为中间模型;usage.go 统一提取三协议 token 用量(含单测) - gateway: 跨协议调度(渠道未声明客户端协议时转为渠道首选格式), streamResponse 按 \n\n 分块逐行转换直通,bufferResponse 转换失败时剥非 JSON 前缀 - gateway: 新增 SetUsageRecorder 注入异步用量记录器 - auth_llm: 修复 key_prefix 查询长度错配([:8] vs 存储的 [:12])导致全部 401; 修复长度 8-11 的 key 切片越界 panic;统一 unauthorized 响应 - usage: 日报表改为增量累加 upsert,避免多次 flush 互相清零;记录协议/错误码/时延等字段 - channel: 新增渠道并发槽 TryAcquire;健康检查支持可配置参数 - api: 新增 admin 渠道/模型/系统配置管理端点(旧端点保留兼容) 前端 - 新增渠道管理、模型管理、系统配置视图与 ChannelModelsDrawer - 新增 ui 基础组件(Button/Badge/Input/Modal)与 protocol.ts - 调整 Toast 样式、密钥页、路由菜单;dev 代理默认指向 3000 端口
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { X } from '@lucide/vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
open: boolean
|
||||
title?: string
|
||||
width?: string
|
||||
}>(),
|
||||
{ width: 'max-w-md' },
|
||||
)
|
||||
const emit = defineEmits<{ close: [] }>()
|
||||
|
||||
const panel = ref<HTMLElement | null>(null)
|
||||
|
||||
function onKey(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape' && props.open) emit('close')
|
||||
}
|
||||
onMounted(() => window.addEventListener('keydown', onKey))
|
||||
onUnmounted(() => window.removeEventListener('keydown', onKey))
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
async (open) => {
|
||||
document.body.style.overflow = open ? 'hidden' : ''
|
||||
if (open) {
|
||||
await nextTick()
|
||||
panel.value?.focus()
|
||||
}
|
||||
},
|
||||
)
|
||||
onUnmounted(() => {
|
||||
document.body.style.overflow = ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<Transition
|
||||
enter-active-class="transition-opacity duration-150"
|
||||
enter-from-class="opacity-0"
|
||||
leave-active-class="transition-opacity duration-150"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div
|
||||
v-if="open"
|
||||
class="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/60 p-4 pt-[12vh] backdrop-blur-sm"
|
||||
@mousedown.self="emit('close')"
|
||||
>
|
||||
<Transition
|
||||
enter-active-class="transition-transform duration-150"
|
||||
enter-from-class="scale-[0.97] opacity-0"
|
||||
leave-active-class="transition-transform duration-150"
|
||||
leave-to-class="scale-[0.97] opacity-0"
|
||||
>
|
||||
<div
|
||||
v-if="open"
|
||||
ref="panel"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
:aria-label="title || '对话框'"
|
||||
tabindex="-1"
|
||||
class="card w-full bg-base-100 shadow-xl outline-none"
|
||||
:class="width"
|
||||
>
|
||||
<div class="flex items-center justify-between border-b border-base-300/60 px-5 py-3.5">
|
||||
<h3 class="text-sm font-semibold text-base-content">{{ title }}</h3>
|
||||
<button class="rounded-md p-1 text-base-content/40 hover:bg-base-200 hover:text-base-content" aria-label="关闭" @click="emit('close')">
|
||||
<X :size="16" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="px-5 py-4">
|
||||
<slot />
|
||||
</div>
|
||||
<div v-if="$slots.footer" class="flex justify-end gap-2 border-t border-base-300/60 px-5 py-3.5">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
Reference in New Issue
Block a user