mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
Merge branch 'fawney19:main' into main
This commit is contained in:
@@ -58,6 +58,11 @@ ADMIN_USERNAME=admin
|
|||||||
# AETHER_GATEWAY_AUTO_PREPARE_DATABASE=true
|
# AETHER_GATEWAY_AUTO_PREPARE_DATABASE=true
|
||||||
|
|
||||||
# PostgreSQL 连接池配置(默认适合单实例/小型部署;高并发可按需调大)
|
# PostgreSQL 连接池配置(默认适合单实例/小型部署;高并发可按需调大)
|
||||||
|
# 推荐计算方式(单实例):
|
||||||
|
# MAX = CPU 核数 × 10(AI 网关偏 IO 等待,可激进些;纯 OLTP 用 × 4)
|
||||||
|
# MIN = MAX × 0.2(保留常驻连接应对突发流量,避免冷启动握手开销)
|
||||||
|
# 多实例部署时请按 实例数 × MAX 控制总和,PG 端 max_connections 至少为该总和 + 20 余量
|
||||||
# AETHER_GATEWAY_DATA_POSTGRES_MIN_CONNECTIONS=1
|
# AETHER_GATEWAY_DATA_POSTGRES_MIN_CONNECTIONS=1
|
||||||
# AETHER_GATEWAY_DATA_POSTGRES_MAX_CONNECTIONS=20
|
# AETHER_GATEWAY_DATA_POSTGRES_MAX_CONNECTIONS=20
|
||||||
# AETHER_GATEWAY_DATA_POSTGRES_IDLE_TIMEOUT_MS=30000
|
# AETHER_GATEWAY_DATA_POSTGRES_STATEMENT_CACHE_CAPACITY=100
|
||||||
|
# AETHER_GATEWAY_DATA_POSTGRES_ACQUIRE_TIMEOUT_MS=3000
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
import { PopoverTrigger } from 'radix-vue'
|
import { PopoverTrigger } from 'radix-vue'
|
||||||
import { useAttrs } from 'vue'
|
import { useAttrs } from 'vue'
|
||||||
|
|
||||||
defineOptions({
|
|
||||||
inheritAttrs: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
withDefaults(defineProps<{
|
withDefaults(defineProps<{
|
||||||
asChild?: boolean
|
asChild?: boolean
|
||||||
as?: string
|
as?: string
|
||||||
@@ -14,6 +10,10 @@ withDefaults(defineProps<{
|
|||||||
as: 'button',
|
as: 'button',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
const attrs = useAttrs()
|
const attrs = useAttrs()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -211,7 +211,6 @@
|
|||||||
两次输入的密码不一致
|
两次输入的密码不一致
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- 登录链接 -->
|
<!-- 登录链接 -->
|
||||||
|
|||||||
@@ -17,6 +17,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
modelValue?: string
|
||||||
|
siteKey: string
|
||||||
|
action?: string
|
||||||
|
disabled?: boolean
|
||||||
|
}>(), {
|
||||||
|
modelValue: '',
|
||||||
|
action: undefined,
|
||||||
|
disabled: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: string]
|
||||||
|
error: [message: string]
|
||||||
|
}>()
|
||||||
|
|
||||||
const TURNSTILE_SCRIPT_URL = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'
|
const TURNSTILE_SCRIPT_URL = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'
|
||||||
|
|
||||||
type TurnstileWidgetId = string
|
type TurnstileWidgetId = string
|
||||||
@@ -46,22 +62,6 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
|
||||||
modelValue?: string
|
|
||||||
siteKey: string
|
|
||||||
action?: string
|
|
||||||
disabled?: boolean
|
|
||||||
}>(), {
|
|
||||||
modelValue: '',
|
|
||||||
action: undefined,
|
|
||||||
disabled: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
'update:modelValue': [value: string]
|
|
||||||
error: [message: string]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const containerRef = ref<HTMLElement | null>(null)
|
const containerRef = ref<HTMLElement | null>(null)
|
||||||
const widgetId = ref<TurnstileWidgetId | null>(null)
|
const widgetId = ref<TurnstileWidgetId | null>(null)
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
|
|||||||
@@ -28,7 +28,10 @@
|
|||||||
{{ manualGlobalModelMode ? '选择已有模型' : '手动添加' }}
|
{{ manualGlobalModelMode ? '选择已有模型' : '手动添加' }}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!manualGlobalModelMode" class="space-y-2">
|
<div
|
||||||
|
v-if="!manualGlobalModelMode"
|
||||||
|
class="space-y-2"
|
||||||
|
>
|
||||||
<Select
|
<Select
|
||||||
:model-value="form.global_model_id"
|
:model-value="form.global_model_id"
|
||||||
:disabled="loadingGlobalModels"
|
:disabled="loadingGlobalModels"
|
||||||
@@ -48,10 +51,16 @@
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="rounded-lg border border-border/60 bg-muted/20 p-3 space-y-3">
|
<div
|
||||||
|
v-else
|
||||||
|
class="rounded-lg border border-border/60 bg-muted/20 p-3 space-y-3"
|
||||||
|
>
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="manual-global-model-name" class="text-xs">模型ID *</Label>
|
<Label
|
||||||
|
for="manual-global-model-name"
|
||||||
|
class="text-xs"
|
||||||
|
>模型ID *</Label>
|
||||||
<Input
|
<Input
|
||||||
id="manual-global-model-name"
|
id="manual-global-model-name"
|
||||||
v-model="form.manual_global_model_name"
|
v-model="form.manual_global_model_name"
|
||||||
@@ -60,7 +69,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="manual-global-model-display-name" class="text-xs">显示名称</Label>
|
<Label
|
||||||
|
for="manual-global-model-display-name"
|
||||||
|
class="text-xs"
|
||||||
|
>显示名称</Label>
|
||||||
<Input
|
<Input
|
||||||
id="manual-global-model-display-name"
|
id="manual-global-model-display-name"
|
||||||
v-model="form.manual_global_model_display_name"
|
v-model="form.manual_global_model_display_name"
|
||||||
@@ -79,7 +91,10 @@
|
|||||||
没有可选择的本地全局模型。可以切换到“手动添加”继续保存。
|
没有可选择的本地全局模型。可以切换到“手动添加”继续保存。
|
||||||
</p>
|
</p>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="provider-model-name" class="text-xs">Provider 模型名 *</Label>
|
<Label
|
||||||
|
for="provider-model-name"
|
||||||
|
class="text-xs"
|
||||||
|
>Provider 模型名 *</Label>
|
||||||
<Input
|
<Input
|
||||||
id="provider-model-name"
|
id="provider-model-name"
|
||||||
v-model="form.provider_model_name"
|
v-model="form.provider_model_name"
|
||||||
|
|||||||
@@ -293,7 +293,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 桌面端表格视图 -->
|
<!-- 桌面端表格视图 -->
|
||||||
<Table :class="['hidden md:table table-fixed w-full', isAdmin ? 'min-w-[1120px]' : 'min-w-[960px]']">
|
<Table
|
||||||
|
class="hidden md:table table-fixed w-full"
|
||||||
|
:class="[isAdmin ? 'min-w-[1120px]' : 'min-w-[960px]']"
|
||||||
|
>
|
||||||
<colgroup v-if="isAdmin">
|
<colgroup v-if="isAdmin">
|
||||||
<col class="w-[8%]">
|
<col class="w-[8%]">
|
||||||
<col class="w-[12%]">
|
<col class="w-[12%]">
|
||||||
@@ -346,7 +349,8 @@
|
|||||||
密钥
|
密钥
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<SortableTableHead
|
<SortableTableHead
|
||||||
:class="['h-12 font-semibold', isAdmin ? 'w-[14%]' : 'w-[22%]']"
|
class="h-12 font-semibold"
|
||||||
|
:class="[isAdmin ? 'w-[14%]' : 'w-[22%]']"
|
||||||
column-key="model"
|
column-key="model"
|
||||||
:sortable="false"
|
:sortable="false"
|
||||||
:filter-active="filterModel !== '__all__'"
|
:filter-active="filterModel !== '__all__'"
|
||||||
@@ -383,7 +387,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</SortableTableHead>
|
</SortableTableHead>
|
||||||
<SortableTableHead
|
<SortableTableHead
|
||||||
:class="['h-12 font-semibold', isAdmin ? 'w-[15%]' : 'w-[14%]']"
|
class="h-12 font-semibold"
|
||||||
|
:class="[isAdmin ? 'w-[15%]' : 'w-[14%]']"
|
||||||
column-key="api_format"
|
column-key="api_format"
|
||||||
:sortable="false"
|
:sortable="false"
|
||||||
:filter-active="filterApiFormat !== '__all__'"
|
:filter-active="filterApiFormat !== '__all__'"
|
||||||
@@ -495,7 +500,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
:class="['font-medium py-4', isAdmin ? 'w-[14%]' : 'w-[22%]']"
|
class="font-medium py-4"
|
||||||
|
:class="[isAdmin ? 'w-[14%]' : 'w-[22%]']"
|
||||||
:title="getModelTooltip(record)"
|
:title="getModelTooltip(record)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -582,7 +588,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
:class="['py-4', isAdmin ? 'w-[15%]' : 'w-[14%]']"
|
class="py-4"
|
||||||
|
:class="[isAdmin ? 'w-[15%]' : 'w-[14%]']"
|
||||||
:title="getApiFormatTooltip(record)"
|
:title="getApiFormatTooltip(record)"
|
||||||
>
|
>
|
||||||
<!-- 有格式转换或同族格式差异:两行显示 -->
|
<!-- 有格式转换或同族格式差异:两行显示 -->
|
||||||
@@ -682,8 +689,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-0.5 grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] gap-x-1 text-xs leading-tight tabular-nums text-muted-foreground">
|
<div class="mt-0.5 grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] gap-x-1 text-xs leading-tight tabular-nums text-muted-foreground">
|
||||||
<span
|
<span
|
||||||
|
class="justify-self-end whitespace-nowrap text-right"
|
||||||
:class="[
|
:class="[
|
||||||
'justify-self-end whitespace-nowrap text-right',
|
|
||||||
hasPositiveTokens(getRecordCacheReadTokens(record)) ? 'text-foreground/70' : ''
|
hasPositiveTokens(getRecordCacheReadTokens(record)) ? 'text-foreground/70' : ''
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
@@ -693,8 +700,8 @@
|
|||||||
/
|
/
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
class="justify-self-start whitespace-nowrap text-left"
|
||||||
:class="[
|
:class="[
|
||||||
'justify-self-start whitespace-nowrap text-left',
|
|
||||||
hasPositiveTokens(getRecordCacheCreationTokens(record)) ? 'text-foreground/70' : ''
|
hasPositiveTokens(getRecordCacheCreationTokens(record)) ? 'text-foreground/70' : ''
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ describe('parseDateLike', () => {
|
|||||||
describe('datetime-local conversion', () => {
|
describe('datetime-local conversion', () => {
|
||||||
it('formats RFC3339 instants for datetime-local inputs using local clock fields', () => {
|
it('formats RFC3339 instants for datetime-local inputs using local clock fields', () => {
|
||||||
const date = new Date('2026-04-12T15:30:00Z')
|
const date = new Date('2026-04-12T15:30:00Z')
|
||||||
const expected = [
|
const expected = `${[
|
||||||
date.getFullYear(),
|
date.getFullYear(),
|
||||||
padDatePart(date.getMonth() + 1),
|
padDatePart(date.getMonth() + 1),
|
||||||
padDatePart(date.getDate()),
|
padDatePart(date.getDate()),
|
||||||
].join('-') + `T${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`
|
].join('-') }T${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`
|
||||||
|
|
||||||
expect(formatDateTimeLocalInput('2026-04-12T15:30:00Z')).toBe(expected)
|
expect(formatDateTimeLocalInput('2026-04-12T15:30:00Z')).toBe(expected)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -41,12 +41,19 @@
|
|||||||
:class="selectedType === '__new__' ? 'bg-primary/10 text-primary' : 'text-foreground hover:bg-muted'"
|
:class="selectedType === '__new__' ? 'bg-primary/10 text-primary' : 'text-foreground hover:bg-muted'"
|
||||||
@click="selectNewConfig()"
|
@click="selectNewConfig()"
|
||||||
>
|
>
|
||||||
<div class="w-7 h-7 rounded-md flex items-center justify-center text-xs font-semibold shrink-0"
|
<div
|
||||||
|
class="w-7 h-7 rounded-md flex items-center justify-center text-xs font-semibold shrink-0"
|
||||||
:class="selectedType === '__new__' ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'"
|
:class="selectedType === '__new__' ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'"
|
||||||
>+</div>
|
>
|
||||||
|
+
|
||||||
|
</div>
|
||||||
<div class="flex-1 min-w-0 text-left">
|
<div class="flex-1 min-w-0 text-left">
|
||||||
<div class="truncate font-medium text-sm">新配置</div>
|
<div class="truncate font-medium text-sm">
|
||||||
<div class="text-[10px] text-muted-foreground">未保存</div>
|
新配置
|
||||||
|
</div>
|
||||||
|
<div class="text-[10px] text-muted-foreground">
|
||||||
|
未保存
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -70,10 +77,12 @@
|
|||||||
src="https://cdn.linux.do/uploads/default/optimized/3X/9/d/9dd49731091ce8656243f3c2b6e5d5e5a7e3e3e3_2_32x32.png"
|
src="https://cdn.linux.do/uploads/default/optimized/3X/9/d/9dd49731091ce8656243f3c2b6e5d5e5a7e3e3e3_2_32x32.png"
|
||||||
class="absolute inset-0 w-full h-full object-cover"
|
class="absolute inset-0 w-full h-full object-cover"
|
||||||
@error="($event.target as HTMLImageElement).remove()"
|
@error="($event.target as HTMLImageElement).remove()"
|
||||||
/>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 min-w-0 text-left">
|
<div class="flex-1 min-w-0 text-left">
|
||||||
<div class="truncate font-medium text-sm">{{ item.display_name }}</div>
|
<div class="truncate font-medium text-sm">
|
||||||
|
{{ item.display_name }}
|
||||||
|
</div>
|
||||||
<div class="text-[10px] text-muted-foreground">
|
<div class="text-[10px] text-muted-foreground">
|
||||||
{{ item.configured ? (item.is_enabled ? '已启用' : '已禁用') : '未配置' }}
|
{{ item.configured ? (item.is_enabled ? '已启用' : '已禁用') : '未配置' }}
|
||||||
</div>
|
</div>
|
||||||
@@ -96,7 +105,6 @@
|
|||||||
|
|
||||||
<!-- 右侧内容区 -->
|
<!-- 右侧内容区 -->
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
|
|
||||||
<!-- 配置表单 -->
|
<!-- 配置表单 -->
|
||||||
<CardSection
|
<CardSection
|
||||||
v-if="selectedType"
|
v-if="selectedType"
|
||||||
|
|||||||
@@ -822,7 +822,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,9 @@
|
|||||||
<div>API 端点</div>
|
<div>API 端点</div>
|
||||||
<div>推理程度</div>
|
<div>推理程度</div>
|
||||||
<div>映射参数</div>
|
<div>映射参数</div>
|
||||||
<div class="md:text-right">状态</div>
|
<div class="md:text-right">
|
||||||
|
状态
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="divide-y">
|
<div class="divide-y">
|
||||||
<div
|
<div
|
||||||
|
|||||||
Reference in New Issue
Block a user