diff --git a/src/App.vue b/src/App.vue
index 8a37246..9552aff 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,5 +1,5 @@
-
- saveCredentials('wecom', { extra: { ...getCreds('wecom').extra, bot_id: v } })" />
+ saveCredentials('wecom', { extra: { ...getCreds('wecom').extra, bot_id: v } })" />
- saveCredentials('wecom', { extra: { ...getCreds('wecom').extra, secret: v } })" />
+ saveCredentials('wecom', { extra: { ...getCreds('wecom').extra, secret: v } })" />
diff --git a/src/components/settings/SessionSettings.vue b/src/components/settings/SessionSettings.vue
index 21c110c..e2be711 100644
--- a/src/components/settings/SessionSettings.vue
+++ b/src/components/settings/SessionSettings.vue
@@ -28,7 +28,7 @@ async function save(values: Record) {
{ label: t('settings.session.modeIdle'), value: 'idle' },
{ label: t('settings.session.modeHourly'), value: 'hourly' },
]"
- size="small" style="width: 140px"
+ size="small" class="input-md"
@update:value="v => save({ mode: v })"
/>
@@ -36,7 +36,7 @@ async function save(values: Record) {
v != null && save({ idle_minutes: v })"
/>
@@ -44,7 +44,7 @@ async function save(values: Record) {
v != null && save({ at_hour: v })"
/>
diff --git a/src/components/settings/SettingRow.vue b/src/components/settings/SettingRow.vue
index 7df0338..c0dfe4d 100644
--- a/src/components/settings/SettingRow.vue
+++ b/src/components/settings/SettingRow.vue
@@ -52,4 +52,20 @@ defineProps<{
.setting-control {
flex-shrink: 0;
}
+
+@media (max-width: $breakpoint-mobile) {
+ .setting-row {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+ }
+
+ .setting-info {
+ margin-right: 0;
+ }
+
+ .setting-control {
+ width: 100%;
+ }
+}
diff --git a/src/components/usage/StatCards.vue b/src/components/usage/StatCards.vue
index f178448..006d7b0 100644
--- a/src/components/usage/StatCards.vue
+++ b/src/components/usage/StatCards.vue
@@ -88,4 +88,10 @@ function formatCost(n: number): string {
grid-template-columns: repeat(2, 1fr);
}
}
+
+@media (max-width: 480px) {
+ .stat-cards {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/stores/app.ts b/src/stores/app.ts
index 6545de9..1bdb8e3 100644
--- a/src/stores/app.ts
+++ b/src/stores/app.ts
@@ -3,6 +3,8 @@ import { ref } from 'vue'
import { checkHealth, fetchAvailableModels, updateDefaultModel, type AvailableModelGroup } from '@/api/system'
export const useAppStore = defineStore('app', () => {
+ const sidebarOpen = ref(false)
+
const connected = ref(false)
const serverVersion = ref('')
const modelGroups = ref([])
@@ -59,7 +61,18 @@ export const useAppStore = defineStore('app', () => {
}
}
+ function toggleSidebar() {
+ sidebarOpen.value = !sidebarOpen.value
+ }
+
+ function closeSidebar() {
+ sidebarOpen.value = false
+ }
+
return {
+ sidebarOpen,
+ toggleSidebar,
+ closeSidebar,
connected,
serverVersion,
modelGroups,
diff --git a/src/styles/global.scss b/src/styles/global.scss
index 1ac23d5..0f35f15 100644
--- a/src/styles/global.scss
+++ b/src/styles/global.scss
@@ -74,3 +74,56 @@ a {
font-weight: 600;
color: $text-primary;
}
+
+// Responsive utility classes for inline width replacement
+.input-sm { width: 90px; }
+.input-md { width: 200px; }
+.input-lg { width: 300px; }
+
+// Mobile drawer backdrop
+.mobile-backdrop {
+ display: none;
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.4);
+ z-index: 999;
+}
+
+// Hamburger button (mobile only)
+.hamburger-btn {
+ display: none;
+ position: fixed;
+ top: 16px;
+ left: 12px;
+ z-index: 1001;
+ width: 36px;
+ height: 36px;
+ border: none;
+ background: $bg-card;
+ border-radius: $radius-sm;
+ cursor: pointer;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+}
+
+// Mobile responsive
+@media (max-width: $breakpoint-mobile) {
+ .mobile-backdrop {
+ display: block;
+ }
+
+ .hamburger-btn {
+ display: flex;
+ }
+
+ .page-header {
+ padding: 16px 12px 16px 52px;
+ }
+
+ .input-sm,
+ .input-md,
+ .input-lg {
+ width: 100%;
+ }
+}
diff --git a/src/styles/variables.scss b/src/styles/variables.scss
index 36865ee..eada616 100644
--- a/src/styles/variables.scss
+++ b/src/styles/variables.scss
@@ -45,6 +45,7 @@ $font-code: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
$sidebar-width: 240px;
$sidebar-collapsed-width: 64px;
$header-height: 60px;
+$breakpoint-mobile: 768px;
// Radius
$radius-sm: 6px;
diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue
index 03527b4..7e21418 100644
--- a/src/views/LoginView.vue
+++ b/src/views/LoginView.vue
@@ -89,11 +89,16 @@ async function handleLogin() {
.login-card {
width: 480px;
- padding: 56px 56px;
+ max-width: calc(100vw - 32px);
+ padding: 56px;
border: 1px solid $border-color;
border-radius: $radius-lg;
background: $bg-card;
text-align: center;
+
+ @media (max-width: $breakpoint-mobile) {
+ padding: 32px 24px;
+ }
}
.login-logo {
diff --git a/src/views/LogsView.vue b/src/views/LogsView.vue
index ff19fcd..7eec108 100644
--- a/src/views/LogsView.vue
+++ b/src/views/LogsView.vue
@@ -93,21 +93,21 @@ onMounted(async () => {
v-model:value="selectedLog"
:options="logOptions"
size="small"
- style="width: 200px"
+ class="input-md"
@update:value="loadLogs"
/>
{ levelFilter = v; loadLogs() }"
/>
{ lineCount = v; loadLogs() }"
/>
(data.value?.user || '').replace(/ยง/g, '\n\n
gap: 16px;
flex: 1;
min-height: 0;
+
+ @media (max-width: $breakpoint-mobile) {
+ flex-direction: column;
+ }
}
.memory-section {
diff --git a/src/views/SettingsView.vue b/src/views/SettingsView.vue
index cfeac46..555b913 100644
--- a/src/views/SettingsView.vue
+++ b/src/views/SettingsView.vue
@@ -63,7 +63,7 @@ async function saveApiServer(values: Record) {
saveApiServer({ host: v })"
/>
@@ -71,7 +71,7 @@ async function saveApiServer(values: Record) {
v != null && saveApiServer({ port: v })"
/>
@@ -79,14 +79,14 @@ async function saveApiServer(values: Record) {
saveApiServer({ key: v })"
/>
saveApiServer({ cors_origins: v })"
/>
diff --git a/src/views/SkillsView.vue b/src/views/SkillsView.vue
index db5352f..84a3445 100644
--- a/src/views/SkillsView.vue
+++ b/src/views/SkillsView.vue
@@ -1,5 +1,5 @@