/**
 * This file is part of the NocoBase (R) project.
 * Copyright (c) 2020-2024 NocoBase Co., Ltd.
 * Authors: NocoBase Team.
 *
 * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
 * For more information, please refer to: https://www.nocobase.com/agreement.
 */

var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var aiConversations_exports = {};
__export(aiConversations_exports, {
  default: () => aiConversations_default
});
module.exports = __toCommonJS(aiConversations_exports);
var import_actions = __toESM(require("@nocobase/actions"));
var import_utils = require("../utils");
var import_ai_employee = require("../ai-employees/ai-employee");
async function getAIEmployee(ctx, username) {
  var _a;
  const filter = {
    username
  };
  if (!((_a = ctx.state.currentRoles) == null ? void 0 : _a.includes("root"))) {
    filter["roles.name"] = ctx.state.currentRoles;
  }
  const employee = await ctx.db.getRepository("aiEmployees").findOne({
    filter
  });
  return employee;
}
function setupSSEHeaders(ctx) {
  ctx.set({
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive"
  });
  ctx.status = 200;
}
function sendErrorResponse(ctx, errorMessage) {
  (0, import_utils.sendSSEError)(ctx, errorMessage);
}
var aiConversations_default = {
  name: "aiConversations",
  actions: {
    async list(ctx, next) {
      var _a;
      const userId = (_a = ctx.auth) == null ? void 0 : _a.user.id;
      if (!userId) {
        return ctx.throw(403);
      }
      ctx.action.mergeParams({
        filter: {
          userId
        }
      });
      return import_actions.default.list(ctx, next);
    },
    async create(ctx, next) {
      var _a;
      const userId = (_a = ctx.auth) == null ? void 0 : _a.user.id;
      const { aiEmployee, systemMessage, skillSettings, conversationSettings } = ctx.action.params.values || {};
      const employee = await getAIEmployee(ctx, aiEmployee.username);
      if (!employee) {
        ctx.throw(400, "AI employee not found");
      }
      const repo = ctx.db.getRepository("aiConversations");
      ctx.body = await repo.create({
        values: {
          userId,
          aiEmployee,
          options: {
            systemMessage,
            skillSettings,
            conversationSettings
          },
          thread: 1
        }
      });
      await next();
    },
    async update(ctx, next) {
      var _a;
      const userId = (_a = ctx.auth) == null ? void 0 : _a.user.id;
      const { filterByTk: sessionId } = ctx.action.params;
      const { title } = ctx.action.params.values || {};
      const repo = ctx.db.getRepository("aiConversations");
      ctx.body = await repo.update({
        filter: {
          userId,
          sessionId
        },
        values: {
          title
        }
      });
      await next();
    },
    async updateOptions(ctx, next) {
      var _a;
      const userId = (_a = ctx.auth) == null ? void 0 : _a.user.id;
      if (!userId) {
        return ctx.throw(403);
      }
      const { filterByTk: sessionId } = ctx.action.params;
      if (!sessionId) {
        return ctx.throw(400, "invalid sessionId");
      }
      const { systemMessage, skillSettings, conversationSettings } = ctx.action.params.values || {};
      if (!systemMessage && !skillSettings && !conversationSettings) {
        return ctx.throw(400, "invalid options");
      }
      const conversation = await ctx.db.getRepository("aiConversations").findOne({
        filter: {
          sessionId,
          userId
        }
      });
      if (!conversation) {
        ctx.throw(400, "invalid sessionId");
      }
      const options = conversation.options ?? {};
      if (systemMessage) {
        options["systemMessage"] = systemMessage;
      }
      if (skillSettings) {
        options["skillSettings"] = skillSettings;
      }
      if (conversationSettings) {
        options["conversationSettings"] = conversationSettings;
      }
      ctx.body = await ctx.db.getRepository("aiConversations").update({
        filter: {
          userId,
          sessionId
        },
        values: {
          options
        }
      });
      await next();
    },
    async destroy(ctx, next) {
      var _a;
      const userId = (_a = ctx.auth) == null ? void 0 : _a.user.id;
      if (!userId) {
        return ctx.throw(403);
      }
      ctx.action.mergeParams({
        filter: {
          userId
        }
      });
      return import_actions.default.destroy(ctx, next);
    },
    async getMessages(ctx, next) {
      var _a, _b;
      const plugin = ctx.app.pm.get("ai");
      const userId = (_a = ctx.auth) == null ? void 0 : _a.user.id;
      if (!userId) {
        return ctx.throw(403);
      }
      const { sessionId, cursor } = ctx.action.params || {};
      if (!sessionId) {
        ctx.throw(400);
      }
      const paginate = ((_b = ctx.action.params) == null ? void 0 : _b.paginate) === "false" ? false : true;
      const conversation = await ctx.db.getRepository("aiConversations").findOne({
        filter: {
          sessionId,
          userId
        }
      });
      if (!conversation) {
        ctx.throw(400);
      }
      const pageSize = 10;
      const maxLimit = 200;
      const messageRepository = ctx.db.getRepository("aiConversations.messages", sessionId);
      const filter = {
        role: {
          $notIn: ["tool"]
        }
      };
      if (paginate && cursor) {
        filter["messageId"] = {
          $lt: cursor
        };
      }
      const rows = await messageRepository.find({
        sort: ["-messageId"],
        limit: paginate ? pageSize + 1 : maxLimit,
        filter
      });
      const hasMore = paginate && rows.length > pageSize;
      const data = hasMore ? rows.slice(0, -1) : rows;
      const newCursor = data.length ? data[data.length - 1].messageId : null;
      const toolCallIds = data.filter((row) => {
        var _a2;
        return ((_a2 = row == null ? void 0 : row.toolCalls) == null ? void 0 : _a2.length) ?? 0 > 0;
      }).flatMap((row) => row.toolCalls).map((toolCall) => toolCall.id);
      const toolMessages = await ctx.db.getRepository("aiToolMessages").find({
        filter: {
          sessionId,
          toolCallId: {
            $in: toolCallIds
          }
        }
      });
      const toolMessageKey = (messageId, toolCallId) => `${messageId}:${toolCallId}`;
      const toolMessageMap = new Map(
        toolMessages.map((toolMessage) => [
          toolMessageKey(toolMessage.messageId, toolMessage.toolCallId),
          toolMessage
        ])
      );
      const toolsList = await plugin.ai.toolsManager.listTools();
      const toolsMap = new Map(toolsList.map((t) => [t.definition.name, t]));
      ctx.body = {
        rows: data.map((row) => {
          var _a2, _b2;
          if (((_a2 = row == null ? void 0 : row.toolCalls) == null ? void 0 : _a2.length) ?? 0 > 0) {
            for (const toolCall of row.toolCalls) {
              const tools = toolsMap.get(toolCall.name);
              const toolMessage = toolMessageMap.get(toolMessageKey(row.messageId, toolCall.id));
              toolCall.invokeStatus = toolMessage == null ? void 0 : toolMessage.invokeStatus;
              toolCall.auto = toolMessage == null ? void 0 : toolMessage.auto;
              toolCall.status = toolMessage == null ? void 0 : toolMessage.status;
              toolCall.content = toolMessage == null ? void 0 : toolMessage.content;
              toolCall.execution = tools == null ? void 0 : tools.execution;
              toolCall.willInterrupt = (tools == null ? void 0 : tools.execution) === "frontend" || (toolMessage == null ? void 0 : toolMessage.auto) === false;
              toolCall.defaultPermission = tools == null ? void 0 : tools.defaultPermission;
            }
          }
          const providerOptions = plugin.aiManager.llmProviders.get((_b2 = row.metadata) == null ? void 0 : _b2.provider);
          if (!providerOptions) {
            return (0, import_utils.parseResponseMessage)(row);
          }
          const Provider = providerOptions.provider;
          const provider = new Provider({
            app: ctx.app
          });
          return provider.parseResponseMessage(row);
        }),
        ...paginate && {
          hasMore,
          cursor: newCursor
        }
      };
      await next();
    },
    async updateToolArgs(ctx, next) {
      var _a;
      const userId = (_a = ctx.auth) == null ? void 0 : _a.user.id;
      if (!userId) {
        return ctx.throw(403);
      }
      const {
        sessionId,
        messageId,
        tool: { id, args }
      } = ctx.action.params.values || {};
      if (!sessionId) {
        ctx.throw(400);
      }
      const conversation = await ctx.db.getRepository("aiConversations").findOne({
        filter: {
          sessionId,
          userId
        }
      });
      if (!conversation) {
        ctx.throw(400);
      }
      const messageRepository = ctx.db.getRepository("aiConversations.messages", sessionId);
      const message = await messageRepository.findOne({
        filter: { messageId }
      });
      const toolCalls = message.toolCalls || [];
      const index = toolCalls.findIndex((toolCall) => toolCall.id === id);
      if (index === -1) {
        return next();
      }
      toolCalls[index] = {
        ...toolCalls[index],
        args
      };
      await messageRepository.update({
        filter: { messageId },
        values: {
          toolCalls
        }
      });
      await next();
    },
    async sendMessages(ctx, next) {
      var _a, _b;
      const {
        sessionId,
        aiEmployee: employeeName,
        messages,
        editingMessageId,
        model,
        webSearch,
        stream = true
      } = ctx.action.params.values || {};
      const shouldStream = stream !== false;
      if (shouldStream) {
        setupSSEHeaders(ctx);
      }
      if (!sessionId) {
        if (shouldStream) {
          sendErrorResponse(ctx, "sessionId is required");
        } else {
          ctx.status = 400;
          ctx.body = { error: "sessionId is required" };
        }
        return next();
      }
      const userMessage = messages.find((message) => message.role === "user");
      if (!userMessage) {
        if (shouldStream) {
          sendErrorResponse(ctx, "user message is required");
        } else {
          ctx.status = 400;
          ctx.body = { error: "user message is required" };
        }
        return next();
      }
      try {
        const conversation = await ctx.db.getRepository("aiConversations").findOne({
          filterByTk: sessionId
        });
        if (!conversation) {
          if (shouldStream) {
            sendErrorResponse(ctx, "conversation not found");
          } else {
            ctx.status = 404;
            ctx.body = { error: "conversation not found" };
          }
          return next();
        }
        if (!conversation.title) {
          const textUserMessage = messages.find(
            (message) => {
              var _a2, _b2;
              return message.role === "user" && ((_a2 = message.content) == null ? void 0 : _a2.type) === "text" && ((_b2 = message.content) == null ? void 0 : _b2.content);
            }
          );
          if (textUserMessage) {
            const content = textUserMessage.content.content;
            conversation.title = content.substring(0, 30);
            await conversation.save();
          }
        }
        const employee = await getAIEmployee(ctx, employeeName);
        if (!employee) {
          if (shouldStream) {
            sendErrorResponse(ctx, "AI employee not found");
          } else {
            ctx.status = 404;
            ctx.body = { error: "AI employee not found" };
          }
          return next();
        }
        const legacy = conversation.thread === 0;
        const aiEmployee = new import_ai_employee.AIEmployee(
          ctx,
          employee,
          sessionId,
          (_a = conversation.options) == null ? void 0 : _a.systemMessage,
          (_b = conversation.options) == null ? void 0 : _b.skillSettings,
          webSearch,
          model,
          legacy
        );
        if (!editingMessageId) {
          const toolMessages = await aiEmployee.cancelToolCall();
          if (toolMessages == null ? void 0 : toolMessages.length) {
            for (let i = toolMessages.length - 1; i >= 0; i--) {
              const toolMessage = toolMessages[i];
              messages.unshift({
                role: toolMessage.role,
                content: toolMessage.content,
                toolCalls: toolMessage.toolCalls,
                attachments: toolMessage.attachments,
                workContext: toolMessage.workContext,
                metadata: toolMessage.metadata
              });
            }
          }
        }
        if (shouldStream) {
          await aiEmployee.stream({ userMessages: messages, messageId: editingMessageId });
        } else {
          ctx.body = await aiEmployee.invoke({ userMessages: messages, messageId: editingMessageId });
        }
      } catch (err) {
        ctx.log.error(err);
        if (shouldStream) {
          sendErrorResponse(ctx, err.message || "Tool call error");
        } else {
          ctx.status = 500;
          ctx.body = { error: err.message || "Tool call error" };
        }
      }
      await next();
    },
    async abort(ctx, next) {
      const { sessionId } = ctx.action.params.values || {};
      const plugin = ctx.app.pm.get("ai");
      plugin.aiEmployeesManager.abortConversation(sessionId);
      await next();
    },
    async resendMessages(ctx, next) {
      var _a, _b;
      setupSSEHeaders(ctx);
      const { sessionId, webSearch, model } = ctx.action.params.values || {};
      let { messageId } = ctx.action.params.values || {};
      if (!sessionId) {
        sendErrorResponse(ctx, "sessionId is required");
        return next();
      }
      try {
        const conversation = await ctx.db.getRepository("aiConversations").findOne({
          filter: {
            sessionId
          }
        });
        if (!conversation) {
          sendErrorResponse(ctx, "conversation not found");
          return next();
        }
        const resendMessages = [];
        if (messageId) {
          const message = await ctx.db.getRepository("aiConversations.messages", sessionId).findOne({
            filter: {
              messageId
            }
          });
          if (!message) {
            sendErrorResponse(ctx, "message not found");
            return next();
          }
        } else {
          const message = await ctx.db.getRepository("aiConversations.messages", sessionId).findOne({
            filter: {
              sessionId
            },
            sort: ["-messageId"]
          });
          if (!message) {
            sendErrorResponse(ctx, "No messages to resend");
            return next();
          }
          messageId = message.messageId;
          if (["user", "tool"].includes(message.role)) {
            resendMessages.push({
              role: message.role,
              content: message.content,
              toolCalls: message.toolCalls,
              attachments: message.attachments,
              workContext: message.workContext,
              metadata: message.metadata
            });
          }
        }
        const employee = await getAIEmployee(ctx, conversation.aiEmployeeUsername);
        if (!employee) {
          sendErrorResponse(ctx, "AI employee not found");
          return next();
        }
        const aiEmployee = new import_ai_employee.AIEmployee(
          ctx,
          employee,
          sessionId,
          (_a = conversation.options) == null ? void 0 : _a.systemMessage,
          (_b = conversation.options) == null ? void 0 : _b.skillSettings,
          webSearch,
          model
        );
        await aiEmployee.stream({ messageId, userMessages: resendMessages.length ? resendMessages : void 0 });
      } catch (err) {
        ctx.log.error(err);
        sendErrorResponse(ctx, err.message || "Chat error warning");
      }
      await next();
    },
    async updateUserDecision(ctx, next) {
      var _a;
      const plugin = ctx.app.pm.get("ai");
      const userId = (_a = ctx.auth) == null ? void 0 : _a.user.id;
      if (!userId) {
        return ctx.throw(403);
      }
      const { sessionId, messageId, toolCallId, userDecision } = ctx.action.params.values || {};
      if (!sessionId) {
        ctx.throw(400);
      }
      const conversation = await ctx.db.getRepository("aiConversations").findOne({
        filter: {
          sessionId,
          userId
        }
      });
      if (!conversation) {
        ctx.throw(400);
      }
      const message = await ctx.db.getRepository("aiConversations.messages", sessionId).findOne({
        filter: {
          messageId
        }
      });
      if (!message) {
        ctx.throw(400);
      }
      const toolCalls = message.toolCalls;
      if (!(toolCalls == null ? void 0 : toolCalls.length)) {
        ctx.throw(400);
      }
      const aiToolMessagesModel = ctx.db.getModel("aiToolMessages");
      const toolCall = await aiToolMessagesModel.findOne({
        where: { sessionId, messageId, toolCallId }
      });
      if (!toolCall) {
        ctx.throw(400);
      }
      const [updated] = await aiToolMessagesModel.update(
        {
          userDecision,
          invokeStatus: "waiting"
        },
        {
          where: {
            sessionId,
            messageId,
            toolCallId,
            invokeStatus: "interrupted"
          }
        }
      );
      const toolCallIds = toolCalls.map((x) => x.id);
      const toolMessages = await ctx.db.getRepository("aiToolMessages").find({
        filter: {
          sessionId,
          messageId,
          toolCallId: {
            $in: toolCallIds
          }
        }
      });
      const toolMessageMap = new Map(
        toolMessages.map((toolMessage) => [toolMessage.toolCallId, toolMessage])
      );
      const toolsList = await plugin.ai.toolsManager.listTools();
      const toolsMap = new Map(toolsList.map((t) => [t.definition.name, t]));
      for (const toolCall2 of toolCalls) {
        const tools = toolsMap.get(toolCall2.name);
        const toolMessage = toolMessageMap.get(toolCall2.id);
        toolCall2.invokeStatus = toolMessage == null ? void 0 : toolMessage.invokeStatus;
        toolCall2.auto = toolMessage == null ? void 0 : toolMessage.auto;
        toolCall2.status = toolMessage == null ? void 0 : toolMessage.status;
        toolCall2.content = toolMessage == null ? void 0 : toolMessage.content;
        toolCall2.execution = tools == null ? void 0 : tools.execution;
        toolCall2.willInterrupt = (tools == null ? void 0 : tools.execution) === "frontend" || (toolMessage == null ? void 0 : toolMessage.auto) === false;
        toolCall2.defaultPermission = tools == null ? void 0 : tools.defaultPermission;
      }
      ctx.body = {
        updated,
        toolCalls
      };
      await next();
    },
    async resumeToolCall(ctx, next) {
      var _a, _b, _c;
      setupSSEHeaders(ctx);
      const { sessionId, messageId, model, webSearch } = ctx.action.params.values || {};
      if (!sessionId) {
        sendErrorResponse(ctx, "sessionId is required");
        return next();
      }
      try {
        const conversation = await ctx.db.getRepository("aiConversations").findOne({
          filter: {
            sessionId,
            userId: (_a = ctx.auth) == null ? void 0 : _a.user.id
          }
        });
        if (!conversation) {
          sendErrorResponse(ctx, "conversation not found");
          return next();
        }
        const employee = await getAIEmployee(ctx, conversation.aiEmployeeUsername);
        if (!employee) {
          sendErrorResponse(ctx, "AI employee not found");
          return next();
        }
        let message;
        if (messageId) {
          message = await ctx.db.getRepository("aiConversations.messages", sessionId).findOne({
            filter: {
              messageId
            }
          });
        } else {
          message = await ctx.db.getRepository("aiConversations.messages", sessionId).findOne({
            sort: ["-messageId"]
          });
        }
        if (!message) {
          sendErrorResponse(ctx, "message not found");
          return next();
        }
        const tools = message.toolCalls;
        if (!(tools == null ? void 0 : tools.length)) {
          sendErrorResponse(ctx, "No tool calls found");
          return next();
        }
        const aiEmployee = new import_ai_employee.AIEmployee(
          ctx,
          employee,
          sessionId,
          (_b = conversation.options) == null ? void 0 : _b.systemMessage,
          (_c = conversation.options) == null ? void 0 : _c.skillSettings,
          webSearch,
          model
        );
        const userDecisions = await aiEmployee.getUserDecisions(messageId);
        await aiEmployee.stream({
          userDecisions
        });
      } catch (err) {
        ctx.log.error(err);
        sendErrorResponse(ctx, err.message || "Tool call error");
      }
      await next();
    }
  }
};
