Files
lobe-chat/.agents/skills/zustand/references/action-patterns.md
T
Innei 346fc4617e ♻️ refactor: migrate AI Rules to Claude Code Skills (#11737)
♻️ refactor: migrate AI Rules to Claude Code Skills system

Migrate all AI Rules from .cursor/rules/ to .agents/skills/ directory:
- Move 23 skills to .agents/skills/ (main directory)
- Update symlinks: .claude/skills, .cursor/skills, .codex/skills
- Create project-overview skill from project documentation
- Add references/ subdirectories for complex skills
- Remove LobeChat references from skill descriptions
- Delete obsolete .cursor/rules/ and .claude/commands/prompts/ directories

Skills structure enables better portability and maintainability across AI tools.
2026-01-23 22:30:18 +08:00

3.0 KiB

Zustand Action Patterns

Optimistic Update Implementation

Standard Flow

internal_updateMessageContent: async (id, content, extra) => {
  const { internal_dispatchMessage, refreshMessages } = get();

  // 1. Immediately update frontend
  internal_dispatchMessage({
    id,
    type: 'updateMessage',
    value: { content },
  });

  // 2. Call backend
  await messageService.updateMessage(id, { content });

  // 3. Refresh for consistency
  await refreshMessages();
},

Create Operations

internal_createMessage: async (message, context) => {
  let tempId = context?.tempMessageId;
  if (!tempId) {
    tempId = internal_createTmpMessage(message);
    internal_toggleMessageLoading(true, tempId);
  }

  try {
    const id = await messageService.createMessage(message);
    await refreshMessages();
    internal_toggleMessageLoading(false, tempId);
    return id;
  } catch (e) {
    internal_toggleMessageLoading(false, tempId);
    internal_dispatchMessage({
      id: tempId,
      type: 'updateMessage',
      value: { error: { type: ChatErrorType.CreateMessageError } },
    });
  }
},

Delete Operations (No Optimistic Update)

internal_removeGenerationTopic: async (id: string) => {
  get().internal_updateGenerationTopicLoading(id, true);

  try {
    await generationTopicService.deleteTopic(id);
    await get().refreshGenerationTopics();
  } finally {
    get().internal_updateGenerationTopicLoading(id, false);
  }
},

Loading State Management

// Define in initialState.ts
export interface ChatMessageState {
  messageEditingIds: string[];
}

// Manage in action
toggleMessageEditing: (id, editing) => {
  set(
    { messageEditingIds: toggleBooleanList(get().messageEditingIds, id, editing) },
    false,
    'toggleMessageEditing'
  );
}

SWR Integration

useFetchMessages: (enable, sessionId, activeTopicId) =>
  useClientDataSWR<ChatMessage[]>(
    enable ? [SWR_USE_FETCH_MESSAGES, sessionId, activeTopicId] : null,
    async ([, sessionId, topicId]) => messageService.getMessages(sessionId, topicId),
    {
      onSuccess: (messages) => {
        const nextMap = { ...get().messagesMap, [messageMapKey(sessionId, activeTopicId)]: messages };
        if (get().messagesInit && isEqual(nextMap, get().messagesMap)) return;
        set({ messagesInit: true, messagesMap: nextMap }, false, n('useFetchMessages'));
      },
    }
  ),

// Cache invalidation
refreshMessages: async () => {
  await mutate([SWR_USE_FETCH_MESSAGES, get().activeId, get().activeTopicId]);
};

Reducer Pattern

export const messagesReducer = (state: ChatMessage[], payload: MessageDispatch): ChatMessage[] => {
  switch (payload.type) {
    case 'updateMessage': {
      return produce(state, (draftState) => {
        const index = draftState.findIndex((i) => i.id === payload.id);
        if (index < 0) return;
        draftState[index] = merge(draftState[index], {
          ...payload.value,
          updatedAt: Date.now(),
        });
      });
    }
    // ...other cases
  }
};