folderId: updateFolderId()

in apps/chat/src/store/conversations/conversations.epics.ts [850:1423]


                      folderId: updateFolderId(conversation.folderId),
                    },
                  }),
                ),
              );
            });
          }
          actions.push(
            of(
              ConversationsActions.updateFolderSuccess({
                folders: updatedFolders,
                conversations: updatedConversations,
                selectedConversationsIds: updatedSelectedConversationsIds,
              }),
            ),
            of(
              UIActions.setOpenedFoldersIds({
                openedFolderIds: updatedOpenedFoldersIds,
                featureType: FeatureType.Chat,
              }),
            ),
          );

          return concat(...actions);
        }),
        catchError((err) => {
          console.error('Error during upload conversations and folders', err);
          return of(ConversationsActions.uploadConversationsFail());
        }),
      );
    }),
  );

const clearConversationsEpic: AppEpic = (action$) =>
  action$.pipe(
    filter(ConversationsActions.clearConversations.match),
    switchMap(() => {
      return concat(
        of(ConversationsActions.clearConversationsSuccess()),
        of(ConversationsActions.deleteFolder({})),
      );
    }),
  );

const deleteConversationsEpic: AppEpic = (action$, state$) =>
  action$.pipe(
    filter(ConversationsActions.deleteConversations.match),
    map(({ payload }) => ({
      conversations: ConversationsSelectors.selectConversations(state$.value),
      selectedConversationsIds:
        ConversationsSelectors.selectSelectedConversationsIds(state$.value),
      conversationIds: new Set(payload.conversationIds),
      suppressErrorMessage: payload.suppressErrorMessage || false,
    })),
    switchMap(
      ({
        conversations,
        selectedConversationsIds,
        conversationIds,
        suppressErrorMessage,
      }) => {
        const otherConversations = conversations.filter(
          (conv) => !conversationIds.has(conv.id),
        );

        const newSelectedConversationsIds = selectedConversationsIds.filter(
          (id) => !conversationIds.has(id),
        );

        const actions: Observable<AnyAction>[] = [];

        const isIsolatedView = SettingsSelectors.selectIsIsolatedView(
          state$.value,
        );

        // No need to recreate conversation for isolated view
        if (!isIsolatedView) {
          if (
            otherConversations.length === 0 ||
            newSelectedConversationsIds.length === 0
          ) {
            actions.push(
              of(
                ConversationsActions.createNewConversations({
                  names: [translate(DEFAULT_CONVERSATION_NAME)],
                  suspendHideSidebar: isMediumScreen(),
                }),
              ),
            );
          } else if (
            newSelectedConversationsIds.length !==
            selectedConversationsIds.length
          ) {
            actions.push(
              of(
                ConversationsActions.selectConversations({
                  conversationIds: newSelectedConversationsIds,
                  suspendHideSidebar: isMediumScreen(),
                }),
              ),
            );
          }
        }

        return concat(
          zip(
            Array.from(conversationIds).map((id) =>
              !isEntityIdLocal({ id })
                ? ConversationService.deleteConversation(
                    getConversationInfoFromId(id),
                  ).pipe(
                    map(() => null),
                    catchError((err) => {
                      const { name } = getConversationInfoFromId(id);
                      !suppressErrorMessage &&
                        console.error(`Error during deleting "${name}"`, err);
                      return of(name);
                    }),
                  )
                : of(null),
            ),
          ).pipe(
            switchMap((failedNames) =>
              concat(
                iif(
                  () =>
                    failedNames.filter(Boolean).length > 0 &&
                    !suppressErrorMessage,
                  of(
                    UIActions.showErrorToast(
                      translate(
                        `An error occurred while deleting the conversation(s): "${failedNames.filter(Boolean).join('", "')}"`,
                      ),
                    ),
                  ),
                  EMPTY,
                ),
                of(
                  ConversationsActions.deleteConversationsComplete({
                    conversationIds,
                  }),
                ),
              ),
            ),
          ),
          ...actions,
        );
      },
    ),
  );

const rateMessageEpic: AppEpic = (action$, state$) =>
  action$.pipe(
    filter(ConversationsActions.rateMessage.match),
    map(({ payload }) => ({
      payload,
      conversations: ConversationsSelectors.selectConversations(state$.value),
    })),
    switchMap(({ conversations, payload }) => {
      const conversation = conversations.find(
        (conv) => conv.id === payload.conversationId,
      );
      if (!conversation) {
        return of(
          ConversationsActions.rateMessageFail({
            error: translate(
              'No conversation exists for rating with provided conversation id',
            ),
          }),
        );
      }
      const message = (conversation as Conversation).messages[
        payload.messageIndex
      ];

      if (!message || !message.responseId) {
        return of(
          ConversationsActions.rateMessageFail({
            error: translate('Message cannot be rated'),
          }),
        );
      }

      const rateBody: RateBody = {
        responseId: message.responseId,
        modelId: conversation.model.id,
        id: conversation.id,
        value: payload.rate > 0 ? true : false,
      };

      return fromFetch('/api/rate', {
        method: HTTPMethod.POST,
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(rateBody),
      }).pipe(
        switchMap((resp) => {
          if (!resp.ok) {
            return throwError(() => resp);
          }
          return from(resp.json());
        }),
        map(() => {
          return ConversationsActions.rateMessageSuccess(payload);
        }),
        catchError((e: Response) => {
          return of(
            ConversationsActions.rateMessageFail({
              error: e,
            }),
          );
        }),
      );
    }),
  );

const updateMessageEpic: AppEpic = (action$, state$) =>
  action$.pipe(
    filter(ConversationsActions.updateMessage.match),
    map(({ payload }) => ({
      payload,
      conversations: ConversationsSelectors.selectConversations(state$.value),
    })),
    switchMap(({ conversations, payload }) => {
      const conversation = conversations.find(
        (conv) => conv.id === payload.conversationId,
      ) as Conversation;
      if (!conversation || !conversation.messages[payload.messageIndex]) {
        return EMPTY;
      }

      const actions = [];
      const messages = [...conversation.messages];
      messages[payload.messageIndex] = {
        ...messages[payload.messageIndex],
        ...payload.values,
      };

      actions.push(
        of(
          ConversationsActions.updateConversation({
            id: payload.conversationId,
            values: {
              messages: [...messages],
            },
          }),
        ),
      );
      const attachments =
        messages[payload.messageIndex].custom_content?.attachments;

      if (attachments) {
        const attachmentParentFolders = uniq(
          attachments
            .map(
              (attachment) =>
                attachment.url &&
                getParentFolderIdsFromEntityId(
                  decodeURIComponent(attachment.url),
                ),
            )
            .filter(Boolean),
        ).flat();

        if (attachmentParentFolders.length) {
          actions.push(
            of(
              FilesActions.updateFoldersStatus({
                foldersIds: attachmentParentFolders,
                status: UploadStatus.UNINITIALIZED,
              }),
            ),
          );
        }
      }

      return concat(...actions);
    }),
  );

const rateMessageSuccessEpic: AppEpic = (action$) =>
  action$.pipe(
    filter(ConversationsActions.rateMessageSuccess.match),
    switchMap(({ payload }) => {
      return of(
        ConversationsActions.updateMessage({
          conversationId: payload.conversationId,
          messageIndex: payload.messageIndex,
          values: {
            like: payload.rate,
          },
        }),
      );
    }),
  );

const sendMessagesEpic: AppEpic = (action$) =>
  action$.pipe(
    filter(ConversationsActions.sendMessages.match),
    switchMap(({ payload }) => {
      return concat(
        of(ConversationsActions.createAbortController()),
        ...payload.conversations.map((conv) => {
          return of(
            ConversationsActions.sendMessage({
              conversation: conv,
              message: payload.message,
              deleteCount: payload.deleteCount,
              activeReplayIndex: payload.activeReplayIndex,
            }),
          );
        }),
      );
    }),
  );

const sendMessageEpic: AppEpic = (action$, state$) =>
  action$.pipe(
    filter(ConversationsActions.sendMessage.match),
    map(({ payload }) => ({
      payload,
      modelsMap: ModelsSelectors.selectModelsMap(state$.value),
      conversations: ConversationsSelectors.selectConversations(state$.value),
      selectedConversationIds:
        ConversationsSelectors.selectSelectedConversationsIds(state$.value),
      overlaySystemPrompt: OverlaySelectors.selectOverlaySystemPrompt(
        state$.value,
      ),
      isOverlay: SettingsSelectors.selectIsOverlay(state$.value),
    })),
    switchMap(
      ({
        payload,
        modelsMap,
        conversations,
        selectedConversationIds,
        overlaySystemPrompt,
        isOverlay,
      }) => {
        const actions: Observable<AnyAction>[] = [];
        const messageModel: Message[EntityType.Model] = {
          id: payload.conversation.model.id,
        };
        const messageSettings: Message['settings'] = {
          prompt: payload.conversation.prompt,
          temperature: payload.conversation.temperature,
          selectedAddons: payload.conversation.selectedAddons,
          assistantModelId: payload.conversation.assistantModelId,
        };

        const assistantMessage: Message = {
          content: '',
          model: messageModel,
          settings: messageSettings,
          role: Role.Assistant,
        };

        const userMessage: Message = {
          ...payload.message,
          model: messageModel,
          settings: messageSettings,
        };

        let currentMessages =
          payload.deleteCount > 0
            ? payload.conversation.messages.slice(
                0,
                payload.deleteCount * -1 || undefined,
              )
            : payload.conversation.messages;

        /*
          Overlay needs to share host application state information
          We storing state information in systemPrompt (message with role: Role.System)
        */
        if (isOverlay && overlaySystemPrompt) {
          currentMessages = updateSystemPromptInMessages(
            currentMessages,
            overlaySystemPrompt,
          );
        }

        const updatedMessages = currentMessages.concat(
          userMessage,
          assistantMessage,
        );

        const conversationRootFolderId = getConversationRootId();

        const newConversationName =
          payload.conversation.replay?.isReplay ||
          updatedMessages.filter((msg) => msg.role === Role.User).length > 1 ||
          payload.conversation.isNameChanged
            ? payload.conversation.name
            : getNextDefaultName(
                getNewConversationName(payload.conversation, payload.message),
                conversations.filter(
                  (conv) =>
                    (conv.folderId === payload.conversation.folderId ||
                      (isEntityIdLocal(payload.conversation) &&
                        conv.folderId === conversationRootFolderId)) &&
                    !selectedConversationIds.includes(conv.id),
                ),
                Math.max(
                  selectedConversationIds.indexOf(payload.conversation.id),
                  0,
                ),
                true,
              );

        const updatedConversation: Conversation = regenerateConversationId({
          ...payload.conversation,
          lastActivityDate: Date.now(),
          replay: payload.conversation.replay
            ? {
                ...payload.conversation.replay,
                activeReplayIndex: payload.activeReplayIndex,
              }
            : undefined,
          messages: updatedMessages,
          name: newConversationName,
          isMessageStreaming: true,
        });

        if (
          updatedConversation.selectedAddons.length > 0 &&
          modelsMap[updatedConversation.model.id]?.type !==
            EntityType.Application
        ) {
          actions.push(
            of(
              AddonsActions.updateRecentAddons({
                addonIds: updatedConversation.selectedAddons,
              }),
            ),
          );
        }

        return concat(
          ...actions,
          of(
            ConversationsActions.updateConversation({
              id: payload.conversation.id,
              values: updatedConversation,
            }),
          ),
          of(
            ModelsActions.updateRecentModels({
              modelId: updatedConversation.model.id,
            }),
          ),
          of(
            ConversationsActions.streamMessage({
              conversation: updatedConversation,
              message: assistantMessage,
            }),
          ),
        );
      },
    ),
  );

const streamMessageEpic: AppEpic = (action$, state$) =>
  action$.pipe(
    filter(ConversationsActions.streamMessage.match),
    map(({ payload }) => ({
      payload,
      modelsMap: ModelsSelectors.selectModelsMap(state$.value),
    })),
    map(({ payload, modelsMap }) => {
      const lastModel = modelsMap[payload.conversation.model.id];
      const selectedAddons = uniq([
        ...payload.conversation.selectedAddons,
        ...(lastModel?.selectedAddons ?? []),
      ]);
      const assistantModelId = payload.conversation.assistantModelId;
      const conversationModelType = lastModel?.type ?? EntityType.Model;
      let modelAdditionalSettings = {};

      if (conversationModelType === EntityType.Model) {
        modelAdditionalSettings = {
          prompt: doesModelAllowSystemPrompt(lastModel)
            ? payload.conversation.prompt
            : undefined,
          temperature: doesModelAllowTemperature(lastModel)
            ? payload.conversation.temperature
            : 1,
          selectedAddons: doesModelAllowAddons(lastModel) ? selectedAddons : [],
        };
      }
      if (conversationModelType === EntityType.Assistant && assistantModelId) {
        modelAdditionalSettings = {
          assistantModel: modelsMap[assistantModelId],
          temperature: doesModelAllowTemperature(lastModel)
            ? payload.conversation.temperature
            : 1,
          selectedAddons: doesModelAllowAddons(lastModel) ? selectedAddons : [],
        };
      }

      const chatBody: ChatBody = {
        model: modelsMap[payload.conversation.model.id],
        messages: payload.conversation.messages
          .filter(
            (message, index) =>
              message.role !== Role.Assistant ||
              index !== payload.conversation.messages.length - 1,
          )
          .map((message) => ({
            content: message.content,
            role: message.role,
            like: void 0,
            ...((message.custom_content?.state ||
              message.custom_content?.attachments ||
              message.custom_content?.form_value ||
              message.custom_content?.form_schema) && {
              custom_content: {
                state: message.custom_content?.state,
                attachments: message.custom_content?.attachments,
                form_value: message.custom_content?.form_value,
                form_schema: message.custom_content?.form_schema,
              },
            }),
          })),
        id: payload.conversation.id,
        ...modelAdditionalSettings,
      };

      return {
        payload,
        chatBody,
      };
    }),
    mergeMap(({ payload, chatBody }) => {
      const conversationSignal =
        ConversationsSelectors.selectConversationSignal(state$.value);
      const decoder = new TextDecoder();
      let eventData = '';
      let message = payload.message;

      return from(
        fetch('/api/chat', {
          method: HTTPMethod.POST,
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(chatBody),
          signal: conversationSignal.signal,
        }),
      ).pipe(
        switchMap((response) => {
          const body = response.body;

          if (!response.ok) {
            return throwError(
              () => new Error('ServerError', { cause: response }),
            );
          }
          if (!body) {
            return throwError(() => new Error('No body received'));
          }

          const reader = body.getReader();
          const subj = new Subject<ReadableStreamReadResult<Uint8Array>>();
          const observable = subj.asObservable();
          const observer = async () => {
            try {
              // eslint-disable-next-line no-constant-condition
              while (true) {
                const val = await reader.read();

                subj.next(val);
                if (val.done) {