Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

### 变更

- 用户可见吉祥物从章鱼换成 XYAI 白色机器人姿势包(欢迎 / 空态 / 探头 / 思考 / 打字 / 任务);桌面启动页与 openXYOS 首页使用同一角色,不再加载章鱼 WebM。
- 桌面更新通道只跟踪 `github.com/XYAIStudio/FreeOS/releases`。不再读取 PyPI `octop`、腾讯云 COS 或其它 Octop 镜像;当前已是最新 FreeOS `v0.0.1` 时不会误报更新。
- 默认主题与残留 Octop `rose` 存储一次性迁移为 XYAI 蓝 `#0033FF`(之后仍可手动选玫瑰粉)。

Expand Down
Binary file removed dashboard/public/octop-mascot-empty.png
Binary file not shown.
Binary file removed dashboard/public/octop-mascot-peek.webm
Binary file not shown.
Binary file removed dashboard/public/octop-mascot-peek.webp
Binary file not shown.
Binary file removed dashboard/public/octop-mascot-tasks.png
Binary file not shown.
Binary file removed dashboard/public/octop-mascot-type.webm
Binary file not shown.
Binary file removed dashboard/public/octop-mascot-type.webp
Binary file not shown.
Binary file added dashboard/public/xyai-mascot-empty.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added dashboard/public/xyai-mascot-peek.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added dashboard/public/xyai-mascot-tasks.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added dashboard/public/xyai-mascot-think.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added dashboard/public/xyai-mascot-type.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added dashboard/public/xyai-mascot-welcome.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 43 additions & 3 deletions dashboard/src/assets/mascot.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
/**
* Shared empty-state mascot (served from ``dashboard/public``).
* Prefer ``OctopEmptyMascot`` / ``EmptyState variant="mascot"`` over hardcoding.
* Shared XYAI / FreeOS mascot pack (served from ``dashboard/public``).
* Prefer ``XyaiMascot`` / ``EmptyState variant="mascot"`` over hardcoding paths.
*/
export const OCTOP_EMPTY_MASCOT_SRC = "/octop-mascot-empty.png";

export type XyaiMascotPose =
| "welcome"
| "empty"
| "peek"
| "think"
| "type"
| "work"
| "tasks"
| "success";

const POSE_FILES: Record<XyaiMascotPose, string> = {
welcome: "xyai-mascot-welcome.webp",
empty: "xyai-mascot-empty.webp",
peek: "xyai-mascot-peek.webp",
think: "xyai-mascot-think.webp",
type: "xyai-mascot-type.webp",
work: "xyai-mascot-type.webp",
tasks: "xyai-mascot-tasks.webp",
success: "xyai-mascot-tasks.webp",
};

/** Distinct stills for tap-to-switch surfaces (welcome + desktop splash). */
export const XYAI_MASCOT_SWITCH_POSES = [
"welcome",
"peek",
"think",
"type",
"tasks",
] as const satisfies readonly XyaiMascotPose[];

export function xyaiMascotSrc(pose: XyaiMascotPose = "empty"): string {
const base = import.meta.env.BASE_URL ?? "/";
return `${base}${POSE_FILES[pose]}`;
}

export function xyaiMascotSwitchSrcs(): string[] {
return XYAI_MASCOT_SWITCH_POSES.map((pose) => xyaiMascotSrc(pose));
}

export const XYAI_EMPTY_MASCOT_SRC = xyaiMascotSrc("empty");
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { OCTOP_EMPTY_MASCOT_SRC } from "../../assets/mascot";
import { xyaiMascotSrc, type XyaiMascotPose } from "../../assets/mascot";
import styles from "./EmptyState.module.less";

interface OctopEmptyMascotProps {
interface XyaiMascotProps {
className?: string;
/** Square edge in px; default 160. */
size?: number;
/** Pose from the shared XYAI pack; default empty-state. */
pose?: XyaiMascotPose;
}

/**
* Octop empty-state mascot image with shared sizing.
* XYAI / FreeOS mascot image with shared sizing.
* Use inside custom empty UIs or pass as ``EmptyState`` icon /
* antd ``Empty`` ``image``.
*/
export function OctopEmptyMascot({
export function XyaiMascot({
className,
size = 160,
}: OctopEmptyMascotProps) {
pose = "empty",
}: XyaiMascotProps) {
return (
<img
src={OCTOP_EMPTY_MASCOT_SRC}
src={xyaiMascotSrc(pose)}
alt=""
draggable={false}
className={className ? `${styles.mascot} ${className}` : styles.mascot}
Expand Down
10 changes: 5 additions & 5 deletions dashboard/src/components/EmptyState/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button } from "antd";
import { Inbox, AlertCircle } from "lucide-react";
import type { ReactNode } from "react";
import { OctopEmptyMascot } from "./OctopEmptyMascot";
import { XyaiMascot } from "./XyaiMascot";

interface EmptyStateProps {
/** Icon element. Overrides the default for the current variant. */
Expand All @@ -18,7 +18,7 @@ interface EmptyStateProps {
* Visual variant:
* - empty: inbox icon
* - error: alert icon
* - mascot: shared Octop empty mascot (prefer for list/detail empty shells)
* - mascot: shared XYAI empty mascot (prefer for list/detail empty shells)
*/
variant?: "empty" | "error" | "mascot";
className?: string;
Expand All @@ -45,7 +45,7 @@ export function EmptyState({
style={{ color: "var(--fn-color-danger)" }}
/>
) : variant === "mascot" ? (
<OctopEmptyMascot />
<XyaiMascot />
) : (
<Inbox
size={40}
Expand Down Expand Up @@ -100,5 +100,5 @@ export function EmptyState({
);
}

export { OctopEmptyMascot } from "./OctopEmptyMascot";
export { OCTOP_EMPTY_MASCOT_SRC } from "../../assets/mascot";
export { XyaiMascot } from "./XyaiMascot";
export { XYAI_EMPTY_MASCOT_SRC, xyaiMascotSrc } from "../../assets/mascot";
4 changes: 2 additions & 2 deletions dashboard/src/components/NotFoundPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button } from "antd";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { OctopEmptyMascot } from "./EmptyState";
import { XyaiMascot } from "./EmptyState";
import styles from "./NotFoundPage.module.less";

/** Catch-all placeholder for dashboard paths that do not match any route. */
Expand All @@ -11,7 +11,7 @@ export default function NotFoundPage() {
return (
<div className={styles.wrap}>
<div className={styles.inner}>
<OctopEmptyMascot size={140} className={styles.mascot} />
<XyaiMascot size={140} className={styles.mascot} />
<h1 className={styles.title}>{t("common.notFound")}</h1>
<p className={styles.hint}>{t("common.notFoundHint")}</p>
<Button type="primary" onClick={() => navigate("/chat")}>
Expand Down
5 changes: 3 additions & 2 deletions dashboard/src/components/StreamConnectingIndicator/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { ReactNode } from "react";
import { xyaiMascotSrc } from "../../assets/mascot";
import styles from "./StreamConnectingIndicator.module.less";

const MASCOT_TYPE = `${import.meta.env.BASE_URL}octop-mascot-type.webp`;
const MASCOT_TYPE = xyaiMascotSrc("work");

interface StreamConnectingIndicatorProps {
/** Status line under the animation (e.g. 「连接中」). */
Expand All @@ -16,7 +17,7 @@ interface StreamConnectingIndicatorProps {

/**
* Shared connecting / waiting-frame indicator for remote browser & desktop.
* Uses the same Octop mascot loop as chat thinking bubbles.
* Uses the same XYAI typing/work mascot as chat thinking bubbles.
*/
export default function StreamConnectingIndicator({
label,
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3253,6 +3253,7 @@
"chatWelcome": {
"greeting": "Hi! Your personal AI sidekick is here",
"mascotSwitchHint": "Tap to switch the mascot",
"mascotAriaLabel": "XYAI mascot",
"descriptionWithAgentSuffix": "— share what's on your mind, work or life",
"description": "Work or life — just tell me what you need, I'll handle it",
"newChat": "New Chat",
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -3250,6 +3250,7 @@
"chatWelcome": {
"greeting": "嗨!你专属的智能伙伴来啦~",
"mascotSwitchHint": "点一下换个表情~",
"mascotAriaLabel": "XYAI 吉祥物",
"descriptionWithAgentSuffix": "无论工作还是生活,说出你的想法",
"description": "无论工作还是生活,说出你的想法,我来搞定",
"newChat": "新建对话",
Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/pages/Admin/Plugins/PluginMarketPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useTranslation } from "react-i18next";
import { OctopEmptyMascot } from "../../../components/EmptyState";
import { XyaiMascot } from "../../../components/EmptyState";
import styles from "./index.module.less";

/** Placeholder marketplace tab — under construction. */
export function PluginMarketPanel() {
const { t } = useTranslation();
return (
<div className={styles.marketEmpty}>
<OctopEmptyMascot size={180} />
<XyaiMascot size={180} />
<div className={styles.emptyTitle}>{t("plugins.marketTitle")}</div>
<div className={styles.emptyHint}>{t("plugins.marketHint")}</div>
</div>
Expand Down
6 changes: 2 additions & 4 deletions dashboard/src/pages/Admin/Storage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { HardDrive, LayoutGrid } from "lucide-react";
import { useTranslation } from "react-i18next";
import PageShell from "../../../layouts/PageShell";
import TabLabel from "../../../components/TabLabel";
import { OctopEmptyMascot } from "../../../components/EmptyState";
import { XyaiMascot } from "../../../components/EmptyState";
import StreamSetupGuide from "../../../components/StreamSetupGuide/StreamSetupGuide";
import {
useStorageBackends,
Expand Down Expand Up @@ -85,9 +85,7 @@ export default function AdminStoragePage() {
return (
<StreamSetupGuide
wide
icon={
<OctopEmptyMascot size={120} className={styles.emptyGuideMascot} />
}
icon={<XyaiMascot size={120} className={styles.emptyGuideMascot} />}
title={t("storage.emptyGuideTitle")}
description={t("storage.emptyGuideDesc")}
steps={[
Expand Down
9 changes: 2 additions & 7 deletions dashboard/src/pages/Agent/Connectors/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { useSearchParams } from "react-router-dom";
import PageShell from "../../../layouts/PageShell";
import TabBar, { type TabBarItem } from "../../../components/TabLabel/TabBar";
import StreamSetupGuide from "../../../components/StreamSetupGuide/StreamSetupGuide";
import { OctopEmptyMascot } from "../../../components/EmptyState/OctopEmptyMascot";
import { XyaiMascot } from "../../../components/EmptyState";
import { useCurrentUser } from "../../../hooks/useCurrentUser";
import { userCan } from "../../../utils/permissions";
import { apiErrorMessage } from "../../../utils/apiError";
Expand Down Expand Up @@ -2214,12 +2214,7 @@ export default function ConnectorsPage() {
) : instances.length === 0 ? (
<StreamSetupGuide
wide
icon={
<OctopEmptyMascot
size={120}
className={styles.emptyGuideMascot}
/>
}
icon={<XyaiMascot size={120} className={styles.emptyGuideMascot} />}
title={t("connectors.emptyGuideTitle")}
description={t("connectors.emptyGuideDesc")}
steps={[
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/pages/Chat/chatWelcome.partial.less
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
// Mascot image shown above the greeting title
.welcomeMascot {
width: 132px;
aspect-ratio: 352 / 320;
aspect-ratio: 1;
height: auto;
object-fit: contain;
margin-bottom: -12px;
Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/pages/Chat/components/AgentNotReadyScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Button, Result, Spin } from "antd";
import { Settings } from "lucide-react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { OctopEmptyMascot } from "../../../components/EmptyState";
import { XyaiMascot } from "../../../components/EmptyState";
import type { OctopAgent } from "../../../context/AgentContext";
import {
formatAgentError,
Expand Down Expand Up @@ -37,7 +37,7 @@ export default function AgentNotReadyScreen({
<div className={styles.noAgentsEmpty}>
<div className={styles.noAgentsEmptyInner}>
<div className={styles.noAgentsEmptyIcon}>
<OctopEmptyMascot className={styles.noAgentsEmptyMascot} />
<XyaiMascot className={styles.noAgentsEmptyMascot} />
</div>
<h1 className={styles.noAgentsEmptyTitle}>
{t("chat.noAgentsTitle")}
Expand Down
3 changes: 2 additions & 1 deletion dashboard/src/pages/Chat/components/ThinkingBubble.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useTranslation } from "react-i18next";
import { xyaiMascotSrc } from "../../../assets/mascot";
import { useElapsedSince } from "../../../hooks/useElapsedSeconds";
import styles from "../index.module.less";

Expand All @@ -12,7 +13,7 @@ export default function ThinkingBubble({
startedAt,
}: ThinkingBubbleProps) {
const { t } = useTranslation();
const typingSrc = `${import.meta.env.BASE_URL}octop-mascot-type.webp`;
const typingSrc = xyaiMascotSrc("type");
const elapsed = useElapsedSince(startedAt);

return (
Expand Down
10 changes: 4 additions & 6 deletions dashboard/src/pages/Chat/components/WelcomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { xyaiMascotSrc, xyaiMascotSwitchSrcs } from "../../../assets/mascot";
import { useWelcomeQuickCardsLayout } from "../hooks/useWelcomeQuickCardsLayout";
import WelcomeQuickCards, { WelcomeQuickCardProbe } from "./WelcomeQuickCards";
import styles from "../index.module.less";

// Animated WebP keeps alpha on Safari; VP9 WebM alpha is unreliable there.
const MASCOT_PEEK = "/octop-mascot-peek.webp";
const MASCOT_TYPE = "/octop-mascot-type.webp";
const MASCOT_IMAGES = [MASCOT_PEEK, MASCOT_TYPE];
const MASCOT_IMAGES = xyaiMascotSwitchSrcs();

function getRandomMascot(current?: string): string {
if (MASCOT_IMAGES.length <= 1) return MASCOT_IMAGES[0];
Expand Down Expand Up @@ -43,7 +41,7 @@ export default function WelcomeScreen({
hideMascot = false,
}: WelcomeScreenProps) {
const { t } = useTranslation();
const [mascotSrc, setMascotSrc] = useState(MASCOT_PEEK);
const [mascotSrc, setMascotSrc] = useState(() => xyaiMascotSrc("welcome"));
const {
welcomeRef,
headingRef,
Expand Down Expand Up @@ -76,7 +74,7 @@ export default function WelcomeScreen({
role="button"
tabIndex={0}
title={t("chatWelcome.mascotSwitchHint")}
aria-label="Octop mascot"
aria-label={t("chatWelcome.mascotAriaLabel")}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/pages/Control/CronJobs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import type { CronJobFormValues } from "./useCronJobs";
import { useCardTableView } from "../../../hooks/useCardTableView";
import { showConfirmModal } from "../../../utils/confirmModal";
import { OctopEmptyMascot } from "../../../components/EmptyState";
import { XyaiMascot } from "../../../components/EmptyState";
import { ResizableTable } from "../../../components/ResizableTable";
import PageShell from "../../../layouts/PageShell";
import { useAgent } from "../../../context/AgentContext";
Expand All @@ -42,7 +42,7 @@ function CronJobsEmptyState({
return (
<div className={styles.emptyState}>
<div className={styles.emptyStateIcon}>
<OctopEmptyMascot />
<XyaiMascot />
</div>
<h2 className={styles.emptyStateTitle}>{t("cronJobs.noJobs")}</h2>
<p className={styles.emptyStateDesc}>{t("cronJobs.noJobsDesc")}</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Download, PlugZap, RefreshCw } from "lucide-react";
import { useTranslation } from "react-i18next";

import StreamSetupGuide from "../../../components/StreamSetupGuide/StreamSetupGuide";
import { OctopEmptyMascot } from "../../../components/EmptyState";
import { XyaiMascot } from "../../../components/EmptyState";
import type { MobileStatusResponse } from "../../../api/modules/mobile";
import styles from "./index.module.less";

Expand Down Expand Up @@ -74,7 +74,7 @@ export default function RemotePhoneIdleGuide({

return (
<StreamSetupGuide
icon={<OctopEmptyMascot size={120} className={styles.setupMascot} />}
icon={<XyaiMascot size={120} className={styles.setupMascot} />}
title={
ready
? isShell
Expand Down
7 changes: 2 additions & 5 deletions dashboard/src/pages/Control/RemoteBrowser/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { useTranslation } from "react-i18next";

import StreamEdgeControls from "../../../components/StreamEdgeControls/StreamEdgeControls";
import StreamSetupGuide from "../../../components/StreamSetupGuide/StreamSetupGuide";
import { OctopEmptyMascot } from "../../../components/EmptyState";
import { XyaiMascot } from "../../../components/EmptyState";
import PageShell from "../../../layouts/PageShell";
import BrowserAiPanel from "../../../components/BrowserAiPanel";
import SkillRecordGuideModal from "../../../components/SkillRecordGuideModal";
Expand Down Expand Up @@ -1467,10 +1467,7 @@ export default function RemoteBrowserPage({
) : (
<StreamSetupGuide
icon={
<OctopEmptyMascot
size={120}
className={styles.setupMascot}
/>
<XyaiMascot size={120} className={styles.setupMascot} />
}
title={
envReady
Expand Down
6 changes: 2 additions & 4 deletions dashboard/src/pages/Control/RemoteDesktop/DesktopPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ import {
type DesktopResolution,
} from "../../../utils/desktopViewport";
import { sendDesktopAction } from "./desktopShortcuts";
import { OctopEmptyMascot } from "../../../components/EmptyState";
import { XyaiMascot } from "../../../components/EmptyState";
import styles from "./DesktopPanel.module.less";

const RESOLUTION_STORAGE_KEY = "octop:remote-desktop:resolution";
Expand Down Expand Up @@ -986,9 +986,7 @@ export default function DesktopPanel({
"pageShell.desktop.subtitle",
"查看并操控 Octop 主机操作系统桌面",
);
const setupMascot = (
<OctopEmptyMascot size={120} className={styles.setupMascot} />
);
const setupMascot = <XyaiMascot size={120} className={styles.setupMascot} />;

if (user === null) {
const loading = (
Expand Down
Loading
Loading