diff --git a/.env.example b/.env.example
index 4c14763..12289d0 100644
--- a/.env.example
+++ b/.env.example
@@ -1,8 +1,16 @@
# ==================== 必须配置(启动前) ====================
# 以下配置项必须在项目启动前设置
-# 数据库密码
+# 数据库配置
+DB_HOST=localhost
+DB_PORT=5432
+DB_USER=postgres
+DB_NAME=aether
DB_PASSWORD=your_secure_password_here
+
+# Redis 配置
+REDIS_HOST=localhost
+REDIS_PORT=6379
REDIS_PASSWORD=your_redis_password_here
# JWT密钥(使用 python generate_keys.py 生成)
diff --git a/dev.sh b/dev.sh
index 43e8dee..e66d786 100755
--- a/dev.sh
+++ b/dev.sh
@@ -8,7 +8,8 @@ source .env
set +a
# 构建 DATABASE_URL
-export DATABASE_URL="postgresql://postgres:${DB_PASSWORD}@localhost:5432/aether"
+export DATABASE_URL="postgresql://${DB_USER:-postgres}:${DB_PASSWORD}@${DB_HOST:-localhost}:${DB_PORT:-5432}/${DB_NAME:-aether}"
+export REDIS_URL=redis://:${REDIS_PASSWORD}@${REDIS_HOST:-localhost}:${REDIS_PORT:-6379}/0
# 启动 uvicorn(热重载模式)
echo "🚀 启动本地开发服务器..."
diff --git a/frontend/src/components/ui/dialog/Dialog.vue b/frontend/src/components/ui/dialog/Dialog.vue
index 26cb66c..9e45f5f 100644
--- a/frontend/src/components/ui/dialog/Dialog.vue
+++ b/frontend/src/components/ui/dialog/Dialog.vue
@@ -92,6 +92,7 @@
diff --git a/frontend/src/composables/useEscapeKey.ts b/frontend/src/composables/useEscapeKey.ts
new file mode 100644
index 0000000..acb4094
--- /dev/null
+++ b/frontend/src/composables/useEscapeKey.ts
@@ -0,0 +1,75 @@
+import { onMounted, onUnmounted, ref } from 'vue'
+
+/**
+ * ESC 键监听 Composable(简化版本,直接使用独立监听器)
+ * 用于按 ESC 键关闭弹窗或其他可关闭的组件
+ *
+ * @param callback - 按 ESC 键时执行的回调函数
+ * @param options - 配置选项
+ */
+export function useEscapeKey(
+ callback: () => void,
+ options: {
+ /** 是否在输入框获得焦点时禁用 ESC 键,默认 true */
+ disableOnInput?: boolean
+ /** 是否只监听一次,默认 false */
+ once?: boolean
+ } = {}
+) {
+ const { disableOnInput = true, once = false } = options
+ const isActive = ref(true)
+
+ function handleKeyDown(event: KeyboardEvent) {
+ // 只处理 ESC 键
+ if (event.key !== 'Escape') return
+
+ // 检查组件是否还活跃
+ if (!isActive.value) return
+
+ // 如果配置了在输入框获得焦点时禁用,则检查当前焦点元素
+ if (disableOnInput) {
+ const activeElement = document.activeElement
+ const isInputElement = activeElement && (
+ activeElement.tagName === 'INPUT' ||
+ activeElement.tagName === 'TEXTAREA' ||
+ activeElement.tagName === 'SELECT' ||
+ activeElement.contentEditable === 'true' ||
+ activeElement.getAttribute('role') === 'textbox' ||
+ activeElement.getAttribute('role') === 'combobox'
+ )
+
+ // 如果焦点在输入框中,不处理 ESC 键
+ if (isInputElement) return
+ }
+
+ // 执行回调
+ callback()
+
+ // 如果只监听一次,则移除监听器
+ if (once) {
+ removeEventListener()
+ }
+ }
+
+ function addEventListener() {
+ document.addEventListener('keydown', handleKeyDown)
+ }
+
+ function removeEventListener() {
+ document.removeEventListener('keydown', handleKeyDown)
+ }
+
+ onMounted(() => {
+ addEventListener()
+ })
+
+ onUnmounted(() => {
+ isActive.value = false
+ removeEventListener()
+ })
+
+ return {
+ addEventListener,
+ removeEventListener
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/features/models/components/ModelDetailDrawer.vue b/frontend/src/features/models/components/ModelDetailDrawer.vue
index bbc21d0..ebbd39d 100644
--- a/frontend/src/features/models/components/ModelDetailDrawer.vue
+++ b/frontend/src/features/models/components/ModelDetailDrawer.vue
@@ -698,6 +698,7 @@ import {
Layers,
BarChart3
} from 'lucide-vue-next'
+import { useEscapeKey } from '@/composables/useEscapeKey'
import { useToast } from '@/composables/useToast'
import Card from '@/components/ui/card.vue'
import Badge from '@/components/ui/badge.vue'
@@ -833,6 +834,16 @@ watch(() => props.open, (newOpen) => {
detailTab.value = 'basic'
}
})
+
+// 添加 ESC 键监听
+useEscapeKey(() => {
+ if (props.open) {
+ handleClose()
+ }
+}, {
+ disableOnInput: true,
+ once: false
+})