You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

140 lines
4.0 KiB

11 months ago
  1. // Plugin CAN ONLY BE USE IN DEVELOPMENT.
  2. const { input } = require("@inquirer/prompts");
  3. const chalk = require("chalk");
  4. const { RetryError } = require("../error");
  5. /**
  6. * Command-line Interface plugin. It prints the messages on the console and asks for feedback
  7. * while the conversation is running in the background.
  8. */
  9. const cli = {
  10. name: "cli",
  11. startupConfig: {
  12. params: {},
  13. },
  14. plugin: function ({ simulateStream = true } = {}) {
  15. return {
  16. name: this.name,
  17. setup(aibitat) {
  18. let printing = [];
  19. aibitat.onError(async (error) => {
  20. console.error(chalk.red(` error: ${error?.message}`));
  21. if (error instanceof RetryError) {
  22. console.error(chalk.red(` retrying in 60 seconds...`));
  23. setTimeout(() => {
  24. aibitat.retry();
  25. }, 60000);
  26. return;
  27. }
  28. });
  29. aibitat.onStart(() => {
  30. console.log();
  31. console.log("🚀 starting chat ...\n");
  32. printing = [Promise.resolve()];
  33. });
  34. aibitat.onMessage(async (message) => {
  35. const next = new Promise(async (resolve) => {
  36. await Promise.all(printing);
  37. await this.print(message, simulateStream);
  38. resolve();
  39. });
  40. printing.push(next);
  41. });
  42. aibitat.onTerminate(async () => {
  43. await Promise.all(printing);
  44. console.log("🚀 chat finished");
  45. });
  46. aibitat.onInterrupt(async (node) => {
  47. await Promise.all(printing);
  48. const feedback = await this.askForFeedback(node);
  49. // Add an extra line after the message
  50. console.log();
  51. if (feedback === "exit") {
  52. console.log("🚀 chat finished");
  53. return process.exit(0);
  54. }
  55. await aibitat.continue(feedback);
  56. });
  57. },
  58. /**
  59. * Print a message on the terminal
  60. *
  61. * @param message
  62. * // message Type { from: string; to: string; content?: string } & {
  63. state: 'loading' | 'error' | 'success' | 'interrupt'
  64. }
  65. * @param simulateStream
  66. */
  67. print: async function (message = {}, simulateStream = true) {
  68. const replying = chalk.dim(`(to ${message.to})`);
  69. const reference = `${chalk.magenta("✎")} ${chalk.bold(
  70. message.from
  71. )} ${replying}:`;
  72. if (!simulateStream) {
  73. console.log(reference);
  74. console.log(message.content);
  75. // Add an extra line after the message
  76. console.log();
  77. return;
  78. }
  79. process.stdout.write(`${reference}\n`);
  80. // Emulate streaming by breaking the cached response into chunks
  81. const chunks = message.content?.split(" ") || [];
  82. const stream = new ReadableStream({
  83. async start(controller) {
  84. for (const chunk of chunks) {
  85. const bytes = new TextEncoder().encode(chunk + " ");
  86. controller.enqueue(bytes);
  87. await new Promise((r) =>
  88. setTimeout(
  89. r,
  90. // get a random number between 10ms and 50ms to simulate a random delay
  91. Math.floor(Math.random() * 40) + 10
  92. )
  93. );
  94. }
  95. controller.close();
  96. },
  97. });
  98. // Stream the response to the chat
  99. for await (const chunk of stream) {
  100. process.stdout.write(new TextDecoder().decode(chunk));
  101. }
  102. // Add an extra line after the message
  103. console.log();
  104. console.log();
  105. },
  106. /**
  107. * Ask for feedback to the user using the terminal
  108. *
  109. * @param node //{ from: string; to: string }
  110. * @returns
  111. */
  112. askForFeedback: function (node = {}) {
  113. return input({
  114. message: `Provide feedback to ${chalk.yellow(
  115. node.to
  116. )} as ${chalk.yellow(
  117. node.from
  118. )}. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: `,
  119. });
  120. },
  121. };
  122. },
  123. };
  124. module.exports = { cli };