Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0d1117" />
<meta name="color-scheme" content="dark" />
<link rel="icon" type="image/png" href="/favicon.png" />
<title>AI Studio 控制台</title>
<script>
document.documentElement.dataset.theme =
localStorage.getItem('user_theme') === 'light' ? 'light' : 'dark'
</script>
</head>
<body>
<div id="app"></div>
Expand Down
Binary file added web/public/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 42 additions & 26 deletions web/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import LogsPanel from '@/components/LogsPanel.vue'
import ModelsTable from '@/components/ModelsTable.vue'
import RequestsPanel from '@/components/RequestsPanel.vue'
import SettingsPanel from '@/components/SettingsPanel.vue'
import ThemeToggle from '@/components/ThemeToggle.vue'
import UiIcon from '@/components/UiIcon.vue'
import { useTabParam } from '@/url'
import type { IconName } from '@/icons'
Expand Down Expand Up @@ -414,34 +415,48 @@ onUnmounted(() => {
class="flex min-w-0 w-full shrink-0 flex-col border-b border-line bg-panel md:w-64 md:border-r md:border-b-0"
>
<div class="flex h-14 items-center justify-between gap-2 border-b border-line px-4">
<div class="flex min-w-0 items-center gap-2">
<div class="h-3 w-3 rounded-full" :class="statusColor"></div>
<h1 class="whitespace-nowrap text-lg font-bold text-white">AI Studio Proxy</h1>
</div>
<DropdownMenuRoot :modal="false">
<DropdownMenuTrigger
class="flex shrink-0 items-center gap-1 whitespace-nowrap rounded border border-gray-700 px-1.5 py-0.5 font-mono text-xs text-gray-500 transition hover:text-white data-[state=open]:text-white"
<div class="flex min-w-0 items-center gap-2.5">
<span
v-tooltip="`${t('app.status')}: ${serviceState.toUpperCase()}`"
class="relative shrink-0"
>
{{ locale }}
<UiIcon name="chevronDown" :size="12" />
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent
align="end"
:side-offset="4"
class="z-[60] overflow-hidden rounded border border-line bg-panel shadow-xl"
<img src="/favicon.png" alt="" class="h-6 w-6" />
<span
class="absolute -right-0.5 -bottom-0.5 h-2.5 w-2.5 rounded-full ring-2 ring-panel"
:class="statusColor"
></span>
</span>
<h1 class="whitespace-nowrap text-base font-bold text-white">AI Studio Proxy</h1>
</div>
<div class="flex shrink-0 items-center gap-1">
<ThemeToggle />
<DropdownMenuRoot :modal="false">
<DropdownMenuTrigger
v-tooltip="t('app.language')"
:aria-label="t('app.language')"
class="flex h-6 w-6 shrink-0 items-center justify-center rounded border border-gray-700 text-gray-500 transition hover:text-white data-[state=open]:text-white"
>
<DropdownMenuItem
v-for="item in availableLocales"
:key="item.code"
class="block w-full cursor-pointer px-4 py-2 text-left text-xs whitespace-nowrap text-gray-300 outline-none data-[highlighted]:bg-blue-600 data-[highlighted]:text-white"
@select="setLocale(item.code)"
<UiIcon name="language" :size="13" />
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent
align="end"
:side-offset="4"
class="z-[60] overflow-hidden rounded border border-line bg-panel shadow-xl"
>
{{ item.label }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
<DropdownMenuItem
v-for="item in availableLocales"
:key="item.code"
class="flex w-full cursor-pointer items-center justify-between gap-4 px-4 py-2 text-left text-xs whitespace-nowrap text-gray-300 outline-none data-[highlighted]:bg-blue-600 data-[highlighted]:text-white"
@select="setLocale(item.code)"
>
{{ item.label }}
<UiIcon v-if="item.code === locale" name="check" :size="12" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</div>
</div>

<nav
Expand Down Expand Up @@ -609,7 +624,8 @@ onUnmounted(() => {

<style scoped>
.launching-shell {
background: radial-gradient(circle at 12% 0%, rgb(34 211 238 / 14%), transparent 55%), #161b22;
background:
radial-gradient(circle at 12% 0%, rgb(34 211 238 / 14%), transparent 55%), var(--color-panel);
}

.launching-scan {
Expand Down
6 changes: 5 additions & 1 deletion web/src/components/LoginPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { ref } from 'vue'
import { api, type AdminSession } from '@/api'
import { useI18n } from '@/i18n'
import ThemeToggle from './ThemeToggle.vue'

const emit = defineEmits<{ authenticated: [session: AdminSession] }>()
const { t, errorText } = useI18n()
Expand Down Expand Up @@ -31,7 +32,10 @@ async function login(): Promise<void> {
class="w-full max-w-sm space-y-5 rounded-lg border border-line bg-panel p-7"
@submit.prevent="login"
>
<h1 class="text-xl font-semibold text-white">AI Studio Proxy</h1>
<div class="flex items-center justify-between gap-2">
<h1 class="text-xl font-semibold text-white">AI Studio Proxy</h1>
<ThemeToggle />
</div>
<label class="block">
<span class="mb-2 block text-sm text-gray-300">{{ t('auth.username') }}</span>
<input
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/LogsPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ watch(autoScroll, () => {
bottom: 0;
left: 50%;
width: 1px;
background: #30363d;
background: var(--color-line);
content: '';
transition: background-color 120ms ease;
}
Expand Down
46 changes: 23 additions & 23 deletions web/src/components/RequestLogCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ const bodyParts = computed(() =>
gap: 8px 12px;
}
.request-heading strong {
color: #e6edf3;
color: var(--color-gray-200);
font-weight: 600;
overflow-wrap: anywhere;
}
Expand All @@ -257,41 +257,41 @@ const bodyParts = computed(() =>
}
.state-completed,
.state-tool_calls {
color: #56d4a0;
background: #56d4a00c;
color: var(--color-green-400);
background: color-mix(in oklab, var(--color-green-400) 8%, transparent);
}
.state-running {
color: #79c0ff;
color: var(--color-blue-400);
}
.state-limited,
.state-blocked,
.state-cancelled {
color: #e3b341;
color: var(--color-yellow-400);
}
.state-failed,
.request-error {
color: #ff7b72;
color: var(--color-red-400);
}
.request-duration {
color: #c9d1d9;
color: var(--color-fg);
margin-left: auto;
font-variant-numeric: tabular-nums;
}
.tool-count {
color: #a5d6ff;
color: var(--color-blue-300);
font-size: 12px;
}
.request-metrics {
display: flex;
flex-wrap: wrap;
gap: 4px 18px;
margin-top: 6px;
color: #8b949e;
color: var(--color-gray-400);
font-size: 12px;
font-variant-numeric: tabular-nums;
}
.request-metrics b {
color: #d2dae3;
color: var(--color-gray-200);
font-weight: 500;
}
.request-error {
Expand All @@ -301,7 +301,7 @@ const bodyParts = computed(() =>
}
.request-details {
margin-top: 5px;
color: #8b949e;
color: var(--color-gray-400);
font-size: 11px;
}
.request-details summary {
Expand All @@ -322,15 +322,15 @@ const bodyParts = computed(() =>
display: none;
}
.request-details summary:hover {
color: #c9d1d9;
background: #30363d66;
color: var(--color-fg);
background: color-mix(in oklab, var(--color-line) 40%, transparent);
}
.request-details summary:focus-visible {
outline: 1px solid #79c0ff;
outline: 1px solid var(--color-blue-400);
outline-offset: 2px;
}
.request-disclosure-icon {
color: #6e7681;
color: var(--color-gray-500);
transition: transform 120ms ease;
}
details[open] > summary > .request-disclosure-icon {
Expand All @@ -339,7 +339,7 @@ details[open] > summary > .request-disclosure-icon {
.request-summary-meta {
min-width: 0;
margin-left: 6px;
color: #6e7681;
color: var(--color-gray-500);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
Expand All @@ -362,11 +362,11 @@ details[open] > summary > .request-disclosure-icon {
margin: 10px 0;
}
.request-details dd {
color: #c9d1d9;
color: var(--color-fg);
overflow-wrap: anywhere;
}
.request-timeline {
border-left: 1px solid #30363d;
border-left: 1px solid var(--color-line);
padding-left: 12px;
margin: 10px 0;
}
Expand All @@ -378,29 +378,29 @@ details[open] > summary > .request-disclosure-icon {
}
.request-timeline time {
flex-shrink: 0;
color: #6e7681;
color: var(--color-gray-500);
}
.request-json pre {
overflow: auto;
max-height: 320px;
padding: 12px;
background: #161b22;
border: 1px solid #30363d;
background: var(--color-panel);
border: 1px solid var(--color-line);
border-radius: 4px;
margin-top: 6px;
white-space: pre;
}
.request-body-note {
margin-top: 6px;
color: #8b949e;
color: var(--color-gray-400);
}
.request-body-part {
margin-top: 8px;
}
.request-body-heading {
display: flex;
justify-content: space-between;
color: #8b949e;
color: var(--color-gray-400);
font-size: 11px;
}
@media (max-width: 767px) {
Expand Down
30 changes: 15 additions & 15 deletions web/src/components/StageLogCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function renderDetails(event: Event): void {
gap: 8px 12px;
}
.stage-heading strong {
color: #e6edf3;
color: var(--color-gray-200);
font-weight: 600;
overflow-wrap: anywhere;
}
Expand All @@ -81,33 +81,33 @@ function renderDetails(event: Event): void {
font-variant-numeric: tabular-nums;
}
.state-running {
color: #79c0ff;
color: var(--color-blue-400);
}
.state-completed {
color: #56d4a0;
background: #56d4a00c;
color: var(--color-green-400);
background: color-mix(in oklab, var(--color-green-400) 8%, transparent);
}
.state-cancelled {
color: #e3b341;
color: var(--color-yellow-400);
}
.state-failed,
.stage-error {
color: #ff7b72;
color: var(--color-red-400);
}
.stage-progress {
color: #8b949e;
color: var(--color-gray-400);
font-size: 12px;
font-variant-numeric: tabular-nums;
}
.stage-detail {
margin: 4px 0 0;
color: #c9d1d9;
color: var(--color-fg);
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.stage-details {
margin-top: 5px;
color: #8b949e;
color: var(--color-gray-400);
font-size: 11px;
}
.stage-details summary {
Expand All @@ -128,15 +128,15 @@ function renderDetails(event: Event): void {
display: none;
}
.stage-details summary:hover {
color: #c9d1d9;
background: #30363d66;
color: var(--color-fg);
background: color-mix(in oklab, var(--color-line) 40%, transparent);
}
.stage-details summary:focus-visible {
outline: 1px solid #79c0ff;
outline: 1px solid var(--color-blue-400);
outline-offset: 2px;
}
.stage-disclosure-icon {
color: #6e7681;
color: var(--color-gray-500);
transition: transform 120ms ease;
}
details[open] > summary > .stage-disclosure-icon {
Expand All @@ -154,7 +154,7 @@ details[open] > summary > .stage-disclosure-icon {
}
}
.stage-timeline {
border-left: 1px solid #30363d;
border-left: 1px solid var(--color-line);
padding-left: 12px;
margin: 10px 0;
}
Expand All @@ -166,7 +166,7 @@ details[open] > summary > .stage-disclosure-icon {
}
.stage-timeline time {
flex-shrink: 0;
color: #6e7681;
color: var(--color-gray-500);
}
.stage-timeline span {
min-width: 0;
Expand Down
Loading