feat: add versioned research templates and alpha variants
This commit is contained in:
@@ -32,6 +32,7 @@ import type {
|
||||
} from "../types";
|
||||
import { PnlChart } from "./PnlChart";
|
||||
import { SelfCorrelationPanel } from "./SelfCorrelationPanel";
|
||||
import { ComparisonPanel } from "../research/ComparisonPanel";
|
||||
import { AlphaSources } from "../research/AlphaSources";
|
||||
import type { UIAction } from "../ai/types";
|
||||
|
||||
@@ -216,6 +217,20 @@ export function AlphaDetail({
|
||||
<Button>在 BRAIN 中打开 ↗</Button>
|
||||
</a>
|
||||
</div>
|
||||
{detail.alpha_type === "REGULAR" &&
|
||||
detail.language === "FASTEXPR" && (
|
||||
<Button
|
||||
onClick={() =>
|
||||
onAction({
|
||||
type: "open_variant",
|
||||
alpha_id: detail.id,
|
||||
nonce: Date.now(),
|
||||
})
|
||||
}
|
||||
>
|
||||
研究此 Alpha 的变体
|
||||
</Button>
|
||||
)}
|
||||
<Tabs activeKey={tab} onChange={setTab}>
|
||||
<TabPane tab="概览与表达式" itemKey="overview">
|
||||
<div className="detail-section">
|
||||
@@ -320,6 +335,11 @@ export function AlphaDetail({
|
||||
/>
|
||||
)}
|
||||
</TabPane>
|
||||
<TabPane tab="基线比较" itemKey="compare">
|
||||
{tab === "compare" && (
|
||||
<ComparisonPanel key={detail.id} baseline={detail.id} />
|
||||
)}
|
||||
</TabPane>
|
||||
<TabPane tab="研究来源" itemKey="sources">
|
||||
{tab === "sources" && id && (
|
||||
<AlphaSources
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Avatar, Modal, Popover, Tooltip } from "@douyinfe/semi-ui-19";
|
||||
import {
|
||||
IconBeaker,
|
||||
IconChevronDown,
|
||||
IconClose,
|
||||
IconCommand,
|
||||
IconExit,
|
||||
IconGridView,
|
||||
IconList,
|
||||
IconMoon,
|
||||
IconSidebar,
|
||||
IconUser,
|
||||
} from "@douyinfe/semi-icons";
|
||||
import type { Account } from "../types";
|
||||
import type { WorkspacePage } from "../ai/workspace";
|
||||
|
||||
const navigation = [
|
||||
{ id: "datasets", label: "数据目录", icon: IconList, group: "数据与素材" },
|
||||
{ id: "operators", label: "算子库", icon: IconList, group: "数据与素材" },
|
||||
{
|
||||
id: "templates",
|
||||
label: "模板工坊",
|
||||
icon: IconGridView,
|
||||
group: "数据与素材",
|
||||
},
|
||||
{ id: "variants", label: "Alpha 变体", icon: IconBeaker, group: "研究实验" },
|
||||
{ id: "backtests", label: "回测研究", icon: IconBeaker, group: "研究实验" },
|
||||
{ id: "alphas", label: "Alpha 管理", icon: IconGridView, group: "研究成果" },
|
||||
{ id: "account", label: "个人信息", icon: IconUser, group: "" },
|
||||
] as const;
|
||||
|
||||
export function AppSidebar({
|
||||
account,
|
||||
page,
|
||||
collapsed,
|
||||
blocked,
|
||||
onCollapse,
|
||||
onNavigate,
|
||||
onLogout,
|
||||
onTheme,
|
||||
}: {
|
||||
account: Account | null;
|
||||
page: WorkspacePage;
|
||||
collapsed: boolean;
|
||||
blocked: boolean;
|
||||
onCollapse: () => void;
|
||||
onNavigate: (page: WorkspacePage) => void;
|
||||
onLogout: () => void;
|
||||
onTheme: () => void;
|
||||
}) {
|
||||
const [userOpen, setUserOpen] = useState(false);
|
||||
const [commandOpen, setCommandOpen] = useState(false);
|
||||
const [recent, setRecent] = useState<WorkspacePage[]>([page]);
|
||||
const name = account?.display_name || "研究员";
|
||||
useEffect(() => {
|
||||
setRecent((old) => [page, ...old.filter((id) => id !== page)].slice(0, 4));
|
||||
}, [page]);
|
||||
useEffect(() => {
|
||||
if (blocked) {
|
||||
setUserOpen(false);
|
||||
setCommandOpen(false);
|
||||
}
|
||||
const shortcut = (event: KeyboardEvent) => {
|
||||
if (
|
||||
!blocked &&
|
||||
(event.metaKey || event.ctrlKey) &&
|
||||
event.key.toLowerCase() === "k"
|
||||
) {
|
||||
event.preventDefault();
|
||||
setCommandOpen((open) => !open);
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", shortcut);
|
||||
return () => window.removeEventListener("keydown", shortcut);
|
||||
}, [blocked]);
|
||||
function navigate(id: WorkspacePage) {
|
||||
setUserOpen(false);
|
||||
setCommandOpen(false);
|
||||
onNavigate(id);
|
||||
}
|
||||
function closeRecent(id: WorkspacePage) {
|
||||
if (recent.length <= 1) return;
|
||||
const next = recent.filter((item) => item !== id);
|
||||
setRecent(next);
|
||||
if (id === page) navigate(next[0]);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<aside
|
||||
className={`app-sidebar ${collapsed ? "is-collapsed" : ""}`}
|
||||
inert={blocked}
|
||||
aria-label="工作空间导航"
|
||||
>
|
||||
<div className="sidebar-user-row">
|
||||
<Popover
|
||||
trigger="click"
|
||||
position={collapsed ? "rightTop" : "bottomLeft"}
|
||||
visible={userOpen}
|
||||
onVisibleChange={setUserOpen}
|
||||
showArrow={false}
|
||||
content={
|
||||
<div className="sidebar-user-menu">
|
||||
<div className="sidebar-user-details">
|
||||
<strong>{name}</strong>
|
||||
{account?.email && account.email !== name && (
|
||||
<span>{account.email}</span>
|
||||
)}
|
||||
<span>
|
||||
WorldQuant{" "}
|
||||
{account?.connection_status === "connected"
|
||||
? "已连接"
|
||||
: "未连接"}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-label="查看个人信息"
|
||||
onClick={() => navigate("account")}
|
||||
>
|
||||
<IconUser />
|
||||
查看个人信息
|
||||
</button>
|
||||
<button
|
||||
aria-label={
|
||||
account?.theme === "dark" ? "切换浅色主题" : "切换深色主题"
|
||||
}
|
||||
onClick={onTheme}
|
||||
>
|
||||
<IconMoon />
|
||||
{account?.theme === "dark" ? "切换浅色主题" : "切换深色主题"}
|
||||
</button>
|
||||
<button
|
||||
aria-label="退出登录"
|
||||
onClick={() => {
|
||||
setUserOpen(false);
|
||||
onLogout();
|
||||
}}
|
||||
>
|
||||
<IconExit />
|
||||
退出登录
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<button
|
||||
className="sidebar-user-trigger"
|
||||
aria-label="用户菜单"
|
||||
aria-expanded={userOpen}
|
||||
>
|
||||
<Avatar size="small" color="grey">
|
||||
{name.slice(0, 2).toUpperCase()}
|
||||
</Avatar>
|
||||
{!collapsed && (
|
||||
<>
|
||||
<span className="account-name">{name}</span>
|
||||
<IconChevronDown size="small" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</Popover>
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<div className="sidebar-command">
|
||||
<button onClick={() => setCommandOpen(true)}>
|
||||
<IconCommand />
|
||||
<span>快捷导航</span>
|
||||
<kbd>⌘K</kbd>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<nav className="sidebar-nav" aria-label="主导航">
|
||||
{["数据与素材", "研究实验", "研究成果"].map((group) => (
|
||||
<div key={group}>
|
||||
{!collapsed && (
|
||||
<div className="sidebar-group-heading">{group}</div>
|
||||
)}
|
||||
{navigation
|
||||
.filter((item) => item.group === group)
|
||||
.map(({ id, label, icon: Icon }) => (
|
||||
<Tooltip
|
||||
key={id}
|
||||
content={label}
|
||||
position="right"
|
||||
visible={collapsed ? undefined : false}
|
||||
>
|
||||
<button
|
||||
className={`sidebar-nav-item ${id === page ? "active" : ""}`}
|
||||
aria-label={label}
|
||||
aria-current={id === page ? "page" : undefined}
|
||||
onClick={() => navigate(id)}
|
||||
>
|
||||
<Icon style={{ fontSize: 17 }} />
|
||||
{!collapsed && <span>{label}</span>}
|
||||
</button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
<div className="sidebar-footer">
|
||||
{!collapsed && (
|
||||
<div className="recent-heading">
|
||||
<span>最近访问</span>
|
||||
<button onClick={() => setRecent([page])}>全部关闭</button>
|
||||
</div>
|
||||
)}
|
||||
<nav aria-label="最近访问">
|
||||
{recent.map((id) => {
|
||||
const item = navigation.find((item) => item.id === id)!;
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
className={`recent-item ${id === page ? "active" : ""}`}
|
||||
>
|
||||
<Tooltip
|
||||
content={item.label}
|
||||
position="right"
|
||||
visible={collapsed ? undefined : false}
|
||||
>
|
||||
<button
|
||||
className="recent-link"
|
||||
aria-label={`最近访问:${item.label}`}
|
||||
onClick={() => navigate(id)}
|
||||
>
|
||||
<Icon style={{ fontSize: 15 }} />
|
||||
{!collapsed && <span>{item.label}</span>}
|
||||
</button>
|
||||
</Tooltip>
|
||||
{!collapsed && (
|
||||
<button
|
||||
className="recent-close"
|
||||
aria-label={`关闭最近访问:${item.label}`}
|
||||
disabled={recent.length === 1}
|
||||
onClick={() => closeRecent(id)}
|
||||
>
|
||||
<IconClose size="small" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="sidebar-collapse">
|
||||
<Tooltip
|
||||
content={collapsed ? "展开导航" : "收起导航"}
|
||||
position="right"
|
||||
>
|
||||
<button
|
||||
onClick={onCollapse}
|
||||
aria-label={collapsed ? "展开导航" : "收起导航"}
|
||||
>
|
||||
<IconSidebar />
|
||||
{!collapsed && <span>收起导航</span>}
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<Modal
|
||||
title="快捷导航"
|
||||
visible={commandOpen}
|
||||
footer={null}
|
||||
onCancel={() => setCommandOpen(false)}
|
||||
width={420}
|
||||
>
|
||||
<div className="command-items">
|
||||
{navigation.map(({ id, label, icon: Icon }) => (
|
||||
<button key={id} onClick={() => navigate(id)}>
|
||||
<Icon />
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button, Input, Select, Toast } from "@douyinfe/semi-ui-19";
|
||||
import { formatTime, patch } from "../api";
|
||||
import type { Account } from "../types";
|
||||
|
||||
export function WorkspacePreferences({
|
||||
account,
|
||||
onChange,
|
||||
}: {
|
||||
account: Account | null;
|
||||
onChange: () => void;
|
||||
}) {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [preferences, setPreferences] = useState({
|
||||
display_name: "研究员",
|
||||
theme: "light",
|
||||
timezone: "Asia/Shanghai",
|
||||
page_size: 25,
|
||||
});
|
||||
useEffect(() => {
|
||||
if (account)
|
||||
setPreferences({
|
||||
display_name: account.display_name,
|
||||
theme: account.theme,
|
||||
timezone: account.timezone,
|
||||
page_size: account.page_size,
|
||||
});
|
||||
}, [
|
||||
account?.display_name,
|
||||
account?.theme,
|
||||
account?.timezone,
|
||||
account?.page_size,
|
||||
]);
|
||||
if (!account) return null;
|
||||
async function save() {
|
||||
setBusy(true);
|
||||
try {
|
||||
await patch("/account/preferences", preferences);
|
||||
onChange();
|
||||
Toast.success("已保存");
|
||||
} catch (error) {
|
||||
Toast.error((error as Error).message);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<section className="workspace-preferences">
|
||||
<div className="section-heading">
|
||||
<h2>工作空间偏好</h2>
|
||||
<span className="muted">
|
||||
更新于 {formatTime(account.last_synced_at, account.timezone)}
|
||||
</span>
|
||||
</div>
|
||||
<form
|
||||
className="preferences-form"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
void save();
|
||||
}}
|
||||
>
|
||||
<div className="preferences-grid">
|
||||
<label>
|
||||
显示名称
|
||||
<Input
|
||||
aria-label="显示名称"
|
||||
value={preferences.display_name}
|
||||
onChange={(display_name) =>
|
||||
setPreferences({ ...preferences, display_name })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
主题
|
||||
<Select
|
||||
aria-label="主题"
|
||||
value={preferences.theme}
|
||||
optionList={[
|
||||
{ value: "light", label: "浅色" },
|
||||
{ value: "dark", label: "深色" },
|
||||
]}
|
||||
onChange={(theme) =>
|
||||
setPreferences({ ...preferences, theme: String(theme) })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
时区
|
||||
<Input
|
||||
aria-label="时区"
|
||||
value={preferences.timezone}
|
||||
onChange={(timezone) =>
|
||||
setPreferences({ ...preferences, timezone })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
每页数量
|
||||
<Select
|
||||
aria-label="每页数量"
|
||||
value={preferences.page_size}
|
||||
optionList={[25, 50, 100].map((value) => ({
|
||||
value,
|
||||
label: `${value} 条`,
|
||||
}))}
|
||||
onChange={(page_size) =>
|
||||
setPreferences({
|
||||
...preferences,
|
||||
page_size: Number(page_size),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<Button
|
||||
htmlType="submit"
|
||||
theme="solid"
|
||||
loading={busy}
|
||||
disabled={Boolean(busy)}
|
||||
>
|
||||
保存偏好
|
||||
</Button>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
.workspace {
|
||||
--sidebar-width: 230px;
|
||||
padding-left: var(--sidebar-width);
|
||||
transition: padding-left 200ms ease-out;
|
||||
}
|
||||
.workspace.sidebar-collapsed {
|
||||
--sidebar-width: 64px;
|
||||
}
|
||||
.app-sidebar {
|
||||
position: fixed;
|
||||
inset: 0 auto 0 0;
|
||||
z-index: 30;
|
||||
width: var(--sidebar-width);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--semi-color-bg-1);
|
||||
border-right: 1px solid var(--semi-color-border);
|
||||
color: var(--semi-color-text-1);
|
||||
transition: width 200ms ease-out;
|
||||
}
|
||||
.app-sidebar button,
|
||||
.sidebar-user-menu button,
|
||||
.command-items button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
border-radius: var(--semi-border-radius-large);
|
||||
}
|
||||
.app-sidebar button:hover,
|
||||
.sidebar-user-menu button:hover,
|
||||
.command-items button:hover {
|
||||
background: var(--semi-color-fill-0);
|
||||
color: var(--semi-color-text-0);
|
||||
}
|
||||
.app-sidebar button:focus-visible {
|
||||
outline: 2px solid var(--semi-color-primary-light-active);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.sidebar-user-row {
|
||||
height: 56px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar-user-trigger {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
.sidebar-user-trigger .semi-avatar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar-user-trigger .account-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.sidebar-command {
|
||||
padding: 0 12px 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar-command button {
|
||||
height: 36px;
|
||||
padding: 0 8px;
|
||||
width: 100%;
|
||||
border: 1px solid var(--semi-color-border);
|
||||
border-radius: var(--semi-border-radius-medium);
|
||||
color: var(--semi-color-text-2);
|
||||
}
|
||||
.sidebar-command button > span:not(.semi-icon) {
|
||||
flex: 1;
|
||||
}
|
||||
.sidebar-command kbd {
|
||||
font-size: 11px;
|
||||
line-height: 20px;
|
||||
font-family: inherit;
|
||||
padding: 0 6px;
|
||||
border: 1px solid var(--semi-color-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.sidebar-nav {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: 0 8px 8px;
|
||||
}
|
||||
.sidebar-nav-item {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
margin-bottom: 4px;
|
||||
padding: 0 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.sidebar-nav-item > span:last-child,
|
||||
.recent-link > span:last-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.sidebar-nav-item .semi-icon {
|
||||
flex-shrink: 0;
|
||||
color: var(--semi-color-text-2);
|
||||
}
|
||||
.app-sidebar .active,
|
||||
.app-sidebar .active:hover,
|
||||
.app-sidebar .active .semi-icon {
|
||||
background: var(--semi-color-primary-light-default);
|
||||
color: var(--semi-color-primary);
|
||||
}
|
||||
.sidebar-footer {
|
||||
flex-shrink: 0;
|
||||
border-top: 1px solid var(--semi-color-border);
|
||||
padding: 8px;
|
||||
}
|
||||
.recent-heading {
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: var(--semi-color-text-2);
|
||||
font-size: 12px;
|
||||
}
|
||||
.recent-heading button {
|
||||
height: 28px;
|
||||
padding: 0 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.recent-item {
|
||||
height: 36px;
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: var(--semi-border-radius-medium);
|
||||
}
|
||||
.recent-link {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 36px;
|
||||
padding: 0 8px;
|
||||
gap: 8px !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
.recent-close {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
margin-right: 4px;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
}
|
||||
.recent-item:hover .recent-close,
|
||||
.recent-close:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
.recent-close:disabled {
|
||||
cursor: default;
|
||||
opacity: 0;
|
||||
}
|
||||
.sidebar-collapse {
|
||||
margin-top: 12px;
|
||||
border-top: 1px solid var(--semi-color-border);
|
||||
padding-top: 12px;
|
||||
}
|
||||
.sidebar-collapse button {
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
padding: 0 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.is-collapsed .sidebar-user-trigger {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
.is-collapsed .sidebar-nav {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.is-collapsed .sidebar-nav-item,
|
||||
.is-collapsed .recent-link,
|
||||
.is-collapsed .sidebar-collapse button {
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
.sidebar-user-menu {
|
||||
min-width: 208px;
|
||||
max-width: 300px;
|
||||
padding: 4px;
|
||||
background: var(--semi-color-bg-3);
|
||||
box-shadow: var(--semi-shadow-elevated);
|
||||
border-radius: var(--semi-border-radius-medium);
|
||||
}
|
||||
.sidebar-user-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 12px 8px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.sidebar-user-details span {
|
||||
font-size: 12px;
|
||||
color: var(--semi-color-text-2);
|
||||
}
|
||||
.sidebar-user-menu button,
|
||||
.command-items button {
|
||||
min-height: 36px;
|
||||
width: 100%;
|
||||
padding: 0 8px;
|
||||
}
|
||||
.main-shell {
|
||||
padding-top: 36px;
|
||||
}
|
||||
.workspace-tools {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: var(--chat-space, 0px);
|
||||
z-index: 1002;
|
||||
background: var(--surface);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
height: 36px;
|
||||
flex-shrink: 0;
|
||||
padding: 2px 16px;
|
||||
}
|
||||
.workspace-tools .semi-button {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
}
|
||||
.main-shell .page-content {
|
||||
padding-top: 0;
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.workspace,
|
||||
.workspace.sidebar-collapsed {
|
||||
padding-left: 0;
|
||||
}
|
||||
.app-sidebar {
|
||||
display: none;
|
||||
}
|
||||
.workspace-tools {
|
||||
padding-right: 12px;
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.workspace,
|
||||
.app-sidebar {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
.settings-drawer .ai-settings-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
.settings-drawer .ai-settings {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-drawer .workspace-preferences {
|
||||
margin-top: 24px;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
.settings-drawer .preferences-form {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.settings-drawer .preferences-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
.settings-drawer .preferences-form > .semi-button {
|
||||
align-self: flex-start;
|
||||
}
|
||||
Reference in New Issue
Block a user