Commit 84410c3f authored by edy's avatar edy

feat(ui): align homepage showcase, expert navigation, and status chip with Windows client

- Homepage: replace starter prompts with 5 digital-employee showcase cards
  (geo/xiaohongshu/douyin/planner/wechat) that navigate to expert projects
- Status chip: change "已就绪" to "运行中" with green dot, green text, green border
- switchExpert: set viewMode after switchProject completes (safer ordering)
- manifest.json: upgrade geo & wechat from home-chat-shortcut to standalone,
  add xhs & douyin standalone entries with full projectMatchKeywords
- Resolve expert key/visual key for planner, wechat, geo, zhihu
- Add ProjectRole shared type; enrich geo expert guide with 4 default roles
- CSS: remove conversation-workspace border/background/shadow, add overflow
  visible for card hover scale, match StatusChip positive tone to Windows client
- Fix /geo/ regex to word-boundary to avoid false substring matches
- Fix activeExpertGuide roles not falling back to guide defaults
- Fix expertMatchesCategory missing wechat/公众号 in isContent regex
- Fix resolveExpertKey planner regex missing 内容账号规划 keyword
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
parent 0ddb0a2e
[ [
{ {
"id": "content-account-planning", "id": "content-account-planning",
"name": "内容账号规划专家", "name": "内容创作者",
"entryMode": "standalone", "entryMode": "standalone",
"projectMatchKeywords": ["内容账号规划", "账号规划", "content account planning"], "projectMatchKeywords": ["内容账号规划", "账号规划", "content account planning"],
"promptFile": "content-account-planning.md", "promptFile": "content-account-planning.md",
...@@ -9,12 +9,44 @@ ...@@ -9,12 +9,44 @@
}, },
{ {
"id": "zhihu", "id": "zhihu",
"name": "知乎专家", "name": "知乎策略师",
"entryMode": "standalone", "entryMode": "standalone",
"projectMatchKeywords": ["知乎", "zhihu"], "projectMatchKeywords": ["知乎", "zhihu"],
"promptFile": "zhihu.md", "promptFile": "zhihu.md",
"description": "负责知乎回答、文章、选题与知乎平台表达方式优化。" "description": "负责知乎回答、文章、选题与知乎平台表达方式优化。"
}, },
{
"id": "xhs",
"name": "小红书运营",
"entryMode": "standalone",
"projectMatchKeywords": ["小红书", "红书", "rednote", "xhs", "种草", "笔记"],
"promptFile": "xhs.md",
"description": "负责小红书笔记、图文、种草内容、选题与平台表达方式优化。"
},
{
"id": "douyin",
"name": "抖音专家",
"entryMode": "standalone",
"projectMatchKeywords": ["抖音", "douyin", "短视频", "直播", "口播", "带货"],
"promptFile": "douyin.md",
"description": "负责抖音短视频脚本、选题、带货内容与平台运营策略。"
},
{
"id": "geo",
"name": "AI推荐引擎优化",
"entryMode": "standalone",
"projectMatchKeywords": ["geo", "aeo", "生成引擎优化", "geo策略"],
"promptFile": "geo.md",
"description": "负责 GEO/AEO 策略、生成引擎优化、AI 搜索可见性与内容结构优化。"
},
{
"id": "wechat-official-account",
"name": "微信公众号运营",
"entryMode": "standalone",
"projectMatchKeywords": ["公众号", "微信公众号", "推文", "排版", "公众号文章"],
"promptFile": "wechat-official-account.md",
"description": "负责公众号选题、文章结构、排版优化与粉丝运营转化。"
},
{ {
"id": "sales-champion", "id": "sales-champion",
"name": "销售冠军", "name": "销售冠军",
...@@ -22,13 +54,6 @@ ...@@ -22,13 +54,6 @@
"description": "跳转到首页对话,继续处理销售目标拆解、成交话术、异议处理与转化推进。", "description": "跳转到首页对话,继续处理销售目标拆解、成交话术、异议处理与转化推进。",
"starterPrompt": "我想以销售冠军的方式推进成交,请帮我梳理目标客户、沟通话术、异议处理和转化动作。" "starterPrompt": "我想以销售冠军的方式推进成交,请帮我梳理目标客户、沟通话术、异议处理和转化动作。"
}, },
{
"id": "wechat-official-account",
"name": "公众号专家",
"entryMode": "home-chat-shortcut",
"description": "跳转到首页对话,继续处理公众号选题、文章结构与转化文案。",
"starterPrompt": "我想做公众号内容,请按公众号文章的写法帮我规划选题、结构和表达。"
},
{ {
"id": "x-platform", "id": "x-platform",
"name": "X专家", "name": "X专家",
...@@ -50,13 +75,6 @@ ...@@ -50,13 +75,6 @@
"description": "跳转到首页对话,继续处理海报主题、卖点提炼与文案结构。", "description": "跳转到首页对话,继续处理海报主题、卖点提炼与文案结构。",
"starterPrompt": "我想做海报内容,请帮我先整理主题、卖点层级、标题和版面文案。" "starterPrompt": "我想做海报内容,请帮我先整理主题、卖点层级、标题和版面文案。"
}, },
{
"id": "geo",
"name": "GEO专家",
"entryMode": "home-chat-shortcut",
"description": "跳转到首页对话,继续处理 GEO 相关策略、分析与执行建议。",
"starterPrompt": "我想做 GEO 方向内容,请先帮我明确目标、策略框架和执行重点。"
},
{ {
"id": "precision-leads", "id": "precision-leads",
"name": "平台精准线索专家", "name": "平台精准线索专家",
......
...@@ -94,6 +94,7 @@ import type { SmokeStreamSnapshot } from "./features/smoke/types"; ...@@ -94,6 +94,7 @@ import type { SmokeStreamSnapshot } from "./features/smoke/types";
import { import {
buildDouyinVideoStatusCard, buildDouyinVideoStatusCard,
resolveExpertKey, resolveExpertKey,
resolveExpertVisualKey,
} from "./features/experts/expertDisplay"; } from "./features/experts/expertDisplay";
import { StatusChip } from "./components/ui/StatusChip"; import { StatusChip } from "./components/ui/StatusChip";
import { import {
...@@ -1256,17 +1257,32 @@ export default function App() { ...@@ -1256,17 +1257,32 @@ export default function App() {
setPrompt(nextPrompt); setPrompt(nextPrompt);
} }
function handleExpertCardClick(visualKey: string) {
const entry = sidebarExpertEntries.find(
(entry) => resolveExpertVisualKey(entry.project, entry.definition) === visualKey
)
if (entry) {
handleExpertSelect(entry)
return
}
// Fallback: fill starter prompt if expert not found
const employee = homeChatCopy.employees.find((emp) => emp.visualKey === visualKey)
if (employee) {
applyStarterPrompt(employee.prompt)
}
}
function clearSelectedSkill() { function clearSelectedSkill() {
setSelectedSkillId(DEFAULT_SKILL.id); setSelectedSkillId(DEFAULT_SKILL.id);
setSkillMenuOpen(false); setSkillMenuOpen(false);
} }
async function switchExpert(projectId: string) { async function switchExpert(projectId: string) {
setViewMode("experts");
if (workspace?.currentProjectId === projectId && viewMode === "experts") { if (workspace?.currentProjectId === projectId && viewMode === "experts") {
return; return;
} }
await switchProject(projectId); await switchProject(projectId);
setViewMode("experts");
} }
// 处理分类弹出层中的专家选择 // 处理分类弹出层中的专家选择
...@@ -1392,7 +1408,7 @@ export default function App() { ...@@ -1392,7 +1408,7 @@ export default function App() {
noExpertsLabel: expertsPageCopy.noExperts, noExpertsLabel: expertsPageCopy.noExperts,
starterQuestionsHint: ui.starterQuestionsHint, starterQuestionsHint: ui.starterQuestionsHint,
homeEmptyTitle: homeChatCopy.emptyTitle, homeEmptyTitle: homeChatCopy.emptyTitle,
homePrompts: homeChatCopy.prompts homeShowcaseEmployees: homeChatCopy.employees
}, },
messages: { messages: {
messageListRef, messageListRef,
...@@ -1441,6 +1457,7 @@ export default function App() { ...@@ -1441,6 +1457,7 @@ export default function App() {
onContinueHomeIntent: continuePendingHomePromptInHome, onContinueHomeIntent: continuePendingHomePromptInHome,
onSwitchHomeIntent: switchExpertAndContinuePendingHomePrompt, onSwitchHomeIntent: switchExpertAndContinuePendingHomePrompt,
onStarterPrompt: applyStarterPrompt, onStarterPrompt: applyStarterPrompt,
onExpertCardClick: handleExpertCardClick,
renderThumbIcon: (direction) => <ThumbIcon direction={direction} />, renderThumbIcon: (direction) => <ThumbIcon direction={direction} />,
renderMarkdownContent, renderMarkdownContent,
buildDouyinVideoStatusCard, buildDouyinVideoStatusCard,
......
...@@ -114,27 +114,37 @@ export const ui = { ...@@ -114,27 +114,37 @@ export const ui = {
export const homeChatCopy = { export const homeChatCopy = {
title: "首页对话", title: "首页对话",
microcopy: "从一个实用小任务开始:整理 Excel、汇总资料、检索信息,或把需求拆成执行清单。", microcopy: "从一个实用小任务开始:整理 Excel、汇总资料、检索信息,或把需求拆成执行清单。",
emptyTitle: "准备好了,随时开始", emptyTitle: "选择一位数字员工,开始协作",
prompts: [ employees: [
{ {
title: "海报内容", visualKey: "geo",
description: "整理主题、卖点层级、标题和版面文案", title: "AI推荐引擎优化",
prompt: "我想做海报内容,请帮我先整理主题、卖点层级、标题和版面文案" description: "搜索与AI引擎可见性优化",
prompt: "你好,我想了解 AI推荐引擎优化 策略,请帮我梳理目标、策略框架和执行重点。"
}, },
{ {
title: "GEO 内容策略", visualKey: "xiaohongshu",
description: "明确目标、策略框架和执行重点", title: "小红书运营",
prompt: "我想做 GEO 方向内容,请先帮我明确目标、策略框架和执行重点" description: "种草笔记、爆文策划与图文",
prompt: "你好,我想做小红书内容运营,请帮我策划一篇种草笔记。"
}, },
{ {
title: "精准线索获取", visualKey: "douyin",
description: "梳理目标人群、线索标准、触达话术和转化路径", title: "抖音视频专家",
prompt: "我想做平台精准线索获取,请帮我梳理目标人群、线索标准、触达话术和转化路径" description: "短视频脚本、口播与分镜",
prompt: "你好,我想做抖音视频内容,请帮我策划短视频脚本。"
}, },
{ {
title: "销售成交推进", visualKey: "planner",
description: "梳理目标客户、沟通话术、异议处理和转化动作", title: "内容创作者",
prompt: "我想以销售冠军的方式推进成交,请帮我梳理目标客户、沟通话术、异议处理和转化动作" description: "多平台内容策划与图文创作",
prompt: "你好,我想做内容创作,请帮我策划多平台内容。"
},
{
visualKey: "wechat",
title: "微信公众号运营",
description: "公众号文章策划与涨粉策略",
prompt: "你好,我想做微信公众号运营,请帮我策划公众号内容。"
} }
] ]
} as const; } as const;
......
...@@ -28,6 +28,13 @@ interface HomeStarterPrompt { ...@@ -28,6 +28,13 @@ interface HomeStarterPrompt {
prompt: string prompt: string
} }
interface HomeShowcaseEmployee {
visualKey: ExpertVisualKey
title: string
description: string
prompt: string
}
interface ConversationWorkspaceStatusProps { interface ConversationWorkspaceStatusProps {
panelActions: ReactNode panelActions: ReactNode
showInlineStartupNotice: boolean showInlineStartupNotice: boolean
...@@ -66,7 +73,7 @@ interface ConversationWorkspaceEmptyStateProps { ...@@ -66,7 +73,7 @@ interface ConversationWorkspaceEmptyStateProps {
noExpertsLabel: string noExpertsLabel: string
starterQuestionsHint: string starterQuestionsHint: string
homeEmptyTitle: string homeEmptyTitle: string
homePrompts: readonly HomeStarterPrompt[] homeShowcaseEmployees: readonly HomeShowcaseEmployee[]
} }
interface ConversationWorkspaceMessagesProps { interface ConversationWorkspaceMessagesProps {
...@@ -118,6 +125,7 @@ interface ConversationWorkspaceActionsProps { ...@@ -118,6 +125,7 @@ interface ConversationWorkspaceActionsProps {
onContinueHomeIntent: () => void | Promise<unknown> onContinueHomeIntent: () => void | Promise<unknown>
onSwitchHomeIntent: () => void | Promise<unknown> onSwitchHomeIntent: () => void | Promise<unknown>
onStarterPrompt: (prompt: string) => void onStarterPrompt: (prompt: string) => void
onExpertCardClick: (visualKey: string) => void
renderThumbIcon: (direction: MessageReaction) => ReactNode renderThumbIcon: (direction: MessageReaction) => ReactNode
renderMarkdownContent: ( renderMarkdownContent: (
content: string, content: string,
...@@ -225,18 +233,21 @@ export function ConversationWorkspaceView({ ...@@ -225,18 +233,21 @@ export function ConversationWorkspaceView({
<div className="home-empty-copy"> <div className="home-empty-copy">
<strong className="home-empty-title">{emptyState.homeEmptyTitle}</strong> <strong className="home-empty-title">{emptyState.homeEmptyTitle}</strong>
</div> </div>
<div className="starter-prompt-list" aria-label="可选任务入口"> <div className="home-showcase-grid" aria-label="数字员工展示">
{emptyState.homePrompts.map((item) => ( {emptyState.homeShowcaseEmployees.map((emp) => (
<button <button
key={item.prompt} key={emp.visualKey}
type="button" type="button"
className="starter-prompt" className="home-showcase-card"
title={item.prompt} data-visual={emp.visualKey}
aria-label={item.prompt} onClick={() => actions.onExpertCardClick(emp.visualKey)}
onClick={() => actions.onStarterPrompt(item.prompt)} aria-label={emp.title}
title={emp.description}
> >
<span className="starter-prompt-title">{item.title}</span> <span className="home-showcase-icon" data-visual={emp.visualKey}>
<span className="starter-prompt-desc">{item.description}</span> {actions.renderExpertIcon(emp.visualKey)}
</span>
<span className="home-showcase-name">{emp.title}</span>
</button> </button>
))} ))}
</div> </div>
......
...@@ -6,7 +6,7 @@ import type { MessageTraceState } from "./useMessageTraces" ...@@ -6,7 +6,7 @@ import type { MessageTraceState } from "./useMessageTraces"
type ViewMode = "chat" | "experts" | "tasks" | "automation" | "plugins" | "settings" | "knowledge" type ViewMode = "chat" | "experts" | "tasks" | "automation" | "plugins" | "settings" | "knowledge"
type MessageReaction = "up" | "down" type MessageReaction = "up" | "down"
export type ExpertKey = "xiaohongshu" | "douyin" | "browser" | "general" export type ExpertKey = "xiaohongshu" | "douyin" | "browser" | "general" | "planner" | "wechat" | "geo" | "zhihu"
export type MessageListMessage = ChatMessage & { export type MessageListMessage = ChatMessage & {
streamState?: "streaming" | "error" streamState?: "streaming" | "error"
......
import type { ReactNode, RefObject } from "react" import type { ReactNode, RefObject } from "react"
import type { ProjectRole } from "@qjclaw/shared-types"
import { Panel } from "../../components/ui/Panel" import { Panel } from "../../components/ui/Panel"
import { ChatWorkspace } from "../chat/ChatWorkspace" import { ChatWorkspace } from "../chat/ChatWorkspace"
export type ExpertKey = "xiaohongshu" | "douyin" | "browser" | "general" export type ExpertKey = "xiaohongshu" | "douyin" | "browser" | "general" | "planner" | "wechat" | "geo" | "zhihu"
export type ExpertVisualKey = export type ExpertVisualKey =
| "xiaohongshu" | "xiaohongshu"
...@@ -40,6 +41,7 @@ export interface ExpertGuideContent { ...@@ -40,6 +41,7 @@ export interface ExpertGuideContent {
placeholder?: string placeholder?: string
prompts: string[] prompts: string[]
starterPrompts?: ExpertStarterPrompt[] starterPrompts?: ExpertStarterPrompt[]
roles?: ProjectRole[]
} }
interface ExpertsViewProps { interface ExpertsViewProps {
......
...@@ -84,7 +84,7 @@ export function ExpertCategoryIcon({ kind }: { kind: ExpertCategoryId }) { ...@@ -84,7 +84,7 @@ export function ExpertCategoryIcon({ kind }: { kind: ExpertCategoryId }) {
function expertMatchesCategory(entry: SidebarExpertEntry, categoryId: ExpertCategoryId): boolean { function expertMatchesCategory(entry: SidebarExpertEntry, categoryId: ExpertCategoryId): boolean {
const expertSeed = `${entry.definition.id} ${entry.displayName}`.toLowerCase() const expertSeed = `${entry.definition.id} ${entry.displayName}`.toLowerCase()
const isContent = /xiaohongshu|xhs|rednote|小红书|douyin|抖音|content-account|内容账号规划|zhihu|知乎/.test(expertSeed) const isContent = /xiaohongshu|xhs|rednote|小红书|douyin|抖音|content-account|内容账号规划|zhihu|知乎|wechat|公众号|微信/.test(expertSeed)
const isAcquisition = /geo|precision-leads|精准线索|线索/.test(expertSeed) const isAcquisition = /geo|precision-leads|精准线索|线索/.test(expertSeed)
const isSales = /sales-champion|销售冠军|销售/.test(expertSeed) const isSales = /sales-champion|销售冠军|销售/.test(expertSeed)
......
...@@ -34,7 +34,7 @@ export function getWorkspaceStatusLabel(chatLaunchState: ChatLaunchState, isBoun ...@@ -34,7 +34,7 @@ export function getWorkspaceStatusLabel(chatLaunchState: ChatLaunchState, isBoun
return "\u5f85\u7ed1\u5b9a" return "\u5f85\u7ed1\u5b9a"
} }
if (chatLaunchState === "ready") { if (chatLaunchState === "ready") {
return "\u5df2\u5c31\u7eea" return "\u8fd0\u884c\u4e2d"
} }
if (chatLaunchState === "error") { if (chatLaunchState === "error") {
return "\u5f02\u5e38" return "\u5f02\u5e38"
......
...@@ -69,8 +69,15 @@ export function useSidebarModel({ ...@@ -69,8 +69,15 @@ export function useSidebarModel({
}, [expertPageProjects, viewMode, visibleProjects, workspace?.currentProjectId]) }, [expertPageProjects, viewMode, visibleProjects, workspace?.currentProjectId])
const activeExpertKey = useMemo(() => resolveExpertKey(activeProject), [activeProject]) const activeExpertKey = useMemo(() => resolveExpertKey(activeProject), [activeProject])
const activeExpertVisualKey = useMemo(() => resolveExpertVisualKey(activeProject), [activeProject]) const activeExpertVisualKey = useMemo(() => resolveExpertVisualKey(activeProject), [activeProject])
const activeExpertName = useMemo(() => getProjectDisplayName(activeProject, defaultChatName), [activeProject, defaultChatName]) const activeExpertName = useMemo(() => {
const activeExpertGuide = useMemo(() => getExpertGuideContent(activeProject), [activeProject]) const entry = [...standaloneExpertEntries, ...projectExpertEntries]
.find((e) => e.project?.id === activeProject?.id)
return entry?.displayName ?? getProjectDisplayName(activeProject, defaultChatName)
}, [activeProject, defaultChatName, standaloneExpertEntries, projectExpertEntries])
const activeExpertGuide = useMemo(() => {
const guide = getExpertGuideContent(activeProject)
return { ...guide, roles: activeProject?.roles ?? guide.roles }
}, [activeProject])
return { return {
projects, projects,
......
...@@ -49,7 +49,7 @@ export const mockTaskPanelItems: TaskPanelItem[] = [ ...@@ -49,7 +49,7 @@ export const mockTaskPanelItems: TaskPanelItem[] = [
{ {
id: "mock-task-content-plan", id: "mock-task-content-plan",
date: getDefaultTaskPanelDate(), date: getDefaultTaskPanelDate(),
expertName: "内容账号规划专家", expertName: "内容创作者",
taskTitle: "整理本周选题方向与发布节奏", taskTitle: "整理本周选题方向与发布节奏",
status: "running", status: "running",
statusDetail: "正在汇总账号定位、目标人群和栏目节奏", statusDetail: "正在汇总账号定位、目标人群和栏目节奏",
...@@ -67,7 +67,7 @@ export const mockTaskPanelItems: TaskPanelItem[] = [ ...@@ -67,7 +67,7 @@ export const mockTaskPanelItems: TaskPanelItem[] = [
{ {
id: "mock-task-content-review", id: "mock-task-content-review",
date: getDefaultTaskPanelDate(), date: getDefaultTaskPanelDate(),
expertName: "内容账号规划专家", expertName: "内容创作者",
taskTitle: "复盘昨日内容表现", taskTitle: "复盘昨日内容表现",
status: "completed", status: "completed",
statusDetail: "已完成互动数据摘要与优化建议", statusDetail: "已完成互动数据摘要与优化建议",
...@@ -113,7 +113,7 @@ export const mockTaskPanelItems: TaskPanelItem[] = [ ...@@ -113,7 +113,7 @@ export const mockTaskPanelItems: TaskPanelItem[] = [
{ {
id: "mock-task-zhihu-research", id: "mock-task-zhihu-research",
date: getDefaultTaskPanelDate(), date: getDefaultTaskPanelDate(),
expertName: "知乎专家", expertName: "知乎策略师",
taskTitle: "整理竞品问答素材", taskTitle: "整理竞品问答素材",
status: "running", status: "running",
statusDetail: "正在提取高赞回答结构和关键词", statusDetail: "正在提取高赞回答结构和关键词",
......
...@@ -39,7 +39,7 @@ function emitMockChatStreamEvent(event: ChatStreamEvent) { ...@@ -39,7 +39,7 @@ function emitMockChatStreamEvent(event: ChatStreamEvent) {
const mockExpertDefinitions: ExpertDefinition[] = [ const mockExpertDefinitions: ExpertDefinition[] = [
{ {
id: "content-account-planner", id: "content-account-planner",
name: "内容账号规划专家", name: "内容创作者",
entryMode: "standalone", entryMode: "standalone",
description: "聚焦内容账号定位、栏目规划与选题节奏。", description: "聚焦内容账号定位、栏目规划与选题节奏。",
starterPrompt: "我想做一个内容账号,请先帮我梳理定位、目标人群、栏目结构和更新节奏。", starterPrompt: "我想做一个内容账号,请先帮我梳理定位、目标人群、栏目结构和更新节奏。",
...@@ -49,7 +49,7 @@ const mockExpertDefinitions: ExpertDefinition[] = [ ...@@ -49,7 +49,7 @@ const mockExpertDefinitions: ExpertDefinition[] = [
}, },
{ {
id: "zhihu", id: "zhihu",
name: "知乎专家", name: "知乎策略师",
entryMode: "standalone", entryMode: "standalone",
description: "聚焦知乎回答选题、结构设计与持续输出。", description: "聚焦知乎回答选题、结构设计与持续输出。",
starterPrompt: "我想做知乎内容,请先帮我梳理选题方向、回答结构和更新节奏。", starterPrompt: "我想做知乎内容,请先帮我梳理选题方向、回答结构和更新节奏。",
...@@ -59,10 +59,11 @@ const mockExpertDefinitions: ExpertDefinition[] = [ ...@@ -59,10 +59,11 @@ const mockExpertDefinitions: ExpertDefinition[] = [
}, },
{ {
id: "wechat-official-account", id: "wechat-official-account",
name: "公众号专家", name: "微信公众号运营",
entryMode: "home-chat-shortcut", entryMode: "standalone",
description: "从首页对话继续处理公众号选题、排版与发布需求。", description: "聚焦公众号文章策划、涨粉策略与内容运营。",
starterPrompt: "我现在要做公众号内容,请按公众号的结构和语气协助我。", starterPrompt: "我想做微信公众号运营,请先帮我梳理选题方向、文章结构和涨粉策略。",
promptFile: "wechat.md",
promptAvailable: true, promptAvailable: true,
projectMatchKeywords: ["公众号", "wechat"] projectMatchKeywords: ["公众号", "wechat"]
}, },
...@@ -95,10 +96,11 @@ const mockExpertDefinitions: ExpertDefinition[] = [ ...@@ -95,10 +96,11 @@ const mockExpertDefinitions: ExpertDefinition[] = [
}, },
{ {
id: "geo", id: "geo",
name: "GEO专家", name: "AI推荐引擎优化",
entryMode: "home-chat-shortcut", entryMode: "standalone",
description: "从首页对话继续处理 GEO 相关策略与内容。", description: "搜索与AI引擎可见性优化",
starterPrompt: "我现在要处理 GEO 相关需求,请先帮我梳理目标、约束和执行方向。", starterPrompt: "你好,我想了解 AI推荐引擎优化 策略,请帮我梳理目标、策略框架和执行重点。",
promptFile: "geo.md",
promptAvailable: true, promptAvailable: true,
projectMatchKeywords: ["geo"] projectMatchKeywords: ["geo"]
}, },
...@@ -123,10 +125,12 @@ const mockExpertDefinitions: ExpertDefinition[] = [ ...@@ -123,10 +125,12 @@ const mockExpertDefinitions: ExpertDefinition[] = [
]; ];
const mockProjects: WorkspaceSummary["projects"] = [ const mockProjects: WorkspaceSummary["projects"] = [
{ id: "content-account-planner", name: "content-account-planner", displayName: "内容账号规划专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "planner" }, { id: "content-account-planner", name: "content-account-planner", displayName: "内容创作者", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "planner" },
{ id: "zhihu", name: "zhihu-expert", displayName: "知乎专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "zhihu" }, { id: "zhihu", name: "zhihu-expert", displayName: "知乎策略师", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "zhihu" },
{ id: "xiaohongshu", name: "openclaw-xiaohongshu-skills-delivery", displayName: "小红书专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "xiaohongshu" }, { id: "xiaohongshu", name: "openclaw-xiaohongshu-skills-delivery", displayName: "小红书运营专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "xiaohongshu" },
{ id: "douyin", name: "openclaw-douyin-skills-delivery", displayName: "抖音专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "douyin" }, { id: "douyin", name: "openclaw-douyin-skills-delivery", displayName: "抖音专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "douyin" },
{ id: "wechat-official-account", name: "wechat-official-account", displayName: "微信公众号运营", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "wechat" },
{ id: "geo", name: "geo", displayName: "AI推荐引擎优化", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "geo" },
{ id: "browser-ops", name: "browser-ops", displayName: "浏览器自动化专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 3, ready: true, platform: "browser" } { id: "browser-ops", name: "browser-ops", displayName: "浏览器自动化专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 3, ready: true, platform: "browser" }
]; ];
...@@ -150,7 +154,7 @@ const mockProjectIntentSuggestions: Record<string, ProjectIntentSuggestion> = { ...@@ -150,7 +154,7 @@ const mockProjectIntentSuggestions: Record<string, ProjectIntentSuggestion> = {
xiaohongshu: { xiaohongshu: {
projectId: "xiaohongshu", projectId: "xiaohongshu",
projectName: "openclaw-xiaohongshu-skills-delivery", projectName: "openclaw-xiaohongshu-skills-delivery",
projectDisplayName: "小红书专家", projectDisplayName: "小红书运营",
score: 0.96, score: 0.96,
confidence: "high", confidence: "high",
reason: "命中了小红书内容创作与发布场景", reason: "命中了小红书内容创作与发布场景",
...@@ -173,7 +177,7 @@ let mockAutomationTasks: AutomationTask[] = [ ...@@ -173,7 +177,7 @@ let mockAutomationTasks: AutomationTask[] = [
title: "每日内容复盘", title: "每日内容复盘",
prompt: "整理昨天内容表现,并给出今天的优化建议。", prompt: "整理昨天内容表现,并给出今天的优化建议。",
expertId: "content-account-planner", expertId: "content-account-planner",
expertName: "内容账号规划专家", expertName: "内容创作者",
enabled: true, enabled: true,
schedule: { kind: "daily", time: "09:30" }, schedule: { kind: "daily", time: "09:30" },
nextRunAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(), nextRunAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
......
...@@ -1173,7 +1173,22 @@ ...@@ -1173,7 +1173,22 @@
color: #4f6965; color: #4f6965;
box-shadow: inset 0 0 0 1px rgba(214, 228, 223, 0.92); box-shadow: inset 0 0 0 1px rgba(214, 228, 223, 0.92);
} }
.status-chip.positive { background: rgba(16, 185, 129, 0.12); color: #0f7f59; } .status-chip.positive {
background: rgba(16, 185, 129, 0.14);
color: #0f7f59;
border: 1px solid rgba(16, 185, 129, 0.28);
box-shadow: none;
}
.status-chip.positive::before {
content: "";
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: #10b981;
margin-right: 6px;
flex-shrink: 0;
}
.status-chip.warning { background: rgba(245, 158, 11, 0.14); color: #b46f0a; } .status-chip.warning { background: rgba(245, 158, 11, 0.14); color: #b46f0a; }
.tabs { .tabs {
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
cursor: not-allowed; cursor: not-allowed;
} }
.shell.openclaw-theme button:hover:not(:disabled), .shell.openclaw-theme button:not(.home-showcase-card):hover:not(:disabled),
.shell.openclaw-theme .catalog-item:hover, .shell.openclaw-theme .catalog-item:hover,
.shell.openclaw-theme .plugin-flat-card:hover, .shell.openclaw-theme .plugin-flat-card:hover,
.shell.openclaw-theme .settings-section-card:hover, .shell.openclaw-theme .settings-section-card:hover,
...@@ -741,26 +741,14 @@ ...@@ -741,26 +741,14 @@
.shell.openclaw-theme .status-chip { .shell.openclaw-theme .status-chip {
border-color: var(--revamp-border); border-color: var(--revamp-border);
padding: 3px 9px; background: var(--revamp-surface-soft);
background: rgba(248, 250, 252, 0.72);
color: var(--revamp-text-muted); color: var(--revamp-text-muted);
font-weight: 500; box-shadow: none;
box-shadow: inset 0 0 0 1px rgba(203, 213, 225, 0.7);
}
.conversation-shell .conversation-panel-actions {
gap: 6px;
} }
.shell.openclaw-theme .status-chip.positive {
.conversation-shell .conversation-panel-actions .status-chip { background: rgba(16, 185, 129, 0.14);
min-height: 24px; color: #0f7f59;
opacity: 0.82; border: 1px solid rgba(16, 185, 129, 0.28);
}
.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 {
...@@ -772,14 +760,10 @@ ...@@ -772,14 +760,10 @@
.conversation-shell .conversation-workspace { .conversation-shell .conversation-workspace {
padding: 18px 20px 20px; padding: 18px 20px 20px;
border-radius: 18px; border-radius: 22px;
border: 1px solid rgba(147, 197, 253, 0.32); border: none;
background: background: transparent;
linear-gradient(180deg, rgba(255, 255, 255, 0.82), rgba(248, 251, 255, 0.74)), box-shadow: none;
var(--revamp-gradient-soft);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.86),
var(--revamp-shadow-soft);
} }
.conversation-shell .message-list { .conversation-shell .message-list {
...@@ -797,6 +781,20 @@ ...@@ -797,6 +781,20 @@
align-content: center; align-content: center;
} }
.conversation-shell :is(
.message-list-home,
.conversation-panel-body,
.conversation-workspace,
.chat-panel,
.conversation-panel,
.content-area,
.conversation-content-area,
.main-shell,
.conversation-main-layout
):has(.home-empty-state) {
overflow: visible;
}
.conversation-shell .message-list-xiaohongshu-empty:has(.expert-empty-state-xiaohongshu) { .conversation-shell .message-list-xiaohongshu-empty:has(.expert-empty-state-xiaohongshu) {
align-content: center; align-content: center;
justify-items: center; justify-items: center;
...@@ -811,7 +809,9 @@ ...@@ -811,7 +809,9 @@
gap: 26px; gap: 26px;
padding: 16px 4px; padding: 16px 4px;
border: 0; border: 0;
border-radius: 0;
background: transparent; background: transparent;
box-shadow: none;
} }
.conversation-shell .home-empty-copy { .conversation-shell .home-empty-copy {
...@@ -1022,6 +1022,146 @@ ...@@ -1022,6 +1022,146 @@
} }
} }
/* ── Home Showcase Grid (Digital Employees) ──────────────────── */
.conversation-shell .home-showcase-grid {
width: min(100%, 780px);
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 14px;
}
/* Brand colour tokens per expert */
.conversation-shell .home-showcase-card[data-visual="geo"] {
--brand: #3b82f6;
--brand-soft: rgba(59, 130, 246, 0.12);
--brand-glow: rgba(59, 130, 246, 0.18);
}
.conversation-shell .home-showcase-card[data-visual="xiaohongshu"] {
--brand: #f43f5e;
--brand-soft: rgba(244, 63, 94, 0.1);
--brand-glow: rgba(244, 63, 94, 0.16);
}
.conversation-shell .home-showcase-card[data-visual="douyin"] {
--brand: #06b6d4;
--brand-soft: rgba(6, 182, 212, 0.1);
--brand-glow: rgba(6, 182, 212, 0.16);
}
.conversation-shell .home-showcase-card[data-visual="planner"] {
--brand: #a855f7;
--brand-soft: rgba(168, 85, 247, 0.1);
--brand-glow: rgba(168, 85, 247, 0.16);
}
.conversation-shell .home-showcase-card[data-visual="wechat"] {
--brand: #22c55e;
--brand-soft: rgba(34, 197, 94, 0.1);
--brand-glow: rgba(34, 197, 94, 0.16);
}
.conversation-shell .home-showcase-card {
position: relative;
z-index: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 22px 10px;
border-radius: 24px;
border: 1px solid color-mix(in srgb, var(--brand) 18%, #cbd5e1);
background: #ffffff;
color: #243044;
text-align: center;
cursor: pointer;
transition:
transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1),
box-shadow 0.28s ease,
border-color 0.28s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}
/* Bottom micro-glow bar */
.conversation-shell .home-showcase-card::after {
content: "";
position: absolute;
bottom: 1px;
left: 0;
right: 0;
height: 2px;
border-radius: 1px;
background: linear-gradient(90deg, transparent, var(--brand), transparent);
opacity: 0.38;
transition: opacity 0.3s ease, transform 0.3s ease;
transform: scaleX(0.52);
transform-origin: center;
}
.conversation-shell .home-showcase-card:hover {
transform: scale(1.2);
z-index: 10;
border-color: color-mix(in srgb, var(--brand) 52%, transparent);
background: #ffffff;
box-shadow: 0 16px 40px var(--brand-glow), 0 0 0 1px var(--brand-soft);
}
.conversation-shell .home-showcase-card:hover::after {
opacity: 0.72;
transform: scaleX(0.76);
}
.conversation-shell .home-showcase-card:focus-visible {
outline: 2px solid var(--brand);
outline-offset: 3px;
box-shadow: 0 0 0 5px var(--brand-soft);
}
/* ── Icon ──────────────────────────────────────────────────── */
.conversation-shell .home-showcase-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 16px;
background: linear-gradient(135deg, rgba(239, 246, 255, 0.96), rgba(219, 234, 254, 0.9));
transition: box-shadow 0.28s ease;
}
.conversation-shell .home-showcase-icon svg {
width: 28px;
height: 28px;
}
/* Per-brand icon backgrounds */
.conversation-shell .home-showcase-card[data-visual="xiaohongshu"] .home-showcase-icon {
background: linear-gradient(135deg, rgba(255, 241, 242, 0.97), rgba(255, 228, 230, 0.92));
}
.conversation-shell .home-showcase-card[data-visual="douyin"] .home-showcase-icon {
background: linear-gradient(135deg, rgba(236, 254, 255, 0.97), rgba(207, 250, 254, 0.92));
}
.conversation-shell .home-showcase-card[data-visual="wechat"] .home-showcase-icon {
background: linear-gradient(135deg, rgba(240, 253, 244, 0.97), rgba(220, 252, 231, 0.92));
}
.conversation-shell .home-showcase-card[data-visual="planner"] .home-showcase-icon {
background: linear-gradient(135deg, rgba(245, 243, 255, 0.97), rgba(237, 233, 254, 0.92));
}
/* Icon glow on card hover */
.conversation-shell .home-showcase-card:hover .home-showcase-icon {
box-shadow: 0 0 18px var(--brand-glow);
}
/* ── Name ──────────────────────────────────────────────────── */
.conversation-shell .home-showcase-name {
font-size: 13px;
font-weight: 600;
color: #1e293b;
line-height: 1.3;
letter-spacing: -0.01em;
}
/* ─────────────────────────────────────────────────────────── */
@media (max-width: 720px) { @media (max-width: 720px) {
.conversation-shell .home-empty-state { .conversation-shell .home-empty-state {
width: min(100%, 620px); width: min(100%, 620px);
...@@ -1032,6 +1172,15 @@ ...@@ -1032,6 +1172,15 @@
grid-template-columns: 1fr; grid-template-columns: 1fr;
width: min(100%, 520px); width: min(100%, 520px);
} }
.conversation-shell .home-showcase-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.conversation-shell .home-showcase-card {
padding: 18px 8px;
}
} }
@media (max-height: 820px) { @media (max-height: 820px) {
...@@ -1066,6 +1215,10 @@ ...@@ -1066,6 +1215,10 @@
min-height: 84px; min-height: 84px;
padding: 12px 14px; padding: 12px 14px;
} }
.conversation-shell .home-showcase-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
......
...@@ -383,6 +383,14 @@ export interface ProjectPackageConfig { ...@@ -383,6 +383,14 @@ export interface ProjectPackageConfig {
entries: ProjectPackageEntry[]; entries: ProjectPackageEntry[];
} }
export interface ProjectRole {
id: string;
name: string;
icon: string;
skillIds: string[];
description: string;
}
export interface ProjectSummary { export interface ProjectSummary {
id: string; id: string;
name: string; name: string;
...@@ -397,6 +405,7 @@ export interface ProjectSummary { ...@@ -397,6 +405,7 @@ export interface ProjectSummary {
platform?: string; platform?: string;
defaultEntryId?: string; defaultEntryId?: string;
defaultEntryType?: ProjectPackageEntryType; defaultEntryType?: ProjectPackageEntryType;
roles?: ProjectRole[];
} }
export interface ProjectIntentSuggestion { export interface ProjectIntentSuggestion {
......
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