[
  {
    "path": ".dockerignore",
    "content": "/node_modules\n"
  },
  {
    "path": ".eslintrc.cjs",
    "content": "module.exports = {\n  env: {\n    commonjs: true,\n    es2021: true,\n    node: true,\n  },\n  extends: 'airbnb',\n  overrides: [\n    {\n      files: [\n        'config/index.js',\n      ],\n      rules: {\n        'max-len': 'off',\n      },\n    },\n  ],\n  parserOptions: {\n    ecmaVersion: 'latest',\n  },\n  rules: {\n    'import/extensions': ['error', 'always', { ignorePackages: true }],\n    'no-console': 'off',\n    'no-param-reassign': 'off',\n    'no-unused-vars': 'off',\n    'max-len': 'off',\n  },\n};\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "content": "---\nname: Question\nabout: Ask a question about this project\ntitle: ''\nlabels: question\nassignees: ''\n\n---\n\n\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n.vscode\n.env\n.env.*\n"
  },
  {
    "path": ".npmrc",
    "content": "tag-version-prefix=\"\"\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 4.9.1 (2024-07-10)\n\n### Bug Fixes\n\n- Update `talk` command\n\n## 4.9.0 (2024-07-10)\n\n### New Features\n\n- Support `gpt-4o` model\n\n## 4.8.4 (2024-07-06)\n\n### Bug Fixes\n\n- Update status page\n\n## 4.8.3 (2024-02-03)\n\n### Bug Fixes\n\n- Fix `maxDuration` for `vercel.json`\n\n## 4.8.2 (2024-02-03)\n\n### Bug Fixes\n\n- Use `gl` param for SerpApi\n- Remove `SERPAPI_LANG` environment variable\n\n## 4.8.1 (2024-02-03)\n\n### Bug Fixes\n\n- Add `maxDuration` for `vercel.json`\n\n## 4.8.0 (2023-12-07)\n\n### New Features\n\n- Support fine-tuned models\n\n## 4.7.6 (2023-11-18)\n\n### Bug Fixes\n\n- Change default max groups to 1000\n- Change default max users to 1000\n- Change default max prompt messages to 4\n- Change default max prompt tokens to 160\n- Change default completion temperature to 1\n- Change default completion max tokens to 64\n\n## 4.7.5 (2023-10-01)\n\n### Bug Fixes\n\n- Update status page\n\n## 4.7.4 (2023-08-26)\n\n### Bug Fixes\n\n- Update status page\n\n## 4.7.3 (2023-08-25)\n\n### Bug Fixes\n\n- Fix commands\n\n## 4.7.2 (2023-08-05)\n\n### Bug Fixes\n\n- Fix `translate` command\n\n## 4.7.1 (2023-08-01)\n\n### Bug Fixes\n\n- Optimize `search` command\n- Add aliases for commands\n\n## 4.7.0 (2023-06-08)\n\n### New Features\n\n- Add `OPENAI_COMPLETION_STOP_SEQUENCES` environment variable\n\n## 4.6.0 (2023-05-03)\n\n### New Features\n\n- Support `gpt-4` model\n\n## 4.5.0 (2023-04-27)\n\n### New Features\n\n- Support `zh_CN` locale\n\n## 4.4.4 (2023-03-21)\n\n### Bug Fixes\n\n- Fix default value of `APP_MAX_GROUPS` environment variable\n- Fix default value of `APP_MAX_USERS` environment variable\n\n## 4.4.3 (2023-03-11)\n\n### Bug Fixes\n\n- Fix wording of `doc` and `report` commands\n\n## 4.4.2 (2023-03-11)\n\n### Bug Fixes\n\n- Add `ERROR_MESSAGE_DISABLED` environment variable\n- Deprecate `ERROR_TIMEOUT_DISABLED` environment variable\n\n## 4.4.1 (2023-03-10)\n\n### Bug Fixes\n\n- Add default max prompt tokens for chat completion api\n\n## 4.4.0 (2023-03-08)\n\n### New Features\n\n- Support snapshots of `gpt-3.5-turbo` model\n\n## 4.3.0 (2023-03-08)\n\n### New Features\n\n- Add `VERCEL_TEAM_ID` environment variable\n\n## 4.2.2 (2023-03-08)\n\n### Bug Fixes\n\n- Optimize error handling\n\n## 4.2.1 (2023-03-07)\n\n### Bug Fixes\n\n- Fix `add-mark` util\n\n## 4.2.0 (2023-03-05)\n\n### New Features\n\n- Add `APP_INIT_PROMPT` environment variable\n\n## 4.1.3 (2023-03-05)\n\n### Bug Fixes\n\n- Fix `add-mark` util\n\n## 4.1.2 (2023-03-05)\n\n### Bug Fixes\n\n- Update `add-mark` util\n\n## 4.1.1 (2023-03-05)\n\n### Bug Fixes\n\n- End text with dot\n\n## 4.1.0 (2023-03-05)\n\n- Support `whisper-1` model\n- Add `opencc` text converter\n- Store display name and group name to storage\n\n## 4.0.4 (2023-03-03)\n\n### Bug Fixes\n\n- Optimize `search` command\n\n## 4.0.3 (2023-03-03)\n\n### Bug Fixes\n\n- Optimize `search` and `draw` commands\n\n## 4.0.2 (2023-03-02)\n\n### Bug Fixes\n\n- Fix prompt messages\n\n## 4.0.1 (2023-03-02)\n\n### Bug Fixes\n\n- Fix `enquire` command\n\n## 4.0.0 (2023-03-02)\n\n### New Features\n\n- Support `gpt-3.5-turbo` model\n\n### Bug Fixes\n\n- Rename `APP_MAX_PROMPT_SENTENCES` environment variable to `APP_MAX_PROMPT_MESSAGES`\n\n## 3.7.0 (2023-02-26)\n\n### New Features\n\n- Add demo for `search` command\n- Add `SERPAPI_LOCATION` environment variable\n- Add `SERPAPI_LANG` environment variable\n\n## 3.6.0 (2023-02-26)\n\n### New Features\n\n- Add `APP_API_TIMEOUT` environment variable\n- Add `APP_MAX_PROMPT_SENTENCES` environment variable\n- Add `APP_MAX_PROMPT_TOKENS` environment variable\n\n## 3.5.0 (2023-02-26)\n\n### New Features\n\n- Rename `HUMAN_BACKGROUND` environment variable to `HUMAN_INIT_PROMPT`\n- Rename `BOT_BACKGROUND` environment variable to `BOT_INIT_PROMPT`\n\n## 3.4.1 (2023-02-25)\n\n### Bug Fixes\n\n- Fix default bot name\n\n## 3.4.0 (2023-02-24)\n\n### New Features\n\n- Add `info` endpoint\n\n## 3.3.5 (2023-02-24)\n\n### Bug Fixes\n\n- Fix prompt wording\n\n## 3.3.4 (2023-02-24)\n\n### Bug Fixes\n\n- Fix prompt wording\n\n## 3.3.3 (2023-02-24)\n\n### Bug Fixes\n\n- Fix tests\n\n## 3.3.2 (2023-02-23)\n\n### Bug Fixes\n\n- Fix prompt wording\n\n## 3.3.1 (2023-02-23)\n\n### Bug Fixes\n\n- Fix prompt wording\n\n## 3.3.0 (2023-02-23)\n\n### New Features\n\n- Add `BOT_TONE` environment variable\n\n## 3.2.1 (2023-02-22)\n\n### Bug Fixes\n\n- Fix timeout wording\n\n## 3.2.0 (2023-02-22)\n\n### New Features\n\n- Add `HUMAN_NAME` environment variable\n- Add `HUMAN_BACKGROUND` environment variable\n- Add `BOT_BACKGROUND` environment variable\n\n## 3.1.0 (2023-02-21)\n\n### New Features\n\n- Implement `forget` command\n\n## 3.0.0 (2023-02-18)\n\n### New Features\n\n- Implement `search` command\n\n## 2.5.1 (2023-02-18)\n\n### New Features\n\n- Rename `BOT_TIMEOUT_DISABLED` environment variable to `ERROR_TIMEOUT_DISABLED`\n\n## 2.5.0 (2023-02-18)\n\n### New Features\n\n- Add `BOT_TIMEOUT_DISABLED` environment variable\n\n## 2.4.0 (2023-02-17)\n\n### New Features\n\n- Add `BOT_DEACTIVATED` environment variable\n\n## 2.3.0 (2023-02-11)\n\n### New Features\n\n- Add `VERCEL_TIMEOUT` environment variable\n- Add `OPENAI_TIMEOUT` environment variable\n- Add `LINE_TIMEOUT` environment variable\n\n## 2.2.0 (2023-02-04)\n\n### New Features\n\n- Implement `retry` command\n\n## 2.1.4 (2023-01-15)\n\n### Bug Fixes\n\n- Ignore non-text message events\n\n## 2.1.3 (2023-01-15)\n\n### Bug Fixes\n\n- Add command aliases\n\n## 2.1.2 (2023-01-15)\n\n### Bug Fixes\n\n- Add command aliases\n\n## 2.1.1 (2023-01-14)\n\n### Bug Fixes\n\n- Fix `enquire` command\n\n## 2.1.0 (2023-01-11)\n\n### New Features\n\n- Add `VERCEL_PROJECT_NAME` environment variable\n\n## 2.0.1 (2023-01-11)\n\n### Bug Fixes\n\n- Add logs for webhook endpoint\n\n## 2.0.0 (2023-01-10)\n\n### New Features\n\n- Implement `sum` command\n- Implement `analyze` command\n- Implement `translate` command\n- Add `BOT_NAME` environment variable\n- Add `APP_MAX_GROUPS` environment variable\n- Add `APP_MAX_USERS` environment variable\n\n### Bug Fixes\n\n- Remove `SETTING_AI_NAME` environment variable\n- Remove `SETTING_AI_ACTIVATED` environment variable\n- Refactor `storage` module\n- Refactor `prompt` module\n- Refactor `history` module\n\n## 1.12.4 (2022-12-31)\n\n### Bug Fixes\n\n- Rename `chat` command to `talk`\n\n## 1.12.3 (2022-12-31)\n\n### Bug Fixes\n\n- Update command template\n\n## 1.12.2 (2022-12-30)\n\n### Bug Fixes\n\n- Fix summarize request wording\n\n## 1.12.1 (2022-12-30)\n\n### Bug Fixes\n\n- Handle non-text messages\n\n## 1.12.0 (2022-12-30)\n\n### New Features\n\n- Implement `summarize` command\n\n## 1.11.3 (2022-12-29)\n\n### Bug Fixes\n\n- Add command aliases\n\n## 1.11.2 (2022-12-26)\n\n### Bug Fixes\n\n- Handle error messages in every commands\n\n## 1.11.1 (2022-12-26)\n\n### Bug Fixes\n\n- Trim AI Name when sending prompt\n\n## 1.11.0 (2022-12-26)\n\n### New Features\n\n- Implement `call` command\n- Add `SETTING_AI_NAME` environment variable\n- Add `SETTING_AI_ACTIVATED` environment variable\n\n### Bug Fixes\n\n- Remove `APP_STORAGE` environment variable\n\n## 1.10.2 (2022-12-25)\n\n### Bug Fixes\n\n- Rename methods\n\n## 1.10.1 (2022-12-25)\n\n### Bug Fixes\n\n- Fix wording of commands\n\n## 1.10.0 (2022-12-25)\n\n### New Features\n\n- Add `OPENAI_IMAGE_GENERATION_SIZE` environment variable\n\n### Bug Fixes\n\n- Remove `SETTING_IMAGE_GENERATION_SIZE` setting\n\n## 1.9.1 (2022-12-24)\n\n### Bug Fixes\n\n- Rename functions and variables\n\n## 1.9.0 (2022-12-24)\n\n### New Features\n\n- Implement dynamic configuration\n- Implement `configure` command\n- Add `SETTING_IMAGE_GENERATION_SIZE` setting\n\n## 1.8.0 (2022-12-24)\n\n### New Features\n\n- Implement `doc` command\n\n### Bug Fixes\n\n- Rename `settings` command to `command`\n\n## 1.7.1 (2022-12-23)\n\n### Bug Fixes\n\n- Fix wording of commands\n\n## 1.7.0 (2022-12-23)\n\n### New Features\n\n- Implement localization\n- Implement command aliases\n\n### Bug Fixes\n\n- Rename `OPENAI_COMPLETION_INIT_LANG` environment variable to `APP_LANG`\n\n## 1.6.0 (2022-12-23)\n\n### New Features\n\n- Implement `settings` command\n\n### Bug Fixes\n\n- Rename `chat --auto-reply off` command to `deactivate`\n- Rename `chat --auto-reply on` command to `activate`\n- Rename `CHAT_AUTO_REPLY` setting to `AI_ACTIVATED`\n\n## 1.5.0 (2022-12-22)\n\n### New Features\n\n- Implement `continue` command with quick reply feature\n\n### Bug Fixes\n\n- Change default max completion tokens to 160\n- Change default max prompt messages to 16\n\n## 1.4.6 (2022-12-20)\n\n### Bug Fixes\n\n- Add comments\n\n## 1.4.5 (2022-12-19)\n\n### Bug Fixes\n\n- Add `ja` initial language\n- Add `ai` alias for `chat` command\n\n## 1.4.4 (2022-12-18)\n\n### Bug Fixes\n\n- Rename `AI_AUTO_REPLY` setting to `CHAT_AUTO_REPLY`\n- Fix case sensitivity of command issues\n\n## 1.4.3 (2022-12-18)\n\n### Bug Fixes\n\n- Rename `ai` command to `chat`\n- Rename `ai --auto-reply off` command to `chat --auto-reply off`\n- Rename `ai --auto-reply on` command to `chat --auto-reply on`\n- Rename `image` command to `draw`\n\n## 1.4.2 (2022-12-18)\n\n### Bug Fixes\n\n- Refactor commands\n\n## 1.4.1 (2022-12-18)\n\n### Bug Fixes\n\n- Refactor tests\n\n## 1.4.0 (2022-12-18)\n\n### New Features\n\n- Implement `image` command\n\n## 1.3.1 (2022-12-18)\n\n### Bug Fixes\n\n- Rename `VERCEL_WEBHOOK_URL` environment variable to `VERCEL_DEPLOY_HOOK_URL`\n\n## 1.3.0 (2022-12-18)\n\n### New Features\n\n- Implement custom webhook path\n- Add `APP_WEBHOOK_PATH` environment variable\n\n## 1.2.1 (2022-12-18)\n\n### Bug Fixes\n\n- Refactor main functions\n\n## 1.2.0 (2022-12-17)\n\n### New Features\n\n- Implement `deploy` command\n- Add `VERCEL_WEBHOOK_URL` environment variable\n\n## 1.1.3 (2022-12-17)\n\n### Bug Fixes\n\n- Fix storage module\n- Fix `ai --auto-reply off` command\n- Fix `ai --auto-reply on` command\n\n## 1.1.2 (2022-12-16)\n\n### Bug Fixes\n\n- Refactor utility functions\n\n## 1.1.1 (2022-12-16)\n\n### Bug Fixes\n\n- Rename `VERCEL_API_KEY` environment variable to `VERCEL_ACCESS_TOKEN`\n- Rename `LINE_API_KEY` environment variable to `LINE_CHANNEL_ACCESS_TOKEN`\n- Rename `LINE_API_SECRET` environment variable to `LINE_CHANNEL_SECRET`\n\n## 1.1.0 (2022-12-16)\n\n### New Features\n\n- Implement `version` command\n- Implement `ai` command\n- Implement `ai --auto-reply off` command\n- Implement `ai --auto-reply on` command\n- Add Vercel API module\n- Add `VERCEL_API_KEY` environment variable\n- Add `LINE_API_SECRET` environment variable\n\n### Bug Fixes\n\n- Fix timeout issues\n\n## 1.0.0 (2022-12-11)\n\n### New Features\n\n- Implement chat feature\n- Add OpenAI API module\n- Add LINE API module\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:18-alpine\n\nWORKDIR /app\n\nCOPY . .\n\nRUN npm ci --only=production\n\nCMD [ \"npm\", \"start\" ]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Memo Chou\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# GPT AI Assistant\n\n<div align=\"center\">\n\n[![license](https://img.shields.io/pypi/l/ansicolortags.svg)](LICENSE) [![Release](https://img.shields.io/github/release/memochou1993/gpt-ai-assistant)](https://GitHub.com/memochou1993/gpt-ai-assistant/releases/)\n\n</div>\n\nGPT AI Assistant is an application that is implemented using the OpenAI API and LINE Messaging API. Through the installation process, you can start chatting with your own AI assistant using the LINE mobile app.\n\n## News\n\n- 2024-07-10: The `4.9` version now support `gpt-4o` OpenAI model. :fire:\n- 2023-05-03: The `4.6` version now support `gpt-4` OpenAI model.\n- 2023-03-05: The `4.1` version now support the audio message of LINE and  `whisper-1` OpenAI model.\n- 2023-03-02: The `4.0` version now support `gpt-3.5-turbo` OpenAI model.\n\n## Documentations\n\n- <a href=\"https://memochou1993.github.io/gpt-ai-assistant-docs/\" target=\"_blank\">中文</a>\n- <a href=\"https://memochou1993.github.io/gpt-ai-assistant-docs/en\" target=\"_blank\">English</a>\n\n## Credits\n\n- [jayer95](https://github.com/jayer95) - Debugging and testing\n- [kkdai](https://github.com/kkdai) - Idea of `sum` command\n- [Dayu0815](https://github.com/Dayu0815) - Idea of `search` command\n- [mics8128](https://github.com/mics8128) - Implementing new features\n- [myh-st](https://github.com/myh-st) - Implementing new features\n- [Jakevin](https://github.com/Jakevin) - Implementing new features\n- [cdcd72](https://github.com/cdcd72) - Implementing new features\n- [All other contributors](https://github.com/memochou1993/gpt-ai-assistant/graphs/contributors)\n\n## Contact\n\nIf there is any question, please contact me at memochou1993@gmail.com. Thank you.\n\n## Changelog\n\nDetailed changes for each release are documented in the [release notes](https://github.com/memochou1993/gpt-ai-assistant/releases).\n\n## License\n\n[MIT](LICENSE)\n"
  },
  {
    "path": "api/index.js",
    "content": "import express from 'express';\nimport { handleEvents, printPrompts } from '../app/index.js';\nimport config from '../config/index.js';\nimport { validateLineSignature } from '../middleware/index.js';\nimport storage from '../storage/index.js';\nimport { fetchVersion, getVersion } from '../utils/index.js';\n\nconst app = express();\n\napp.use(express.json({\n  verify: (req, res, buf) => {\n    req.rawBody = buf.toString();\n  },\n}));\n\napp.get('/', async (req, res) => {\n  if (config.APP_URL) {\n    res.redirect(config.APP_URL);\n    return;\n  }\n  const currentVersion = getVersion();\n  const latestVersion = await fetchVersion();\n  res.status(200).send({ status: 'OK', currentVersion, latestVersion });\n});\n\napp.post(config.APP_WEBHOOK_PATH, validateLineSignature, async (req, res) => {\n  try {\n    await storage.initialize();\n    await handleEvents(req.body.events);\n    res.sendStatus(200);\n  } catch (err) {\n    console.error(err.message);\n    res.sendStatus(500);\n  }\n  if (config.APP_DEBUG) printPrompts();\n});\n\nif (config.APP_PORT) {\n  app.listen(config.APP_PORT);\n}\n\nexport default app;\n"
  },
  {
    "path": "app/actions/action.js",
    "content": "class Action {\n  type;\n}\n\nexport default Action;\n"
  },
  {
    "path": "app/actions/index.js",
    "content": "import Action from './action.js';\nimport MessageAction from './message.js';\n\nexport {\n  Action,\n  MessageAction,\n};\n"
  },
  {
    "path": "app/actions/message.js",
    "content": "import { ACTION_TYPE_MESSAGE } from '../../services/line.js';\nimport Action from './action.js';\n\nclass MessageAction extends Action {\n  type = ACTION_TYPE_MESSAGE;\n\n  label;\n\n  text;\n\n  constructor({\n    label,\n    text,\n  }) {\n    super();\n    this.label = label;\n    this.text = text;\n  }\n}\n\nexport default MessageAction;\n"
  },
  {
    "path": "app/app.js",
    "content": "import { replyMessage } from '../utils/index.js';\nimport {\n  activateHandler,\n  commandHandler,\n  continueHandler,\n  deactivateHandler,\n  deployHandler,\n  docHandler,\n  drawHandler,\n  forgetHandler,\n  enquireHandler,\n  reportHandler,\n  retryHandler,\n  searchHandler,\n  talkHandler,\n  versionHandler,\n} from './handlers/index.js';\nimport Context from './context.js';\nimport Event from './models/event.js';\n\n/**\n * @param {Context} context\n * @returns {Promise<Context>}\n */\nconst handleContext = async (context) => (\n  activateHandler(context)\n  || commandHandler(context)\n  || continueHandler(context)\n  || deactivateHandler(context)\n  || deployHandler(context)\n  || docHandler(context)\n  || drawHandler(context)\n  || forgetHandler(context)\n  || enquireHandler(context)\n  || reportHandler(context)\n  || retryHandler(context)\n  || searchHandler(context)\n  || versionHandler(context)\n  || talkHandler(context)\n  || context\n);\n\nconst handleEvents = async (events = []) => (\n  (Promise.all(\n    (await Promise.all(\n      (await Promise.all(\n        events\n          .map((event) => new Event(event))\n          .filter((event) => event.isMessage)\n          .filter((event) => event.isText || event.isAudio || event.isImage)\n          .map((event) => new Context(event))\n          .map((context) => context.initialize()),\n      ))\n        .map((context) => (context.error ? context : handleContext(context))),\n    ))\n      .filter((context) => context.messages.length > 0)\n      .map((context) => replyMessage(context)),\n  ))\n);\n\nexport default handleEvents;\n"
  },
  {
    "path": "app/commands/analyze-analyze.js",
    "content": "import { TYPE_ANALYZE } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_ANALYZE,\n  label: t('__COMMAND_ANALYZE_ANALYZE_LABEL'),\n  text: t('__COMMAND_ANALYZE_ANALYZE_TEXT'),\n  prompt: t('__COMMAND_ANALYZE_ANALYZE_PROMPT'),\n  aliases: [\n    '/analyze',\n    'Analyze',\n  ],\n});\n"
  },
  {
    "path": "app/commands/analyze-literarily.js",
    "content": "import { TYPE_ANALYZE } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_ANALYZE,\n  label: t('__COMMAND_ANALYZE_LITERARILY_LABEL'),\n  text: t('__COMMAND_ANALYZE_LITERARILY_TEXT'),\n  prompt: t('__COMMAND_ANALYZE_LITERARILY_PROMPT'),\n  aliases: [\n    '/analyze-literarily',\n    'Analyze literarily',\n  ],\n});\n"
  },
  {
    "path": "app/commands/analyze-mathematically.js",
    "content": "import { TYPE_ANALYZE } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_ANALYZE,\n  label: t('__COMMAND_ANALYZE_MATHEMATICALLY_LABEL'),\n  text: t('__COMMAND_ANALYZE_MATHEMATICALLY_TEXT'),\n  prompt: t('__COMMAND_ANALYZE_MATHEMATICALLY_PROMPT'),\n  aliases: [\n    '/analyze-mathematically',\n    'Analyze mathematically',\n  ],\n});\n"
  },
  {
    "path": "app/commands/analyze-numerologically.js",
    "content": "import { TYPE_ANALYZE } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_ANALYZE,\n  label: t('__COMMAND_ANALYZE_NUMEROLOGICALLY_LABEL'),\n  text: t('__COMMAND_ANALYZE_NUMEROLOGICALLY_TEXT'),\n  prompt: t('__COMMAND_ANALYZE_NUMEROLOGICALLY_PROMPT'),\n  aliases: [\n    '/analyze-numerologically',\n    'Analyze numerologically',\n  ],\n});\n"
  },
  {
    "path": "app/commands/analyze-philosophically.js",
    "content": "import { TYPE_ANALYZE } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_ANALYZE,\n  label: t('__COMMAND_ANALYZE_PHILOSOPHICALLY_LABEL'),\n  text: t('__COMMAND_ANALYZE_PHILOSOPHICALLY_TEXT'),\n  prompt: t('__COMMAND_ANALYZE_PHILOSOPHICALLY_PROMPT'),\n  aliases: [\n    '/analyze-philosophically',\n    'Analyze philosophically',\n  ],\n});\n"
  },
  {
    "path": "app/commands/analyze-psychologically.js",
    "content": "import { TYPE_ANALYZE } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_ANALYZE,\n  label: t('__COMMAND_ANALYZE_PSYCHOLOGICALLY_LABEL'),\n  text: t('__COMMAND_ANALYZE_PSYCHOLOGICALLY_TEXT'),\n  prompt: t('__COMMAND_ANALYZE_PSYCHOLOGICALLY_PROMPT'),\n  aliases: [\n    '/analyze-psychologically',\n    'Analyze psychologically',\n  ],\n});\n"
  },
  {
    "path": "app/commands/bot-activate.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_BOT_ACTIVATE_LABEL'),\n  text: t('__COMMAND_BOT_ACTIVATE_TEXT'),\n  reply: t('__COMMAND_BOT_ACTIVATE_REPLY'),\n  aliases: [\n    ...t('__COMMAND_BOT_ACTIVATE_ALIASES'),\n    '/activate',\n    'Activate',\n  ],\n});\n"
  },
  {
    "path": "app/commands/bot-continue.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_BOT_CONTINUE_LABEL'),\n  text: t('__COMMAND_BOT_CONTINUE_TEXT'),\n  aliases: [\n    ...t('__COMMAND_BOT_CONTINUE_ALIASES'),\n    '/continue',\n    'Continue',\n  ],\n});\n"
  },
  {
    "path": "app/commands/bot-deactivate.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_BOT_DEACTIVATE_LABEL'),\n  text: t('__COMMAND_BOT_DEACTIVATE_TEXT'),\n  reply: t('__COMMAND_BOT_DEACTIVATE_REPLY'),\n  aliases: [\n    ...t('__COMMAND_BOT_DEACTIVATE_ALIASES'),\n    '/deactivate',\n    'Deactivate',\n  ],\n});\n"
  },
  {
    "path": "app/commands/bot-draw-demo.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_BOT_DRAW_DEMO_LABEL'),\n  text: t('__COMMAND_BOT_DRAW_DEMO_TEXT'),\n});\n"
  },
  {
    "path": "app/commands/bot-draw.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_BOT_DRAW_LABEL'),\n  text: t('__COMMAND_BOT_DRAW_TEXT'),\n  prompt: t('__COMMAND_BOT_DRAW_PROMPT'),\n  aliases: [\n    ...t('__COMMAND_BOT_DRAW_ALIASES'),\n    '/draw',\n    'Draw',\n  ],\n});\n"
  },
  {
    "path": "app/commands/bot-forget.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_BOT_FORGET_LABEL'),\n  text: t('__COMMAND_BOT_FORGET_TEXT'),\n  reply: t('__COMMAND_BOT_FORGET_REPLY'),\n  aliases: [\n    ...t('__COMMAND_BOT_FORGET_ALIASES'),\n    '/forget',\n    'Forget',\n  ],\n});\n"
  },
  {
    "path": "app/commands/bot-retry.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_BOT_RETRY_LABEL'),\n  text: t('__COMMAND_BOT_RETRY_TEXT'),\n});\n"
  },
  {
    "path": "app/commands/bot-search-demo.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_BOT_SEARCH_DEMO_LABEL'),\n  text: t('__COMMAND_BOT_SEARCH_DEMO_TEXT'),\n});\n"
  },
  {
    "path": "app/commands/bot-search.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_BOT_SEARCH_LABEL'),\n  text: t('__COMMAND_BOT_SEARCH_TEXT'),\n  aliases: [\n    ...t('__COMMAND_BOT_SEARCH_ALIASES'),\n    '/search',\n    'Search',\n  ],\n});\n"
  },
  {
    "path": "app/commands/bot-summon-demo.js",
    "content": "import config from '../../config/index.js';\nimport { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_BOT_SUMMON_DEMO_LABEL'),\n  text: `${config.BOT_NAME} ${t('__COMMAND_BOT_SUMMON_DEMO_TEXT')}`,\n});\n"
  },
  {
    "path": "app/commands/bot-talk-demo.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_BOT_TALK_DEMO_LABEL'),\n  text: t('__COMMAND_BOT_TALK_DEMO_TEXT'),\n});\n"
  },
  {
    "path": "app/commands/bot-talk.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_BOT_TALK_LABEL'),\n  text: t('__COMMAND_BOT_TALK_TEXT'),\n  aliases: [\n    ...t('__COMMAND_BOT_TALK_ALIASES'),\n    '/talk',\n    'Talk',\n  ],\n});\n"
  },
  {
    "path": "app/commands/command.js",
    "content": "class Command {\n  type;\n\n  label;\n\n  text;\n\n  reply;\n\n  prompt;\n\n  aliases;\n\n  constructor({\n    type,\n    label,\n    text,\n    reply = '',\n    prompt = '',\n    aliases = [],\n  }) {\n    this.type = type;\n    this.label = label;\n    this.text = text;\n    this.reply = reply;\n    this.prompt = prompt;\n    this.aliases = aliases;\n  }\n}\n\nexport default Command;\n"
  },
  {
    "path": "app/commands/index.js",
    "content": "import { TYPE_ANALYZE, TYPE_SUM, TYPE_TRANSLATE } from '../../constants/command.js';\nimport COMMAND_ANALYZE_ANALYZE from './analyze-analyze.js';\nimport COMMAND_ANALYZE_LITERARILY from './analyze-literarily.js';\nimport COMMAND_ANALYZE_MATHEMATICALLY from './analyze-mathematically.js';\nimport COMMAND_ANALYZE_NUMEROLOGICALLY from './analyze-numerologically.js';\nimport COMMAND_ANALYZE_PHILOSOPHICALLY from './analyze-philosophically.js';\nimport COMMAND_ANALYZE_PSYCHOLOGICALLY from './analyze-psychologically.js';\nimport COMMAND_BOT_ACTIVATE from './bot-activate.js';\nimport COMMAND_BOT_CONTINUE from './bot-continue.js';\nimport COMMAND_BOT_DEACTIVATE from './bot-deactivate.js';\nimport COMMAND_BOT_DRAW_DEMO from './bot-draw-demo.js';\nimport COMMAND_BOT_DRAW from './bot-draw.js';\nimport COMMAND_BOT_FORGET from './bot-forget.js';\nimport COMMAND_BOT_RETRY from './bot-retry.js';\nimport COMMAND_BOT_SEARCH from './bot-search.js';\nimport COMMAND_BOT_SEARCH_DEMO from './bot-search-demo.js';\nimport COMMAND_BOT_SUMMON_DEMO from './bot-summon-demo.js';\nimport COMMAND_BOT_TALK_DEMO from './bot-talk-demo.js';\nimport COMMAND_BOT_TALK from './bot-talk.js';\nimport Command from './command.js';\nimport COMMAND_SUM_ADVISE from './sum-advise.js';\nimport COMMAND_SUM_APOLOGIZE from './sum-apologize.js';\nimport COMMAND_SUM_BLAME from './sum-blame.js';\nimport COMMAND_SUM_COMFORT from './sum-comfort.js';\nimport COMMAND_SUM_COMPLAIN from './sum-complain.js';\nimport COMMAND_SUM_ENCOURAGE from './sum-encourage.js';\nimport COMMAND_SUM_LAUGH from './sum-laugh.js';\nimport COMMAND_SUM_SUM from './sum-sum.js';\nimport COMMAND_SYS_COMMAND from './sys-command.js';\nimport COMMAND_SYS_DEPLOY from './sys-deploy.js';\nimport COMMAND_SYS_DOC from './sys-doc.js';\nimport COMMAND_SYS_REPORT from './sys-report.js';\nimport COMMAND_SYS_VERSION from './sys-version.js';\nimport COMMAND_TRANSLATE_TO_EN from './translate-to-en.js';\nimport COMMAND_TRANSLATE_TO_JA from './translate-to-ja.js';\n\nexport const ALL_COMMANDS = [\n  COMMAND_ANALYZE_ANALYZE,\n  COMMAND_ANALYZE_LITERARILY,\n  COMMAND_ANALYZE_MATHEMATICALLY,\n  COMMAND_ANALYZE_NUMEROLOGICALLY,\n  COMMAND_ANALYZE_PHILOSOPHICALLY,\n  COMMAND_ANALYZE_PSYCHOLOGICALLY,\n  COMMAND_BOT_ACTIVATE,\n  COMMAND_BOT_CONTINUE,\n  COMMAND_BOT_DEACTIVATE,\n  COMMAND_BOT_DRAW_DEMO,\n  COMMAND_BOT_DRAW,\n  COMMAND_BOT_FORGET,\n  COMMAND_BOT_RETRY,\n  COMMAND_BOT_SEARCH,\n  COMMAND_BOT_SUMMON_DEMO,\n  COMMAND_BOT_TALK_DEMO,\n  COMMAND_BOT_TALK,\n  COMMAND_SUM_ADVISE,\n  COMMAND_SUM_APOLOGIZE,\n  COMMAND_SUM_BLAME,\n  COMMAND_SUM_COMFORT,\n  COMMAND_SUM_COMPLAIN,\n  COMMAND_SUM_ENCOURAGE,\n  COMMAND_SUM_LAUGH,\n  COMMAND_SUM_SUM,\n  COMMAND_SYS_COMMAND,\n  COMMAND_SYS_DEPLOY,\n  COMMAND_SYS_DOC,\n  COMMAND_SYS_REPORT,\n  COMMAND_SYS_VERSION,\n  COMMAND_TRANSLATE_TO_EN,\n  COMMAND_TRANSLATE_TO_JA,\n];\n\nexport const INFO_COMMANDS = [\n  COMMAND_SYS_VERSION,\n  COMMAND_SYS_DOC,\n  COMMAND_SYS_REPORT,\n];\n\nexport const GENERAL_COMMANDS = [\n  COMMAND_SYS_COMMAND,\n  COMMAND_BOT_SUMMON_DEMO,\n  COMMAND_BOT_TALK_DEMO,\n  COMMAND_BOT_DRAW_DEMO,\n  COMMAND_TRANSLATE_TO_EN,\n  COMMAND_TRANSLATE_TO_JA,\n  COMMAND_BOT_SEARCH_DEMO,\n  COMMAND_BOT_FORGET,\n  COMMAND_SUM_SUM,\n  COMMAND_ANALYZE_ANALYZE,\n];\n\nexport const ENQUIRE_COMMANDS = ALL_COMMANDS.filter(({ type }) => (\n  type === TYPE_SUM\n  || type === TYPE_ANALYZE\n  || type === TYPE_TRANSLATE\n));\n\nexport {\n  COMMAND_ANALYZE_ANALYZE,\n  COMMAND_ANALYZE_LITERARILY,\n  COMMAND_ANALYZE_MATHEMATICALLY,\n  COMMAND_ANALYZE_NUMEROLOGICALLY,\n  COMMAND_ANALYZE_PHILOSOPHICALLY,\n  COMMAND_ANALYZE_PSYCHOLOGICALLY,\n  COMMAND_BOT_ACTIVATE,\n  COMMAND_BOT_CONTINUE,\n  COMMAND_BOT_DEACTIVATE,\n  COMMAND_BOT_DRAW_DEMO,\n  COMMAND_BOT_DRAW,\n  COMMAND_BOT_FORGET,\n  COMMAND_BOT_RETRY,\n  COMMAND_BOT_SEARCH,\n  COMMAND_BOT_SUMMON_DEMO,\n  COMMAND_BOT_TALK_DEMO,\n  COMMAND_BOT_TALK,\n  Command,\n  COMMAND_SUM_ADVISE,\n  COMMAND_SUM_APOLOGIZE,\n  COMMAND_SUM_BLAME,\n  COMMAND_SUM_COMFORT,\n  COMMAND_SUM_COMPLAIN,\n  COMMAND_SUM_ENCOURAGE,\n  COMMAND_SUM_LAUGH,\n  COMMAND_SUM_SUM,\n  COMMAND_SYS_COMMAND,\n  COMMAND_SYS_DEPLOY,\n  COMMAND_SYS_DOC,\n  COMMAND_SYS_REPORT,\n  COMMAND_SYS_VERSION,\n  COMMAND_TRANSLATE_TO_EN,\n  COMMAND_TRANSLATE_TO_JA,\n};\n"
  },
  {
    "path": "app/commands/sum-advise.js",
    "content": "import { TYPE_SUM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SUM,\n  label: t('__COMMAND_SUM_ADVISE_LABEL'),\n  text: t('__COMMAND_SUM_ADVISE_TEXT'),\n  prompt: t('__COMMAND_SUM_ADVISE_PROMPT'),\n  aliases: [\n    '/advise',\n    'Advise',\n  ],\n});\n"
  },
  {
    "path": "app/commands/sum-apologize.js",
    "content": "import { TYPE_SUM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SUM,\n  label: t('__COMMAND_SUM_APOLOGIZE_LABEL'),\n  text: t('__COMMAND_SUM_APOLOGIZE_TEXT'),\n  prompt: t('__COMMAND_SUM_APOLOGIZE_PROMPT'),\n  aliases: [\n    '/apologize',\n    'Apologize',\n  ],\n});\n"
  },
  {
    "path": "app/commands/sum-blame.js",
    "content": "import { TYPE_SUM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SUM,\n  label: t('__COMMAND_SUM_BLAME_LABEL'),\n  text: t('__COMMAND_SUM_BLAME_TEXT'),\n  prompt: t('__COMMAND_SUM_BLAME_PROMPT'),\n  aliases: [\n    '/blame',\n    'Blame',\n  ],\n});\n"
  },
  {
    "path": "app/commands/sum-comfort.js",
    "content": "import { TYPE_SUM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SUM,\n  label: t('__COMMAND_SUM_COMFORT_LABEL'),\n  text: t('__COMMAND_SUM_COMFORT_TEXT'),\n  prompt: t('__COMMAND_SUM_COMFORT_PROMPT'),\n  aliases: [\n    '/comfort',\n    'Comfort',\n  ],\n});\n"
  },
  {
    "path": "app/commands/sum-complain.js",
    "content": "import { TYPE_SUM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SUM,\n  label: t('__COMMAND_SUM_COMPLAIN_LABEL'),\n  text: t('__COMMAND_SUM_COMPLAIN_TEXT'),\n  prompt: t('__COMMAND_SUM_COMPLAIN_PROMPT'),\n  aliases: [\n    '/complain',\n    'Complain',\n  ],\n});\n"
  },
  {
    "path": "app/commands/sum-encourage.js",
    "content": "import { TYPE_SUM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SUM,\n  label: t('__COMMAND_SUM_ENCOURAGE_LABEL'),\n  text: t('__COMMAND_SUM_ENCOURAGE_TEXT'),\n  prompt: t('__COMMAND_SUM_ENCOURAGE_PROMPT'),\n  aliases: [\n    '/encourage',\n    'Encourage',\n  ],\n});\n"
  },
  {
    "path": "app/commands/sum-laugh.js",
    "content": "import { TYPE_SUM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SUM,\n  label: t('__COMMAND_SUM_LAUGH_LABEL'),\n  text: t('__COMMAND_SUM_LAUGH_TEXT'),\n  prompt: t('__COMMAND_SUM_LAUGH_PROMPT'),\n  aliases: [\n    '/laugh',\n    'Laugh',\n  ],\n});\n"
  },
  {
    "path": "app/commands/sum-sum.js",
    "content": "import { TYPE_SUM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SUM,\n  label: t('__COMMAND_SUM_SUM_LABEL'),\n  text: t('__COMMAND_SUM_SUM_TEXT'),\n  prompt: t('__COMMAND_SUM_SUM_PROMPT'),\n  aliases: [\n    '/sum',\n    'Sum',\n  ],\n});\n"
  },
  {
    "path": "app/commands/sys-command.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_SYS_COMMAND_LABEL'),\n  text: t('__COMMAND_SYS_COMMAND_TEXT'),\n  aliases: [\n    '/command',\n    'Command',\n  ],\n});\n"
  },
  {
    "path": "app/commands/sys-deploy.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_SYS_DEPLOY_LABEL'),\n  text: t('__COMMAND_SYS_DEPLOY_TEXT'),\n  reply: t('__COMMAND_SYS_DEPLOY_REPLY'),\n  aliases: [\n    '/restart',\n    'Restart',\n  ],\n});\n"
  },
  {
    "path": "app/commands/sys-doc.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_SYS_DOC_LABEL'),\n  text: t('__COMMAND_SYS_DOC_TEXT'),\n  reply: t('__COMMAND_SYS_DOC_REPLY'),\n  aliases: [\n    '/doc',\n    'Doc',\n  ],\n});\n"
  },
  {
    "path": "app/commands/sys-report.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_SYS_REPORT_LABEL'),\n  text: t('__COMMAND_SYS_REPORT_TEXT'),\n  aliases: [\n    '/report',\n    'Report',\n  ],\n});\n"
  },
  {
    "path": "app/commands/sys-version.js",
    "content": "import { TYPE_SYSTEM } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_SYSTEM,\n  label: t('__COMMAND_SYS_VERSION_LABEL'),\n  text: t('__COMMAND_SYS_VERSION_TEXT'),\n  reply: t('__COMMAND_SYS_VERSION_REPLY'),\n  aliases: [\n    '/version',\n    'Version',\n  ],\n});\n"
  },
  {
    "path": "app/commands/translate-to-en.js",
    "content": "import { TYPE_TRANSLATE } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_TRANSLATE,\n  label: t('__COMMAND_TRANSLATE_TO_EN_LABEL'),\n  text: t('__COMMAND_TRANSLATE_TO_EN_TEXT'),\n  prompt: t('__COMMAND_TRANSLATE_TO_EN_PROMPT'),\n  aliases: [\n    '/translate-to-en',\n    'Translate to English',\n    'Translate to EN',\n  ],\n});\n"
  },
  {
    "path": "app/commands/translate-to-ja.js",
    "content": "import { TYPE_TRANSLATE } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport Command from './command.js';\n\nexport default new Command({\n  type: TYPE_TRANSLATE,\n  label: t('__COMMAND_TRANSLATE_TO_JA_LABEL'),\n  text: t('__COMMAND_TRANSLATE_TO_JA_TEXT'),\n  prompt: t('__COMMAND_TRANSLATE_TO_JA_PROMPT'),\n  aliases: [\n    '/translate-to-ja',\n    'Translate to Japanese',\n    'Translate to JA',\n  ],\n});\n"
  },
  {
    "path": "app/context.js",
    "content": "import { AxiosError } from 'axios';\nimport fs from 'fs';\nimport config from '../config/index.js';\nimport { t } from '../locales/index.js';\nimport {\n  MESSAGE_TYPE_IMAGE, MESSAGE_TYPE_TEXT, SOURCE_TYPE_GROUP, SOURCE_TYPE_USER,\n} from '../services/line.js';\nimport {\n  addMark,\n  convertText,\n  fetchAudio,\n  fetchImage,\n  fetchGroup,\n  fetchUser,\n  generateTranscription,\n} from '../utils/index.js';\nimport { Command, COMMAND_BOT_FORGET, COMMAND_BOT_RETRY } from './commands/index.js';\nimport { updateHistory } from './history/index.js';\nimport {\n  ImageMessage, Message, TemplateMessage, TextMessage,\n} from './messages/index.js';\nimport { Bot, Event, Source } from './models/index.js';\nimport { getSources, setSources } from './repository/index.js';\n\nclass Context {\n  /**\n   * @type {Event}\n   */\n  event;\n\n  /**\n   * @type {Source}\n   */\n  source;\n\n  /**\n   * @type {string}\n   */\n  transcription;\n\n  /**\n   * @type {Array<Message>}\n   */\n  messages = [];\n\n  /**\n   * @param {Event} event\n   */\n  constructor(event) {\n    this.event = event;\n  }\n\n  get id() {\n    if (this.event.isGroup) return this.event.source.groupId;\n    return this.event.source.userId;\n  }\n\n  /**\n   * @returns {string}\n   */\n  get replyToken() {\n    return this.event.replyToken;\n  }\n\n  /**\n   * @returns {string}\n   */\n  get groupId() {\n    return this.event.groupId;\n  }\n\n  /**\n   * @returns {string}\n   */\n  get userId() {\n    return this.event.userId;\n  }\n\n  /**\n   * @returns {string}\n   */\n  get trimmedText() {\n    if (this.event.isText) {\n      const text = this.event.text.replaceAll('　', ' ').replace(config.BOT_NAME, '').trim();\n      return addMark(text);\n    }\n    if (this.event.isAudio) {\n      const text = this.transcription.replace(config.BOT_NAME, '').trim();\n      return addMark(text);\n    }\n    if (this.event.isImage) {\n      return this.transcription.trim();\n    }\n    return '?';\n  }\n\n  get hasBotName() {\n    if (this.event.isText) {\n      const text = this.event.text.replaceAll('　', ' ').trim().toLowerCase();\n      return text.startsWith(config.BOT_NAME.toLowerCase());\n    }\n    if (this.event.isAudio) {\n      const text = this.transcription.toLowerCase();\n      return text.startsWith(config.BOT_NAME.toLowerCase());\n    }\n    if (this.event.isImage) {\n      const text = this.transcription.toLowerCase();\n      return text.startsWith(config.BOT_NAME.toLowerCase());\n    }\n    return false;\n  }\n\n  async initialize() {\n    try {\n      this.validate();\n      await this.register();\n    } catch (err) {\n      return this.pushError(err);\n    }\n    if (this.event.isAudio) {\n      try {\n        await this.transcribeAudio();\n      } catch (err) {\n        return this.pushError(err);\n      }\n    }\n    if (this.event.isImage) {\n      try {\n        await this.transcribeImage();\n      } catch (err) {\n        return this.pushError(err);\n      }\n    }\n    updateHistory(this.id, (history) => history.write(this.source.name, this.trimmedText));\n    return this;\n  }\n\n  /**\n   * @throws {Error}\n   */\n  validate() {\n    const sources = getSources();\n    const groups = Object.values(sources).filter(({ type }) => type === SOURCE_TYPE_GROUP);\n    const users = Object.values(sources).filter(({ type }) => type === SOURCE_TYPE_USER);\n    if (this.event.isGroup && !sources[this.groupId] && groups.length >= config.APP_MAX_GROUPS) {\n      throw new Error(t('__ERROR_MAX_GROUPS_REACHED'));\n    }\n    if (!sources[this.userId] && users.length >= config.APP_MAX_USERS) {\n      throw new Error(t('__ERROR_MAX_USERS_REACHED'));\n    }\n  }\n\n  async register() {\n    const sources = getSources();\n    const newSources = {};\n    if (this.event.isGroup && !sources[this.groupId]) {\n      const { groupName } = await fetchGroup(this.groupId);\n      newSources[this.groupId] = new Source({\n        type: SOURCE_TYPE_GROUP,\n        name: groupName,\n        bot: new Bot({\n          isActivated: !config.BOT_DEACTIVATED,\n        }),\n      });\n    }\n    if (!sources[this.userId]) {\n      const { displayName } = await fetchUser(this.userId);\n      newSources[this.userId] = new Source({\n        type: SOURCE_TYPE_USER,\n        name: displayName,\n        bot: new Bot({\n          isActivated: !config.BOT_DEACTIVATED,\n        }),\n      });\n    }\n    Object.assign(sources, newSources);\n    if (Object.keys(newSources).length > 0) await setSources(sources);\n    this.source = new Source(sources[this.id]);\n  }\n\n  async transcribeAudio() {\n    const buffer = await fetchAudio(this.event.messageId);\n    const file = `/tmp/${this.event.messageId}.m4a`;\n    fs.writeFileSync(file, buffer);\n    const { text } = await generateTranscription({ file, buffer });\n    this.transcription = convertText(text);\n  }\n\n  async transcribeImage() {\n    const base64String = await fetchImage(this.event.messageId);\n    this.transcription = base64String;\n  }\n\n  /**\n   * @param {Object} param\n   * @param {string} param.text\n   * @param {Array<string>} param.aliases\n   * @returns {boolean}\n   */\n  hasCommand({\n    text,\n    aliases,\n  }) {\n    const content = this.trimmedText.toLowerCase();\n    if (aliases.some((alias) => content.startsWith(alias.toLowerCase()))) return true;\n    if (content.startsWith(text.toLowerCase())) return true;\n    return false;\n  }\n\n  /**\n   * @param {string} text\n   * @param {Array<Command>} actions\n   * @returns {Context}\n   */\n  pushText(text, actions = []) {\n    if (!text) return this;\n    const message = new TextMessage({\n      type: MESSAGE_TYPE_TEXT,\n      text: convertText(text),\n    });\n    message.setQuickReply(actions);\n    this.messages.push(message);\n    return this;\n  }\n\n  /**\n   * @param {string} url\n   * @param {Array<Command>} actions\n   * @returns {Context}\n   */\n  pushImage(url, actions = []) {\n    if (!url) return this;\n    const message = new ImageMessage({\n      type: MESSAGE_TYPE_IMAGE,\n      originalContentUrl: url,\n      previewImageUrl: url,\n    });\n    message.setQuickReply(actions);\n    this.messages.push(message);\n    return this;\n  }\n\n  /**\n   * @param {string} url\n   * @param {Array<Command>} buttons\n   * @param {Array<Command>} actions\n   * @returns {Context}\n   */\n  pushTemplate(text, buttons = [], actions = []) {\n    if (!text) return this;\n    const message = new TemplateMessage({\n      text,\n      actions: buttons,\n    });\n    message.setQuickReply(actions);\n    this.messages.push(message);\n    return this;\n  }\n\n  /**\n   * @param {AxiosError} err\n   * @returns {Context}\n   */\n  pushError(err) {\n    this.error = err;\n    console.log(this.error.message);\n    if (err.code === 'ECONNABORTED') {\n      if (config.ERROR_MESSAGE_DISABLED) return this;\n      return this.pushText(t('__ERROR_ECONNABORTED'), [COMMAND_BOT_RETRY, COMMAND_BOT_FORGET]);\n    }\n    if (err.response?.status >= 500) {\n      if (config.ERROR_MESSAGE_DISABLED) return this;\n      return this.pushText(t('__ERROR_UNKNOWN'), [COMMAND_BOT_RETRY, COMMAND_BOT_FORGET]);\n    }\n    if (err.config?.baseURL) this.pushText(`${err.config.method.toUpperCase()} ${err.config.baseURL}${err.config.url}`);\n    if (err.response) this.pushText(`Request failed with status code ${err.response.status}`);\n    this.pushText(err.message);\n    return this;\n  }\n}\n\nexport default Context;\n"
  },
  {
    "path": "app/handlers/activate.js",
    "content": "import config from '../../config/index.js';\nimport { t } from '../../locales/index.js';\nimport { COMMAND_BOT_ACTIVATE, GENERAL_COMMANDS } from '../commands/index.js';\nimport Context from '../context.js';\nimport { updateHistory } from '../history/index.js';\nimport { updateSources } from '../repository/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => context.hasCommand(COMMAND_BOT_ACTIVATE);\n\n/**\n * @param {Context} context\n * @returns {Context}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    updateHistory(context.id, (history) => history.erase());\n    if (!config.VERCEL_ACCESS_TOKEN) context.pushText(t('__ERROR_MISSING_ENV')('VERCEL_ACCESS_TOKEN'));\n    try {\n      await updateSources(context.id, (source) => {\n        source.bot.isActivated = true;\n      });\n      context.pushText(COMMAND_BOT_ACTIVATE.reply, GENERAL_COMMANDS);\n    } catch (err) {\n      context.pushError(err);\n    }\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/handlers/command.js",
    "content": "import {\n  COMMAND_BOT_ACTIVATE,\n  COMMAND_SYS_COMMAND,\n  COMMAND_BOT_DEACTIVATE,\n  GENERAL_COMMANDS,\n  INFO_COMMANDS,\n} from '../commands/index.js';\nimport Context from '../context.js';\nimport { updateHistory } from '../history/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => context.hasCommand(COMMAND_SYS_COMMAND);\n\n/**\n * @param {Context} context\n * @returns {Context}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    updateHistory(context.id, (history) => history.erase());\n    try {\n      const buttons = [...INFO_COMMANDS];\n      buttons.splice(2, 0, context.source.bot.isActivated ? COMMAND_BOT_DEACTIVATE : COMMAND_BOT_ACTIVATE);\n      context.pushTemplate(COMMAND_SYS_COMMAND.label, buttons, GENERAL_COMMANDS);\n    } catch (err) {\n      context.pushError(err);\n    }\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/handlers/continue.js",
    "content": "import { generateCompletion } from '../../utils/index.js';\nimport { ALL_COMMANDS, COMMAND_BOT_CONTINUE } from '../commands/index.js';\nimport Context from '../context.js';\nimport { updateHistory } from '../history/index.js';\nimport { getPrompt, setPrompt } from '../prompt/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => context.hasCommand(COMMAND_BOT_CONTINUE);\n\n/**\n * @param {Context} context\n * @returns {Promise<Context>}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    updateHistory(context.id, (history) => history.erase());\n    const prompt = getPrompt(context.userId);\n    const { lastMessage } = prompt;\n    if (lastMessage.isEnquiring) prompt.erase();\n    try {\n      const { text, isFinishReasonStop } = await generateCompletion({ prompt });\n      prompt.patch(text);\n      if (lastMessage.isEnquiring && !isFinishReasonStop) prompt.write('', lastMessage.content);\n      setPrompt(context.userId, prompt);\n      if (!lastMessage.isEnquiring) updateHistory(context.id, (history) => history.patch(text));\n      const defaultActions = ALL_COMMANDS.filter(({ type }) => type === lastMessage.content);\n      const actions = isFinishReasonStop ? defaultActions : [COMMAND_BOT_CONTINUE];\n      context.pushText(text, actions);\n    } catch (err) {\n      context.pushError(err);\n    }\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/handlers/deactivate.js",
    "content": "import config from '../../config/index.js';\nimport { t } from '../../locales/index.js';\nimport { COMMAND_BOT_DEACTIVATE, GENERAL_COMMANDS } from '../commands/index.js';\nimport Context from '../context.js';\nimport { updateHistory } from '../history/index.js';\nimport { updateSources } from '../repository/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => context.hasCommand(COMMAND_BOT_DEACTIVATE);\n\n/**\n * @param {Context} context\n * @returns {Promise<Context>}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    updateHistory(context.id, (history) => history.erase());\n    if (!config.VERCEL_ACCESS_TOKEN) context.pushText(t('__ERROR_MISSING_ENV')('VERCEL_ACCESS_TOKEN'));\n    try {\n      await updateSources(context.id, (source) => {\n        source.bot.isActivated = false;\n      });\n      context.pushText(COMMAND_BOT_DEACTIVATE.reply, GENERAL_COMMANDS);\n    } catch (err) {\n      context.pushError(err);\n    }\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/handlers/deploy.js",
    "content": "import config from '../../config/index.js';\nimport { t } from '../../locales/index.js';\nimport { deploy } from '../../services/vercel.js';\nimport { COMMAND_SYS_DEPLOY } from '../commands/index.js';\nimport Context from '../context.js';\nimport { updateHistory } from '../history/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => context.hasCommand(COMMAND_SYS_DEPLOY);\n\n/**\n * @param {Context} context\n * @returns {Promise<Context>}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    updateHistory(context.id, (history) => history.erase());\n    if (!config.VERCEL_DEPLOY_HOOK_URL) context.pushText(t('__ERROR_MISSING_ENV')('VERCEL_DEPLOY_HOOK_URL'));\n    try {\n      await deploy();\n      context.pushText(COMMAND_SYS_DEPLOY.reply);\n    } catch (err) {\n      context.pushError(err);\n    }\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/handlers/doc.js",
    "content": "import { COMMAND_SYS_DOC, GENERAL_COMMANDS } from '../commands/index.js';\nimport Context from '../context.js';\nimport { updateHistory } from '../history/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => context.hasCommand(COMMAND_SYS_DOC);\n\n/**\n * @param {Context} context\n * @returns {Promise<Context>}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    updateHistory(context.id, (history) => history.erase());\n    context.pushText('https://memochou1993.github.io/gpt-ai-assistant-docs/', GENERAL_COMMANDS);\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/handlers/draw.js",
    "content": "import config from '../../config/index.js';\nimport { MOCK_TEXT_OK } from '../../constants/mock.js';\nimport { ROLE_AI, ROLE_HUMAN } from '../../services/openai.js';\nimport { generateImage } from '../../utils/index.js';\nimport { COMMAND_BOT_DRAW } from '../commands/index.js';\nimport Context from '../context.js';\nimport { updateHistory } from '../history/index.js';\nimport { getPrompt, setPrompt } from '../prompt/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => context.hasCommand(COMMAND_BOT_DRAW);\n\n/**\n * @param {Context} context\n * @returns {Promise<Context>}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    const prompt = getPrompt(context.userId);\n    prompt.write(ROLE_HUMAN, `${context.trimmedText}`).write(ROLE_AI);\n    try {\n      const trimmedText = context.trimmedText.replace(COMMAND_BOT_DRAW.text, '');\n      const { url } = await generateImage({ prompt: trimmedText });\n      prompt.patch(MOCK_TEXT_OK);\n      setPrompt(context.userId, prompt);\n      updateHistory(context.id, (history) => history.write(config.BOT_NAME, MOCK_TEXT_OK));\n      context.pushImage(url);\n    } catch (err) {\n      context.pushError(err);\n    }\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/handlers/enquire.js",
    "content": "import { TYPE_TRANSLATE } from '../../constants/command.js';\nimport { t } from '../../locales/index.js';\nimport { ROLE_AI, ROLE_HUMAN } from '../../services/openai.js';\nimport { generateCompletion, getCommand } from '../../utils/index.js';\nimport { ALL_COMMANDS, COMMAND_BOT_CONTINUE, ENQUIRE_COMMANDS } from '../commands/index.js';\nimport Context from '../context.js';\nimport { getHistory, updateHistory } from '../history/index.js';\nimport { getPrompt, setPrompt, Prompt } from '../prompt/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => (\n  [...ENQUIRE_COMMANDS]\n    .sort((a, b) => b.text.length - a.text.length)\n    .some((command) => context.hasCommand(command))\n);\n\n/**\n * @param {Context} context\n * @returns {Promise<Context>}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    updateHistory(context.id, (history) => history.erase());\n    const command = getCommand(context.trimmedText);\n    const history = getHistory(context.id);\n    if (!history.lastMessage) return context;\n    const reference = command.type === TYPE_TRANSLATE ? history.lastMessage.content : history.toString();\n    const content = `${command.prompt}\\n${t('__COMPLETION_QUOTATION_MARK_OPENING')}\\n${reference}\\n${t('__COMPLETION_QUOTATION_MARK_CLOSING')}`;\n    const partial = (new Prompt()).write(ROLE_HUMAN, content);\n    const prompt = getPrompt(context.userId);\n    prompt.write(ROLE_HUMAN, content).write(ROLE_AI);\n    try {\n      const { text, isFinishReasonStop } = await generateCompletion({ prompt: partial });\n      prompt.patch(text);\n      if (!isFinishReasonStop) prompt.write('', command.type);\n      setPrompt(context.userId, prompt);\n      const defaultActions = ALL_COMMANDS.filter(({ type }) => type === command.type);\n      const actions = isFinishReasonStop ? defaultActions : [COMMAND_BOT_CONTINUE];\n      context.pushText(text, actions);\n    } catch (err) {\n      context.pushError(err);\n    }\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/handlers/forget.js",
    "content": "import { COMMAND_BOT_FORGET } from '../commands/index.js';\nimport Context from '../context.js';\nimport { removeHistory } from '../history/index.js';\nimport { removePrompt } from '../prompt/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => context.hasCommand(COMMAND_BOT_FORGET);\n\n/**\n * @param {Context} context\n * @returns {Promise<Context>}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    removePrompt(context.userId);\n    removeHistory(context.userId);\n    context.pushText(COMMAND_BOT_FORGET.reply);\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/handlers/index.js",
    "content": "import activateHandler from './activate.js';\nimport commandHandler from './command.js';\nimport continueHandler from './continue.js';\nimport deactivateHandler from './deactivate.js';\nimport deployHandler from './deploy.js';\nimport docHandler from './doc.js';\nimport drawHandler from './draw.js';\nimport forgetHandler from './forget.js';\nimport enquireHandler from './enquire.js';\nimport reportHandler from './report.js';\nimport retryHandler from './retry.js';\nimport searchHandler from './search.js';\nimport talkHandler from './talk.js';\nimport versionHandler from './version.js';\n\nexport {\n  activateHandler,\n  commandHandler,\n  continueHandler,\n  deactivateHandler,\n  deployHandler,\n  docHandler,\n  drawHandler,\n  forgetHandler,\n  enquireHandler,\n  reportHandler,\n  retryHandler,\n  searchHandler,\n  talkHandler,\n  versionHandler,\n};\n"
  },
  {
    "path": "app/handlers/report.js",
    "content": "import { COMMAND_SYS_REPORT, GENERAL_COMMANDS } from '../commands/index.js';\nimport Context from '../context.js';\nimport { updateHistory } from '../history/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => context.hasCommand(COMMAND_SYS_REPORT);\n\n/**\n * @param {Context} context\n * @returns {Promise<Context>}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    updateHistory(context.id, (history) => history.erase());\n    context.pushText('https://github.com/memochou1993/gpt-ai-assistant/issues', GENERAL_COMMANDS);\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/handlers/retry.js",
    "content": "import config from '../../config/index.js';\nimport { ROLE_AI } from '../../services/openai.js';\nimport { generateCompletion } from '../../utils/index.js';\nimport { COMMAND_BOT_CONTINUE, COMMAND_BOT_RETRY, GENERAL_COMMANDS } from '../commands/index.js';\nimport Context from '../context.js';\nimport { updateHistory } from '../history/index.js';\nimport { getPrompt, setPrompt } from '../prompt/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => context.hasCommand(COMMAND_BOT_RETRY);\n\n/**\n * @param {Context} context\n * @returns {Promise<Context>}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    updateHistory(context.id, (history) => history.erase());\n    const prompt = getPrompt(context.userId);\n    prompt.erase().write(ROLE_AI);\n    try {\n      const { text, isFinishReasonStop } = await generateCompletion({ prompt });\n      prompt.patch(text);\n      setPrompt(context.userId, prompt);\n      updateHistory(context.id, (history) => history.write(config.BOT_NAME, text));\n      const actions = isFinishReasonStop ? [] : [COMMAND_BOT_CONTINUE];\n      context.pushText(text, actions);\n    } catch (err) {\n      context.pushError(err);\n    }\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/handlers/search.js",
    "content": "import config from '../../config/index.js';\nimport { t } from '../../locales/index.js';\nimport { ROLE_AI, ROLE_HUMAN } from '../../services/openai.js';\nimport { fetchAnswer, generateCompletion } from '../../utils/index.js';\nimport { COMMAND_BOT_CONTINUE, COMMAND_BOT_SEARCH } from '../commands/index.js';\nimport Context from '../context.js';\nimport { updateHistory } from '../history/index.js';\nimport { getPrompt, setPrompt } from '../prompt/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => context.hasCommand(COMMAND_BOT_SEARCH);\n\n/**\n * @param {Context} context\n * @returns {Promise<Context>}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    let trimmedText = context.trimmedText.replace(COMMAND_BOT_SEARCH.text, '');\n    const prompt = getPrompt(context.userId);\n    if (!config.SERPAPI_API_KEY) context.pushText(t('__ERROR_MISSING_ENV')('SERPAPI_API_KEY'));\n    try {\n      const { answer } = await fetchAnswer(trimmedText);\n      trimmedText = `${t('__COMPLETION_SEARCH')(answer || t('__COMPLETION_SEARCH_NOT_FOUND'), trimmedText)}`;\n    } catch (err) {\n      return context.pushError(err);\n    }\n    prompt.write(ROLE_HUMAN, `${trimmedText}`).write(ROLE_AI);\n    try {\n      const { text, isFinishReasonStop } = await generateCompletion({ prompt });\n      prompt.patch(text);\n      setPrompt(context.userId, prompt);\n      updateHistory(context.id, (history) => history.write(config.BOT_NAME, text));\n      const actions = isFinishReasonStop ? [] : [COMMAND_BOT_CONTINUE];\n      context.pushText(text, actions);\n    } catch (err) {\n      context.pushError(err);\n    }\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/handlers/talk.js",
    "content": "import config from '../../config/index.js';\nimport { t } from '../../locales/index.js';\nimport { ROLE_AI, ROLE_HUMAN } from '../../services/openai.js';\nimport { generateCompletion } from '../../utils/index.js';\nimport { COMMAND_BOT_CONTINUE, COMMAND_BOT_FORGET, COMMAND_BOT_TALK } from '../commands/index.js';\nimport Context from '../context.js';\nimport { updateHistory } from '../history/index.js';\nimport { getPrompt, setPrompt } from '../prompt/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => (\n  context.hasCommand(COMMAND_BOT_TALK)\n  || context.hasBotName\n  || context.source.bot.isActivated\n);\n\n/**\n * @param {Context} context\n * @returns {Promise<Context>}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    const prompt = getPrompt(context.userId);\n    try {\n      if (context.event.isText) {\n        prompt.write(ROLE_HUMAN, `${t('__COMPLETION_DEFAULT_AI_TONE')(config.BOT_TONE)}${context.trimmedText}`).write(ROLE_AI);\n      }\n      if (context.event.isImage) {\n        const { trimmedText } = context;\n        prompt.writeImage(ROLE_HUMAN, trimmedText).write(ROLE_AI);\n      }\n      const { text, isFinishReasonStop } = await generateCompletion({ prompt });\n      prompt.patch(text);\n      setPrompt(context.userId, prompt);\n      updateHistory(context.id, (history) => history.write(config.BOT_NAME, text));\n      const actions = isFinishReasonStop ? [COMMAND_BOT_FORGET] : [COMMAND_BOT_CONTINUE];\n      context.pushText(text, actions);\n    } catch (err) {\n      context.pushError(err);\n    }\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/handlers/version.js",
    "content": "import { t } from '../../locales/index.js';\nimport { fetchVersion, getVersion } from '../../utils/index.js';\nimport { COMMAND_SYS_VERSION, GENERAL_COMMANDS } from '../commands/index.js';\nimport Context from '../context.js';\nimport { updateHistory } from '../history/index.js';\n\n/**\n * @param {Context} context\n * @returns {boolean}\n */\nconst check = (context) => context.hasCommand(COMMAND_SYS_VERSION);\n\n/**\n * @param {Context} context\n * @returns {Promise<Context>}\n */\nconst exec = (context) => check(context) && (\n  async () => {\n    updateHistory(context.id, (history) => history.erase());\n    const current = getVersion();\n    const latest = await fetchVersion();\n    const isLatest = current === latest;\n    const text = t('__COMMAND_SYS_VERSION_REPLY')(current, isLatest);\n    context.pushText(text, GENERAL_COMMANDS);\n    if (!isLatest) context.pushText(t('__MESSAGE_NEW_VERSION_AVAILABLE')(latest));\n    return context;\n  }\n)();\n\nexport default exec;\n"
  },
  {
    "path": "app/history/history.js",
    "content": "import { encode } from 'gpt-3-encoder';\nimport config from '../../config/index.js';\nimport { t } from '../../locales/index.js';\nimport { addMark } from '../../utils/index.js';\nimport Message from './message.js';\n\nconst MAX_MESSAGES = config.APP_MAX_PROMPT_MESSAGES / 2;\nconst MAX_TOKENS = config.APP_MAX_PROMPT_TOKENS / 2;\n\nclass History {\n  messages = [];\n\n  /**\n   * @returns {Message}\n   */\n  get lastMessage() {\n    return this.messages.length > 0 ? this.messages[this.messages.length - 1] : null;\n  }\n\n  get tokenCount() {\n    const encoded = encode(this.toString());\n    return encoded.length;\n  }\n\n  erase() {\n    if (this.messages.length > 0) {\n      this.messages.pop();\n    }\n    return this;\n  }\n\n  /**\n   * @param {string} role\n   * @param {string} content\n   */\n  write(role, content) {\n    if (this.messages.length >= MAX_MESSAGES || this.tokenCount >= MAX_TOKENS) {\n      this.messages.shift();\n    }\n    this.messages.push(new Message({ role, content: addMark(content) }));\n    return this;\n  }\n\n  /**\n   * @param {string} role\n   * @param {string} content\n   */\n  writeImage(role, content = '') {\n    const imageContent = [\n      {\n        type: 'text',\n        text: t('__COMPLETION_VISION'),\n      },\n      {\n        type: 'image',\n        image_url: {\n          url: content,\n        },\n      },\n    ];\n    this.messages.push(new Message({ role, content: imageContent }));\n    return this;\n  }\n\n  /**\n   * @param {string} content\n   */\n  patch(content) {\n    if (this.messages.length < 1) return;\n    this.messages[this.messages.length - 1].content += content;\n  }\n\n  toString() {\n    return this.messages.map((record) => record.toString()).join('\\n');\n  }\n}\n\nexport default History;\n"
  },
  {
    "path": "app/history/index.js",
    "content": "import History from './history.js';\n\nconst histories = new Map();\n\n/**\n * @param {string} contextId\n * @returns {History}\n */\nconst getHistory = (contextId) => histories.get(contextId) || new History();\n\n/**\n * @param {string} contextId\n * @param {History} history\n * @returns {History}\n */\nconst setHistory = (contextId, history) => histories.set(contextId, history);\n\n/**\n * @param {string} contextId\n * @param {function(History)} callback\n */\nconst updateHistory = (contextId, callback) => {\n  const history = getHistory(contextId);\n  callback(history);\n  setHistory(contextId, history);\n};\n\n/**\n * @param {string} userId\n */\nconst removeHistory = (userId) => {\n  histories.delete(userId);\n};\n\nconst printHistories = () => {\n  const messages = Array.from(histories.keys())\n    .filter((contextId) => getHistory(contextId).messages.length > 0)\n    .map((contextId) => `\\n=== ${contextId.slice(0, 6)} ===\\n\\n${getHistory(contextId).toString()}\\n`);\n  if (messages.length < 1) return;\n  console.info(messages.join(''));\n};\n\nexport {\n  getHistory,\n  setHistory,\n  updateHistory,\n  removeHistory,\n  printHistories,\n};\n\nexport default histories;\n"
  },
  {
    "path": "app/history/message.js",
    "content": "class Message {\n  role;\n\n  content;\n\n  constructor({\n    role,\n    content,\n  }) {\n    this.role = role;\n    this.content = content;\n  }\n\n  toString() {\n    return `${this.role}: ${this.content}`;\n  }\n}\n\nexport default Message;\n"
  },
  {
    "path": "app/index.js",
    "content": "import handleEvents from './app.js';\nimport { printHistories } from './history/index.js';\nimport {\n  getPrompt, printPrompts, removePrompt, setPrompt,\n} from './prompt/index.js';\n\nexport {\n  handleEvents,\n  printHistories,\n  getPrompt,\n  printPrompts,\n  removePrompt,\n  setPrompt,\n};\n"
  },
  {
    "path": "app/messages/image.js",
    "content": "import { MESSAGE_TYPE_IMAGE } from '../../services/line.js';\nimport Message from './message.js';\n\nclass ImageMessage extends Message {\n  type = MESSAGE_TYPE_IMAGE;\n\n  originalContentUrl;\n\n  previewImageUrl;\n\n  constructor({\n    originalContentUrl,\n    previewImageUrl,\n  }) {\n    super();\n    this.originalContentUrl = originalContentUrl;\n    this.previewImageUrl = previewImageUrl;\n  }\n}\n\nexport default ImageMessage;\n"
  },
  {
    "path": "app/messages/index.js",
    "content": "import Message from './message.js';\nimport ImageMessage from './image.js';\nimport TemplateMessage from './template.js';\nimport TextMessage from './text.js';\n\nexport {\n  Message,\n  ImageMessage,\n  TemplateMessage,\n  TextMessage,\n};\n"
  },
  {
    "path": "app/messages/message.js",
    "content": "import { QUICK_REPLY_TYPE_ACTION } from '../../services/line.js';\nimport { MessageAction } from '../actions/index.js';\nimport { Command } from '../commands/index.js';\n\nclass Message {\n  type;\n\n  quickReply;\n\n  /**\n   * @param {Array<Command>} actions\n   */\n  setQuickReply(actions = []) {\n    if (actions.length < 1) return;\n    this.quickReply = {\n      items: actions.map((action) => ({\n        type: QUICK_REPLY_TYPE_ACTION,\n        action: new MessageAction(action),\n      })),\n    };\n  }\n}\n\nexport default Message;\n"
  },
  {
    "path": "app/messages/template.js",
    "content": "import { MESSAGE_TYPE_TEMPLATE, TEMPLATE_TYPE_BUTTONS } from '../../services/line.js';\nimport { MessageAction } from '../actions/index.js';\nimport Message from './message.js';\n\nclass TemplateMessage extends Message {\n  type = MESSAGE_TYPE_TEMPLATE;\n\n  altText;\n\n  template;\n\n  constructor({\n    text,\n    actions,\n  }) {\n    super();\n    this.altText = text;\n    this.template = {\n      type: TEMPLATE_TYPE_BUTTONS,\n      text,\n      actions: actions.map((action) => new MessageAction(action)),\n    };\n  }\n}\n\nexport default TemplateMessage;\n"
  },
  {
    "path": "app/messages/text.js",
    "content": "import { MESSAGE_TYPE_TEXT } from '../../services/line.js';\nimport Message from './message.js';\n\nclass TextMessage extends Message {\n  type = MESSAGE_TYPE_TEXT;\n\n  text;\n\n  constructor({\n    text,\n  }) {\n    super();\n    this.text = text;\n  }\n}\n\nexport default TextMessage;\n"
  },
  {
    "path": "app/models/bot.js",
    "content": "class Bot {\n  isActivated;\n\n  constructor({\n    isActivated,\n  }) {\n    this.isActivated = isActivated;\n  }\n}\n\nexport default Bot;\n"
  },
  {
    "path": "app/models/event.js",
    "content": "import {\n  EVENT_TYPE_MESSAGE,\n  MESSAGE_TYPE_AUDIO,\n  MESSAGE_TYPE_STICKER,\n  MESSAGE_TYPE_TEXT,\n  MESSAGE_TYPE_IMAGE,\n  SOURCE_TYPE_GROUP,\n} from '../../services/line.js';\n\nclass Event {\n  type;\n\n  replyToken;\n\n  source;\n\n  message;\n\n  constructor({\n    type,\n    replyToken,\n    source,\n    message,\n  }) {\n    this.type = type;\n    this.replyToken = replyToken;\n    this.source = source;\n    this.message = message;\n  }\n\n  /**\n   * @returns {boolean}\n   */\n  get isMessage() {\n    return this.type === EVENT_TYPE_MESSAGE;\n  }\n\n  /**\n   * @returns {boolean}\n   */\n  get isGroup() {\n    return this.source.type === SOURCE_TYPE_GROUP;\n  }\n\n  /**\n   * @returns {boolean}\n   */\n  get isText() {\n    return this.message.type === MESSAGE_TYPE_TEXT;\n  }\n\n  /**\n   * @returns {boolean}\n   */\n  get isSticker() {\n    return this.message.type === MESSAGE_TYPE_STICKER;\n  }\n\n  /**\n   * @returns {boolean}\n   */\n  get isAudio() {\n    return this.message.type === MESSAGE_TYPE_AUDIO;\n  }\n\n  /**\n   * @returns {boolean}\n   */\n  get isImage() {\n    return this.message.type === MESSAGE_TYPE_IMAGE;\n  }\n\n  /**\n   * @returns {string}\n   */\n  get groupId() {\n    return this.source.groupId;\n  }\n\n  /**\n   * @returns {string}\n   */\n  get userId() {\n    return this.source.userId;\n  }\n\n  /**\n   * @returns {string}\n   */\n  get messageId() {\n    return this.message.id;\n  }\n\n  /**\n   * @returns {string}\n   */\n  get text() {\n    return this.message.text;\n  }\n}\n\nexport default Event;\n"
  },
  {
    "path": "app/models/index.js",
    "content": "import Bot from './bot.js';\nimport Event from './event.js';\nimport Source from './source.js';\n\nexport {\n  Bot,\n  Event,\n  Source,\n};\n\nexport default null;\n"
  },
  {
    "path": "app/models/source.js",
    "content": "import { t } from '../../locales/index.js';\nimport { SOURCE_TYPE_GROUP } from '../../services/line.js';\n\nclass Source {\n  type;\n\n  name;\n\n  bot;\n\n  createdAt;\n\n  constructor({\n    type,\n    name,\n    bot,\n  }) {\n    this.type = type;\n    this.name = name || (type === SOURCE_TYPE_GROUP ? t('__SOURCE_NAME_SOME_GROUP') : t('__SOURCE_NAME_SOMEONE'));\n    this.bot = bot;\n    this.createdAt = Math.floor(Date.now() / 1000);\n  }\n}\n\nexport default Source;\n"
  },
  {
    "path": "app/prompt/index.js",
    "content": "import Prompt from './prompt.js';\n\nconst prompts = new Map();\n\n/**\n * @param {string} userId\n * @returns {Prompt}\n */\nconst getPrompt = (userId) => prompts.get(userId) || new Prompt();\n\n/**\n * @param {string} userId\n * @param {Prompt} prompt\n */\nconst setPrompt = (userId, prompt) => {\n  prompts.set(userId, prompt);\n};\n\n/**\n * @param {string} userId\n */\nconst removePrompt = (userId) => {\n  prompts.delete(userId);\n};\n\nconst printPrompts = () => {\n  if (Array.from(prompts.keys()).length < 1) return;\n  const content = Array.from(prompts.keys()).map((userId) => `\\n=== ${userId.slice(0, 6)} ===\\n${getPrompt(userId)}\\n`).join('');\n  console.info(content);\n};\n\nexport {\n  Prompt,\n  getPrompt,\n  setPrompt,\n  removePrompt,\n  printPrompts,\n};\n\nexport default prompts;\n"
  },
  {
    "path": "app/prompt/message.js",
    "content": "import { TYPE_ANALYZE, TYPE_SUM, TYPE_TRANSLATE } from '../../constants/command.js';\n\nclass Message {\n  role;\n\n  content;\n\n  constructor({\n    role,\n    content,\n  }) {\n    this.role = role;\n    this.content = content;\n  }\n\n  get isEnquiring() {\n    return this.content === TYPE_SUM\n    || this.content === TYPE_ANALYZE\n    || this.content === TYPE_TRANSLATE;\n  }\n\n  toString() {\n    if (Array.isArray(this.content)) {\n      return `\\n${this.role}: ${this.content[0].text}`;\n    }\n    return this.role ? `\\n${this.role}: ${this.content}` : this.content;\n  }\n}\n\nexport default Message;\n"
  },
  {
    "path": "app/prompt/prompt.js",
    "content": "import { encode } from 'gpt-3-encoder';\nimport config from '../../config/index.js';\nimport { t } from '../../locales/index.js';\nimport { ROLE_AI, ROLE_HUMAN, ROLE_SYSTEM } from '../../services/openai.js';\nimport { addMark } from '../../utils/index.js';\nimport Message from './message.js';\n\nconst MAX_MESSAGES = config.APP_MAX_PROMPT_MESSAGES + 3;\nconst MAX_TOKENS = config.APP_MAX_PROMPT_TOKENS;\n\nclass Prompt {\n  messages = [];\n\n  constructor() {\n    this\n      .write(ROLE_SYSTEM, config.APP_INIT_PROMPT || t('__COMPLETION_DEFAULT_SYSTEM_PROMPT'))\n      .write(ROLE_HUMAN, `${t('__COMPLETION_DEFAULT_HUMAN_PROMPT')(config.HUMAN_NAME)}${config.HUMAN_INIT_PROMPT}`)\n      .write(ROLE_AI, `${t('__COMPLETION_DEFAULT_AI_PROMPT')(config.BOT_NAME)}${config.BOT_INIT_PROMPT}`);\n  }\n\n  /**\n   * @returns {Message}\n   */\n  get lastMessage() {\n    return this.messages.length > 0 ? this.messages[this.messages.length - 1] : null;\n  }\n\n  get tokenCount() {\n    const encoded = encode(this.toString());\n    return encoded.length;\n  }\n\n  erase() {\n    if (this.messages.length > 0) {\n      this.messages.pop();\n    }\n    return this;\n  }\n\n  /**\n   * @param {string} role\n   * @param {string} content\n   */\n  write(role, content = '') {\n    if (this.messages.length >= MAX_MESSAGES || this.tokenCount >= MAX_TOKENS) {\n      this.messages.splice(3, 1);\n    }\n    this.messages.push(new Message({ role, content: addMark(content) }));\n    return this;\n  }\n\n  /**\n   * @param {string} role\n   * @param {string} content\n   */\n  writeImage(role, content = '') {\n    const imageContent = [\n      {\n        type: 'text',\n        text: t('__COMPLETION_VISION'),\n      },\n      {\n        type: 'image_url',\n        image_url: {\n          url: content,\n        },\n      },\n    ];\n    this.messages.push(new Message({ role, content: imageContent }));\n    return this;\n  }\n\n  /**\n   * @param {string} content\n   */\n  patch(content) {\n    this.messages[this.messages.length - 1].content += content;\n  }\n\n  toString() {\n    return this.messages.map((sentence) => sentence.toString()).join('');\n  }\n}\n\nexport default Prompt;\n"
  },
  {
    "path": "app/repository/index.js",
    "content": "import { getSources, updateSources, setSources } from './source.js';\n\nexport {\n  getSources,\n  updateSources,\n  setSources,\n};\n\nexport default null;\n"
  },
  {
    "path": "app/repository/source.js",
    "content": "import storage from '../../storage/index.js';\nimport { Source } from '../models/index.js';\n\nconst FIELD_KEY = 'sources';\n\n/**\n * @returns {Object.<string, Source>}\n */\nconst getSources = () => storage.getItem(FIELD_KEY) || {};\n\n/**\n * @param {Object.<string, Source>} sources\n */\nconst setSources = (sources) => storage.setItem(FIELD_KEY, sources);\n\n/**\n * @param {string} contextId\n * @param {function(Source)} callback\n */\nconst updateSources = async (contextId, callback) => {\n  const sources = getSources();\n  callback(sources[contextId]);\n  await setSources(sources);\n};\n\nexport {\n  getSources,\n  setSources,\n  updateSources,\n};\n"
  },
  {
    "path": "babel.config.cjs",
    "content": "module.exports = {\n  presets: [\n    '@babel/preset-env',\n  ],\n};\n"
  },
  {
    "path": "config/index.js",
    "content": "import dotenv from 'dotenv';\n\nconst { env } = process;\n\ndotenv.config({\n  path: env.NODE_ENV ? `.env.${env.NODE_ENV}` : '.env',\n});\n\nconst config = Object.freeze({\n  APP_ENV: env.NODE_ENV || 'production',\n  APP_DEBUG: env.APP_DEBUG === 'true' || false,\n  APP_URL: env.APP_URL || null,\n  APP_PORT: env.APP_PORT || null,\n  APP_LANG: env.APP_LANG || 'zh_TW',\n  APP_WEBHOOK_PATH: env.APP_WEBHOOK_PATH || '/webhook',\n  APP_API_TIMEOUT: env.APP_API_TIMEOUT || 9000,\n  APP_MAX_GROUPS: Number(env.APP_MAX_GROUPS) || 1000,\n  APP_MAX_USERS: Number(env.APP_MAX_USERS) || 1000,\n  APP_MAX_PROMPT_MESSAGES: Number(env.APP_MAX_PROMPT_MESSAGES) || 4,\n  APP_MAX_PROMPT_TOKENS: Number(env.APP_MAX_PROMPT_TOKENS) || 256,\n  APP_INIT_PROMPT: env.APP_INIT_PROMPT || '',\n  HUMAN_NAME: env.HUMAN_NAME || '',\n  HUMAN_INIT_PROMPT: env.HUMAN_INIT_PROMPT || '',\n  BOT_NAME: env.BOT_NAME || 'AI',\n  BOT_INIT_PROMPT: env.BOT_INIT_PROMPT || '',\n  BOT_TONE: env.BOT_TONE || '',\n  BOT_DEACTIVATED: env.BOT_DEACTIVATED === 'true' || false,\n  ERROR_MESSAGE_DISABLED: env.ERROR_MESSAGE_DISABLED === 'true' || false,\n  VERCEL_ENV: env.VERCEL_ENV || null,\n  VERCEL_TIMEOUT: env.VERCEL_TIMEOUT || env.APP_API_TIMEOUT,\n  VERCEL_PROJECT_NAME: env.VERCEL_PROJECT_NAME || env.VERCEL_GIT_REPO_SLUG || null,\n  VERCEL_TEAM_ID: env.VERCEL_TEAM_ID || null,\n  VERCEL_ACCESS_TOKEN: env.VERCEL_ACCESS_TOKEN || null,\n  VERCEL_DEPLOY_HOOK_URL: env.VERCEL_DEPLOY_HOOK_URL || null,\n  OPENAI_TIMEOUT: env.OPENAI_TIMEOUT || env.APP_API_TIMEOUT,\n  OPENAI_API_KEY: env.OPENAI_API_KEY || null,\n  OPENAI_BASE_URL: env.OPENAI_BASE_URL || 'https://api.openai.com',\n  OPENAI_COMPLETION_MODEL: env.OPENAI_COMPLETION_MODEL || 'gpt-3.5-turbo',\n  OPENAI_COMPLETION_TEMPERATURE: Number(env.OPENAI_COMPLETION_TEMPERATURE) || 1,\n  OPENAI_COMPLETION_MAX_TOKENS: Number(env.OPENAI_COMPLETION_MAX_TOKENS) || 64,\n  OPENAI_COMPLETION_FREQUENCY_PENALTY: Number(env.OPENAI_COMPLETION_FREQUENCY_PENALTY) || 0,\n  OPENAI_COMPLETION_PRESENCE_PENALTY: Number(env.OPENAI_COMPLETION_PRESENCE_PENALTY) || 0.6,\n  OPENAI_COMPLETION_STOP_SEQUENCES: env.OPENAI_COMPLETION_STOP_SEQUENCES ? String(env.OPENAI_COMPLETION_STOP_SEQUENCES).split(',') : [' assistant:', ' user:'],\n  OPENAI_IMAGE_GENERATION_MODEL: env.OPENAI_IMAGE_GENERATION_MODEL || 'dall-e-2',\n  OPENAI_IMAGE_GENERATION_SIZE: env.OPENAI_IMAGE_GENERATION_SIZE || '256x256',\n  OPENAI_IMAGE_GENERATION_QUALITY: env.OPENAI_IMAGE_GENERATION_QUALITY || 'standard',\n  OPENAI_VISION_MODEL: env.OPENAI_VISION_MODEL || 'gpt-4o',\n  LINE_TIMEOUT: env.LINE_TIMEOUT || env.APP_API_TIMEOUT,\n  LINE_CHANNEL_ACCESS_TOKEN: env.LINE_CHANNEL_ACCESS_TOKEN || null,\n  LINE_CHANNEL_SECRET: env.LINE_CHANNEL_SECRET || null,\n  SERPAPI_TIMEOUT: env.SERPAPI_TIMEOUT || env.APP_API_TIMEOUT,\n  SERPAPI_API_KEY: env.SERPAPI_API_KEY || null,\n  SERPAPI_LOCATION: env.SERPAPI_LOCATION || 'tw',\n});\n\nexport default config;\n"
  },
  {
    "path": "constants/command.js",
    "content": "export const TYPE_SUM = 'sum';\nexport const TYPE_ANALYZE = 'analyze';\nexport const TYPE_SYSTEM = 'system';\nexport const TYPE_TRANSLATE = 'translate';\n"
  },
  {
    "path": "constants/mock.js",
    "content": "export const MOCK_TEXT_OK = 'OK!';\n\nexport const MOCK_GROUP_01 = '000001';\nexport const MOCK_GROUP_02 = '000002';\n\nexport const MOCK_USER_01 = '000001';\nexport const MOCK_USER_02 = '000002';\n\nconst mockGroups = {};\nmockGroups[MOCK_GROUP_01] = { groupName: 'group' };\nmockGroups[MOCK_USER_02] = { groupName: 'group 2' };\n\nconst mockUsers = {};\nmockUsers[MOCK_USER_01] = { displayName: 'user' };\nmockUsers[MOCK_USER_02] = { displayName: 'user 2' };\n\nexport {\n  mockGroups,\n  mockUsers,\n};\n"
  },
  {
    "path": "docker-compose.yaml",
    "content": "version: \"3\"\n\nservices:\n  app:\n    container_name: gpt-ai-assistant\n    build: .\n    restart: always\n    ports:\n      - \"${APP_PORT}:${APP_PORT}\"\n"
  },
  {
    "path": "locales/en.js",
    "content": "const en = {\n  __COMMAND_ANALYZE_ANALYZE_LABEL: 'Analyze',\n  __COMMAND_ANALYZE_ANALYZE_TEXT: 'Analyze',\n  __COMMAND_ANALYZE_ANALYZE_PROMPT: 'Please analyze the following statements.',\n  __COMMAND_ANALYZE_LITERARILY_LABEL: 'Literarily',\n  __COMMAND_ANALYZE_LITERARILY_TEXT: 'Analyze literarily',\n  __COMMAND_ANALYZE_LITERARILY_PROMPT: 'Please analyze the following statements literarily.',\n  __COMMAND_ANALYZE_MATHEMATICALLY_LABEL: 'Mathematically',\n  __COMMAND_ANALYZE_MATHEMATICALLY_TEXT: 'Analyze mathematically',\n  __COMMAND_ANALYZE_MATHEMATICALLY_PROMPT: 'Please analyze the following statements mathematically.',\n  __COMMAND_ANALYZE_NUMEROLOGICALLY_LABEL: 'Numerologically',\n  __COMMAND_ANALYZE_NUMEROLOGICALLY_TEXT: 'Analyze numerologically',\n  __COMMAND_ANALYZE_NUMEROLOGICALLY_PROMPT: 'Please analyze the following statements numerologically.',\n  __COMMAND_ANALYZE_PHILOSOPHICALLY_LABEL: 'Philosophically',\n  __COMMAND_ANALYZE_PHILOSOPHICALLY_TEXT: 'Analyze philosophically',\n  __COMMAND_ANALYZE_PHILOSOPHICALLY_PROMPT: 'Please analyze the following statements philosophically.',\n  __COMMAND_ANALYZE_PSYCHOLOGICALLY_LABEL: 'Psychologically',\n  __COMMAND_ANALYZE_PSYCHOLOGICALLY_TEXT: 'Analyze psychologically',\n  __COMMAND_ANALYZE_PSYCHOLOGICALLY_PROMPT: 'Please analyze the following statements psychologically.',\n  __COMMAND_BOT_ACTIVATE_LABEL: 'Activate',\n  __COMMAND_BOT_ACTIVATE_TEXT: 'Activate',\n  __COMMAND_BOT_ACTIVATE_ALIASES: [],\n  __COMMAND_BOT_ACTIVATE_REPLY: 'Activated',\n  __COMMAND_BOT_CONTINUE_LABEL: 'Continue',\n  __COMMAND_BOT_CONTINUE_TEXT: 'Continue',\n  __COMMAND_BOT_CONTINUE_ALIASES: [],\n  __COMMAND_BOT_DEACTIVATE_LABEL: 'Deactivate',\n  __COMMAND_BOT_DEACTIVATE_TEXT: 'Deactivate',\n  __COMMAND_BOT_DEACTIVATE_ALIASES: [],\n  __COMMAND_BOT_DEACTIVATE_REPLY: 'Deactivated',\n  __COMMAND_BOT_DRAW_LABEL: 'Draw',\n  __COMMAND_BOT_DRAW_TEXT: 'Draw',\n  __COMMAND_BOT_DRAW_ALIASES: [],\n  __COMMAND_BOT_DRAW_DEMO_LABEL: 'Draw',\n  __COMMAND_BOT_DRAW_DEMO_TEXT: 'Draw a cat',\n  __COMMAND_BOT_FORGET_LABEL: 'Forget',\n  __COMMAND_BOT_FORGET_TEXT: 'Forget',\n  __COMMAND_BOT_FORGET_ALIASES: [],\n  __COMMAND_BOT_FORGET_REPLY: 'Forgot',\n  __COMMAND_BOT_RETRY_LABEL: 'Retry',\n  __COMMAND_BOT_RETRY_TEXT: 'Retry',\n  __COMMAND_BOT_RETRY_ALIASES: [],\n  __COMMAND_BOT_SEARCH_LABEL: 'Search',\n  __COMMAND_BOT_SEARCH_TEXT: 'Search',\n  __COMMAND_BOT_SEARCH_ALIASES: [],\n  __COMMAND_BOT_SEARCH_DEMO_LABEL: 'Search',\n  __COMMAND_BOT_SEARCH_DEMO_TEXT: 'Search date',\n  __COMMAND_BOT_SUMMON_DEMO_LABEL: 'Summon',\n  __COMMAND_BOT_SUMMON_DEMO_TEXT: 'What\\'s up?',\n  __COMMAND_BOT_TALK_LABEL: 'Talk',\n  __COMMAND_BOT_TALK_TEXT: 'Talk',\n  __COMMAND_BOT_TALK_ALIASES: [],\n  __COMMAND_BOT_TALK_DEMO_LABEL: 'Talk',\n  __COMMAND_BOT_TALK_DEMO_TEXT: 'Talk me about yourself',\n  __COMMAND_SUM_ADVISE_LABEL: 'Advise',\n  __COMMAND_SUM_ADVISE_TEXT: 'Advise',\n  __COMMAND_SUM_ADVISE_PROMPT: 'Please summarize the following content briefly and advise appropriately.',\n  __COMMAND_SUM_APOLOGIZE_LABEL: 'Apologize',\n  __COMMAND_SUM_APOLOGIZE_TEXT: 'Apologize',\n  __COMMAND_SUM_APOLOGIZE_PROMPT: 'Please summarize the following content briefly and apologize sincerely.',\n  __COMMAND_SUM_BLAME_LABEL: 'Blame',\n  __COMMAND_SUM_BLAME_TEXT: 'Blame',\n  __COMMAND_SUM_BLAME_PROMPT: 'Please summarize the following content briefly and blame strongly.',\n  __COMMAND_SUM_COMFORT_LABEL: 'Comfort',\n  __COMMAND_SUM_COMFORT_TEXT: 'Comfort',\n  __COMMAND_SUM_COMFORT_PROMPT: 'Please summarize the following content briefly and comfort warmly.',\n  __COMMAND_SUM_COMPLAIN_LABEL: 'Complain',\n  __COMMAND_SUM_COMPLAIN_TEXT: 'Complain',\n  __COMMAND_SUM_COMPLAIN_PROMPT: 'Please summarize the following content briefly and complain gently.',\n  __COMMAND_SUM_ENCOURAGE_LABEL: 'Encourage',\n  __COMMAND_SUM_ENCOURAGE_TEXT: 'Encourage',\n  __COMMAND_SUM_ENCOURAGE_PROMPT: 'Please summarize the following content briefly and encourage enthusiastically.',\n  __COMMAND_SUM_LAUGH_LABEL: 'Laugh',\n  __COMMAND_SUM_LAUGH_TEXT: 'Laugh',\n  __COMMAND_SUM_LAUGH_PROMPT: 'Please summarize the following content briefly and laugh rudely.',\n  __COMMAND_SUM_SUM_LABEL: 'Sum',\n  __COMMAND_SUM_SUM_TEXT: 'Sum',\n  __COMMAND_SUM_SUM_PROMPT: 'Please summarize the following content and provide some details.',\n  __COMMAND_SYS_COMMAND_LABEL: 'Command',\n  __COMMAND_SYS_COMMAND_TEXT: 'Command',\n  __COMMAND_SYS_DOC_LABEL: 'Documentation',\n  __COMMAND_SYS_DOC_TEXT: 'Documentation',\n  __COMMAND_SYS_DEPLOY_LABEL: 'Restart',\n  __COMMAND_SYS_DEPLOY_TEXT: 'Restart',\n  __COMMAND_SYS_DEPLOY_REPLY: 'Restarting',\n  __COMMAND_SYS_REPORT_LABEL: 'Report',\n  __COMMAND_SYS_REPORT_TEXT: 'Report',\n  __COMMAND_SYS_VERSION_LABEL: 'Version',\n  __COMMAND_SYS_VERSION_TEXT: 'Version',\n  __COMMAND_SYS_VERSION_REPLY: (version, isLatest) => `Your version is ${isLatest ? 'up-to-date' : version}.`,\n  __COMMAND_TRANSLATE_TO_EN_LABEL: '翻成英文', // TODO\n  __COMMAND_TRANSLATE_TO_EN_TEXT: '翻成英文', // TODO\n  __COMMAND_TRANSLATE_TO_EN_PROMPT: '請將以下內容翻譯成英文。', // TODO\n  __COMMAND_TRANSLATE_TO_JA_LABEL: '翻成日文', // TODO\n  __COMMAND_TRANSLATE_TO_JA_TEXT: '翻成日文', // TODO\n  __COMMAND_TRANSLATE_TO_JA_PROMPT: '請將以下內容翻譯成日文。', // TODO\n  __COMPLETION_DEFAULT_SYSTEM_PROMPT: '', // TODO\n  __COMPLETION_DEFAULT_HUMAN_PROMPT: (name) => (name ? `I am ${name}` : 'Hello'),\n  __COMPLETION_DEFAULT_AI_PROMPT: (name) => (name ? `I am ${name}` : 'Hello'),\n  __COMPLETION_DEFAULT_AI_TONE: (tone) => (tone ? `以${tone}的語氣回應我：` : ''), // TODO\n  __COMPLETION_SEARCH: (a, q) => `根據「${a}」查詢結果，回答「${q}」問題`, // TODO\n  __COMPLETION_SEARCH_NOT_FOUND: '查無資料', // TODO\n  __COMPLETION_QUOTATION_MARK_OPENING: '\"',\n  __COMPLETION_QUOTATION_MARK_CLOSING: '\"',\n  __COMPLETION_VISION: 'What\\'s in this image?',\n  __ERROR_ECONNABORTED: 'Timed out',\n  __ERROR_UNKNOWN: 'Something went wrong',\n  __ERROR_MAX_GROUPS_REACHED: 'Maximum groups reached',\n  __ERROR_MAX_USERS_REACHED: 'Maximum users reached',\n  __ERROR_MISSING_ENV: (v) => `Missing environment variable: ${v}`,\n  __MESSAGE_NEW_VERSION_AVAILABLE: (version) => `A new version ${version} is now available!`,\n  __SOURCE_NAME_SOME_GROUP: 'Someone Group',\n  __SOURCE_NAME_SOMEONE: 'Someone',\n};\n\nexport default en;\n"
  },
  {
    "path": "locales/index.js",
    "content": "import config from '../config/index.js';\nimport en from './en.js';\nimport ja from './ja.js';\nimport zh from './zh.js';\n\nconst locales = {\n  en,\n  ja,\n  zh,\n  zh_TW: zh,\n  zh_CN: zh,\n};\n\nconst t = (key) => locales[config.APP_LANG][key];\n\nexport {\n  t,\n};\n\nexport default null;\n"
  },
  {
    "path": "locales/ja.js",
    "content": "const ja = {\n  __COMMAND_ANALYZE_ANALYZE_LABEL: '分析して',\n  __COMMAND_ANALYZE_ANALYZE_TEXT: '分析して',\n  __COMMAND_ANALYZE_ANALYZE_PROMPT: '以下の内容を詳しく分析してください。',\n  __COMMAND_ANALYZE_LITERARILY_LABEL: '文学的に分析して',\n  __COMMAND_ANALYZE_LITERARILY_TEXT: '文学的に分析して',\n  __COMMAND_ANALYZE_LITERARILY_PROMPT: '以下の内容を文学的に詳しく分析してください。',\n  __COMMAND_ANALYZE_MATHEMATICALLY_LABEL: '数学的に分析して',\n  __COMMAND_ANALYZE_MATHEMATICALLY_TEXT: '数学的に分析して',\n  __COMMAND_ANALYZE_MATHEMATICALLY_PROMPT: '以下の内容を数学的に詳しく分析してください。',\n  __COMMAND_ANALYZE_NUMEROLOGICALLY_LABEL: '算命学的に分析して',\n  __COMMAND_ANALYZE_NUMEROLOGICALLY_TEXT: '算命学的に分析して',\n  __COMMAND_ANALYZE_NUMEROLOGICALLY_PROMPT: '以下の内容を算命学的に詳しく分析してください。',\n  __COMMAND_ANALYZE_PHILOSOPHICALLY_LABEL: '哲学的に分析して',\n  __COMMAND_ANALYZE_PHILOSOPHICALLY_TEXT: '哲学的に分析して',\n  __COMMAND_ANALYZE_PHILOSOPHICALLY_PROMPT: '以下の内容を哲学的に詳しく分析してください。',\n  __COMMAND_ANALYZE_PSYCHOLOGICALLY_LABEL: '心理学的に分析して',\n  __COMMAND_ANALYZE_PSYCHOLOGICALLY_TEXT: '心理学的に分析して',\n  __COMMAND_ANALYZE_PSYCHOLOGICALLY_PROMPT: '以下の内容を心理学的に詳しく分析してください。',\n  __COMMAND_BOT_ACTIVATE_LABEL: '自動応答をオンにする',\n  __COMMAND_BOT_ACTIVATE_TEXT: '自動応答をオンにする',\n  __COMMAND_BOT_ACTIVATE_ALIASES: [],\n  __COMMAND_BOT_ACTIVATE_REPLY: 'オンにしました',\n  __COMMAND_BOT_CONTINUE_LABEL: '続いて',\n  __COMMAND_BOT_CONTINUE_TEXT: '続いて',\n  __COMMAND_BOT_CONTINUE_ALIASES: [],\n  __COMMAND_BOT_DEACTIVATE_LABEL: '自動応答をオフにする',\n  __COMMAND_BOT_DEACTIVATE_TEXT: '自動応答をオフにする',\n  __COMMAND_BOT_DEACTIVATE_ALIASES: [],\n  __COMMAND_BOT_DEACTIVATE_REPLY: 'オフにしました',\n  __COMMAND_BOT_DRAW_LABEL: '描いて',\n  __COMMAND_BOT_DRAW_TEXT: '描いて',\n  __COMMAND_BOT_DRAW_ALIASES: [],\n  __COMMAND_BOT_DRAW_DEMO_LABEL: '描いて',\n  __COMMAND_BOT_DRAW_DEMO_TEXT: '猫を描いて',\n  __COMMAND_BOT_FORGET_LABEL: '忘記', // TODO\n  __COMMAND_BOT_FORGET_TEXT: '忘記', // TODO\n  __COMMAND_BOT_FORGET_ALIASES: [],\n  __COMMAND_BOT_FORGET_REPLY: '已忘記', // TODO\n  __COMMAND_BOT_RETRY_LABEL: 'リトライ',\n  __COMMAND_BOT_RETRY_TEXT: 'リトライ',\n  __COMMAND_BOT_RETRY_ALIASES: [],\n  __COMMAND_BOT_SEARCH_LABEL: '查詢', // TODO\n  __COMMAND_BOT_SEARCH_TEXT: '查詢', // TODO\n  __COMMAND_BOT_SEARCH_ALIASES: [],\n  __COMMAND_BOT_SEARCH_DEMO_LABEL: '查詢', // TODO\n  __COMMAND_BOT_SEARCH_DEMO_TEXT: '查詢日期', // TODO\n  __COMMAND_BOT_SUMMON_DEMO_LABEL: 'サモン',\n  __COMMAND_BOT_SUMMON_DEMO_TEXT: '元気？',\n  __COMMAND_BOT_TALK_LABEL: '話して',\n  __COMMAND_BOT_TALK_TEXT: '話して',\n  __COMMAND_BOT_TALK_ALIASES: [],\n  __COMMAND_BOT_TALK_DEMO_LABEL: '話して',\n  __COMMAND_BOT_TALK_DEMO_TEXT: '自分のことを話して',\n  __COMMAND_SUM_ADVISE_LABEL: 'アドバイスをして',\n  __COMMAND_SUM_ADVISE_TEXT: 'アドバイスをして',\n  __COMMAND_SUM_ADVISE_PROMPT: '以下の内容を要約し、いいアドバイスをください。',\n  __COMMAND_SUM_APOLOGIZE_LABEL: '謝って',\n  __COMMAND_SUM_APOLOGIZE_TEXT: '謝って',\n  __COMMAND_SUM_APOLOGIZE_PROMPT: '以下の内容を要約し、ちゃんと謝ってください。',\n  __COMMAND_SUM_BLAME_LABEL: '責めて',\n  __COMMAND_SUM_BLAME_TEXT: '責めて',\n  __COMMAND_SUM_BLAME_PROMPT: '以下の内容を要約し、強く非難してください。',\n  __COMMAND_SUM_COMFORT_LABEL: '慰めて',\n  __COMMAND_SUM_COMFORT_TEXT: '慰めて',\n  __COMMAND_SUM_COMFORT_PROMPT: '以下の内容を要約し、暖かく慰めてください。',\n  __COMMAND_SUM_ENCOURAGE_LABEL: '励んで',\n  __COMMAND_SUM_ENCOURAGE_TEXT: '励んで',\n  __COMMAND_SUM_ENCOURAGE_PROMPT: '以下の内容を要約し、熱心に励んでください。',\n  __COMMAND_SUM_COMPLAIN_LABEL: '愚痴を言って',\n  __COMMAND_SUM_COMPLAIN_TEXT: '愚痴を言って',\n  __COMMAND_SUM_COMPLAIN_PROMPT: '以下の内容を要約し、軽く愚痴を言ってください。',\n  __COMMAND_SUM_LAUGH_LABEL: '笑って',\n  __COMMAND_SUM_LAUGH_TEXT: '笑って',\n  __COMMAND_SUM_LAUGH_PROMPT: '以下の内容を要約し、面白く笑ってください。',\n  __COMMAND_SUM_SUM_LABEL: '要約して',\n  __COMMAND_SUM_SUM_TEXT: '要約して',\n  __COMMAND_SUM_SUM_PROMPT: '以下の内容を要約し、説明してください。',\n  __COMMAND_SYS_COMMAND_LABEL: 'コマンド',\n  __COMMAND_SYS_COMMAND_TEXT: 'コマンド',\n  __COMMAND_SYS_DOC_LABEL: 'ドキュメンテーション',\n  __COMMAND_SYS_DOC_TEXT: 'ドキュメンテーション',\n  __COMMAND_SYS_DEPLOY_LABEL: '再起動',\n  __COMMAND_SYS_DEPLOY_TEXT: '再起動',\n  __COMMAND_SYS_DEPLOY_REPLY: '再起動しています',\n  __COMMAND_SYS_REPORT_LABEL: 'バグレポート',\n  __COMMAND_SYS_REPORT_TEXT: 'バグレポート',\n  __COMMAND_SYS_VERSION_LABEL: 'バージョン',\n  __COMMAND_SYS_VERSION_TEXT: 'バージョン',\n  __COMMAND_SYS_VERSION_REPLY: (version, isLatest) => `Your version is ${isLatest ? 'up-to-date' : version}.`,\n  __COMMAND_TRANSLATE_TO_EN_LABEL: '翻成英文', // TODO\n  __COMMAND_TRANSLATE_TO_EN_TEXT: '翻成英文', // TODO\n  __COMMAND_TRANSLATE_TO_EN_PROMPT: '請將以下內容翻譯成英文。', // TODO\n  __COMMAND_TRANSLATE_TO_JA_LABEL: '翻成日文', // TODO\n  __COMMAND_TRANSLATE_TO_JA_TEXT: '翻成日文', // TODO\n  __COMMAND_TRANSLATE_TO_JA_PROMPT: '請將以下內容翻譯成日文。', // TODO\n  __COMPLETION_DEFAULT_SYSTEM_PROMPT: '', // TODO\n  __COMPLETION_DEFAULT_HUMAN_PROMPT: (name) => (name ? `私は${name}です` : 'こんにちは'),\n  __COMPLETION_DEFAULT_AI_PROMPT: (name) => (name ? `私は${name}です` : 'こんにちは'),\n  __COMPLETION_DEFAULT_AI_TONE: (tone) => (tone ? `以${tone}的語氣回應我：` : ''), // TODO\n  __COMPLETION_SEARCH: (a, q) => `根據「${a}」查詢結果，回答「${q}」問題`, // TODO\n  __COMPLETION_SEARCH_NOT_FOUND: '查無資料', // TODO\n  __COMPLETION_QUOTATION_MARK_OPENING: '「',\n  __COMPLETION_QUOTATION_MARK_CLOSING: '」',\n  __COMPLETION_VISION: 'この画像には何がありますか？',\n  __ERROR_ECONNABORTED: '接続がタイムアウトしました。',\n  __ERROR_UNKNOWN: '技術的な問題が発生しています。',\n  __ERROR_MAX_GROUPS_REACHED: '最大ユーザー数に達しています。',\n  __ERROR_MAX_USERS_REACHED: '最大グループ数に達しています。',\n  __ERROR_MISSING_ENV: (v) => `「${v}」環境変数が見つかりません。`,\n  __MESSAGE_NEW_VERSION_AVAILABLE: (version) => `A new version ${version} is now available!`,\n  __SOURCE_NAME_SOME_GROUP: 'あるグループ',\n  __SOURCE_NAME_SOMEONE: 'あるユーザー',\n};\n\nexport default ja;\n"
  },
  {
    "path": "locales/zh.js",
    "content": "const zh = {\n  __COMMAND_ANALYZE_ANALYZE_LABEL: '分析',\n  __COMMAND_ANALYZE_ANALYZE_TEXT: '分析',\n  __COMMAND_ANALYZE_ANALYZE_PROMPT: '分析以下內容，並給予細節。',\n  __COMMAND_ANALYZE_LITERARILY_LABEL: '文學分析',\n  __COMMAND_ANALYZE_LITERARILY_TEXT: '文學分析',\n  __COMMAND_ANALYZE_LITERARILY_PROMPT: '使用文學的角度分析以下內容，並給予細節。',\n  __COMMAND_ANALYZE_MATHEMATICALLY_LABEL: '數學分析',\n  __COMMAND_ANALYZE_MATHEMATICALLY_TEXT: '數學分析',\n  __COMMAND_ANALYZE_MATHEMATICALLY_PROMPT: '使用數學的角度分析以下內容，並給予細節。',\n  __COMMAND_ANALYZE_NUMEROLOGICALLY_LABEL: '命理學分析',\n  __COMMAND_ANALYZE_NUMEROLOGICALLY_TEXT: '命理學分析',\n  __COMMAND_ANALYZE_NUMEROLOGICALLY_PROMPT: '使用命理學的角度分析以下內容，並給予細節。',\n  __COMMAND_ANALYZE_PHILOSOPHICALLY_LABEL: '哲學分析',\n  __COMMAND_ANALYZE_PHILOSOPHICALLY_TEXT: '哲學分析',\n  __COMMAND_ANALYZE_PHILOSOPHICALLY_PROMPT: '使用哲學的角度分析以下內容，並給予細節。',\n  __COMMAND_ANALYZE_PSYCHOLOGICALLY_LABEL: '心理學分析',\n  __COMMAND_ANALYZE_PSYCHOLOGICALLY_TEXT: '心理學分析',\n  __COMMAND_ANALYZE_PSYCHOLOGICALLY_PROMPT: '使用心理學的角度分析以下內容，並給予細節。',\n  __COMMAND_BOT_ACTIVATE_LABEL: '開啟自動回覆',\n  __COMMAND_BOT_ACTIVATE_TEXT: '開啟自動回覆',\n  __COMMAND_BOT_ACTIVATE_ALIASES: ['說話', '開始說話'],\n  __COMMAND_BOT_ACTIVATE_REPLY: '已開啟自動回覆',\n  __COMMAND_BOT_CONTINUE_LABEL: '繼續',\n  __COMMAND_BOT_CONTINUE_TEXT: '繼續',\n  __COMMAND_BOT_CONTINUE_ALIASES: [],\n  __COMMAND_BOT_DEACTIVATE_LABEL: '關閉自動回覆',\n  __COMMAND_BOT_DEACTIVATE_TEXT: '關閉自動回覆',\n  __COMMAND_BOT_DEACTIVATE_ALIASES: ['閉嘴', '停止說話'],\n  __COMMAND_BOT_DEACTIVATE_REPLY: '已關閉自動回覆',\n  __COMMAND_BOT_DRAW_LABEL: '請畫',\n  __COMMAND_BOT_DRAW_TEXT: '請畫',\n  __COMMAND_BOT_DRAW_ALIASES: ['畫', '描繪'],\n  __COMMAND_BOT_DRAW_DEMO_LABEL: '請畫',\n  __COMMAND_BOT_DRAW_DEMO_TEXT: '請畫貓咪',\n  __COMMAND_BOT_FORGET_LABEL: '忘記',\n  __COMMAND_BOT_FORGET_TEXT: '忘記',\n  __COMMAND_BOT_FORGET_ALIASES: ['重來'],\n  __COMMAND_BOT_FORGET_REPLY: '已忘記',\n  __COMMAND_BOT_RETRY_LABEL: '重試',\n  __COMMAND_BOT_RETRY_TEXT: '重試',\n  __COMMAND_BOT_RETRY_ALIASES: [],\n  __COMMAND_BOT_SEARCH_LABEL: '查詢',\n  __COMMAND_BOT_SEARCH_TEXT: '查詢',\n  __COMMAND_BOT_SEARCH_ALIASES: ['搜尋', '查找'],\n  __COMMAND_BOT_SEARCH_DEMO_LABEL: '查詢',\n  __COMMAND_BOT_SEARCH_DEMO_TEXT: '查詢日期',\n  __COMMAND_BOT_SUMMON_DEMO_LABEL: '呼叫',\n  __COMMAND_BOT_SUMMON_DEMO_TEXT: '你好嗎？',\n  __COMMAND_BOT_TALK_LABEL: '請問',\n  __COMMAND_BOT_TALK_TEXT: '請問',\n  __COMMAND_BOT_TALK_ALIASES: ['呼叫'],\n  __COMMAND_BOT_TALK_DEMO_LABEL: '請問',\n  __COMMAND_BOT_TALK_DEMO_TEXT: '請問你好嗎',\n  __COMMAND_SUM_ADVISE_LABEL: '建議',\n  __COMMAND_SUM_ADVISE_TEXT: '建議',\n  __COMMAND_SUM_ADVISE_PROMPT: '總結以下內容，並給予適當的建議。',\n  __COMMAND_SUM_APOLOGIZE_LABEL: '道歉',\n  __COMMAND_SUM_APOLOGIZE_TEXT: '道歉',\n  __COMMAND_SUM_APOLOGIZE_PROMPT: '總結以下內容，並給予誠懇的道歉。',\n  __COMMAND_SUM_BLAME_LABEL: '譴責',\n  __COMMAND_SUM_BLAME_TEXT: '譴責',\n  __COMMAND_SUM_BLAME_PROMPT: '總結以下內容，並給予嚴厲的譴責。',\n  __COMMAND_SUM_COMFORT_LABEL: '安慰',\n  __COMMAND_SUM_COMFORT_TEXT: '安慰',\n  __COMMAND_SUM_COMFORT_PROMPT: '總結以下內容，並給予溫暖的安慰。',\n  __COMMAND_SUM_COMPLAIN_LABEL: '抱怨',\n  __COMMAND_SUM_COMPLAIN_TEXT: '抱怨',\n  __COMMAND_SUM_COMPLAIN_PROMPT: '總結以下內容，並給予輕微的抱怨。',\n  __COMMAND_SUM_ENCOURAGE_LABEL: '鼓勵',\n  __COMMAND_SUM_ENCOURAGE_TEXT: '鼓勵',\n  __COMMAND_SUM_ENCOURAGE_PROMPT: '總結以下內容，並給予熱烈的鼓勵。',\n  __COMMAND_SUM_LAUGH_LABEL: '嘲諷',\n  __COMMAND_SUM_LAUGH_TEXT: '嘲諷',\n  __COMMAND_SUM_LAUGH_PROMPT: '總結以下內容，並給予刻薄的嘲諷。',\n  __COMMAND_SUM_SUM_LABEL: '總結',\n  __COMMAND_SUM_SUM_TEXT: '總結',\n  __COMMAND_SUM_SUM_PROMPT: '總結以下內容，並給予細節。',\n  __COMMAND_SYS_COMMAND_LABEL: '指令',\n  __COMMAND_SYS_COMMAND_TEXT: '指令',\n  __COMMAND_SYS_DOC_LABEL: '查看文件',\n  __COMMAND_SYS_DOC_TEXT: '文件',\n  __COMMAND_SYS_DEPLOY_LABEL: '重新啟動',\n  __COMMAND_SYS_DEPLOY_TEXT: '重新啟動',\n  __COMMAND_SYS_DEPLOY_REPLY: '正在重新啟動',\n  __COMMAND_SYS_REPORT_LABEL: '回報問題',\n  __COMMAND_SYS_REPORT_TEXT: '回報',\n  __COMMAND_SYS_VERSION_LABEL: '檢查更新',\n  __COMMAND_SYS_VERSION_TEXT: '版本',\n  __COMMAND_SYS_VERSION_REPLY: (version, isLatest) => `目前版本為 ${version}${isLatest ? '，已更新到最新版本' : ''}。`,\n  __COMMAND_TRANSLATE_TO_EN_LABEL: '上一句翻成英文',\n  __COMMAND_TRANSLATE_TO_EN_TEXT: '上一句翻成英文',\n  __COMMAND_TRANSLATE_TO_EN_PROMPT: '將以下內容翻譯成英文。',\n  __COMMAND_TRANSLATE_TO_JA_LABEL: '上一句翻成日文',\n  __COMMAND_TRANSLATE_TO_JA_TEXT: '上一句翻成日文',\n  __COMMAND_TRANSLATE_TO_JA_PROMPT: '將以下內容翻譯成日文。',\n  __COMPLETION_DEFAULT_SYSTEM_PROMPT: '以下將使用繁體中文進行對話。',\n  __COMPLETION_DEFAULT_HUMAN_PROMPT: (name) => (name ? `我是${name}` : '哈囉'),\n  __COMPLETION_DEFAULT_AI_PROMPT: (name) => (name ? `我是${name}` : '哈囉'),\n  __COMPLETION_DEFAULT_AI_TONE: (tone) => (tone ? `以${tone}的語氣回應我：` : ''),\n  __COMPLETION_SEARCH: (a, q) => `根據「${a}」查詢結果，回答「${q}」問題`,\n  __COMPLETION_SEARCH_NOT_FOUND: '查無資料',\n  __COMPLETION_QUOTATION_MARK_OPENING: '「',\n  __COMPLETION_QUOTATION_MARK_CLOSING: '」',\n  __COMPLETION_VISION: '這張圖片裡有什麼？',\n  __ERROR_ECONNABORTED: '這個問題太複雜了',\n  __ERROR_UNKNOWN: '系統出了點狀況',\n  __ERROR_MAX_GROUPS_REACHED: '群組數量到達上限了',\n  __ERROR_MAX_USERS_REACHED: '用戶數量到達上限了',\n  __ERROR_MISSING_ENV: (v) => `缺少環境變數：${v}`,\n  __MESSAGE_NEW_VERSION_AVAILABLE: (version) => `最新版本為 ${version}，請從 GitHub 更新。`,\n  __SOURCE_NAME_SOME_GROUP: '某群組',\n  __SOURCE_NAME_SOMEONE: '某用戶',\n};\n\nexport default zh;\n"
  },
  {
    "path": "middleware/index.js",
    "content": "import validateLineSignature from './validate-line-signature.js';\n\nexport {\n  validateLineSignature,\n};\n\nexport default null;\n"
  },
  {
    "path": "middleware/validate-line-signature.js",
    "content": "import config from '../config/index.js';\nimport { validateSignature } from '../utils/index.js';\n\nconst validateLineSignature = (req, res, next) => {\n  const secret = config.LINE_CHANNEL_SECRET || '';\n  const signature = req.header('x-line-signature');\n  if (!validateSignature(req.rawBody, secret, signature)) {\n    res.sendStatus(403);\n    return;\n  }\n  next();\n};\n\nexport default validateLineSignature;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"gpt-ai-assistant\",\n  \"version\": \"4.9.1\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"nodemon api/index.js\",\n    \"start\": \"node api/index.js\",\n    \"test\": \"jest\"\n  },\n  \"dependencies\": {\n    \"axios\": \"^1.2.1\",\n    \"dotenv\": \"^16.0.3\",\n    \"express\": \"^4.18.2\",\n    \"form-data\": \"^4.0.0\",\n    \"gpt-3-encoder\": \"^1.1.3\",\n    \"opencc-js\": \"^1.0.5\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.20.5\",\n    \"@babel/preset-env\": \"^7.20.2\",\n    \"@jest/globals\": \"^29.3.1\",\n    \"babel-jest\": \"^29.3.1\",\n    \"eslint\": \"^8.29.0\",\n    \"eslint-config-airbnb\": \"^19.0.4\",\n    \"jest\": \"^29.3.1\",\n    \"nodemon\": \"^2.0.20\"\n  }\n}\n"
  },
  {
    "path": "services/line.js",
    "content": "import axios from 'axios';\nimport config from '../config/index.js';\nimport { handleFulfilled, handleRejected, handleRequest } from './utils/index.js';\n\nexport const EVENT_TYPE_MESSAGE = 'message';\nexport const EVENT_TYPE_POSTBACK = 'postback';\n\nexport const SOURCE_TYPE_USER = 'user';\nexport const SOURCE_TYPE_GROUP = 'group';\n\nexport const MESSAGE_TYPE_TEXT = 'text';\nexport const MESSAGE_TYPE_STICKER = 'sticker';\nexport const MESSAGE_TYPE_AUDIO = 'audio';\nexport const MESSAGE_TYPE_IMAGE = 'image';\nexport const MESSAGE_TYPE_TEMPLATE = 'template';\n\nexport const TEMPLATE_TYPE_BUTTONS = 'buttons';\n\nexport const ACTION_TYPE_MESSAGE = 'message';\nexport const ACTION_TYPE_POSTBACK = 'postback';\n\nexport const QUICK_REPLY_TYPE_ACTION = 'action';\n\nconst client = axios.create({\n  baseURL: 'https://api.line.me',\n  timeout: config.LINE_TIMEOUT,\n  headers: {\n    'Accept-Encoding': 'gzip, deflate, compress',\n  },\n});\n\nclient.interceptors.request.use((c) => {\n  c.headers.Authorization = `Bearer ${config.LINE_CHANNEL_ACCESS_TOKEN}`;\n  return handleRequest(c);\n});\n\nclient.interceptors.response.use(handleFulfilled, (err) => {\n  if (err.response?.data?.message) {\n    err.message = err.response.data.message;\n  }\n  return handleRejected(err);\n});\n\nconst reply = ({\n  replyToken,\n  messages,\n}) => client.post('/v2/bot/message/reply', {\n  replyToken,\n  messages,\n});\n\nconst fetchGroupSummary = ({\n  groupId,\n}) => client.get(`/v2/bot/group/${groupId}/summary`);\n\nconst fetchProfile = ({\n  userId,\n}) => client.get(`/v2/bot/profile/${userId}`);\n\nconst dataClient = axios.create({\n  baseURL: 'https://api-data.line.me',\n  timeout: config.LINE_TIMEOUT,\n  headers: {\n    'Accept-Encoding': 'gzip, deflate, compress',\n  },\n});\n\ndataClient.interceptors.request.use((c) => {\n  c.headers.Authorization = `Bearer ${config.LINE_CHANNEL_ACCESS_TOKEN}`;\n  return handleRequest(c);\n});\n\ndataClient.interceptors.response.use(handleFulfilled, (err) => {\n  if (err.response?.data?.message) {\n    err.message = err.response.data.message;\n  }\n  return handleRejected(err);\n});\n\nconst fetchContent = ({\n  messageId,\n}) => dataClient.get(`/v2/bot/message/${messageId}/content`, {\n  responseType: 'arraybuffer',\n});\n\nexport {\n  reply,\n  fetchGroupSummary,\n  fetchProfile,\n  fetchContent,\n};\n"
  },
  {
    "path": "services/openai.js",
    "content": "import axios from 'axios';\nimport FormData from 'form-data';\nimport config from '../config/index.js';\nimport { handleFulfilled, handleRejected, handleRequest } from './utils/index.js';\n\nexport const ROLE_SYSTEM = 'system';\nexport const ROLE_AI = 'assistant';\nexport const ROLE_HUMAN = 'user';\n\nexport const FINISH_REASON_STOP = 'stop';\nexport const FINISH_REASON_LENGTH = 'length';\n\nexport const IMAGE_SIZE_256 = '256x256';\nexport const IMAGE_SIZE_512 = '512x512';\nexport const IMAGE_SIZE_1024 = '1024x1024';\n\nexport const MODEL_GPT_3_5_TURBO = 'gpt-3.5-turbo';\nexport const MODEL_GPT_4_OMNI = 'gpt-4o';\nexport const MODEL_WHISPER_1 = 'whisper-1';\nexport const MODEL_DALL_E_3 = 'dall-e-3';\n\nconst client = axios.create({\n  baseURL: config.OPENAI_BASE_URL,\n  timeout: config.OPENAI_TIMEOUT,\n  headers: {\n    'Accept-Encoding': 'gzip, deflate, compress',\n  },\n});\n\nclient.interceptors.request.use((c) => {\n  c.headers.Authorization = `Bearer ${config.OPENAI_API_KEY}`;\n  return handleRequest(c);\n});\n\nclient.interceptors.response.use(handleFulfilled, (err) => {\n  if (err.response?.data?.error?.message) {\n    err.message = err.response.data.error.message;\n  }\n  return handleRejected(err);\n});\n\nconst hasImage = ({ messages }) => (\n  messages.some(({ content }) => (\n    Array.isArray(content) && content.some((item) => item.image_url)\n  ))\n);\n\nconst createChatCompletion = ({\n  model = config.OPENAI_COMPLETION_MODEL,\n  messages,\n  temperature = config.OPENAI_COMPLETION_TEMPERATURE,\n  maxTokens = config.OPENAI_COMPLETION_MAX_TOKENS,\n  frequencyPenalty = config.OPENAI_COMPLETION_FREQUENCY_PENALTY,\n  presencePenalty = config.OPENAI_COMPLETION_PRESENCE_PENALTY,\n}) => {\n  const body = {\n    model: hasImage({ messages }) ? config.OPENAI_VISION_MODEL : model,\n    messages,\n    temperature,\n    max_tokens: maxTokens,\n    frequency_penalty: frequencyPenalty,\n    presence_penalty: presencePenalty,\n  };\n  return client.post('/v1/chat/completions', body);\n};\n\nconst createImage = ({\n  model = config.OPENAI_IMAGE_GENERATION_MODEL,\n  prompt,\n  size = config.OPENAI_IMAGE_GENERATION_SIZE,\n  quality = config.OPENAI_IMAGE_GENERATION_QUALITY,\n  n = 1,\n}) => {\n  // set image size to 1024 when using the DALL-E 3 model and the requested size is 256 or 512.\n  if (model === MODEL_DALL_E_3 && [IMAGE_SIZE_256, IMAGE_SIZE_512].includes(size)) {\n    size = IMAGE_SIZE_1024;\n  }\n\n  return client.post('/v1/images/generations', {\n    model,\n    prompt,\n    size,\n    quality,\n    n,\n  });\n};\n\nconst createAudioTranscriptions = ({\n  buffer,\n  file,\n  model = MODEL_WHISPER_1,\n}) => {\n  const formData = new FormData();\n  formData.append('file', buffer, file);\n  formData.append('model', model);\n  return client.post('/v1/audio/transcriptions', formData.getBuffer(), {\n    headers: formData.getHeaders(),\n  });\n};\n\nexport {\n  createAudioTranscriptions,\n  createChatCompletion,\n  createImage,\n};\n"
  },
  {
    "path": "services/serpapi.js",
    "content": "import axios from 'axios';\nimport config from '../config/index.js';\nimport { handleFulfilled, handleRejected, handleRequest } from './utils/index.js';\n\nconst client = axios.create({\n  baseURL: 'https://serpapi.com',\n  timeout: config.SERPAPI_TIMEOUT,\n  headers: {\n    'Accept-Encoding': 'gzip, deflate, compress',\n  },\n});\n\nclient.interceptors.request.use((c) => {\n  c.params = {\n    key: config.SERPAPI_API_KEY,\n    ...c.params,\n  };\n  return handleRequest(c);\n});\n\nclient.interceptors.response.use(handleFulfilled, (err) => {\n  if (err.response?.data?.error) {\n    err.message = err.response.data.error;\n  }\n  return handleRejected(err);\n});\n\nconst search = ({\n  gl = config.SERPAPI_LOCATION,\n  q,\n}) => client.get('/search', {\n  params: {\n    gl,\n    q,\n  },\n});\n\nexport {\n  search,\n};\n\nexport default null;\n"
  },
  {
    "path": "services/utils/index.js",
    "content": "import config from '../../config/index.js';\n\nconst handleRequest = (c) => {\n  c.metadata = { startTime: new Date() };\n  return c;\n};\n\nconst handleFulfilled = (response) => {\n  if (config.APP_DEBUG) console.info(`[${response.status}] ${response.config.method.toUpperCase()} ${response.config.baseURL}${response.config.url} (${(new Date() - response.config.metadata.startTime)}ms)`);\n  return response;\n};\n\nconst handleRejected = (err) => {\n  if (config.APP_DEBUG) console.info(`[${err.response?.status || 500}] ${err.config.method.toUpperCase()} ${err.config.baseURL}${err.config.url} (${(new Date() - err.config.metadata.startTime)}ms)`);\n  return Promise.reject(err);\n};\n\nexport {\n  handleRequest,\n  handleFulfilled,\n  handleRejected,\n};\n"
  },
  {
    "path": "services/vercel.js",
    "content": "import axios from 'axios';\nimport config from '../config/index.js';\nimport { handleFulfilled, handleRejected, handleRequest } from './utils/index.js';\n\nexport const ENV_TYPE_ENCRYPTED = 'encrypted';\nexport const ENV_TYPE_PLAIN = 'plain';\n\nexport const ENV_TARGET_PRODUCTION = 'production';\nexport const ENV_TARGET_PREVIEW = 'preview';\nexport const ENV_TARGET_DEVELOPMENT = 'development';\n\nconst client = axios.create({\n  baseURL: 'https://api.vercel.com',\n  timeout: config.VERCEL_TIMEOUT,\n  headers: {\n    'Accept-Encoding': 'gzip, deflate, compress',\n  },\n});\n\nclient.interceptors.request.use((c) => {\n  c.headers.Authorization = `Bearer ${config.VERCEL_ACCESS_TOKEN}`;\n  return handleRequest(c);\n});\n\nclient.interceptors.response.use(handleFulfilled, (err) => {\n  if (err.response?.data?.error?.message) {\n    err.message = err.response.data.error.message;\n  }\n  return handleRejected(err);\n});\n\nconst fetchEnvironments = () => client.get(`/v9/projects/${config.VERCEL_PROJECT_NAME}/env`, {\n  params: {\n    ...(config.VERCEL_TEAM_ID ? { teamId: config.VERCEL_TEAM_ID } : {}),\n  },\n});\n\nconst createEnvironment = ({\n  key,\n  value,\n  type = ENV_TYPE_ENCRYPTED,\n  target = [ENV_TARGET_PRODUCTION, ENV_TARGET_PREVIEW, ENV_TARGET_DEVELOPMENT],\n}) => client.post(`/v10/projects/${config.VERCEL_PROJECT_NAME}/env`, {\n  key: String(key),\n  value: String(value),\n  type,\n  target,\n}, {\n  params: {\n    ...(config.VERCEL_TEAM_ID ? { teamId: config.VERCEL_TEAM_ID } : {}),\n  },\n});\n\nconst updateEnvironment = ({\n  id,\n  value,\n  type = ENV_TYPE_ENCRYPTED,\n  target = [ENV_TARGET_PRODUCTION, ENV_TARGET_PREVIEW, ENV_TARGET_DEVELOPMENT],\n}) => client.patch(`/v9/projects/${config.VERCEL_PROJECT_NAME}/env/${id}`, {\n  value: String(value),\n  type,\n  target,\n}, {\n  params: {\n    ...(config.VERCEL_TEAM_ID ? { teamId: config.VERCEL_TEAM_ID } : {}),\n  },\n});\n\nconst deploy = () => axios.post(config.VERCEL_DEPLOY_HOOK_URL);\n\nexport {\n  fetchEnvironments,\n  createEnvironment,\n  updateEnvironment,\n  deploy,\n};\n"
  },
  {
    "path": "storage/index.js",
    "content": "import config from '../config/index.js';\nimport { createEnvironment, ENV_TYPE_PLAIN, updateEnvironment } from '../services/vercel.js';\nimport { fetchEnvironment } from '../utils/index.js';\n\nconst ENV_KEY = 'APP_STORAGE';\n\nclass Storage {\n  env;\n\n  data = {};\n\n  async initialize() {\n    if (!config.VERCEL_ACCESS_TOKEN) return;\n    this.env = await fetchEnvironment(ENV_KEY);\n    if (!this.env) {\n      const { data } = await createEnvironment({\n        key: ENV_KEY,\n        value: JSON.stringify(this.data),\n        type: ENV_TYPE_PLAIN,\n      });\n      this.env = data.created;\n    }\n    this.data = JSON.parse(this.env.value);\n  }\n\n  /**\n   * @param {string} key\n   * @returns {string}\n   */\n  getItem(key) {\n    return this.data[key];\n  }\n\n  /**\n   * @param {string} key\n   * @param {string} value\n   */\n  async setItem(key, value) {\n    this.data[key] = value;\n    if (!config.VERCEL_ACCESS_TOKEN) return;\n    await updateEnvironment({\n      id: this.env.id,\n      value: JSON.stringify(this.data, null, config.VERCEL_ENV ? 0 : 2),\n      type: ENV_TYPE_PLAIN,\n    });\n  }\n}\n\nconst storage = new Storage();\n\nexport default storage;\n"
  },
  {
    "path": "tests/activate.test.js",
    "content": "import {\n  afterEach, beforeEach, expect, test,\n} from '@jest/globals';\nimport { getPrompt, handleEvents, removePrompt } from '../app/index.js';\nimport { COMMAND_BOT_ACTIVATE, COMMAND_BOT_DEACTIVATE } from '../app/commands/index.js';\nimport { t } from '../locales/index.js';\nimport {\n  createEvents, MOCK_TEXT_OK, MOCK_USER_01, TIMEOUT,\n} from './utils.js';\n\nbeforeEach(async () => {\n  const events = [\n    ...createEvents([COMMAND_BOT_DEACTIVATE.text]),\n  ];\n  await handleEvents(events);\n});\n\nafterEach(() => {\n  removePrompt(MOCK_USER_01);\n});\n\ntest('COMMAND_BOT_ACTIVATE', async () => {\n  const events = [\n    ...createEvents(['嗨！']), // should be ignored\n    ...createEvents([COMMAND_BOT_ACTIVATE.text]),\n    ...createEvents(['嗨！']),\n  ];\n  let results;\n  try {\n    results = await handleEvents(events);\n  } catch (err) {\n    console.error(err);\n  }\n  expect(getPrompt(MOCK_USER_01).messages.length).toEqual(5);\n  const replies = results.map(({ messages }) => messages.map(({ text }) => text));\n  expect(replies).toEqual(\n    [\n      [\n        t('__ERROR_MISSING_ENV')('VERCEL_ACCESS_TOKEN'),\n        COMMAND_BOT_ACTIVATE.reply,\n      ],\n      [MOCK_TEXT_OK],\n    ],\n  );\n}, TIMEOUT);\n"
  },
  {
    "path": "tests/command.test.js",
    "content": "import {\n  afterEach,\n  beforeEach,\n  expect,\n  test,\n} from '@jest/globals';\nimport { getPrompt, handleEvents, removePrompt } from '../app/index.js';\nimport { COMMAND_SYS_COMMAND } from '../app/commands/index.js';\nimport { createEvents, TIMEOUT, MOCK_USER_01 } from './utils.js';\n\nbeforeEach(() => {\n  //\n});\n\nafterEach(() => {\n  removePrompt(MOCK_USER_01);\n});\n\ntest('COMMAND_SYS_COMMAND', async () => {\n  const events = [\n    ...createEvents([`${COMMAND_SYS_COMMAND.text}`]),\n  ];\n  let results;\n  try {\n    results = await handleEvents(events);\n  } catch (err) {\n    console.error(err);\n  }\n  expect(getPrompt(MOCK_USER_01).messages.length).toEqual(3);\n  const replies = results.map(({ messages }) => messages.map(({ altText }) => altText));\n  expect(replies).toEqual(\n    [\n      [COMMAND_SYS_COMMAND.label],\n    ],\n  );\n}, TIMEOUT);\n"
  },
  {
    "path": "tests/continue.test.js",
    "content": "import {\n  afterEach, beforeEach, expect, test,\n} from '@jest/globals';\nimport { getPrompt, handleEvents, removePrompt } from '../app/index.js';\nimport { COMMAND_BOT_CONTINUE } from '../app/commands/index.js';\nimport {\n  createEvents, TIMEOUT, MOCK_USER_01, MOCK_TEXT_OK,\n} from './utils.js';\n\nbeforeEach(() => {\n  //\n});\n\nafterEach(() => {\n  removePrompt(MOCK_USER_01);\n});\n\ntest('COMMAND_BOT_CONTINUE', async () => {\n  const events = [\n    ...createEvents([COMMAND_BOT_CONTINUE.text]),\n  ];\n  let results;\n  try {\n    results = await handleEvents(events);\n  } catch (err) {\n    console.error(err);\n  }\n  expect(getPrompt(MOCK_USER_01).messages.length).toEqual(3);\n  const replies = results.map(({ messages }) => messages.map(({ text }) => text));\n  expect(replies).toEqual(\n    [\n      [MOCK_TEXT_OK],\n    ],\n  );\n}, TIMEOUT);\n"
  },
  {
    "path": "tests/deactivate.test.js",
    "content": "import {\n  afterEach, beforeEach, expect, test,\n} from '@jest/globals';\nimport { getPrompt, handleEvents, removePrompt } from '../app/index.js';\nimport { COMMAND_BOT_ACTIVATE, COMMAND_BOT_DEACTIVATE } from '../app/commands/index.js';\nimport { t } from '../locales/index.js';\nimport storage from '../storage/index.js';\nimport {\n  createEvents, MOCK_TEXT_OK, MOCK_USER_01, TIMEOUT,\n} from './utils.js';\n\nbeforeEach(async () => {\n  const events = [\n    ...createEvents([COMMAND_BOT_ACTIVATE.text]),\n  ];\n  await handleEvents(events);\n});\n\nafterEach(() => {\n  removePrompt(MOCK_USER_01);\n});\n\ntest('COMMAND_BOT_DEACTIVATE', async () => {\n  const events = [\n    ...createEvents(['嗨！']),\n    ...createEvents([COMMAND_BOT_DEACTIVATE.text]),\n    ...createEvents(['嗨！']), // should be ignored\n  ];\n  let results;\n  try {\n    results = await handleEvents(events);\n  } catch (err) {\n    console.error(err);\n  }\n  expect(getPrompt(MOCK_USER_01).messages.length).toEqual(5);\n  const replies = results.map(({ messages }) => messages.map(({ text }) => text));\n  expect(replies).toEqual(\n    [\n      [MOCK_TEXT_OK],\n      [\n        t('__ERROR_MISSING_ENV')('VERCEL_ACCESS_TOKEN'),\n        COMMAND_BOT_DEACTIVATE.reply,\n      ],\n    ],\n  );\n}, TIMEOUT);\n"
  },
  {
    "path": "tests/default.test.js",
    "content": "import {\n  afterEach, beforeEach, expect, test,\n} from '@jest/globals';\nimport {\n  getPrompt, handleEvents, removePrompt, printHistories,\n} from '../app/index.js';\nimport config from '../config/index.js';\nimport {\n  createEvents, TIMEOUT, MOCK_USER_01, MOCK_TEXT_OK,\n} from './utils.js';\n\nbeforeEach(() => {\n  //\n});\n\nafterEach(() => {\n  removePrompt(MOCK_USER_01);\n});\n\ntest('DEFAULT', async () => {\n  const events = [\n    ...createEvents(['嗨！']),\n  ];\n  let results;\n  try {\n    results = await handleEvents(events);\n  } catch (err) {\n    console.error(err);\n  }\n  if (config.APP_DEBUG) printHistories();\n  expect(getPrompt(MOCK_USER_01).messages.length).toEqual(5);\n  const replies = results.map(({ messages }) => messages.map(({ text }) => text));\n  expect(replies).toEqual(\n    [\n      [MOCK_TEXT_OK],\n    ],\n  );\n}, TIMEOUT);\n"
  },
  {
    "path": "tests/draw.test.js",
    "content": "import {\n  afterEach, beforeEach, expect, test,\n} from '@jest/globals';\nimport { handleEvents, getPrompt, removePrompt } from '../app/index.js';\nimport { COMMAND_BOT_DRAW } from '../app/commands/index.js';\nimport {\n  createEvents, TIMEOUT, MOCK_USER_01, MOCK_TEXT_OK,\n} from './utils.js';\n\nbeforeEach(() => {\n  //\n});\n\nafterEach(() => {\n  removePrompt(MOCK_USER_01);\n});\n\ntest('COMMAND_BOT_DRAW', async () => {\n  const events = [\n    ...createEvents([`${COMMAND_BOT_DRAW.text}人工智慧`]),\n  ];\n  let results;\n  try {\n    results = await handleEvents(events);\n  } catch (err) {\n    console.error(err);\n  }\n  expect(getPrompt(MOCK_USER_01).messages.length).toEqual(5);\n  const replies = results.map(({ messages }) => messages.map(({ originalContentUrl }) => originalContentUrl));\n  expect(replies).toEqual(\n    [\n      [MOCK_TEXT_OK],\n    ],\n  );\n}, TIMEOUT);\n"
  },
  {
    "path": "tests/enquire.test.js",
    "content": "import {\n  afterEach, beforeEach, expect, test,\n} from '@jest/globals';\nimport { COMMAND_BOT_TALK, COMMAND_SUM_SUM } from '../app/commands/index.js';\nimport { getPrompt, handleEvents, removePrompt } from '../app/index.js';\nimport { MOCK_GROUP_01 } from '../constants/mock.js';\nimport {\n  createEvents, MOCK_TEXT_OK, MOCK_USER_01, MOCK_USER_02, TIMEOUT,\n} from './utils.js';\n\nbeforeEach(async () => {\n  //\n});\n\nafterEach(() => {\n  removePrompt(MOCK_USER_01);\n  removePrompt(MOCK_USER_02);\n});\n\ntest('COMMAND_ENQUIRE', async () => {\n  try {\n    await handleEvents(createEvents([`${COMMAND_BOT_TALK.text}人工智慧`], MOCK_GROUP_01, MOCK_USER_01));\n  } catch (err) {\n    console.error(err);\n  }\n  const events = [\n    ...createEvents([`${COMMAND_SUM_SUM.text}`], MOCK_GROUP_01, MOCK_USER_02),\n  ];\n  let results;\n  try {\n    results = await handleEvents(events);\n  } catch (err) {\n    console.error(err);\n  }\n  expect(getPrompt(MOCK_USER_01).messages.length).toEqual(5);\n  expect(getPrompt(MOCK_USER_02).messages.length).toEqual(6);\n  const replies = results.map(({ messages }) => messages.map(({ text }) => text));\n  expect(replies).toEqual(\n    [\n      [MOCK_TEXT_OK],\n    ],\n  );\n}, TIMEOUT);\n"
  },
  {
    "path": "tests/summon.test.js",
    "content": "import {\n  afterEach, beforeEach, expect, test,\n} from '@jest/globals';\nimport { getPrompt, handleEvents, removePrompt } from '../app/index.js';\nimport config from '../config/index.js';\nimport {\n  createEvents, MOCK_TEXT_OK, MOCK_USER_01, TIMEOUT,\n} from './utils.js';\n\nbeforeEach(() => {\n  //\n});\n\nafterEach(() => {\n  removePrompt(MOCK_USER_01);\n});\n\ntest('COMMAND_BOT_SUMMON', async () => {\n  const events = [\n    ...createEvents([`${config.BOT_NAME} 你好`]),\n  ];\n  let results;\n  try {\n    results = await handleEvents(events);\n  } catch (err) {\n    console.error(err);\n  }\n  expect(getPrompt(MOCK_USER_01).messages.length).toEqual(5);\n  const replies = results.map(({ messages }) => messages.map(({ text }) => text));\n  expect(replies).toEqual(\n    [\n      [MOCK_TEXT_OK],\n    ],\n  );\n}, TIMEOUT);\n"
  },
  {
    "path": "tests/talk.test.js",
    "content": "import {\n  afterEach, beforeEach, expect, test,\n} from '@jest/globals';\nimport { getPrompt, handleEvents, removePrompt } from '../app/index.js';\nimport { COMMAND_BOT_TALK } from '../app/commands/index.js';\nimport {\n  createEvents, TIMEOUT, MOCK_USER_01, MOCK_TEXT_OK,\n} from './utils.js';\n\nbeforeEach(() => {\n  //\n});\n\nafterEach(() => {\n  removePrompt(MOCK_USER_01);\n});\n\ntest('COMMAND_BOT_TALK', async () => {\n  const events = [\n    ...createEvents([`${COMMAND_BOT_TALK.text}人工智慧`]),\n  ];\n  let results;\n  try {\n    results = await handleEvents(events);\n  } catch (err) {\n    console.error(err);\n  }\n  expect(getPrompt(MOCK_USER_01).messages.length).toEqual(5);\n  const replies = results.map(({ messages }) => messages.map(({ text }) => text));\n  expect(replies).toEqual(\n    [\n      [MOCK_TEXT_OK],\n    ],\n  );\n}, TIMEOUT);\n"
  },
  {
    "path": "tests/utils.js",
    "content": "import Event from '../app/models/event.js';\nimport { MOCK_TEXT_OK, MOCK_USER_01, MOCK_USER_02 } from '../constants/mock.js';\nimport {\n  EVENT_TYPE_MESSAGE, MESSAGE_TYPE_TEXT, SOURCE_TYPE_GROUP, SOURCE_TYPE_USER,\n} from '../services/line.js';\n\nexport const TIMEOUT = 9 * 1000;\n\nconst createEvents = (\n  messages,\n  groupId,\n  userId = MOCK_USER_01,\n) => messages.map((text) => new Event({\n  replyToken: '',\n  type: EVENT_TYPE_MESSAGE,\n  source: { type: groupId ? SOURCE_TYPE_GROUP : SOURCE_TYPE_USER, userId, groupId },\n  message: { type: MESSAGE_TYPE_TEXT, text },\n}));\n\nexport {\n  MOCK_TEXT_OK,\n  MOCK_USER_01,\n  MOCK_USER_02,\n  createEvents,\n};\n"
  },
  {
    "path": "tests/version.test.js",
    "content": "import {\n  afterEach, beforeEach, expect, test,\n} from '@jest/globals';\nimport { getPrompt, handleEvents, removePrompt } from '../app/index.js';\nimport { COMMAND_SYS_VERSION } from '../app/commands/index.js';\nimport { t } from '../locales/index.js';\nimport { fetchVersion, getVersion } from '../utils/index.js';\nimport { createEvents, MOCK_USER_01, TIMEOUT } from './utils.js';\n\nbeforeEach(() => {\n  //\n});\n\nafterEach(() => {\n  removePrompt(MOCK_USER_01);\n});\n\ntest('COMMAND_SYS_VERSION', async () => {\n  const events = [\n    ...createEvents([COMMAND_SYS_VERSION.text]),\n  ];\n  let results;\n  try {\n    results = await handleEvents(events);\n  } catch (err) {\n    console.error(err);\n  }\n  const current = getVersion();\n  const latest = await fetchVersion();\n  const isLatest = current === latest;\n  expect(getPrompt(MOCK_USER_01).messages.length).toEqual(3);\n  const replies = results.map(({ messages }) => messages.map(({ text }) => text));\n  expect(replies).toEqual(\n    [\n      [t('__COMMAND_SYS_VERSION_REPLY')(current, isLatest)],\n    ],\n  );\n}, TIMEOUT);\n"
  },
  {
    "path": "utils/add-mark.js",
    "content": "import config from '../config/index.js';\n\nconst addMark = (text) => {\n  if (!text) return text;\n  const marks = ['？', '。', '！', '?', '.', '!'];\n  if (marks.some((mark) => text.endsWith(mark))) {\n    return text;\n  }\n  switch (config.APP_LANG) {\n    case 'zh':\n    case 'zh_TW':\n    case 'zh_CN':\n    case 'ja':\n      return `${text}。`;\n    default:\n      return `${text}.`;\n  }\n};\n\nexport default addMark;\n"
  },
  {
    "path": "utils/convert-text.js",
    "content": "import * as OpenCC from 'opencc-js';\nimport config from '../config/index.js';\n\nconst convertText = (text) => {\n  if (config.APP_LANG === 'zh_TW') {\n    const converter = OpenCC.Converter({ from: 'cn', to: 'tw' });\n    return converter(text);\n  }\n  if (config.APP_LANG === 'zh_CN') {\n    const converter = OpenCC.Converter({ from: 'tw', to: 'cn' });\n    return converter(text);\n  }\n  return text;\n};\n\nexport default convertText;\n"
  },
  {
    "path": "utils/fetch-answer.js",
    "content": "import config from '../config/index.js';\nimport { search } from '../services/serpapi.js';\n\nclass OrganicResult {\n  answer;\n\n  constructor({\n    answer,\n  } = {}) {\n    this.answer = answer;\n  }\n}\n\nconst fetchAnswer = async (q) => {\n  if (config.APP_ENV !== 'production' || !config.SERPAPI_API_KEY) return new OrganicResult();\n  const res = await search({ q });\n  const { answer_box: answerBox, knowledge_graph: knowledgeGraph, organic_results: organicResults } = res.data;\n  let answer = organicResults[0].snippet;\n  if (answerBox?.answer) answer += answerBox.answer;\n  if (answerBox?.result) answer += answerBox.result;\n  if (answerBox?.snippet) answer += answerBox.snippet;\n  if (knowledgeGraph?.description) answer += `${knowledgeGraph.title} - ${knowledgeGraph.description}`;\n  return new OrganicResult({ answer });\n};\n\nexport default fetchAnswer;\n"
  },
  {
    "path": "utils/fetch-audio.js",
    "content": "import { fetchContent } from '../services/line.js';\n\n/**\n * @param {string} messageId\n * @returns {Promise<Buffer>}\n */\nconst fetchAudio = async (messageId) => {\n  const { data } = await fetchContent({ messageId });\n  return Buffer.from(data, 'binary');\n};\n\nexport default fetchAudio;\n"
  },
  {
    "path": "utils/fetch-environment.js",
    "content": "import { fetchEnvironments } from '../services/vercel.js';\n\nconst fetchEnvironment = async (key) => {\n  const { data } = await fetchEnvironments();\n  return data.envs.find((env) => env.key === key);\n};\n\nexport default fetchEnvironment;\n"
  },
  {
    "path": "utils/fetch-group.js",
    "content": "import config from '../config/index.js';\nimport { mockGroups } from '../constants/mock.js';\nimport { t } from '../locales/index.js';\nimport { fetchGroupSummary } from '../services/line.js';\n\nclass Group {\n  groupName;\n\n  constructor({\n    groupName,\n  }) {\n    this.groupName = groupName;\n  }\n}\n\n/**\n * @param {string} groupId\n * @returns {Promise<Group>}\n */\nconst fetchGroup = async (groupId) => {\n  if (config.APP_ENV !== 'production') return new Group(mockGroups[groupId]);\n  try {\n    const { data } = await fetchGroupSummary({ groupId });\n    return new Group(data);\n  } catch {\n    return new Group({ groupName: t('__SOURCE_NAME_SOME_GROUP') });\n  }\n};\n\nexport default fetchGroup;\n"
  },
  {
    "path": "utils/fetch-image.js",
    "content": "import { fetchContent } from '../services/line.js';\n\n/**\n * @param {string} messageId\n * @returns {Promise<string>}\n */\nconst fetchImage = async (messageId) => {\n  const { data } = await fetchContent({ messageId });\n  return `data:image/jpeg;base64,${Buffer.from(data, 'binary').toString('base64')}`;\n};\n\nexport default fetchImage;\n"
  },
  {
    "path": "utils/fetch-user.js",
    "content": "import config from '../config/index.js';\nimport { mockUsers } from '../constants/mock.js';\nimport { t } from '../locales/index.js';\nimport { fetchProfile } from '../services/line.js';\n\nclass User {\n  displayName;\n\n  constructor({\n    displayName,\n  }) {\n    this.displayName = displayName;\n  }\n}\n\n/**\n * @param {string} userId\n * @returns {Promise<User>}\n */\nconst fetchUser = async (userId) => {\n  if (config.APP_ENV !== 'production') return new User(mockUsers[userId]);\n  try {\n    const { data } = await fetchProfile({ userId });\n    return new User(data);\n  } catch {\n    return new User({ displayName: t('__SOURCE_NAME_SOMEONE') });\n  }\n};\n\nexport default fetchUser;\n"
  },
  {
    "path": "utils/fetch-version.js",
    "content": "import axios from 'axios';\n\n/**\n * @returns {Promise<string>}\n */\nconst fetchVersion = async () => {\n  const { data } = await axios.get('https://raw.githubusercontent.com/memochou1993/gpt-ai-assistant/main/package.json');\n  return data.version;\n};\n\nexport default fetchVersion;\n"
  },
  {
    "path": "utils/generate-completion.js",
    "content": "import config from '../config/index.js';\nimport { MOCK_TEXT_OK } from '../constants/mock.js';\nimport { createChatCompletion, FINISH_REASON_STOP } from '../services/openai.js';\n\nclass Completion {\n  text;\n\n  finishReason;\n\n  constructor({\n    text,\n    finishReason,\n  }) {\n    this.text = text;\n    this.finishReason = finishReason;\n  }\n\n  get isFinishReasonStop() {\n    return this.finishReason === FINISH_REASON_STOP;\n  }\n}\n\n/**\n * @param {Object} param\n * @param {Prompt} param.prompt\n * @returns {Promise<Completion>}\n */\nconst generateCompletion = async ({\n  prompt,\n}) => {\n  if (config.APP_ENV !== 'production') return new Completion({ text: MOCK_TEXT_OK });\n  const { data } = await createChatCompletion({ messages: prompt.messages });\n  const [choice] = data.choices;\n  return new Completion({\n    text: choice.message.content.trim(),\n    finishReason: choice.finish_reason,\n  });\n};\n\nexport default generateCompletion;\n"
  },
  {
    "path": "utils/generate-image.js",
    "content": "import config from '../config/index.js';\nimport { MOCK_TEXT_OK } from '../constants/mock.js';\nimport { createImage } from '../services/openai.js';\n\nclass Image {\n  url;\n\n  constructor({\n    url,\n  }) {\n    this.url = url;\n  }\n}\n\n/**\n * @param {Object} param\n * @param {string} param.prompt\n * @returns {Promise<Image>}\n */\nconst generateImage = async ({\n  prompt,\n}) => {\n  if (config.APP_ENV !== 'production') return new Image({ url: MOCK_TEXT_OK });\n  const { data } = await createImage({ prompt });\n  const [image] = data.data;\n  return new Image(image);\n};\n\nexport default generateImage;\n"
  },
  {
    "path": "utils/generate-transcription.js",
    "content": "import { createAudioTranscriptions } from '../services/openai.js';\n\nclass Transcription {\n  text;\n\n  constructor({\n    text,\n  }) {\n    this.text = text;\n  }\n}\n\n/**\n * @param {Object} param\n * @param {Buffer} param.buffer\n * @param {string} param.file\n * @returns {Promise<Image>}\n */\nconst generateTranscription = async ({\n  buffer,\n  file,\n}) => {\n  const { data } = await createAudioTranscriptions({ buffer, file });\n  return new Transcription(data);\n};\n\nexport default generateTranscription;\n"
  },
  {
    "path": "utils/get-command.js",
    "content": "import { Command, ALL_COMMANDS } from '../app/commands/index.js';\n\n/**\n * @param {string} text\n * @returns {Command}\n */\nconst getCommand = (text) => (\n  Object.values(ALL_COMMANDS)\n    .sort((a, b) => b.text.length - a.text.length)\n    .find((c) => (\n      c.aliases.includes(text) || text.toLowerCase().includes(c.text.toLowerCase())\n    ))\n);\n\nexport default getCommand;\n"
  },
  {
    "path": "utils/get-version.js",
    "content": "import fs from 'fs';\n\n/**\n * @returns {string}\n */\nconst getVersion = () => JSON.parse(fs.readFileSync('package.json')).version;\n\nexport default getVersion;\n"
  },
  {
    "path": "utils/index.js",
    "content": "import addMark from './add-mark.js';\nimport convertText from './convert-text.js';\nimport fetchAnswer from './fetch-answer.js';\nimport fetchAudio from './fetch-audio.js';\nimport fetchImage from './fetch-image.js';\nimport fetchEnvironment from './fetch-environment.js';\nimport fetchGroup from './fetch-group.js';\nimport fetchUser from './fetch-user.js';\nimport fetchVersion from './fetch-version.js';\nimport generateCompletion from './generate-completion.js';\nimport generateImage from './generate-image.js';\nimport generateTranscription from './generate-transcription.js';\nimport getCommand from './get-command.js';\nimport getVersion from './get-version.js';\nimport replyMessage from './reply-message.js';\nimport validateSignature from './validate-signature.js';\n\nexport {\n  addMark,\n  convertText,\n  fetchAnswer,\n  fetchAudio,\n  fetchImage,\n  fetchEnvironment,\n  fetchGroup,\n  fetchUser,\n  fetchVersion,\n  generateCompletion,\n  generateImage,\n  generateTranscription,\n  getCommand,\n  getVersion,\n  replyMessage,\n  validateSignature,\n};\n"
  },
  {
    "path": "utils/reply-message.js",
    "content": "import config from '../config/index.js';\nimport { reply } from '../services/line.js';\n\nconst replyMessage = ({\n  replyToken,\n  messages,\n}) => {\n  if (config.APP_ENV !== 'production') return { replyToken, messages };\n  return reply({ replyToken, messages });\n};\n\nexport default replyMessage;\n"
  },
  {
    "path": "utils/validate-signature.js",
    "content": "import {\n  createHmac,\n  timingSafeEqual,\n} from 'crypto';\n\nconst s2b = (str, encoding) => Buffer.from(str, encoding);\n\nconst safeCompare = (a, b) => {\n  if (a.length !== b.length) {\n    return false;\n  }\n  return timingSafeEqual(a, b);\n};\n\nconst validateSignature = (\n  body,\n  secret,\n  signature,\n) => safeCompare(\n  createHmac('SHA256', secret).update(body).digest(),\n  s2b(signature, 'base64'),\n);\n\nexport default validateSignature;\n"
  },
  {
    "path": "vercel.json",
    "content": "{\n  \"rewrites\": [\n    {\n      \"source\": \"/(.*)\",\n      \"destination\": \"/api\"\n    }\n  ],\n  \"functions\": {\n    \"api/**/*\": {\n      \"maxDuration\": 10\n    }\n  }\n}\n"
  }
]