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",
"name": "内容账号规划专家",
"name": "内容创作者",
"entryMode": "standalone",
"projectMatchKeywords": ["内容账号规划", "账号规划", "content account planning"],
"promptFile": "content-account-planning.md",
......@@ -9,12 +9,44 @@
},
{
"id": "zhihu",
"name": "知乎专家",
"name": "知乎策略师",
"entryMode": "standalone",
"projectMatchKeywords": ["知乎", "zhihu"],
"promptFile": "zhihu.md",
"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",
"name": "销售冠军",
......@@ -22,13 +54,6 @@
"description": "跳转到首页对话,继续处理销售目标拆解、成交话术、异议处理与转化推进。",
"starterPrompt": "我想以销售冠军的方式推进成交,请帮我梳理目标客户、沟通话术、异议处理和转化动作。"
},
{
"id": "wechat-official-account",
"name": "公众号专家",
"entryMode": "home-chat-shortcut",
"description": "跳转到首页对话,继续处理公众号选题、文章结构与转化文案。",
"starterPrompt": "我想做公众号内容,请按公众号文章的写法帮我规划选题、结构和表达。"
},
{
"id": "x-platform",
"name": "X专家",
......@@ -50,13 +75,6 @@
"description": "跳转到首页对话,继续处理海报主题、卖点提炼与文案结构。",
"starterPrompt": "我想做海报内容,请帮我先整理主题、卖点层级、标题和版面文案。"
},
{
"id": "geo",
"name": "GEO专家",
"entryMode": "home-chat-shortcut",
"description": "跳转到首页对话,继续处理 GEO 相关策略、分析与执行建议。",
"starterPrompt": "我想做 GEO 方向内容,请先帮我明确目标、策略框架和执行重点。"
},
{
"id": "precision-leads",
"name": "平台精准线索专家",
......
......@@ -94,6 +94,7 @@ import type { SmokeStreamSnapshot } from "./features/smoke/types";
import {
buildDouyinVideoStatusCard,
resolveExpertKey,
resolveExpertVisualKey,
} from "./features/experts/expertDisplay";
import { StatusChip } from "./components/ui/StatusChip";
import {
......@@ -1256,17 +1257,32 @@ export default function App() {
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() {
setSelectedSkillId(DEFAULT_SKILL.id);
setSkillMenuOpen(false);
}
async function switchExpert(projectId: string) {
setViewMode("experts");
if (workspace?.currentProjectId === projectId && viewMode === "experts") {
return;
}
await switchProject(projectId);
setViewMode("experts");
}
// 处理分类弹出层中的专家选择
......@@ -1392,7 +1408,7 @@ export default function App() {
noExpertsLabel: expertsPageCopy.noExperts,
starterQuestionsHint: ui.starterQuestionsHint,
homeEmptyTitle: homeChatCopy.emptyTitle,
homePrompts: homeChatCopy.prompts
homeShowcaseEmployees: homeChatCopy.employees
},
messages: {
messageListRef,
......@@ -1441,6 +1457,7 @@ export default function App() {
onContinueHomeIntent: continuePendingHomePromptInHome,
onSwitchHomeIntent: switchExpertAndContinuePendingHomePrompt,
onStarterPrompt: applyStarterPrompt,
onExpertCardClick: handleExpertCardClick,
renderThumbIcon: (direction) => <ThumbIcon direction={direction} />,
renderMarkdownContent,
buildDouyinVideoStatusCard,
......
......@@ -114,27 +114,37 @@ export const ui = {
export const homeChatCopy = {
title: "首页对话",
microcopy: "从一个实用小任务开始:整理 Excel、汇总资料、检索信息,或把需求拆成执行清单。",
emptyTitle: "准备好了,随时开始",
prompts: [
emptyTitle: "选择一位数字员工,开始协作",
employees: [
{
title: "海报内容",
description: "整理主题、卖点层级、标题和版面文案",
prompt: "我想做海报内容,请帮我先整理主题、卖点层级、标题和版面文案"
visualKey: "geo",
title: "AI推荐引擎优化",
description: "搜索与AI引擎可见性优化",
prompt: "你好,我想了解 AI推荐引擎优化 策略,请帮我梳理目标、策略框架和执行重点。"
},
{
title: "GEO 内容策略",
description: "明确目标、策略框架和执行重点",
prompt: "我想做 GEO 方向内容,请先帮我明确目标、策略框架和执行重点"
visualKey: "xiaohongshu",
title: "小红书运营",
description: "种草笔记、爆文策划与图文",
prompt: "你好,我想做小红书内容运营,请帮我策划一篇种草笔记。"
},
{
title: "精准线索获取",
description: "梳理目标人群、线索标准、触达话术和转化路径",
prompt: "我想做平台精准线索获取,请帮我梳理目标人群、线索标准、触达话术和转化路径"
visualKey: "douyin",
title: "抖音视频专家",
description: "短视频脚本、口播与分镜",
prompt: "你好,我想做抖音视频内容,请帮我策划短视频脚本。"
},
{
title: "销售成交推进",
description: "梳理目标客户、沟通话术、异议处理和转化动作",
prompt: "我想以销售冠军的方式推进成交,请帮我梳理目标客户、沟通话术、异议处理和转化动作"
visualKey: "planner",
title: "内容创作者",
description: "多平台内容策划与图文创作",
prompt: "你好,我想做内容创作,请帮我策划多平台内容。"
},
{
visualKey: "wechat",
title: "微信公众号运营",
description: "公众号文章策划与涨粉策略",
prompt: "你好,我想做微信公众号运营,请帮我策划公众号内容。"
}
]
} as const;
......
......@@ -28,6 +28,13 @@ interface HomeStarterPrompt {
prompt: string
}
interface HomeShowcaseEmployee {
visualKey: ExpertVisualKey
title: string
description: string
prompt: string
}
interface ConversationWorkspaceStatusProps {
panelActions: ReactNode
showInlineStartupNotice: boolean
......@@ -66,7 +73,7 @@ interface ConversationWorkspaceEmptyStateProps {
noExpertsLabel: string
starterQuestionsHint: string
homeEmptyTitle: string
homePrompts: readonly HomeStarterPrompt[]
homeShowcaseEmployees: readonly HomeShowcaseEmployee[]
}
interface ConversationWorkspaceMessagesProps {
......@@ -118,6 +125,7 @@ interface ConversationWorkspaceActionsProps {
onContinueHomeIntent: () => void | Promise<unknown>
onSwitchHomeIntent: () => void | Promise<unknown>
onStarterPrompt: (prompt: string) => void
onExpertCardClick: (visualKey: string) => void
renderThumbIcon: (direction: MessageReaction) => ReactNode
renderMarkdownContent: (
content: string,
......@@ -225,18 +233,21 @@ export function ConversationWorkspaceView({
<div className="home-empty-copy">
<strong className="home-empty-title">{emptyState.homeEmptyTitle}</strong>
</div>
<div className="starter-prompt-list" aria-label="可选任务入口">
{emptyState.homePrompts.map((item) => (
<div className="home-showcase-grid" aria-label="数字员工展示">
{emptyState.homeShowcaseEmployees.map((emp) => (
<button
key={item.prompt}
key={emp.visualKey}
type="button"
className="starter-prompt"
title={item.prompt}
aria-label={item.prompt}
onClick={() => actions.onStarterPrompt(item.prompt)}
className="home-showcase-card"
data-visual={emp.visualKey}
onClick={() => actions.onExpertCardClick(emp.visualKey)}
aria-label={emp.title}
title={emp.description}
>
<span className="starter-prompt-title">{item.title}</span>
<span className="starter-prompt-desc">{item.description}</span>
<span className="home-showcase-icon" data-visual={emp.visualKey}>
{actions.renderExpertIcon(emp.visualKey)}
</span>
<span className="home-showcase-name">{emp.title}</span>
</button>
))}
</div>
......
......@@ -6,7 +6,7 @@ import type { MessageTraceState } from "./useMessageTraces"
type ViewMode = "chat" | "experts" | "tasks" | "automation" | "plugins" | "settings" | "knowledge"
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 & {
streamState?: "streaming" | "error"
......
import type { ReactNode, RefObject } from "react"
import type { ProjectRole } from "@qjclaw/shared-types"
import { Panel } from "../../components/ui/Panel"
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 =
| "xiaohongshu"
......@@ -40,6 +41,7 @@ export interface ExpertGuideContent {
placeholder?: string
prompts: string[]
starterPrompts?: ExpertStarterPrompt[]
roles?: ProjectRole[]
}
interface ExpertsViewProps {
......
import type { ExpertDefinition } from "@qjclaw/shared-types"
import type { ExpertDefinition, ProjectRole } from "@qjclaw/shared-types"
import type { MessageListMessage } from "../chat/MessageList"
import type { ExpertGuideContent, ExpertKey, ExpertStarterPrompt } from "./ExpertsView"
import type { ExpertProject, ExpertVisualKey } from "../shell/ExpertTree"
......@@ -11,24 +11,24 @@ export interface VideoStatusCardContent {
const XIAOHONGSHU_STARTER_PROMPTS: ExpertStarterPrompt[] = [
{
title: "帮我做一篇推荐平价火锅的笔记",
description: "适合餐饮探店、门店种草和收藏型内容。",
prompt: "帮我做一篇推荐平价火锅的笔记"
title: "推荐平价火锅笔记",
description: "适合门店探店、团购引流和高性价比种草。",
prompt: "帮我做一篇推荐平价火锅的笔记,面向周末聚餐的年轻人,突出人均价格、招牌菜、环境氛围和适合拍照的点,给我标题、正文结构、封面文案和配图建议。"
},
{
title: "做一篇制作抹茶奶酪欧包教程的爆文",
description: "适合教程结构、标题钩子和步骤拆解。",
prompt: "做一篇制作抹茶奶酪欧包教程的爆文"
title: "抹茶奶酪欧包教程爆文",
description: "把教程步骤改成更容易收藏转发的笔记。",
prompt: "做一篇制作抹茶奶酪欧包教程的爆文,面向烘焙新手,突出材料清单、关键步骤、失败避坑和成品口感,给我小红书标题、正文、标签和封面文案。"
},
{
title: "给通勤女生做一篇春季穿搭笔记",
description: "适合人群细分、场景搭配和封面建议。",
prompt: "给通勤女生做一篇春季穿搭笔记"
title: "通勤女生春季穿搭笔记",
description: "围绕职场通勤、显瘦配色和一周搭配展开。",
prompt: "给通勤女生做一篇春季穿搭笔记,面向 25-35 岁职场女性,突出显瘦、舒适、适合办公室和约会切换,给我 5 个标题、正文结构、单品搭配和拍摄建议。"
},
{
title: "做一篇针对年轻女性推荐防晒霜的爆款图文",
description: "适合美妆护肤种草、卖点提炼和图文结构。",
prompt: "做一篇针对年轻女性推荐防晒霜的爆款图文"
title: "年轻女性防晒霜爆款图文",
description: "适合护肤品卖点拆解、场景种草和转化。",
prompt: "帮我策划一篇年轻女性防晒霜爆款图文笔记,面向 20-30 岁通勤和户外场景用户,突出肤感、防晒力、妆前适配和补涂体验,给我标题、正文、封面文案、配图脚本和标签。"
}
]
......@@ -45,9 +45,24 @@ export function resolveExpertKey(project: ExpertProject | undefined): ExpertKey
if (/xiaohongshu|xhs|rednote|小红书/.test(seed)) {
return "xiaohongshu"
}
if (/douyin|tiktok|抖音/.test(seed)) {
if (/douyin|抖音/.test(seed)) {
return "douyin"
}
if (/tiktok/.test(seed)) {
return "douyin"
}
if (/wechat|weixin|公众号|微信/.test(seed)) {
return "wechat"
}
if (/zhihu|知乎/.test(seed)) {
return "zhihu"
}
if (/content-account|planner|账号规划|内容账号规划|内容创作/.test(seed)) {
return "planner"
}
if (/(^|[\s-])geo($|[\s-])/.test(seed)) {
return "geo"
}
if (/browser|automation|chrome|playwright|web|浏览器|自动化/.test(seed)) {
return "browser"
}
......@@ -75,7 +90,7 @@ export function resolveExpertVisualKey(project?: ExpertProject, definition?: Exp
if (/zhihu|知乎/.test(seed)) {
return "zhihu"
}
if (/content-account|planner|账号规划|内容账号规划/.test(seed)) {
if (/content-account|planner|账号规划|内容账号规划|内容创作/.test(seed)) {
return "planner"
}
if (/precision-leads|线索|lead/.test(seed)) {
......@@ -90,7 +105,7 @@ export function resolveExpertVisualKey(project?: ExpertProject, definition?: Exp
if (/(^|[\s-])x($|[\s-])|twitter/.test(seed)) {
return "x"
}
if (/geo/.test(seed)) {
if (/(^|[\s-])geo($|[\s-])/.test(seed)) {
return "geo"
}
if (/browser|automation|chrome|playwright|web|浏览器|自动化/.test(seed)) {
......@@ -126,9 +141,9 @@ function getExpertGuide(project: ExpertProject | undefined): ExpertGuideContent
case "xiaohongshu":
return {
greeting: "准备好了,开始种草",
summary: "说清产品、场景、人群和目标,我会先拆选题、标题、笔记结构和配图方向。",
prompts: XIAOHONGSHU_STARTER_PROMPTS.map((item) => item.prompt),
starterPrompts: XIAOHONGSHU_STARTER_PROMPTS
summary: "",
starterPrompts: XIAOHONGSHU_STARTER_PROMPTS,
prompts: XIAOHONGSHU_STARTER_PROMPTS.map((item) => item.prompt)
}
case "douyin":
return {
......@@ -141,6 +156,62 @@ function getExpertGuide(project: ExpertProject | undefined): ExpertGuideContent
"分析这个选题为什么更适合抖音而不是小红书。"
]
}
case "planner":
return {
greeting: "今天想创作什么内容?",
summary: "说清目标、平台和受众,我帮你拆成可执行的创作方案。",
intro: "最好一次说明主题、面向谁、想达到什么目标、偏好什么形式和风格。",
requirementChecklist: ["主题", "平台", "受众", "目标", "形式", "风格"],
prompts: [
"帮我策划一周的多平台内容日历",
"把一个产品卖点改成不同平台的种草内容",
"针对25-35岁职场女性策划一个内容IP定位"
]
}
case "wechat":
return {
greeting: "来,一起打磨一篇好文章",
summary: "从选题到涨粉,我帮你把公众号内容体系跑通。",
intro: "公众号文章需要兼顾打开率、完读率和转化。你可以直接说选题方向,我会帮你补全框架、标题和结构。",
requirementChecklist: ["选题方向", "文章类型", "目标读者", "核心信息", "期望效果"],
workflowSteps: ["选题规划与内容日历", "标题优化(提高打开率)", "文章结构梳理与排版建议", "涨粉策略与粉丝运营"],
prompts: [
"帮我策划一篇公众号科普长文,主题是XXX",
"分析我这个公众号的选题方向,给出优化建议",
"给我5个能涨粉的互动活动方案"
]
}
case "geo":
return {
greeting: "让 AI 搜索引擎主动引用你的品牌",
summary: "",
intro: "告诉我你的品牌/产品、核心关键词和目标平台(百度AI搜索/DeepSeek/豆包),我会先让诊断师评估你的AI可见性现状。",
requirementChecklist: ["品牌/产品", "目标平台", "核心关键词", "竞品参考"],
roles: [
{ id: "diagnostician", name: "诊断师", icon: "search", skillIds: ["geo-diagnostician"], description: "品牌AI可见性评估与信息一致性审计" },
{ id: "strategist", name: "策略师", icon: "lightbulb", skillIds: ["geo-strategist"], description: "关键词策略与渠道矩阵" },
{ id: "content-creator", name: "内容师", icon: "pen", skillIds: ["geo-content-creator"], description: "AI优化内容创作与效力自检" },
{ id: "tech-advisor", name: "技术顾问", icon: "settings", skillIds: ["geo-tech-advisor"], description: "Schema部署与监测体系" },
] satisfies ProjectRole[],
prompts: [
"帮我诊断瑞幸咖啡在百度AI中的AI可见性,竞品星巴克",
"为茶百道制定GEO优化策略,目标平台豆包",
"对比一下霸王茶姬、茶颜悦色和奈雪的茶,在豆包中被推荐的差异"
]
}
case "zhihu":
return {
greeting: "在知乎,专业是最好的流量",
summary: "帮你选题、写回答、建人设,把专业积累变成影响力。",
intro: "知乎用户看重专业深度和真实经验。告诉我你的专业领域和人设目标,我来帮你策划内容策略。",
requirementChecklist: ["专业领域", "人设定位", "目标受众", "内容类型", "更新频率"],
workflowSteps: ["人设定位:你希望被记住的专业标签是什么?", "选题策略:追踪热点问题 vs 深耕垂直领域", "回答结构:开篇抓人 → 专业拆解 → 金句收尾"],
prompts: [
"帮我找5个XX领域的高关注问题,写深度回答",
"给我设计一个知乎个人主页的专业定位方案",
"把这篇公众号文章改写成知乎回答风格"
]
}
case "browser":
return {
greeting: "适合处理浏览器自动化、采集、表单填写和发布流程设计。",
......@@ -171,9 +242,9 @@ export function getExpertGuideContent(project: ExpertProject | undefined): Exper
}
return {
greeting: "先给我一个视频目标,我来生成文案和分镜预览。",
summary: "",
intro: "补齐这些信息后,我会先出预览,不会直接开跑视频。",
greeting: "先把这条抖音视频的条件说清楚,我会先帮你整理需求并给出预览。",
summary: "适合抖音视频需求补全、文案与分镜预览、数字人口播和纯画面路线判断。",
intro: "最好一次说明主题、给谁看、想达到什么目标、风格、时长、做数字人还是纯画面,以及现在有没有图片或音频。",
requirementChecklist: [
"主题",
"人群",
......@@ -186,12 +257,12 @@ export function getExpertGuideContent(project: ExpertProject | undefined): Exper
routeOptions: [
{
title: "数字人口播",
detail: "适合讲解、产品说明和知识类内容。继续前确认人物图片和配音方式。",
detail: "适合数字人口播、知识讲解、产品说明。继续生成前通常要确认人物图片,以及现成音频或男声/女声配音。",
accent: "host"
},
{
title: "纯画面展示",
detail: "适合氛围片、空镜、场景展示和旁白视频。先确认秒数和画面风格。",
detail: "适合氛围片、空镜、场景展示、画面加旁白。先把视频秒数说清楚,再出文案和分镜预览。",
accent: "visual"
}
],
......
......@@ -84,7 +84,7 @@ export function ExpertCategoryIcon({ kind }: { kind: ExpertCategoryId }) {
function expertMatchesCategory(entry: SidebarExpertEntry, categoryId: ExpertCategoryId): boolean {
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 isSales = /sales-champion|销售冠军|销售/.test(expertSeed)
......
......@@ -34,7 +34,7 @@ export function getWorkspaceStatusLabel(chatLaunchState: ChatLaunchState, isBoun
return "\u5f85\u7ed1\u5b9a"
}
if (chatLaunchState === "ready") {
return "\u5df2\u5c31\u7eea"
return "\u8fd0\u884c\u4e2d"
}
if (chatLaunchState === "error") {
return "\u5f02\u5e38"
......
......@@ -69,8 +69,15 @@ export function useSidebarModel({
}, [expertPageProjects, viewMode, visibleProjects, workspace?.currentProjectId])
const activeExpertKey = useMemo(() => resolveExpertKey(activeProject), [activeProject])
const activeExpertVisualKey = useMemo(() => resolveExpertVisualKey(activeProject), [activeProject])
const activeExpertName = useMemo(() => getProjectDisplayName(activeProject, defaultChatName), [activeProject, defaultChatName])
const activeExpertGuide = useMemo(() => getExpertGuideContent(activeProject), [activeProject])
const activeExpertName = useMemo(() => {
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 {
projects,
......
......@@ -49,7 +49,7 @@ export const mockTaskPanelItems: TaskPanelItem[] = [
{
id: "mock-task-content-plan",
date: getDefaultTaskPanelDate(),
expertName: "内容账号规划专家",
expertName: "内容创作者",
taskTitle: "整理本周选题方向与发布节奏",
status: "running",
statusDetail: "正在汇总账号定位、目标人群和栏目节奏",
......@@ -67,7 +67,7 @@ export const mockTaskPanelItems: TaskPanelItem[] = [
{
id: "mock-task-content-review",
date: getDefaultTaskPanelDate(),
expertName: "内容账号规划专家",
expertName: "内容创作者",
taskTitle: "复盘昨日内容表现",
status: "completed",
statusDetail: "已完成互动数据摘要与优化建议",
......@@ -113,7 +113,7 @@ export const mockTaskPanelItems: TaskPanelItem[] = [
{
id: "mock-task-zhihu-research",
date: getDefaultTaskPanelDate(),
expertName: "知乎专家",
expertName: "知乎策略师",
taskTitle: "整理竞品问答素材",
status: "running",
statusDetail: "正在提取高赞回答结构和关键词",
......
......@@ -39,7 +39,7 @@ function emitMockChatStreamEvent(event: ChatStreamEvent) {
const mockExpertDefinitions: ExpertDefinition[] = [
{
id: "content-account-planner",
name: "内容账号规划专家",
name: "内容创作者",
entryMode: "standalone",
description: "聚焦内容账号定位、栏目规划与选题节奏。",
starterPrompt: "我想做一个内容账号,请先帮我梳理定位、目标人群、栏目结构和更新节奏。",
......@@ -49,7 +49,7 @@ const mockExpertDefinitions: ExpertDefinition[] = [
},
{
id: "zhihu",
name: "知乎专家",
name: "知乎策略师",
entryMode: "standalone",
description: "聚焦知乎回答选题、结构设计与持续输出。",
starterPrompt: "我想做知乎内容,请先帮我梳理选题方向、回答结构和更新节奏。",
......@@ -59,10 +59,11 @@ const mockExpertDefinitions: ExpertDefinition[] = [
},
{
id: "wechat-official-account",
name: "公众号专家",
entryMode: "home-chat-shortcut",
description: "从首页对话继续处理公众号选题、排版与发布需求。",
starterPrompt: "我现在要做公众号内容,请按公众号的结构和语气协助我。",
name: "微信公众号运营",
entryMode: "standalone",
description: "聚焦公众号文章策划、涨粉策略与内容运营。",
starterPrompt: "我想做微信公众号运营,请先帮我梳理选题方向、文章结构和涨粉策略。",
promptFile: "wechat.md",
promptAvailable: true,
projectMatchKeywords: ["公众号", "wechat"]
},
......@@ -95,10 +96,11 @@ const mockExpertDefinitions: ExpertDefinition[] = [
},
{
id: "geo",
name: "GEO专家",
entryMode: "home-chat-shortcut",
description: "从首页对话继续处理 GEO 相关策略与内容。",
starterPrompt: "我现在要处理 GEO 相关需求,请先帮我梳理目标、约束和执行方向。",
name: "AI推荐引擎优化",
entryMode: "standalone",
description: "搜索与AI引擎可见性优化",
starterPrompt: "你好,我想了解 AI推荐引擎优化 策略,请帮我梳理目标、策略框架和执行重点。",
promptFile: "geo.md",
promptAvailable: true,
projectMatchKeywords: ["geo"]
},
......@@ -123,10 +125,12 @@ const mockExpertDefinitions: ExpertDefinition[] = [
];
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: "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: "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: "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: "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" }
];
......@@ -150,7 +154,7 @@ const mockProjectIntentSuggestions: Record<string, ProjectIntentSuggestion> = {
xiaohongshu: {
projectId: "xiaohongshu",
projectName: "openclaw-xiaohongshu-skills-delivery",
projectDisplayName: "小红书专家",
projectDisplayName: "小红书运营",
score: 0.96,
confidence: "high",
reason: "命中了小红书内容创作与发布场景",
......@@ -173,7 +177,7 @@ let mockAutomationTasks: AutomationTask[] = [
title: "每日内容复盘",
prompt: "整理昨天内容表现,并给出今天的优化建议。",
expertId: "content-account-planner",
expertName: "内容账号规划专家",
expertName: "内容创作者",
enabled: true,
schedule: { kind: "daily", time: "09:30" },
nextRunAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
......
......@@ -1173,7 +1173,22 @@
color: #4f6965;
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; }
.tabs {
......
......@@ -35,7 +35,7 @@
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 .plugin-flat-card:hover,
.shell.openclaw-theme .settings-section-card:hover,
......@@ -741,26 +741,14 @@
.shell.openclaw-theme .status-chip {
border-color: var(--revamp-border);
padding: 3px 9px;
background: rgba(248, 250, 252, 0.72);
background: var(--revamp-surface-soft);
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;
box-shadow: none;
}
.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);
.shell.openclaw-theme .status-chip.positive {
background: rgba(16, 185, 129, 0.14);
color: #0f7f59;
border: 1px solid rgba(16, 185, 129, 0.28);
}
.conversation-shell .conversation-panel-kicker {
......@@ -772,14 +760,10 @@
.conversation-shell .conversation-workspace {
padding: 18px 20px 20px;
border-radius: 18px;
border: 1px solid rgba(147, 197, 253, 0.32);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.82), rgba(248, 251, 255, 0.74)),
var(--revamp-gradient-soft);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.86),
var(--revamp-shadow-soft);
border-radius: 22px;
border: none;
background: transparent;
box-shadow: none;
}
.conversation-shell .message-list {
......@@ -797,6 +781,20 @@
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) {
align-content: center;
justify-items: center;
......@@ -811,7 +809,9 @@
gap: 26px;
padding: 16px 4px;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.conversation-shell .home-empty-copy {
......@@ -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) {
.conversation-shell .home-empty-state {
width: min(100%, 620px);
......@@ -1032,6 +1172,15 @@
grid-template-columns: 1fr;
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) {
......@@ -1066,6 +1215,10 @@
min-height: 84px;
padding: 12px 14px;
}
.conversation-shell .home-showcase-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (prefers-reduced-motion: reduce) {
......
......@@ -383,6 +383,14 @@ export interface ProjectPackageConfig {
entries: ProjectPackageEntry[];
}
export interface ProjectRole {
id: string;
name: string;
icon: string;
skillIds: string[];
description: string;
}
export interface ProjectSummary {
id: string;
name: string;
......@@ -397,6 +405,7 @@ export interface ProjectSummary {
platform?: string;
defaultEntryId?: string;
defaultEntryType?: ProjectPackageEntryType;
roles?: ProjectRole[];
}
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