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 {
......
import type { ExpertDefinition } from "@qjclaw/shared-types" import type { ExpertDefinition, ProjectRole } from "@qjclaw/shared-types"
import type { MessageListMessage } from "../chat/MessageList" import type { MessageListMessage } from "../chat/MessageList"
import type { ExpertGuideContent, ExpertKey, ExpertStarterPrompt } from "./ExpertsView" import type { ExpertGuideContent, ExpertKey, ExpertStarterPrompt } from "./ExpertsView"
import type { ExpertProject, ExpertVisualKey } from "../shell/ExpertTree" import type { ExpertProject, ExpertVisualKey } from "../shell/ExpertTree"
...@@ -11,24 +11,24 @@ export interface VideoStatusCardContent { ...@@ -11,24 +11,24 @@ export interface VideoStatusCardContent {
const XIAOHONGSHU_STARTER_PROMPTS: ExpertStarterPrompt[] = [ const XIAOHONGSHU_STARTER_PROMPTS: ExpertStarterPrompt[] = [
{ {
title: "帮我做一篇推荐平价火锅的笔记", title: "推荐平价火锅笔记",
description: "适合餐饮探店、门店种草和收藏型内容。", description: "适合门店探店、团购引流和高性价比种草。",
prompt: "帮我做一篇推荐平价火锅的笔记" prompt: "帮我做一篇推荐平价火锅的笔记,面向周末聚餐的年轻人,突出人均价格、招牌菜、环境氛围和适合拍照的点,给我标题、正文结构、封面文案和配图建议。"
}, },
{ {
title: "做一篇制作抹茶奶酪欧包教程的爆文", title: "抹茶奶酪欧包教程爆文",
description: "适合教程结构、标题钩子和步骤拆解。", description: "把教程步骤改成更容易收藏转发的笔记。",
prompt: "做一篇制作抹茶奶酪欧包教程的爆文" prompt: "做一篇制作抹茶奶酪欧包教程的爆文,面向烘焙新手,突出材料清单、关键步骤、失败避坑和成品口感,给我小红书标题、正文、标签和封面文案。"
}, },
{ {
title: "给通勤女生做一篇春季穿搭笔记", title: "通勤女生春季穿搭笔记",
description: "适合人群细分、场景搭配和封面建议。", description: "围绕职场通勤、显瘦配色和一周搭配展开。",
prompt: "给通勤女生做一篇春季穿搭笔记" prompt: "给通勤女生做一篇春季穿搭笔记,面向 25-35 岁职场女性,突出显瘦、舒适、适合办公室和约会切换,给我 5 个标题、正文结构、单品搭配和拍摄建议。"
}, },
{ {
title: "做一篇针对年轻女性推荐防晒霜的爆款图文", title: "年轻女性防晒霜爆款图文",
description: "适合美妆护肤种草、卖点提炼和图文结构。", description: "适合护肤品卖点拆解、场景种草和转化。",
prompt: "做一篇针对年轻女性推荐防晒霜的爆款图文" prompt: "帮我策划一篇年轻女性防晒霜爆款图文笔记,面向 20-30 岁通勤和户外场景用户,突出肤感、防晒力、妆前适配和补涂体验,给我标题、正文、封面文案、配图脚本和标签。"
} }
] ]
...@@ -45,9 +45,24 @@ export function resolveExpertKey(project: ExpertProject | undefined): ExpertKey ...@@ -45,9 +45,24 @@ export function resolveExpertKey(project: ExpertProject | undefined): ExpertKey
if (/xiaohongshu|xhs|rednote|小红书/.test(seed)) { if (/xiaohongshu|xhs|rednote|小红书/.test(seed)) {
return "xiaohongshu" return "xiaohongshu"
} }
if (/douyin|tiktok|抖音/.test(seed)) { if (/douyin|抖音/.test(seed)) {
return "douyin"
}
if (/tiktok/.test(seed)) {
return "douyin" 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)) { if (/browser|automation|chrome|playwright|web|浏览器|自动化/.test(seed)) {
return "browser" return "browser"
} }
...@@ -75,7 +90,7 @@ export function resolveExpertVisualKey(project?: ExpertProject, definition?: Exp ...@@ -75,7 +90,7 @@ export function resolveExpertVisualKey(project?: ExpertProject, definition?: Exp
if (/zhihu|知乎/.test(seed)) { if (/zhihu|知乎/.test(seed)) {
return "zhihu" return "zhihu"
} }
if (/content-account|planner|账号规划|内容账号规划/.test(seed)) { if (/content-account|planner|账号规划|内容账号规划|内容创作/.test(seed)) {
return "planner" return "planner"
} }
if (/precision-leads|线索|lead/.test(seed)) { if (/precision-leads|线索|lead/.test(seed)) {
...@@ -90,7 +105,7 @@ export function resolveExpertVisualKey(project?: ExpertProject, definition?: Exp ...@@ -90,7 +105,7 @@ export function resolveExpertVisualKey(project?: ExpertProject, definition?: Exp
if (/(^|[\s-])x($|[\s-])|twitter/.test(seed)) { if (/(^|[\s-])x($|[\s-])|twitter/.test(seed)) {
return "x" return "x"
} }
if (/geo/.test(seed)) { if (/(^|[\s-])geo($|[\s-])/.test(seed)) {
return "geo" return "geo"
} }
if (/browser|automation|chrome|playwright|web|浏览器|自动化/.test(seed)) { if (/browser|automation|chrome|playwright|web|浏览器|自动化/.test(seed)) {
...@@ -126,9 +141,9 @@ function getExpertGuide(project: ExpertProject | undefined): ExpertGuideContent ...@@ -126,9 +141,9 @@ function getExpertGuide(project: ExpertProject | undefined): ExpertGuideContent
case "xiaohongshu": case "xiaohongshu":
return { return {
greeting: "准备好了,开始种草", greeting: "准备好了,开始种草",
summary: "说清产品、场景、人群和目标,我会先拆选题、标题、笔记结构和配图方向。", summary: "",
prompts: XIAOHONGSHU_STARTER_PROMPTS.map((item) => item.prompt), starterPrompts: XIAOHONGSHU_STARTER_PROMPTS,
starterPrompts: XIAOHONGSHU_STARTER_PROMPTS prompts: XIAOHONGSHU_STARTER_PROMPTS.map((item) => item.prompt)
} }
case "douyin": case "douyin":
return { return {
...@@ -141,6 +156,62 @@ function getExpertGuide(project: ExpertProject | undefined): ExpertGuideContent ...@@ -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": case "browser":
return { return {
greeting: "适合处理浏览器自动化、采集、表单填写和发布流程设计。", greeting: "适合处理浏览器自动化、采集、表单填写和发布流程设计。",
...@@ -171,9 +242,9 @@ export function getExpertGuideContent(project: ExpertProject | undefined): Exper ...@@ -171,9 +242,9 @@ export function getExpertGuideContent(project: ExpertProject | undefined): Exper
} }
return { return {
greeting: "先给我一个视频目标,我来生成文案和分镜预览。", greeting: "先把这条抖音视频的条件说清楚,我会先帮你整理需求并给出预览。",
summary: "", summary: "适合抖音视频需求补全、文案与分镜预览、数字人口播和纯画面路线判断。",
intro: "补齐这些信息后,我会先出预览,不会直接开跑视频。", intro: "最好一次说明主题、给谁看、想达到什么目标、风格、时长、做数字人还是纯画面,以及现在有没有图片或音频。",
requirementChecklist: [ requirementChecklist: [
"主题", "主题",
"人群", "人群",
...@@ -186,12 +257,12 @@ export function getExpertGuideContent(project: ExpertProject | undefined): Exper ...@@ -186,12 +257,12 @@ export function getExpertGuideContent(project: ExpertProject | undefined): Exper
routeOptions: [ routeOptions: [
{ {
title: "数字人口播", title: "数字人口播",
detail: "适合讲解、产品说明和知识类内容。继续前确认人物图片和配音方式。", detail: "适合数字人口播、知识讲解、产品说明。继续生成前通常要确认人物图片,以及现成音频或男声/女声配音。",
accent: "host" accent: "host"
}, },
{ {
title: "纯画面展示", title: "纯画面展示",
detail: "适合氛围片、空镜、场景展示和旁白视频。先确认秒数和画面风格。", detail: "适合氛围片、空镜、场景展示、画面加旁白。先把视频秒数说清楚,再出文案和分镜预览。",
accent: "visual" accent: "visual"
} }
], ],
......
...@@ -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;
}
.conversation-shell .conversation-panel-actions .status-chip {
min-height: 24px;
opacity: 0.82;
} }
.shell.openclaw-theme .status-chip.positive {
.conversation-shell .conversation-panel-actions .status-chip.warning { background: rgba(16, 185, 129, 0.14);
color: #9a5f08; color: #0f7f59;
background: rgba(255, 251, 235, 0.76); border: 1px solid rgba(16, 185, 129, 0.28);
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