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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

## [Unreleased]

- style(branding): 重设计 logo/favicon 为「通透青绿圆环 + Tabler prompt `>_` 笔划」(方案 d05):全透明底 + 青绿渐变(`#22d3ee` → `#2dd4bf`)细圆环(stroke 0.5,仅原 1/3、通透若隐)+ 饱和青绿渐变 `>_`(stroke 2.5),极细环与饱和笔划形成权重对比、`>_` 为视觉主角,青绿承载终端/代码语义;基础笔划改用 Tabler prompt(chevron `M5 7l5 5l-5 5` + 下划线 `M13 17l6 0`,下划线长 6 单位、不会小尺寸点化);渐变改 `gradientUnits="userSpaceOnUse"` 绝对坐标,修复纯水平下划线(包围盒高度 0)在默认 objectBoundingBox 渐变下不着色而「消失」的问题;`_LOGO_DEFS` / `_PROMPT_PATHS` 单一事实源统一供 SVG favicon、ICO 回退(省略 0.5px 细环、保留饱和笔划)与页面 logo 消费;`.logo` 容器改全透明、SVG 内嵌圆环 + 青绿 drop-shadow 辉光(实机验证);

## [v0.5.2a5](https://github.com/ThreeFish-AI/coding-proxy/releases/tag/v0.5.2a5) - 2026-07-04

- fix(dashboard): 供应商队列拖拽由原生 HTML5 DnD 改为 Pointer Events 实现,修复 v0.5.2a4 引入的拖拽排序功能在浏览器中无法拖动的问题;引入 4px 拖拽阈值、乐观实时重排与指针捕获机制,同步支持触屏设备;拖拽完成后顺序立即影响 cc 运行时供应商优先级调度(实机验证);为 `/dashboard` 响应增加 `Cache-Control: no-cache`,防止浏览器缓存旧内联脚本;补齐前端守卫测试断言 (#271);
Expand Down
105 changes: 44 additions & 61 deletions src/coding/proxy/server/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,86 +11,73 @@

from ..logging.db import TimePeriod

# ── 品牌图标:Tabler terminal-2 核心笔划(> 与 _),不含外框 ──────────────
# 容器为「淡色磨砂卡片」(#f5f6fb 浅底 + 品牌紫描边环),> 与 _ 笔划改用品牌渐变
# (#667eea -> #764ba2)——色彩反转:品牌紫由背景迁移至图标笔划,轮廓成为视觉主角,
# 更简约、小尺寸下更清晰(Linear/Vercel 风)。保留 Tabler 原始外框笔划会双重描边、
# 边缘发糊,故仅取 > 与 _ 两条。
# 单一事实源:_TERMINAL2_PATHS 与 _LOGO_DEFS 同时供 favicon SVG(f-string)与页面
# logo({{TERMINAL2}} / {{LOGO_DEFS}} 模板替换)消费。
_BRAND_FROM, _BRAND_TO = "#667eea", "#764ba2"
# ── 品牌图标:Tabler prompt 核心笔划(> 与 _)+ 通透青绿圆环 ──────────────
# 设计(方案 d05):全透明底 + 青绿渐变细圆环(stroke 0.5,仅原 1/3,通透若隐)+
# 饱和青绿渐变 >_ 笔划(stroke 2.5)。极细环与饱和笔划形成权重对比,>_ 为绝对主角;
# 青绿(#22d3ee -> #2dd4bf)承载终端/代码语义,通透明快。基础笔划取自 Tabler prompt
# (chevron M5 7l5 5l-5 5 + 下划线 M13 17l6 0,下划线长 6 单位、不会小尺寸点化)。
# 渐变用 gradientUnits="userSpaceOnUse" + 绝对坐标:纯水平下划线包围盒高度为 0,若用
# 默认 objectBoundingBox 渐变会映射失效而不着色(下划线消失),userSpaceOnUse 规避。
# 单一事实源:_PROMPT_PATHS 与 _LOGO_DEFS 同时供 favicon SVG(f-string)与页面
# logo({{PROMPT}} / {{LOGO_DEFS}} 模板替换)消费。
_BRAND_FROM, _BRAND_TO = "#22d3ee", "#2dd4bf"
_LOGO_DEFS = (
'<defs><linearGradient id="cpBrand" x1="0" y1="0" x2="1" y2="1">'
'<defs><linearGradient id="cpBrand" gradientUnits="userSpaceOnUse" '
'x1="4" y1="4" x2="20" y2="20">'
f'<stop offset="0" stop-color="{_BRAND_FROM}"/>'
f'<stop offset="1" stop-color="{_BRAND_TO}"/>'
"</linearGradient></defs>"
)
_TERMINAL2_PATHS = '<path d="M8 9l3 3l-3 3" /><path d="M13 15l3 0" />'
_PROMPT_PATHS = '<path d="M5 7l5 5l-5 5" /><path d="M13 17l6 0" />'


# ── Favicon (SVG, 现代浏览器主选) ──────────────────────────────────────────
def _build_favicon_svg() -> str:
"""生成 24×24 SVG favicon:淡色磨砂卡片 + 品牌渐变 terminal-2 笔划.
"""生成 24×24 SVG favicon:通透青绿圆环 + 品牌渐变 prompt 笔划(方案 d05).

容器为 ``#f5f6fb`` 浅底圆角方块 + 品牌紫描边环;``>`` 与 ``_`` 笔划由白色改为品牌
渐变(``url(#cpBrand)``),使图标轮廓成为主角。独立 SVG 文档无 CSS 上下文,
``currentColor`` 不可靠,故颜色硬编码;``<defs>`` 复用 ``_LOGO_DEFS`` 单一事实源。
全透明底 + 青绿渐变细圆环(``r=11``、``stroke-width=0.5``、``stroke-opacity=.55``,
通透若隐)+ 饱和青绿渐变 ``>`` 与 ``_`` 笔划(``stroke-width=2.5``)。极细环与饱和
笔划形成权重对比,``>_`` 为视觉主角。独立 SVG 文档无 CSS 上下文,``currentColor``
不可靠,故渐变复用 ``_LOGO_DEFS``(userSpaceOnUse,规避水平下划线不着色)单一事实源。
"""
return (
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">'
f"{_LOGO_DEFS}"
'<rect x="1" y="1" width="22" height="22" rx="6" fill="#f5f6fb" '
'stroke="#667eea" stroke-opacity=".18" stroke-width="1"/>'
f'<g fill="none" stroke="url(#cpBrand)" stroke-width="2" '
f'stroke-linecap="round" stroke-linejoin="round">{_TERMINAL2_PATHS}</g>'
'<circle cx="12" cy="12" r="11" fill="none" stroke="url(#cpBrand)" '
'stroke-width="0.5" stroke-opacity=".55"/>'
f'<g fill="none" stroke="url(#cpBrand)" stroke-width="2.5" '
f'stroke-linecap="round" stroke-linejoin="round">{_PROMPT_PATHS}</g>'
"</svg>"
)


# ── Favicon (ICO, 32×32 光栅回退) ─────────────────────────────────────────
def _build_favicon() -> bytes:
"""程序化生成 32×32 ICO:淡色磨砂卡片 + 品牌渐变 terminal-2 笔划(旧浏览器/Safari 回退).
"""程序化生成 32×32 ICO:全透明底 + 青绿渐变 prompt 笔划(旧浏览器/Safari 回退).

项目无 Pillow/cairosvg 等图像库,故以纯 Python 按像素光栅化:``#f5f6fb`` 浅底圆角
方块 + 品牌渐变(#667eea -> #764ba2)``>`` 折线与 ``_`` 下划线。沿用原实现「全零
AND-mask + per-pixel alpha」的圆角透明方案(现代渲染器按 alpha 通道处理透明)。
32px 下细描边环易锯齿,故 ICO 仅保留浅底 + 渐变笔划,精修外观由 SVG 承载。
项目无 Pillow/cairosvg 等图像库,故以纯 Python 按像素光栅化:全透明底 + 青绿渐变
(#22d3ee -> #2dd4bf)``>`` 折线与 ``_`` 下划线。沿用「全零 AND-mask + per-pixel
alpha」透明方案(现代渲染器按 alpha 通道处理透明)。方案 d05 的细圆环(0.5px)在
32px 下不足 1 像素、无法渲染,故 ICO 省略圆环、仅保留饱和 ``>_`` 笔划(符合「图标
为主角、环极弱」的设计意图),完整外观由 SVG 主版承载。
"""
import math
import struct

width = height = 32
radius = 8
scale = width / 24.0 # SVG 24 单位 -> 像素

# 图标笔划品牌渐变:#667eea -> #764ba2(与 _LOGO_DEFS 同源)
r0, g0, b0 = 0x66, 0x7E, 0xEA
r1, g1, b1 = 0x76, 0x4B, 0xA2
# 容器底色:#f5f6fb(近白、极浅冷蓝紫)
tile_r, tile_g, tile_b = 0xF5, 0xF6, 0xFB

def inside(x: int, y: int) -> bool:
"""满铺圆角方块(四角半径 radius)包含判定."""
cx = min(x, width - 1 - x)
cy = min(y, height - 1 - y)
if cx >= radius or cy >= radius:
return True
return (radius - cx) ** 2 + (radius - cy) ** 2 <= radius * radius

# 像素缓冲:[B, G, R, A],默认全透明
# 图标笔划青绿渐变:#22d3ee -> #2dd4bf(与 _LOGO_DEFS 同源)
r0, g0, b0 = 0x22, 0xD3, 0xEE
r1, g1, b1 = 0x2D, 0xD4, 0xBF

# 像素缓冲:[B, G, R, A],默认全透明(全透底,无容器填充)
buf: list[list[list[int]]] = [
[[0, 0, 0, 0] for _ in range(width)] for _ in range(height)
]

# 1) 浅底圆角背景(扁平 #f5f6fb)
for y in range(height):
for x in range(width):
if not inside(x, y):
continue
buf[y][x] = [tile_b, tile_g, tile_r, 255]

def brand_at(px: float, py: float) -> tuple[int, int, int]:
"""按对角参数 t 返回 (px,py) 处的品牌渐变 RGB(与 SVG url(#cpBrand) 同向)."""
"""按对角参数 t 返回 (px,py) 处的青绿渐变 RGB(与 SVG url(#cpBrand) 同向)."""
t = (px + py) / (width + height - 2)
return (
int(r0 + (r1 - r0) * t),
Expand All @@ -99,7 +86,7 @@ def brand_at(px: float, py: float) -> tuple[int, int, int]:
)

def stamp(px: float, py: float) -> None:
"""以 (px, py) 为中心盖 ~3px 品牌渐变方块(笔划加粗,保证小尺寸可辨识)."""
"""以 (px, py) 为中心盖 ~3px 青绿渐变方块(笔划加粗,保证小尺寸可辨识)."""
ix, iy = int(round(px)), int(round(py))
cr, cg, cb = brand_at(px, py)
for oy in (-1, 0, 1):
Expand All @@ -109,18 +96,18 @@ def stamp(px: float, py: float) -> None:
buf[ny][nx] = [cb, cg, cr, 255]

def line(x0: float, y0: float, x1: float, y1: float) -> None:
"""稠密采样 + stamp 形成品牌渐变粗线段."""
"""稠密采样 + stamp 形成青绿渐变粗线段."""
dist = math.hypot(x1 - x0, y1 - y0)
steps = max(1, int(dist * 3))
for i in range(steps + 1):
s = i / steps
stamp(x0 + (x1 - x0) * s, y0 + (y1 - y0) * s)

# 2) terminal-2 笔划(SVG 24 单位 -> 像素)
# > 折线 (8,9)->(11,12)->(8,15);_ 下划线 (13,15)->(16,15)
line(8 * scale, 9 * scale, 11 * scale, 12 * scale)
line(11 * scale, 12 * scale, 8 * scale, 15 * scale)
line(13 * scale, 15 * scale, 16 * scale, 15 * scale)
# prompt 笔划(SVG 24 单位 -> 像素)
# > 折线 (5,7)->(10,12)->(5,17);_ 下划线 (13,17)->(19,17)
line(5 * scale, 7 * scale, 10 * scale, 12 * scale)
line(10 * scale, 12 * scale, 5 * scale, 17 * scale)
line(13 * scale, 17 * scale, 19 * scale, 17 * scale)

# 3) 打包 ICO(BMP bottom-up;全零 AND-mask,信任 per-pixel alpha 实现圆角透明)
pixel_rows: list[bytes] = []
Expand Down Expand Up @@ -222,13 +209,9 @@ def line(x0: float, y0: float, x1: float, y1: float) -> None:
.header-left { display: flex; align-items: center; gap: 12px; }
.logo {
width: 32px; height: 32px;
background: #f5f6fb;
border: 1px solid rgba(102,126,234,.18);
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
box-shadow: 0 4px 12px rgba(102,126,234,.18);
}
.logo svg { width: 20px; height: 20px; display: block; }
.logo svg { width: 28px; height: 28px; display: block; filter: drop-shadow(0 0 3px rgba(45,212,191,.3)); }
h1 { font-size: 18px; font-weight: 600; color: var(--text-primary); letter-spacing: -.3px; }
.header-right { display: flex; align-items: center; gap: 12px; }
.badge {
Expand Down Expand Up @@ -815,7 +798,7 @@ def line(x0: float, y0: float, x1: float, y1: float) -> None:
<body>
<header>
<div class="header-left">
<div class="logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="url(#cpBrand)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">{{LOGO_DEFS}}{{TERMINAL2}}</svg></div>
<div class="logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="url(#cpBrand)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">{{LOGO_DEFS}}<circle cx="12" cy="12" r="11" fill="none" stroke="url(#cpBrand)" stroke-width="0.5" stroke-opacity=".55"/><g>{{PROMPT}}</g></svg></div>
<h1>Coding Proxy Dashboard</h1>
<span class="badge" id="version-badge">v-.-.-</span>
</div>
Expand Down Expand Up @@ -2385,7 +2368,7 @@ def line(x0: float, y0: float, x1: float, y1: float) -> None:
</script>
</body>
</html>
""".replace("{{TERMINAL2}}", _TERMINAL2_PATHS).replace("{{LOGO_DEFS}}", _LOGO_DEFS)
""".replace("{{PROMPT}}", _PROMPT_PATHS).replace("{{LOGO_DEFS}}", _LOGO_DEFS)


# ── 数据计算工具 ──────────────────────────────────────────────────────────
Expand Down
Loading