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

@@ -2,7 +2,7 @@
## 项目
本仓库包含 SenlinAI 项目工作台 MVP。
本仓库包含森林AI项目工作台 MVP。
该产品是一个以项目为中心、面向私有化部署的工作台服务于个人知识工作者、团队负责人和可信的内部用户。它用于组织项目任务、inbox 条目、Markdown 笔记、文件、AI 会话、项目内标签,以及轻量级任务分发。

View File

@@ -1,6 +1,6 @@
# SenlinAI Agent Workbench
# 森林AI
SenlinAI Agent Workbench 是一个以项目为中心的私有化工作台 MVP。
森林AI 是一个以项目为中心的私有化工作台 MVP。
## 本地开发

View File

@@ -1,8 +1,8 @@
[package]
name = "senlinai_workbench"
version = "0.1.0"
description = "SenlinAI project workbench desktop shell"
authors = ["SenlinAI"]
description = "森林AI 项目工作台桌面端"
authors = ["森林AI"]
edition = "2021"
[lib]

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -3,5 +3,5 @@ pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.run(tauri::generate_context!())
.expect("error while running SenlinAI Workbench");
.expect("启动森林AI时发生错误");
}

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "SenlinAI Workbench",
"productName": "森林AI",
"version": "0.1.0",
"identifier": "ai.senlin.workbench",
"build": {
@@ -12,7 +12,7 @@
"app": {
"windows": [
{
"title": "项目工作台",
"title": "森林AI",
"width": 1280,
"height": 820
}
@@ -24,6 +24,12 @@
"bundle": {
"active": true,
"targets": "all",
"icon": ["icons/icon.ico"]
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}

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="搜索项目、频道、文档、任务、联系人..." />

View File

@@ -1,14 +1,14 @@
# SenlinAI UI Design Guide
# 森林AI UI Design Guide
## Purpose
This file defines the shared visual direction for SenlinAI Workbench. It keeps future UI work consistent across the login page, project shell, channel pages, object inspector, and desktop wrapper.
This file defines the shared visual direction for 森林AI. It keeps future UI work consistent across the login page, project shell, channel pages, object inspector, and desktop wrapper.
The visual reference is shadcn/ui: quiet, structured, component-driven, and practical. This is a visual reference only. The web app must remain Svelte and TypeScript, and must not add React or shadcn/ui as runtime dependencies.
## Product Feel
SenlinAI is a private project workbench for focused knowledge work. The UI should feel calm, precise, and durable rather than decorative. It should support long sessions, dense information, fast scanning, and frequent switching between projects and channels.
森林AI is a private project workbench for focused knowledge work. The UI should feel calm, precise, and durable rather than decorative. It should support long sessions, dense information, fast scanning, and frequent switching between projects and channels.
Use a restrained desktop-app tone:

10
docs/senlinai-icon.svg Normal file
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