Compare commits

...
6 Commits
Author SHA1 Message Date
zyycnandCodex afb68e97d6 chore: bump version to 3.0.7
Co-authored-by: Codex <noreply@openai.com>
2026-07-26 04:55:44 +08:00
zyycnandCodex 93b9741af2 fix(update): preserve streamed log history
Include each update event ID in the SSE JSON payload so the frontend appends distinct progress rows instead of replacing one undefined-ID row.

Co-authored-by: Codex <noreply@openai.com>
2026-07-26 04:55:29 +08:00
zyycnandCodex 013d592454 chore: bump version to 3.0.6
Co-authored-by: Codex <noreply@openai.com>
2026-07-26 04:35:54 +08:00
zyycnandCodex 0f56e9b80c feat(ui): replace product branding icon
Use the new product mark across the favicon, login panel, and sidebar with a consistent dark brand background and cache-busted browser icon.

Co-authored-by: Codex <noreply@openai.com>
2026-07-26 04:34:57 +08:00
zyycnandCodex 6ec5ecb9ac fix(ui): preserve numeric input model values
Keep number inputs on the string model contract so settings edits reach form state and persist after refresh.

Co-authored-by: Codex <noreply@openai.com>
2026-07-26 04:34:28 +08:00
zyycnandCodex 460d439351 fix(ui): stabilize button loading spinner
Co-authored-by: Codex <noreply@openai.com>
2026-07-26 03:50:16 +08:00
11 changed files with 46 additions and 19 deletions
@@ -201,6 +201,7 @@ struct RestartAcceptedView {
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct SystemUpdateEventView {
id: String,
operation_id: Option<String>,
level: &'static str,
step: Option<String>,
@@ -404,6 +405,7 @@ where
impl From<SystemUpdateEvent> for SystemUpdateEventView {
fn from(event: SystemUpdateEvent) -> Self {
Self {
id: event.id,
operation_id: event.operation_id,
level: update_event_level_name(event.level),
step: event.step,
@@ -418,6 +420,7 @@ impl From<SystemUpdateEvent> for SystemUpdateEventView {
impl SystemUpdateEventView {
fn into_json(self) -> serde_json::Value {
serde_json::json!({
"id": self.id,
"operationId": self.operation_id,
"level": self.level,
"step": self.step,
@@ -38,7 +38,7 @@ fn update_request_should_preserve_target_version_for_domain_validation() {
}
#[tokio::test]
async fn update_event_stream_should_preserve_download_progress_percent() {
async fn update_event_stream_should_preserve_event_id_and_download_progress_percent() {
let fixture = AdminTestFixture::with_system(Arc::new(ProgressSystem)).await;
fixture.auth.insert_session("valid-session");
let response = app(fixture.state())
@@ -57,6 +57,10 @@ async fn update_event_stream_should_preserve_download_progress_percent() {
.expect("update event body");
let body = String::from_utf8(body.to_vec()).expect("UTF-8 event stream");
assert!(
body.contains(r#""id":"update-progress-1""#),
"unexpected update event stream: {body}"
);
assert!(
body.contains(r#""progressPercent":40"#),
"unexpected update event stream: {body}"
+1 -1
View File
@@ -6,7 +6,7 @@
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg?v=20260726" />
<title>Codex Proxy RS</title>
</head>
<body>
+7 -7
View File
@@ -1,9 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="16" fill="#111827"/>
<g transform="translate(10 8) scale(1.85)" fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 5c.67 0 1.35.09 2 .26 1.78-2 5.03-2.84 6.42-2.26 1.4.58-.42 7-.42 7 .57 1.07 1 2.24 1 3.44C21 17.9 16.97 21 12 21s-9-3.1-9-7.56c0-1.25.5-2.4 1-3.44 0 0-1.89-6.42-.5-7S8.22 3.26 10 5.26A8.3 8.3 0 0 1 12 5Z"/>
<path d="M8 14v.5"/>
<path d="M16 14v.5"/>
<path d="M11.25 16.25h1.5L12 17l-.75-.75Z"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="48 48 416 416" role="img" aria-labelledby="title">
<title id="title">Codex Proxy</title>
<rect x="48" y="48" width="416" height="416" rx="88" fill="#1c2a3d" />
<g fill="none" stroke="#fff" stroke-linecap="round" stroke-width="36">
<path d="M156 307A112 112 0 0 1 307 156" />
<path d="M356 205A112 112 0 0 1 205 356" />
</g>
<path d="m407 105-119 153-183 149 124-157Z" fill="#fff" />
</svg>

Before

Width:  |  Height:  |  Size: 594 B

After

Width:  |  Height:  |  Size: 457 B

+5 -1
View File
@@ -108,7 +108,11 @@ const labelClasses = computed(() => [
:title="title || (iconOnly ? label : undefined)"
:aria-busy="loading"
>
<LoaderCircle v-if="loading" class="animate-spin" :size="loadingIconSize[size]" />
<LoaderCircle
v-if="loading"
class="shrink-0 animate-spin origin-center transform-view will-change-transform"
:size="loadingIconSize[size]"
/>
<span v-if="$slots.icon && !loading" class="inline-flex shrink-0">
<slot name="icon" />
</span>
+7 -1
View File
@@ -24,6 +24,11 @@ const props = withDefaults(
const model = defineModel<string>({ default: '' })
// number 输入也保持字符串契约,避免原生 v-model 隐式转换为 number。
function updateModel(event: Event) {
model.value = (event.target as HTMLInputElement).value
}
const containerClasses = computed(() => [
'relative inline-flex h-(--cp-input-height-default) w-full min-w-0 items-center gap-2.5 overflow-visible rounded-(--cp-input-radius-base) border-0 px-3.5 text-[13px] text-(--cp-text-primary) shadow-(--cp-shadow-input) transition-[background-color,box-shadow,color] duration-[160ms]',
props.disabled
@@ -64,7 +69,7 @@ const inputClasses = computed(() => [
<slot name="prefix" />
</span>
<input
v-model="model"
:value="model"
class="base-input__field"
:class="inputClasses"
:name="name"
@@ -74,6 +79,7 @@ const inputClasses = computed(() => [
:autocomplete="autocomplete"
:aria-label="ariaLabel"
:aria-invalid="error ? 'true' : undefined"
@input="updateModel"
>
<span v-if="$slots.suffix" :class="iconClasses">
<slot name="suffix" />
@@ -1,7 +1,6 @@
<script setup lang="ts">
import {
ArrowUpCircle,
Cat,
ChartNoAxesColumn,
Info,
KeyRound,
@@ -340,9 +339,14 @@ onBeforeUnmount(() => {
<BaseMotionIcon
aria-hidden="true"
variant="brand"
class="inline-flex size-11 items-center justify-center relative -top-0.5 rounded-(--cp-icon-button-radius) bg-(--cp-bg-muted) text-(--cp-text-primary)"
class="inline-flex size-11 items-center justify-center relative -top-0.5 rounded-(--cp-icon-button-radius) bg-(--cp-brand-mark-bg)"
>
<Cat :size="27" stroke-width="2" />
<img
src="/favicon.svg"
alt=""
class="block size-9 select-none"
draggable="false"
>
</BaseMotionIcon>
<span
v-show="brandLabelVisible"
+1
View File
@@ -8,6 +8,7 @@
--cp-bg-tertiary: #eef6ff;
--cp-bg-nav-active: #e9eef5;
--cp-bg-dark: #111827;
--cp-brand-mark-bg: #1c2a3d;
--cp-white: #ffffff;
--cp-transparent: #ffffff00;
--cp-overlay-scrim: #0e17264d;
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ArrowRight, Cat, CircleAlert, Eye, EyeOff, KeyRound, Mail, Moon, Sun } from '@lucide/vue'
import { ArrowRight, CircleAlert, Eye, EyeOff, KeyRound, Mail, Moon, Sun } from '@lucide/vue'
import { computed, shallowRef } from 'vue'
import BaseButton from '@/components/base/BaseButton.vue'
@@ -58,7 +58,12 @@ function togglePasswordVisible(): void {
<header class="flex min-w-0 items-center justify-between gap-4.5 max-[560px]:gap-3.5">
<div class="flex min-w-0 items-center gap-3">
<BaseMotionIcon aria-hidden="true" variant="brand" class="login-logo">
<Cat :size="20" :stroke-width="2.1" />
<img
src="/favicon.svg"
alt=""
class="block size-8 select-none"
draggable="false"
>
</BaseMotionIcon>
<span class="grid min-w-0 gap-1">
<strong
+2 -2
View File
@@ -93,7 +93,7 @@ async function handleSubmit(): Promise<void> {
--login-form-bg-c: color-mix(in srgb, var(--cp-bg-muted) 78%, var(--cp-info-bg));
--login-form-shadow: color-mix(in srgb, var(--cp-text-primary) 20%, transparent);
--login-form-line: color-mix(in srgb, var(--cp-info-border) 54%, transparent);
--login-logo-bg: color-mix(in srgb, var(--cp-info-bg) 74%, var(--cp-bg-surface));
--login-logo-bg: var(--cp-brand-mark-bg);
--login-logo-text: var(--cp-accent-primary);
--login-title: var(--cp-text-primary);
--login-brand-title: var(--cp-text-strong);
@@ -159,7 +159,7 @@ async function handleSubmit(): Promise<void> {
--login-form-bg-c: color-mix(in srgb, var(--cp-bg-page) 90%, transparent);
--login-form-shadow: color-mix(in srgb, var(--cp-bg-dark) 70%, transparent);
--login-form-line: color-mix(in srgb, var(--cp-info-text) 66%, transparent);
--login-logo-bg: color-mix(in srgb, var(--cp-bg-surface) 84%, transparent);
--login-logo-bg: var(--cp-brand-mark-bg);
--login-logo-text: var(--cp-info-text);
--login-title: var(--cp-text-strong);
--login-brand-title: var(--cp-white);
+1 -1
View File
@@ -1 +1 @@
version: "3.0.5"
version: "3.0.7"