Skip to main content

Documentation Index

Fetch the complete documentation index at: https://tech.illasoft.com/llms.txt

Use this file to discover all available pages before exploring further.

概述

使用 Zustand 进行轻量级状态管理。

Store 结构

store
poisson.ts
auth.ts
feed.ts
Thread 数据和用户 profile 数据通过 TanStack Query 从服务端获取,不使用 Zustand store。

Poisson Store

usePoissonStore(来自 store/poisson.ts)管理 Generator 相关状态,使用嵌套 map 模式 poissonMap[threadId] 存储每个 thread 的独立状态。
// store/poisson.ts:8-29 (权威)
type EditMode =
  | "chat" | "upscale" | "filter" | "adjust" | "crop" | "chooseFilter"
  | "inpaint" | "expand" | "replaceBg" | "eraser" | "mask"
  | "generateVideo" | "generateMusic" | "generateInstrumental" | "addVocals"
  | "generateImage"
  // 2026-03+ 新增 5 个
  | "trimVideo" | "trimAudio" | "motionControl" | "videoExtend" | "videoEdit";

type KiraModel = "lite" | "nova" | "crazy" | "ultra";

interface PoissonState {
  // 模型选择(顶层,通过 persist 中间件持久化到 localStorage)
  model: KiraModel;

  // 嵌套 map,按 threadId 隔离状态
  poissonMap: Record<string, {
    currentImage: Image;
    currentVersionId: string;
    versions: Version[];
    messages: UIMessage[];
    tempMessages: UIMessage[];
    executingTool: { toolName: string; args: object } | null;
    chatStatus: ChatStatus;
    inputState: InputState;
    errorInfo: ToolError[] | null;
    editMode: EditMode;
    editModeData?: any;
    isReceivingMessage: boolean;
    isReceivingVersions: boolean;
    currentSelectVideo?: Video;
    currentSelectAudio?: Audio;
    currentSelectMediaType: "image" | "video" | "audio";
  }>;

  // Actions(均需传入 threadId)
  setCurrentImage: (threadId: string, image: Image) => void;
  setEditMode: (threadId: string, mode: EditMode, data?: any) => void;
  getEditMode: (threadId: string) => EditMode;   // 默认 "chat"
  getEditModeData: (threadId: string) => any;
  setCurrentSelectVideo: (threadId: string, video: Video) => void;
  setCurrentSelectAudio: (threadId: string, audio: Audio) => void;
  getCurrentSelectMediaType: (threadId: string) => "image" | "video" | "audio" | null;
  setCurrentVersionId: (threadId: string, versionId: string) => void;   // 自动选首媒体
  appendThreadVersion: (threadId: string, version: Version) => void;
  updateVersions: (threadId: string) => void;    // 异步重新 fetch versions
}

export const usePoissonStore = create<PoissonState>(
  persist(/* ... */, { name: 'poisson-store', partialize: (state) => ({ model: state.model }) })
);
Store 使用 Zustand 的 persist 中间件,通过 localStorage 持久化 model 字段。

Auth Store

useAuthStore(来自 store/auth.ts)仅管理认证状态。用户 profile 数据通过 TanStack Query 获取。
// store/auth.ts
interface AuthState {
  isAuth: boolean;
  login: () => void;
  logout: () => void;
}

Feed State Store

useFeedStateStore(来自 store/feed.ts)管理 Feed 相关状态。
// store/feed.ts
interface FeedState {
  deletedFeed: string[];             // 已删除的 feed ID 列表

  localDeleteFeed: (feedId: string) => void;    // 本地标记删除
  localRecoverFeed: (feedId: string) => void;   // 本地恢复
  getDeletedFeed: (feedId: string) => boolean;    // 判断是否已删除
}

使用方式

// 组件中使用
function ImagePanel({ threadId }: { threadId: string }) {
  const currentImage = usePoissonStore(
    (state) => state.poissonMap[threadId]?.currentImage
  );

  return <img src={currentImage?.url} />;
}

自定义 Hooks

仅 4 个 hook,集中在 Centrifugo 订阅和任务重提交:
Hook用途文件
useMusicNotificationHandler订阅 Centrifugo audio_status 推送,同步 store (versions / messages / currentSelectAudio)hooks/use-music-notification-handler.ts
useVideoNotificationHandler同上,video_statushooks/use-video-notification-handler.ts
useSubmitMusicTaskPOST /music/submit/:taskId(insufficient_credits 恢复);失败触发 billing dialoghooks/use-submit-music-task.ts
useSubmitVideoTask同上视频版hooks/use-submit-video-task.ts
不存在 useKiraChat hook —— 它实际上是 components/chat/kirachat.tsx 的 React component。不存在 ImageUploaderProvider / ImageUploaderContext —— 图片上传走 lib/utils.tsuploadImage() 工具函数(内含 CSAM 451 错误识别)。

CSAM 错误处理(前端)

// lib/utils.ts:12
export const CSAM_BLOCKED_ERROR = "csam_blocked";

// lib/utils.ts:44-87 uploadImage()
const res = await fetch(`/image/check/${imageId}`, {...});
if (res.status === 451) {
  throw new Error(CSAM_BLOCKED_ERROR);
}
上传组件捕获 CSAM_BLOCKED_ERROR 后 toast i18n key error.csam_not_allowed

最佳实践

  1. 每个功能模块一个 Store
  2. 状态和 Actions 定义在同一个 Store 中
  3. 使用选择器避免不必要的重渲染