Commit 3234cfd9 authored by edy's avatar edy

Polish home empty state

parent e3f149ca
...@@ -1144,11 +1144,21 @@ export default function App() { ...@@ -1144,11 +1144,21 @@ export default function App() {
) : null; ) : null;
const panelActions = ( const panelActions = (
<> <>
<StatusChip tone={workspaceStatusTone}>{workspaceStatusLabel}</StatusChip> <StatusChip
{isMockDesktopApi ? <StatusChip tone="warning">Mock API</StatusChip> : null} tone={workspaceStatusTone}
title={"工作区状态:" + workspaceStatusLabel}
ariaLabel={"工作区状态:" + workspaceStatusLabel}
>
{workspaceStatusLabel}
</StatusChip>
{isMockDesktopApi ? (
<StatusChip tone="warning" title="当前使用模拟桌面 API" ariaLabel="当前使用模拟桌面 API">
Mock API
</StatusChip>
) : null}
</> </>
); );
const composerPlaceholder = isBound ? "" : ui.taskDisabledPlaceholder; const composerPlaceholder = isBound ? ui.taskPlaceholder : ui.taskDisabledPlaceholder;
const conversationWorkspaceProps = { const conversationWorkspaceProps = {
viewMode, viewMode,
workspaceRef: conversationWorkspaceRef, workspaceRef: conversationWorkspaceRef,
......
...@@ -5,8 +5,14 @@ export type StatusChipTone = "positive" | "warning" | "info" ...@@ -5,8 +5,14 @@ export type StatusChipTone = "positive" | "warning" | "info"
interface StatusChipProps { interface StatusChipProps {
tone: StatusChipTone tone: StatusChipTone
children: ReactNode children: ReactNode
title?: string
ariaLabel?: string
} }
export function StatusChip({ tone, children }: StatusChipProps) { export function StatusChip({ tone, children, title, ariaLabel }: StatusChipProps) {
return <span className={"status-chip " + tone}>{children}</span> return (
<span className={"status-chip " + tone} title={title} aria-label={ariaLabel}>
{children}
</span>
)
} }
...@@ -39,7 +39,7 @@ export const ui = { ...@@ -39,7 +39,7 @@ export const ui = {
expertReady: "\u5df2\u5c31\u7eea", expertReady: "\u5df2\u5c31\u7eea",
activeExpert: "\u5f53\u524d\u4e13\u5bb6", activeExpert: "\u5f53\u524d\u4e13\u5bb6",
starterQuestionsHint: "\u70b9\u51fb\u95ee\u9898\u540e\u4f1a\u5148\u586b\u5165\u8f93\u5165\u6846\uff0c\u4f60\u53ef\u4ee5\u7ee7\u7eed\u8865\u5145\u540e\u518d\u53d1\u9001\u3002", starterQuestionsHint: "\u70b9\u51fb\u95ee\u9898\u540e\u4f1a\u5148\u586b\u5165\u8f93\u5165\u6846\uff0c\u4f60\u53ef\u4ee5\u7ee7\u7eed\u8865\u5145\u540e\u518d\u53d1\u9001\u3002",
taskPlaceholder: "\u8f93\u5165\u4f60\u7684\u9700\u6c42\uff0cEnter \u53d1\u9001\uff0cShift+Enter \u6362\u884c\u3002", taskPlaceholder: "有问题,尽管问",
taskDisabledPlaceholder: "\u8bf7\u5148\u7ed1\u5b9a\u5458\u5de5\u5bc6\u94a5\u540e\u5f00\u59cb\u5bf9\u8bdd\u3002", taskDisabledPlaceholder: "\u8bf7\u5148\u7ed1\u5b9a\u5458\u5de5\u5bc6\u94a5\u540e\u5f00\u59cb\u5bf9\u8bdd\u3002",
send: "\u53d1\u9001", send: "\u53d1\u9001",
sending: "\u53d1\u9001\u4e2d...", sending: "\u53d1\u9001\u4e2d...",
...@@ -114,13 +114,28 @@ export const ui = { ...@@ -114,13 +114,28 @@ export const ui = {
export const homeChatCopy = { export const homeChatCopy = {
title: "首页对话", title: "首页对话",
microcopy: "从一个实用小任务开始:整理 Excel、汇总资料、检索信息,或把需求拆成执行清单。", microcopy: "从一个实用小任务开始:整理 Excel、汇总资料、检索信息,或把需求拆成执行清单。",
emptyTitle: " 先选一个能立刻开始的小任务", emptyTitle: "准备好了,随时开始",
// emptyDesc: "首页适合做 4 类事:Excel 整理、资料汇总、公开信息检索、需求拆解;明确要做小红书或抖音内容时,再切到对应专家继续处理",
prompts: [ prompts: [
"我想做海报内容,请帮我先整理主题、卖点层级、标题和版面文案", {
"我想做 GEO 方向内容,请先帮我明确目标、策略框架和执行重点", title: "海报内容",
"我想做平台精准线索获取,请帮我梳理目标人群、线索标准、触达话术和转化路径", description: "整理主题、卖点层级、标题和版面文案",
"我想以销售冠军的方式推进成交,请帮我梳理目标客户、沟通话术、异议处理和转化动作" prompt: "我想做海报内容,请帮我先整理主题、卖点层级、标题和版面文案"
},
{
title: "GEO 内容策略",
description: "明确目标、策略框架和执行重点",
prompt: "我想做 GEO 方向内容,请先帮我明确目标、策略框架和执行重点"
},
{
title: "精准线索获取",
description: "梳理目标人群、线索标准、触达话术和转化路径",
prompt: "我想做平台精准线索获取,请帮我梳理目标人群、线索标准、触达话术和转化路径"
},
{
title: "销售成交推进",
description: "梳理目标客户、沟通话术、异议处理和转化动作",
prompt: "我想以销售冠军的方式推进成交,请帮我梳理目标客户、沟通话术、异议处理和转化动作"
}
] ]
} as const; } as const;
...@@ -128,4 +143,3 @@ export const expertsPageCopy = { ...@@ -128,4 +143,3 @@ export const expertsPageCopy = {
title: "专家页", title: "专家页",
noExperts: "当前还没有可用专家,先在首页直接对话即可。" noExperts: "当前还没有可用专家,先在首页直接对话即可。"
} as const; } as const;
...@@ -22,6 +22,12 @@ import type { MessageTraceState } from "./useMessageTraces" ...@@ -22,6 +22,12 @@ import type { MessageTraceState } from "./useMessageTraces"
type ViewMode = "chat" | "experts" | "plugins" | "settings" | "knowledge" type ViewMode = "chat" | "experts" | "plugins" | "settings" | "knowledge"
type MessageReaction = "up" | "down" type MessageReaction = "up" | "down"
interface HomeStarterPrompt {
title: string
description: string
prompt: string
}
interface ConversationWorkspaceViewProps { interface ConversationWorkspaceViewProps {
viewMode: ViewMode viewMode: ViewMode
workspaceRef: RefObject<HTMLDivElement | null> workspaceRef: RefObject<HTMLDivElement | null>
...@@ -66,7 +72,7 @@ interface ConversationWorkspaceViewProps { ...@@ -66,7 +72,7 @@ interface ConversationWorkspaceViewProps {
noExpertsLabel: string noExpertsLabel: string
starterQuestionsHint: string starterQuestionsHint: string
homeEmptyTitle: string homeEmptyTitle: string
homePrompts: readonly string[] homePrompts: readonly HomeStarterPrompt[]
onStarterPrompt: (prompt: string) => void onStarterPrompt: (prompt: string) => void
messages: MessageListMessage[] messages: MessageListMessage[]
showEmptyState: boolean showEmptyState: boolean
...@@ -227,8 +233,10 @@ export function ConversationWorkspaceView({ ...@@ -227,8 +233,10 @@ export function ConversationWorkspaceView({
onResizePointerMove, onResizePointerMove,
onResizePointerEnd onResizePointerEnd
}: ConversationWorkspaceViewProps) { }: ConversationWorkspaceViewProps) {
const homeMicrocopyStatus = selectedSkillIsDefault ? "默认工作区" : "已切换"
const panelLead = viewMode === "chat" ? ( const panelLead = viewMode === "chat" ? (
<div className="home-microcopy" aria-label="current conversation"> <div className="home-microcopy" aria-label={`工作区,${selectedSkillBadge}`}>
<span className="home-microcopy-icon-shell" aria-hidden="true"> <span className="home-microcopy-icon-shell" aria-hidden="true">
<span className="home-microcopy-icon"> <span className="home-microcopy-icon">
{homeLeadIcon} {homeLeadIcon}
...@@ -236,10 +244,10 @@ export function ConversationWorkspaceView({ ...@@ -236,10 +244,10 @@ export function ConversationWorkspaceView({
<span className="home-microcopy-icon-beam" /> <span className="home-microcopy-icon-beam" />
</span> </span>
<span className="home-microcopy-body"> <span className="home-microcopy-body">
<span className="home-microcopy-text">当前对话</span> <strong className="home-microcopy-title">{selectedSkillBadge}</strong>
</span> </span>
<span className={"home-microcopy-tag" + (selectedSkillIsDefault ? " brand" : "")}> <span className={"home-microcopy-status" + (selectedSkillIsDefault ? " brand" : "")}>
{selectedSkillBadge} {homeMicrocopyStatus}
</span> </span>
</div> </div>
) : ( ) : (
...@@ -265,11 +273,22 @@ export function ConversationWorkspaceView({ ...@@ -265,11 +273,22 @@ export function ConversationWorkspaceView({
/> />
) : ( ) : (
<div className="empty-state home-empty-state"> <div className="empty-state home-empty-state">
<span className="empty-state-kicker">{selectedSkillBadge}</span> <div className="home-empty-copy">
<strong>{homeEmptyTitle}</strong> <strong className="home-empty-title">{homeEmptyTitle}</strong>
<div className="starter-prompt-list"> </div>
<div className="starter-prompt-list" aria-label="可选任务入口">
{homePrompts.map((item) => ( {homePrompts.map((item) => (
<button key={item} type="button" className="starter-prompt" onClick={() => onStarterPrompt(item)}>{item}</button> <button
key={item.prompt}
type="button"
className="starter-prompt"
title={item.prompt}
aria-label={item.prompt}
onClick={() => onStarterPrompt(item.prompt)}
>
<span className="starter-prompt-title">{item.title}</span>
<span className="starter-prompt-desc">{item.description}</span>
</button>
))} ))}
</div> </div>
</div> </div>
......
...@@ -21,6 +21,61 @@ ...@@ -21,6 +21,61 @@
.window-controls { .window-controls {
display: none; display: none;
position: fixed;
top: 10px;
right: 12px;
z-index: 50;
align-items: center;
gap: 6px;
padding: 4px;
border-radius: 999px;
border: 1px solid rgba(203, 213, 225, 0.78);
background: rgba(255, 255, 255, 0.86);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
}
.shell.show-window-controls .window-controls {
display: inline-flex;
}
.window-control-button {
width: 28px;
height: 28px;
display: inline-grid;
place-items: center;
flex: 0 0 auto;
padding: 0;
border: 1px solid transparent;
border-radius: 999px;
background: transparent;
color: #64748b;
transition: background 140ms ease, border-color 140ms ease, color 140ms ease, box-shadow 140ms ease;
}
.window-control-button svg {
width: 13px;
height: 13px;
display: block;
}
.window-control-button:hover {
border-color: rgba(148, 163, 184, 0.48);
background: rgba(241, 245, 249, 0.94);
color: #334155;
}
.window-control-button.close:hover {
border-color: rgba(248, 113, 113, 0.54);
background: rgba(254, 226, 226, 0.92);
color: #b91c1c;
}
.window-control-button:focus-visible {
outline: 2px solid rgba(37, 99, 235, 0.64);
outline-offset: 2px;
box-shadow: 0 0 0 4px rgba(147, 197, 253, 0.28);
} }
.conversation-shell { .conversation-shell {
......
...@@ -1262,47 +1262,98 @@ button.secondary { ...@@ -1262,47 +1262,98 @@ button.secondary {
.home-microcopy { .home-microcopy {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 10px; gap: 9px;
align-self: flex-start; align-self: flex-start;
min-height: 36px; min-height: 42px;
padding: 0 14px 0 8px; padding: 5px 8px 5px 7px;
border-radius: 999px; border-radius: 999px;
background: rgba(255, 255, 255, 0.82); border: 1px solid rgba(214, 224, 236, 0.86);
box-shadow: inset 0 0 0 1px rgba(214, 224, 236, 0.94); background: rgba(255, 255, 255, 0.84);
box-shadow:
0 8px 18px rgba(35, 52, 82, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.82);
}
.home-microcopy-icon-shell {
position: relative;
width: 28px;
height: 28px;
flex: 0 0 auto;
display: inline-grid;
place-items: center;
} }
.home-microcopy-icon { .home-microcopy-icon {
width: 24px; position: relative;
height: 24px; z-index: 1;
width: 28px;
height: 28px;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 999px; border-radius: 999px;
background: linear-gradient(135deg, rgba(255, 244, 237, 0.98), rgba(255, 250, 246, 0.94)); background: linear-gradient(135deg, rgba(239, 246, 255, 0.98), rgba(255, 255, 255, 0.94));
box-shadow: inset 0 0 0 1px rgba(191, 219, 254, 0.72);
}
.home-microcopy-icon-beam {
position: absolute;
inset: 3px -2px -2px 3px;
border-radius: 999px;
background: rgba(37, 99, 235, 0.12);
} }
.home-microcopy-icon svg { .home-microcopy-icon svg {
width: 18px; width: 19px;
height: 18px; height: 19px;
}
.home-microcopy-body {
min-width: 0;
display: grid;
align-items: center;
} }
.home-microcopy-text { .home-microcopy-title {
color: #4c6480; min-width: 0;
color: #1e293b;
font-size: 13px; font-size: 13px;
line-height: 1; font-weight: 700;
line-height: 1.1;
letter-spacing: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.home-microcopy-tag { .home-microcopy-status {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
min-height: 22px; gap: 5px;
padding: 0 9px; min-height: 20px;
padding: 0 8px;
border-radius: 999px; border-radius: 999px;
background: #f6f8fb; background: rgba(248, 250, 252, 0.88);
color: #5f738e; color: #64748b;
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
box-shadow: inset 0 0 0 1px #e3e9f0; box-shadow: inset 0 0 0 1px rgba(226, 232, 240, 0.96);
}
.home-microcopy-status::before {
content: "";
width: 6px;
height: 6px;
flex: 0 0 auto;
border-radius: 999px;
background: #60a5fa;
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.12);
}
.home-microcopy-status.brand {
color: #2563eb;
background: rgba(239, 246, 255, 0.84);
box-shadow: inset 0 0 0 1px rgba(191, 219, 254, 0.82);
} }
.empty-state { .empty-state {
...@@ -1341,8 +1392,10 @@ button.secondary { ...@@ -1341,8 +1392,10 @@ button.secondary {
} }
.home-microcopy { .home-microcopy {
flex-wrap: wrap; max-width: 100%;
line-height: 1.5;
} }
}
.home-microcopy-status {
display: none;
}
}
...@@ -553,8 +553,26 @@ ...@@ -553,8 +553,26 @@
.shell.openclaw-theme .status-chip { .shell.openclaw-theme .status-chip {
border-color: var(--revamp-border); border-color: var(--revamp-border);
background: var(--revamp-surface-soft); padding: 3px 9px;
background: rgba(248, 250, 252, 0.72);
color: var(--revamp-text-muted); color: var(--revamp-text-muted);
font-weight: 500;
box-shadow: inset 0 0 0 1px rgba(203, 213, 225, 0.7);
}
.conversation-shell .conversation-panel-actions {
gap: 6px;
}
.conversation-shell .conversation-panel-actions .status-chip {
min-height: 24px;
opacity: 0.82;
}
.conversation-shell .conversation-panel-actions .status-chip.warning {
color: #9a5f08;
background: rgba(255, 251, 235, 0.76);
box-shadow: inset 0 0 0 1px rgba(253, 230, 138, 0.68);
} }
.conversation-shell .conversation-panel-kicker { .conversation-shell .conversation-panel-kicker {
...@@ -564,11 +582,11 @@ ...@@ -564,11 +582,11 @@
} }
.conversation-shell .conversation-workspace { .conversation-shell .conversation-workspace {
padding: 18px 22px 20px; padding: 16px 18px 18px;
border-radius: 22px; border-radius: 22px;
border: 1px solid var(--revamp-border); border: 1px solid rgba(226, 232, 240, 0.82);
background: var(--revamp-surface); background: rgba(255, 255, 255, 0.66);
box-shadow: var(--revamp-shadow); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.78);
} }
.conversation-shell .message-list { .conversation-shell .message-list {
...@@ -582,6 +600,176 @@ ...@@ -582,6 +600,176 @@
scrollbar-color: rgba(148, 163, 184, 0.52) transparent; scrollbar-color: rgba(148, 163, 184, 0.52) transparent;
} }
.conversation-shell .message-list-home:has(.home-empty-state) {
align-content: center;
}
.conversation-shell .home-empty-state {
width: min(100%, 760px);
justify-self: center;
align-self: center;
display: grid;
justify-items: center;
gap: 26px;
padding: 16px 4px;
border: 0;
background: transparent;
}
.conversation-shell .home-empty-copy {
display: grid;
justify-items: center;
gap: 9px;
width: min(100%, 620px);
text-align: center;
}
.conversation-shell .home-empty-title {
color: #3b82f6;
font-size: 24px;
line-height: 1.28;
letter-spacing: 0;
background: linear-gradient(92deg, #60a5fa 0%, #7dd3fc 28%, #bfdbfe 52%, #3b82f6 76%, #60a5fa 100%);
background-size: 260% 100%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: home-title-flow 5.6s ease-in-out infinite;
text-shadow: 0 10px 24px rgba(59, 130, 246, 0.1);
}
.conversation-shell .home-empty-state .starter-prompt-list {
width: min(100%, 680px);
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.conversation-shell .home-empty-state .starter-prompt {
min-height: 90px;
position: relative;
display: grid;
align-content: start;
gap: 6px;
padding: 15px 16px 14px;
border-radius: 14px;
border-color: rgba(191, 219, 254, 0.62);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(248, 251, 255, 0.9)),
radial-gradient(circle at 16px 0, rgba(59, 130, 246, 0.1), transparent 42%);
color: var(--revamp-text);
line-height: 1.5;
overflow: hidden;
box-shadow:
0 10px 24px rgba(35, 52, 82, 0.06),
inset 0 1px 0 rgba(255, 255, 255, 0.88);
transition: border-color 140ms ease, background 140ms ease, box-shadow 140ms ease;
}
.conversation-shell .home-empty-state .starter-prompt::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 3px;
background: linear-gradient(180deg, rgba(96, 165, 250, 0.88), rgba(56, 189, 248, 0.42));
opacity: 0.72;
}
.conversation-shell .home-empty-state .starter-prompt::after {
content: "";
position: absolute;
inset: 0 0 auto 0;
height: 1px;
background: linear-gradient(90deg, rgba(147, 197, 253, 0), rgba(147, 197, 253, 0.72), rgba(147, 197, 253, 0));
opacity: 0.68;
}
.conversation-shell .home-empty-state .starter-prompt:hover {
border-color: rgba(96, 165, 250, 0.88);
background:
linear-gradient(180deg, #ffffff, rgba(248, 251, 255, 0.96)),
radial-gradient(circle at 18px 0, rgba(59, 130, 246, 0.14), transparent 44%);
box-shadow:
0 14px 28px rgba(37, 99, 235, 0.11),
inset 0 1px 0 rgba(255, 255, 255, 0.92);
}
.conversation-shell .home-empty-state .starter-prompt:focus-visible {
outline: 2px solid rgba(37, 99, 235, 0.62);
outline-offset: 3px;
box-shadow: 0 0 0 5px rgba(147, 197, 253, 0.28);
}
.conversation-shell .starter-prompt-title,
.conversation-shell .starter-prompt-desc {
display: block;
position: relative;
min-width: 0;
}
.conversation-shell .starter-prompt-title {
color: #172554;
font-size: 14px;
font-weight: 700;
}
.conversation-shell .starter-prompt-desc {
color: #64748b;
font-size: 12px;
line-height: 1.56;
}
@keyframes home-title-flow {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
@media (max-width: 720px) {
.conversation-shell .home-empty-state {
width: min(100%, 620px);
}
.conversation-shell .home-empty-state .starter-prompt-list {
grid-template-columns: 1fr;
width: min(100%, 520px);
}
}
@media (max-height: 820px) {
.conversation-shell .home-empty-state {
gap: 20px;
padding-block: 10px;
}
.conversation-shell .home-empty-copy {
gap: 7px;
}
.conversation-shell .home-empty-title {
font-size: 22px;
}
.conversation-shell .home-empty-state .starter-prompt {
min-height: 82px;
padding: 12px 14px;
}
}
@media (prefers-reduced-motion: reduce) {
.conversation-shell .home-empty-title {
animation: none;
background-position: 50% 50%;
}
}
.conversation-shell .message-card { .conversation-shell .message-card {
position: relative; position: relative;
width: 100%; width: 100%;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment