feat(brand): apply 森林AI identity

This commit is contained in:
2026-07-22 15:06:51 +08:00
parent bbc3a752dc
commit 98573cf4ca
21 changed files with 91 additions and 37 deletions

View File

@@ -1,6 +1,6 @@
# SenlinAI Acro React
# 森林AI Web 客户端
SenlinAI primary web client built with React, TypeScript, Vite, and Arco Design.
森林AI主 Web 客户端,使用 ReactTypeScriptVite Arco Design 构建。
This app is the active web client under `apps/web_v1`.

View File

@@ -1,10 +1,12 @@
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/svg+xml" href="/senlinai-icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>web_v1</title>
<meta name="application-name" content="森林AI" />
<meta name="description" content="森林AI 私有化项目工作台" />
<title>森林AI</title>
</head>
<body>
<div id="root"></div>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 731 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<title>森林AI 图标</title>
<rect x="4" y="4" width="120" height="120" rx="28" fill="#165DFF"/>
<path d="M64 23C43 23 27 40 27 61c0 13 7 24 17 30h40c10-6 17-17 17-30 0-21-16-38-37-38Z" fill="#F4F8FF"/>
<path d="M64 99V65M64 76 47 61M64 82l18-16M64 68l12-13" fill="none" stroke="#F4F8FF" stroke-linecap="round" stroke-linejoin="round" stroke-width="10"/>
<path d="M42 58c6-15 29-22 45-7M39 69c12-11 38-13 50 2M48 78c9-5 24-6 33-1" fill="none" stroke="#165DFF" stroke-linecap="round" stroke-width="5"/>
<circle cx="47" cy="61" r="6" fill="#14B8A6"/>
<circle cx="76" cy="55" r="6" fill="#60A5FA"/>
<circle cx="82" cy="66" r="6" fill="#A7F3D0"/>
</svg>

After

Width:  |  Height:  |  Size: 731 B

View File

@@ -25,6 +25,23 @@ const requiredFiles = [
const failures = requiredFiles.filter((file) => !existsSync(file)).map((file) => `missing ${file}`)
const html = readFileSync('index.html', 'utf8')
if (!html.includes('<html lang="zh-CN">')) failures.push('index language must be zh-CN')
if (!html.includes('<title>森林AI</title>')) failures.push('document title must be 森林AI')
if (!html.includes('href="/senlinai-icon.svg"')) failures.push('favicon must use /senlinai-icon.svg')
if (!existsSync('public/senlinai-icon.svg')) failures.push('missing public/senlinai-icon.svg')
for (const file of ['src/pages/login.tsx', 'src/pages/projects/project-topbar.tsx', 'src/pages/projects/project-rail.tsx']) {
const source = readFileSync(file, 'utf8')
if (!source.includes('/senlinai-icon.svg')) failures.push(`${file} must use the brand icon`)
}
for (const forbidden of ['森林Agent', 'SenlinAI Workbench']) {
for (const file of requiredFiles.filter((name) => name.endsWith('.tsx'))) {
if (readFileSync(file, 'utf8').includes(forbidden)) failures.push(`${file} contains legacy product name ${forbidden}`)
}
}
if (existsSync('src/App.tsx')) {
failures.push('legacy src/App.tsx should be removed after page split')
}

View File

@@ -113,23 +113,23 @@
gap: 16px;
}
.brand-symbol {
.brand-icon {
width: 32px;
height: 32px;
display: grid;
place-items: center;
display: block;
border-radius: 8px;
background: var(--senlin-primary);
color: white;
font-weight: 800;
font-size: 18px;
}
.brand-lockup.large .brand-symbol {
.brand-lockup.large .brand-icon {
width: 46px;
height: 46px;
border-radius: 12px;
font-size: 24px;
}
.rail-brand-icon {
width: 40px;
height: 40px;
border-radius: 10px;
}
.brand-name {

View File

@@ -38,8 +38,8 @@ export function LoginPage({ onLogin }: { onLogin: (input: { server: string; emai
<Card className="login-card" bordered>
<div className="login-brand-panel">
<div className="brand-lockup large">
<span className="brand-symbol">S</span>
<Title heading={2}>Agent</Title>
<img className="brand-icon" src="/senlinai-icon.svg" alt="" aria-hidden="true" />
<Title heading={2}>AI</Title>
</div>
<Text className="login-slogan"> · </Text>
<Text type="secondary"> · · AI Agent</Text>
@@ -51,7 +51,7 @@ export function LoginPage({ onLogin }: { onLogin: (input: { server: string; emai
</div>
<div className="login-form-panel">
<Title heading={4}> SenlinAI</Title>
<Title heading={4}>AI</Title>
<Text type="secondary"></Text>
<Form layout="vertical" className="login-form">
<Form.Item label="服务器地址IP 或域名优先)">
@@ -81,7 +81,7 @@ export function LoginPage({ onLogin }: { onLogin: (input: { server: string; emai
<div className="login-value-panel">
<Title heading={3}> · · </Title>
<Paragraph>
SenlinAI
AI
</Paragraph>
<ValuePoint icon={<IconSafe />} title="私有部署,数据自有" desc="所有数据与模型运行在您的环境中,安全可控。" />
<ValuePoint icon={<IconBook />} title="知识沉淀,结构清晰" desc="从收集到整理,形成可复用的团队知识库。" />

View File

@@ -1,6 +1,6 @@
import type { CSSProperties } from 'react'
import { Badge, Button, Layout, Space } from '@arco-design/web-react'
import { IconCompass, IconDashboard, IconPlus } from '@arco-design/web-react/icon'
import { IconCompass, IconPlus } from '@arco-design/web-react/icon'
import type { Project, WorkbenchView } from './project-types'
const { Sider } = Layout
@@ -26,10 +26,10 @@ export function ProjectRail({
<Sider className="project-rail" width={96}>
<Button
className={activeView === 'workspace' ? 'dashboard-button active' : 'dashboard-button'}
icon={<IconDashboard />}
icon={<img className="brand-icon rail-brand-icon" src="/senlinai-icon.svg" alt="" aria-hidden="true" />}
onClick={onSelectWorkspace}
aria-label="工作台"
title="工作台"
aria-label="森林AI 工作台"
title="森林AI 工作台"
/>
<Button
className={activeView === 'workspace-explore' ? 'dashboard-button active' : 'dashboard-button'}

View File

@@ -11,8 +11,8 @@ export function ProjectTopbar({ theme, onToggleTheme }: { theme: Theme; onToggle
return (
<Header className="topbar">
<div className="brand-lockup">
<span className="brand-symbol">S</span>
<Text className="brand-name">SenlinAI</Text>
<img className="brand-icon" src="/senlinai-icon.svg" alt="" aria-hidden="true" />
<Text className="brand-name">AI</Text>
</div>
<div className="global-search" role="search">
<Input size="large" prefix={<IconSearch />} placeholder="搜索项目、频道、文档、任务、联系人..." />