diff --git a/backend/src/intelligence/prompt/followup.ts b/backend/src/intelligence/prompt/followup.ts
index b03cdcff..ffd2b276 100644
--- a/backend/src/intelligence/prompt/followup.ts
+++ b/backend/src/intelligence/prompt/followup.ts
@@ -1,4 +1,4 @@
-import { ChatPromptTemplate, MessagesPlaceholder } from "langchain/prompts";
+import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
export const followUpPromptTemplate = ChatPromptTemplate.fromMessages([
[
diff --git a/backend/src/intelligence/prompt/github-issue.ts b/backend/src/intelligence/prompt/github-issue.ts
index 76f59827..8d9e9cef 100644
--- a/backend/src/intelligence/prompt/github-issue.ts
+++ b/backend/src/intelligence/prompt/github-issue.ts
@@ -1,7 +1,7 @@
-import { ChatPromptTemplate, FewShotChatMessagePromptTemplate } from "langchain/prompts";
+import { ChatPromptTemplate, FewShotChatMessagePromptTemplate } from "@langchain/core/prompts";
const examplePrompt = ChatPromptTemplate.fromTemplate(
- "## Title\n{title}\n## Issue Type\n{issueType}\n## Content\n{content}"
+ "[Example]\n## Title\n{title}\n## Issue Type\n{issueType}\n## Content\n{content}"
);
const examples = [
@@ -73,8 +73,9 @@ Keep the product simple`,
];
export const githubIssuePromptTemplate = new FewShotChatMessagePromptTemplate({
- prefix: `I want you to act as a GitHub Issue writer. I will provide brief information about the GitHub issue I want to create, and you should write the GitHub issue based on the examples I provide.
- The types of issues you can write are bug 🐞 or enhancement 🌟. Please ensure that you follow the template used in each type of issue example provided. Please write your responses in English.`,
+ prefix: `I want you to act as a GitHub Issue writer. I will provide brief information about the GitHub issue I want to create, and you should write the GitHub issue.
+The types of issues you can write are bug 🐞 or enhancement 🌟. Please ensure that you follow the template used in each type of issue example provided. Do not provide the example as it is. Please write your responses in English.
+If there is insufficient information to create the issue, request additional information.`,
suffix: "Brief information about the GitHub issue: {content}",
examplePrompt,
examples,
diff --git a/backend/src/intelligence/prompt/github-pr.ts b/backend/src/intelligence/prompt/github-pr.ts
index 66443aed..9f236347 100644
--- a/backend/src/intelligence/prompt/github-pr.ts
+++ b/backend/src/intelligence/prompt/github-pr.ts
@@ -1,6 +1,8 @@
-import { ChatPromptTemplate, FewShotChatMessagePromptTemplate } from "langchain/prompts";
+import { ChatPromptTemplate, FewShotChatMessagePromptTemplate } from "@langchain/core/prompts";
-const examplePrompt = ChatPromptTemplate.fromTemplate("## Title\n{title}\n## Content\n{content}");
+const examplePrompt = ChatPromptTemplate.fromTemplate(
+ "[Example]\n## Title\n{title}\n## Content\n{content}"
+);
const examples = [
{
@@ -100,10 +102,9 @@ Fixes #
];
export const githubPrPromptTemplate = new FewShotChatMessagePromptTemplate({
- prefix: `I want you to act as a GitHub PR Writer for me. I'll provide you with brief notes about GitHub PR, and you just need to write the PR using the examples I've provided.
-Make sure to adhere to the template that we commonly follow in Example.
-If the information is not provided by the user, please refrain from attaching document links found elsewhere. Please respond in English.
-Please refer to the example for guidance, but generate results based on the information provided in the Brief Information section.`,
+ prefix: `I want you to act as a GitHub PR Writer for me. I'll provide you with brief notes about GitHub PR, and you just need to write the PR.
+Please ensure that you follow the template used in example provided. Do not provide the example as it is. Please write your responses in English.
+If there is insufficient information to create the PR, request additional information.`,
suffix: "Brief information about the GitHub PR: {content}",
examplePrompt,
examples,
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 22771ef8..dad1a08b 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -33,6 +33,7 @@
"color": "^4.2.3",
"lib0": "^0.2.88",
"lodash": "^4.17.21",
+ "match-sorter": "^6.3.3",
"moment": "^2.30.1",
"notistack": "^2.0.8",
"randomcolor": "^0.6.2",
@@ -47,7 +48,7 @@
"react-social-login-buttons": "^3.9.1",
"react-use": "^17.5.0",
"redux-persist": "^6.0.0",
- "yorkie-js-sdk": "^0.4.13"
+ "yorkie-js-sdk": "^0.4.15-rc"
},
"devDependencies": {
"@types/color": "^3.0.6",
@@ -715,70 +716,6 @@
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
"integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
},
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz",
- "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz",
- "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz",
- "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz",
- "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/@esbuild/darwin-arm64": {
"version": "0.19.11",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz",
@@ -795,294 +732,6 @@
"node": ">=12"
}
},
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz",
- "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz",
- "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz",
- "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz",
- "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz",
- "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz",
- "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz",
- "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz",
- "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz",
- "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz",
- "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz",
- "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz",
- "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz",
- "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz",
- "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz",
- "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz",
- "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz",
- "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.19.11",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz",
- "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -1813,32 +1462,6 @@
"node": ">=14.0.0"
}
},
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz",
- "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz",
- "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ]
- },
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.9.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz",
@@ -1852,136 +1475,6 @@
"darwin"
]
},
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz",
- "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz",
- "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz",
- "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz",
- "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz",
- "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz",
- "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz",
- "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz",
- "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz",
- "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz",
- "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
- },
"node_modules/@swc/helpers": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.3.tgz",
@@ -4634,6 +4127,15 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/match-sorter": {
+ "version": "6.3.3",
+ "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.3.tgz",
+ "integrity": "sha512-sgiXxrRijEe0SzHKGX4HouCpfHRPnqteH42UdMEW7BlWy990ZkzcvonJGv4Uu9WE7Y1f8Yocm91+4qFPCbmNww==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.8",
+ "remove-accents": "0.5.0"
+ }
+ },
"node_modules/mdast-util-find-and-replace": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz",
@@ -6445,6 +5947,11 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/remove-accents": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz",
+ "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="
+ },
"node_modules/reselect": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz",
@@ -7267,9 +6774,9 @@
}
},
"node_modules/yorkie-js-sdk": {
- "version": "0.4.13",
- "resolved": "https://registry.npmjs.org/yorkie-js-sdk/-/yorkie-js-sdk-0.4.13.tgz",
- "integrity": "sha512-6Im/SRxJoGcUOf5nHIQDDZnKbLBDUgD66xZxcrAXvZS4KblQnRU54/MdloIF9BzNJlJGwWSk9KjgYwLY73nntg==",
+ "version": "0.4.15-rc",
+ "resolved": "https://registry.npmjs.org/yorkie-js-sdk/-/yorkie-js-sdk-0.4.15-rc.tgz",
+ "integrity": "sha512-phH4zcT7qr908dclFtgs57fPT7F5sBdEtc+5VGL6kAEUOsIONtz/cPLsJCE2+RkGgCFQ8iCevhl9rr1dxE9kWA==",
"dependencies": {
"@types/google-protobuf": "^3.15.5",
"@types/long": "^4.0.1",
diff --git a/frontend/package.json b/frontend/package.json
index 85b002c1..e527458d 100755
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -36,6 +36,7 @@
"codemirror-toolbar": "^0.0.3",
"color": "^4.2.3",
"lib0": "^0.2.88",
+ "match-sorter": "^6.3.3",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"notistack": "^2.0.8",
@@ -51,7 +52,7 @@
"react-social-login-buttons": "^3.9.1",
"react-use": "^17.5.0",
"redux-persist": "^6.0.0",
- "yorkie-js-sdk": "^0.4.13"
+ "yorkie-js-sdk": "^0.4.15-rc"
},
"devDependencies": {
"@types/color": "^3.0.6",
diff --git a/frontend/public/yorkie.png b/frontend/public/yorkie.png
new file mode 100644
index 00000000..6f57b5f0
Binary files /dev/null and b/frontend/public/yorkie.png differ
diff --git a/frontend/src/components/editor/DocumentView.tsx b/frontend/src/components/editor/DocumentView.tsx
index 8f3c6d01..a9bbd3cf 100644
--- a/frontend/src/components/editor/DocumentView.tsx
+++ b/frontend/src/components/editor/DocumentView.tsx
@@ -43,7 +43,7 @@ function DocumentView() {
width: 8,
borderRadius: 0,
cursor: "col-resize",
- zIndex: 100,
+ zIndex: 0,
}}
/>
();
const editorStore = useSelector(selectEditor);
@@ -42,6 +44,7 @@ function Editor() {
}),
EditorView.lineWrapping,
keymap.of([indentWithTab]),
+ intelligencePivot,
],
});
@@ -49,11 +52,12 @@ function Editor() {
state,
parent: element,
});
+ dispatch(setCmView(view));
return () => {
view?.destroy();
};
- }, [editorStore.client, editorStore.doc, element, themeMode]);
+ }, [dispatch, editorStore.client, editorStore.doc, element, themeMode]);
return (
{
const editorText = editorStore.doc?.getRoot().content?.toString() || "";
// Add soft line break
- setContent(
- editorText
- .split("\n")
- .map((line) => line + " ")
- .join("\n")
- );
+ setContent(addSoftLineBreak(editorText));
};
updatePreviewContent();
@@ -46,9 +42,13 @@ function Preview() {
return (
);
diff --git a/frontend/src/components/editor/YorkieIntelligence.tsx b/frontend/src/components/editor/YorkieIntelligence.tsx
new file mode 100644
index 00000000..1fcac643
--- /dev/null
+++ b/frontend/src/components/editor/YorkieIntelligence.tsx
@@ -0,0 +1,85 @@
+import { Card, CardActionArea, Fade, Popper, Stack, Typography, useTheme } from "@mui/material";
+import { useEffect, useState } from "react";
+import { createPortal } from "react-dom";
+import { useDebounce } from "react-use";
+import { INTELLIGENCE_FOOTER_ID, INTELLIGENCE_HEADER_ID } from "../../constants/intelligence";
+import YorkieIntelligenceFooter from "./YorkieIntelligenceFooter";
+
+function YorkieIntelligence() {
+ const theme = useTheme();
+ const [footerOpen, setFooterOpen] = useState(false);
+ const [intelligenceHeaderPivot, setIntelligenceHeaderPivot] = useState
(null);
+ const [intelligenceFooterPivot, setIntelligenceFooterPivot] = useState(null);
+ const [debouncedPivot, setDebouncedPivot] = useState(null);
+
+ useDebounce(
+ () => {
+ setDebouncedPivot(intelligenceHeaderPivot);
+ },
+ 500,
+ [intelligenceHeaderPivot]
+ );
+
+ useEffect(() => {
+ document.addEventListener("selectionchange", function () {
+ const intelligenceHeaderPivot = document.getElementById(INTELLIGENCE_HEADER_ID);
+ const intelligenceFooterPivot = document.getElementById(INTELLIGENCE_FOOTER_ID);
+ setIntelligenceHeaderPivot(intelligenceHeaderPivot);
+ setIntelligenceFooterPivot(intelligenceFooterPivot);
+
+ if (!intelligenceHeaderPivot) {
+ setFooterOpen(false);
+ setDebouncedPivot(null);
+ }
+ });
+ }, []);
+
+ const handleFooterOpen = () => {
+ setFooterOpen((prev) => !prev);
+ };
+
+ if (!debouncedPivot || !intelligenceFooterPivot) return;
+
+ return (
+ <>
+
+ {({ TransitionProps }) => (
+
+
+
+
+
+ Yorkie Intelligence
+
+
+
+
+ )}
+
+ {footerOpen &&
+ createPortal(
+ ,
+ intelligenceFooterPivot
+ )}
+ >
+ );
+}
+
+export default YorkieIntelligence;
diff --git a/frontend/src/components/editor/YorkieIntelligenceFeature.tsx b/frontend/src/components/editor/YorkieIntelligenceFeature.tsx
new file mode 100644
index 00000000..c04ee28b
--- /dev/null
+++ b/frontend/src/components/editor/YorkieIntelligenceFeature.tsx
@@ -0,0 +1,215 @@
+import {
+ Box,
+ Button,
+ CircularProgress,
+ Fade,
+ FormControl,
+ IconButton,
+ InputAdornment,
+ Stack,
+ Typography,
+ useTheme,
+} from "@mui/material";
+import { INTELLIGENCE_FOOTER_ID, IntelligenceFeature } from "../../constants/intelligence";
+import ContentCopyIcon from "@mui/icons-material/ContentCopy";
+import RefreshIcon from "@mui/icons-material/Refresh";
+import { FormContainer, TextFieldElement, useForm } from "react-hook-form-mui";
+import SendIcon from "@mui/icons-material/Send";
+import { useIntelligenceFeatureStream, useIntelligenceStream } from "../../hooks/api/intelligence";
+import { useEffect, useMemo, useRef, useState } from "react";
+import clipboard from "clipboardy";
+import { useSnackbar } from "notistack";
+import MarkdownPreview from "@uiw/react-markdown-preview";
+import { useCurrentTheme } from "../../hooks/useCurrentTheme";
+import { addSoftLineBreak } from "../../utils/document";
+import { useSelector } from "react-redux";
+import { selectEditor } from "../../store/editorSlice";
+
+interface YorkieIntelligenceFeatureProps {
+ title: string;
+ feature: IntelligenceFeature;
+ onClose: () => void;
+}
+
+function YorkieIntelligenceFeature(props: YorkieIntelligenceFeatureProps) {
+ const { title, feature, onClose } = props;
+ const theme = useTheme();
+ const currentTheme = useCurrentTheme();
+ const editorStore = useSelector(selectEditor);
+ const {
+ data: featureData,
+ memoryKey,
+ isLoading: isFeatureLoading,
+ isComplete: isFeatureComplete,
+ mutateAsync: mutateIntelligenceFeature,
+ } = useIntelligenceFeatureStream(feature);
+ const {
+ data: followUpData,
+ isLoading: isFollowUpLoading,
+ isComplete: isFollowUpComplete,
+ mutateAsync: mutateIntelligence,
+ } = useIntelligenceStream(memoryKey);
+ const [content, setContent] = useState("");
+ const intelligenceFooterPivot = document.getElementById(INTELLIGENCE_FOOTER_ID);
+ const isLoading = useMemo(
+ () => isFeatureLoading || isFollowUpLoading,
+ [isFeatureLoading, isFollowUpLoading]
+ );
+ const isComplete = useMemo(
+ () => isFeatureComplete || isFollowUpComplete,
+ [isFeatureComplete, isFollowUpComplete]
+ );
+ const data = useMemo(() => followUpData || featureData, [featureData, followUpData]);
+ const { enqueueSnackbar } = useSnackbar();
+ const markdownPreviewRef = useRef(null);
+ const formContext = useForm<{ content: string }>();
+ const { reset, formState } = formContext;
+
+ useEffect(() => {
+ if (formState.isSubmitSuccessful) {
+ reset({ content: "" });
+ }
+ }, [formState.isSubmitSuccessful, reset]);
+
+ useEffect(() => {
+ setContent(intelligenceFooterPivot?.getAttribute("content") ?? "");
+ }, [intelligenceFooterPivot]);
+
+ useEffect(() => {
+ if (!content) return;
+
+ mutateIntelligenceFeature(content);
+ }, [content, mutateIntelligenceFeature]);
+
+ useEffect(() => {
+ if (data && markdownPreviewRef.current) {
+ markdownPreviewRef.current.scrollTo({
+ behavior: "smooth",
+ top: markdownPreviewRef.current.scrollHeight,
+ });
+ }
+ }, [data]);
+
+ const handleCopyContent = async () => {
+ if (!data) return;
+
+ await clipboard.write(data);
+ enqueueSnackbar("URL Copied!", { variant: "success" });
+ };
+
+ const handleRetry = async () => {
+ mutateIntelligence(
+ "Recreate the last statement with a paraphrase or adjust it slightly to better suit the user's input."
+ );
+ };
+
+ const handleRequestSubmit = (data: { content: string }) => {
+ mutateIntelligence(data.content);
+ };
+
+ const handleAddContent = (replace: boolean = false) => {
+ if (!editorStore.cmView) return;
+ const selection = editorStore.cmView.state.selection.main;
+ let from = Math.min(selection.to, selection.from);
+ const to = Math.max(selection.to, selection.from);
+ let insert = data as string;
+
+ if (!replace) {
+ from = to;
+ insert = `\n${insert}`;
+ }
+
+ const selectionFrom = replace ? from : from + 1;
+ const selectionTo = from + insert.length;
+
+ editorStore.cmView?.dispatch({
+ changes: { from, to, insert },
+ selection: {
+ anchor: selectionFrom,
+ head: selectionTo,
+ },
+ });
+ editorStore.doc?.update((root, presence) => {
+ root.content.edit(from, to, insert);
+ presence.set({
+ selection: root.content.indexRangeToPosRange([selectionFrom, selectionTo]),
+ });
+ });
+ onClose();
+ };
+
+ return (
+
+
+ {title}
+
+ {isLoading && }
+
+ {!isLoading && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+ endAdornment: (
+
+
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+
+ );
+}
+
+export default YorkieIntelligenceFeature;
diff --git a/frontend/src/components/editor/YorkieIntelligenceFeatureList.tsx b/frontend/src/components/editor/YorkieIntelligenceFeatureList.tsx
new file mode 100644
index 00000000..feb9c640
--- /dev/null
+++ b/frontend/src/components/editor/YorkieIntelligenceFeatureList.tsx
@@ -0,0 +1,63 @@
+import { ListItemIcon, ListItemText, MenuItem, MenuList, Stack, TextField } from "@mui/material";
+import { useMemo, useState } from "react";
+import GitHubIcon from "@mui/icons-material/GitHub";
+import { matchSorter } from "match-sorter";
+import { IntelligenceFeature } from "../../constants/intelligence";
+
+const featureInfoList = [
+ {
+ title: "Write GitHub Issue",
+ icon: ,
+ feature: IntelligenceFeature.GITHUB_ISSUE,
+ },
+ {
+ title: "Write GitHub Pull Request",
+ icon: ,
+ feature: IntelligenceFeature.GITHUB_PR,
+ },
+];
+
+interface YorkieIntelligenceFeatureListProps {
+ onSelectFeature: (feature: IntelligenceFeature, title: string) => void;
+}
+
+function YorkieIntelligenceFeatureList(props: YorkieIntelligenceFeatureListProps) {
+ const { onSelectFeature } = props;
+ const [featureText, setFeatureText] = useState("");
+ const filteredFeatureInfoList = useMemo(() => {
+ return matchSorter(featureInfoList, featureText, { keys: ["title", "feature"] });
+ }, [featureText]);
+
+ const handleFeatureTextChange: React.ChangeEventHandler<
+ HTMLInputElement | HTMLTextAreaElement
+ > = (e) => {
+ setFeatureText(e.target.value);
+ };
+
+ return (
+
+
+
+ {filteredFeatureInfoList.map((featureInfo) => (
+
+ ))}
+
+
+ );
+}
+
+export default YorkieIntelligenceFeatureList;
diff --git a/frontend/src/components/editor/YorkieIntelligenceFooter.tsx b/frontend/src/components/editor/YorkieIntelligenceFooter.tsx
new file mode 100644
index 00000000..3f757c40
--- /dev/null
+++ b/frontend/src/components/editor/YorkieIntelligenceFooter.tsx
@@ -0,0 +1,104 @@
+import { Box, Card, Popover, useTheme } from "@mui/material";
+import YorkieIntelligenceFeatureList from "./YorkieIntelligenceFeatureList";
+import { useEffect, useMemo, useRef, useState } from "react";
+import { IntelligenceFeature } from "../../constants/intelligence";
+import YorkieIntelligenceFeature from "./YorkieIntelligenceFeature";
+import { useSelector } from "react-redux";
+import { selectEditor } from "../../store/editorSlice";
+import CloseIntelligenceModal from "../modals/CloseIntelligenceModal";
+
+interface YorkieIntelligenceFooterProps {
+ onClose: () => void;
+}
+
+function YorkieIntelligenceFooter(props: YorkieIntelligenceFooterProps) {
+ const { onClose } = props;
+ const theme = useTheme();
+ const editorStore = useSelector(selectEditor);
+ const anchorRef = useRef(null);
+ const [selectedTitle, setSelectedTitle] = useState(null);
+ const [selectedFeature, setSelectedFeature] = useState(null);
+ const [anchorEl, setAnchorEl] = useState();
+ const [closeModalOpen, setCloseModalOpen] = useState(false);
+ const cardRef = useRef(null);
+
+ const width = useMemo(
+ () => editorStore.cmView!.contentDOM.getBoundingClientRect().width - 12,
+ [editorStore.cmView]
+ );
+
+ useEffect(() => {
+ if (!anchorRef.current) return;
+
+ setAnchorEl(anchorRef.current);
+
+ return () => {
+ setAnchorEl(undefined);
+ };
+ }, []);
+
+ const handleSelectFeature = (feature: IntelligenceFeature, title: string) => {
+ setSelectedFeature(feature);
+ setSelectedTitle(title);
+ };
+
+ const handleCloseModalOpen = () => {
+ setCloseModalOpen((prev) => !prev);
+ };
+
+ return (
+
+
+
+
+ {selectedFeature && selectedTitle ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+}
+
+export default YorkieIntelligenceFooter;
diff --git a/frontend/src/components/modals/CloseIntelligenceModal.tsx b/frontend/src/components/modals/CloseIntelligenceModal.tsx
new file mode 100644
index 00000000..7301daeb
--- /dev/null
+++ b/frontend/src/components/modals/CloseIntelligenceModal.tsx
@@ -0,0 +1,53 @@
+import { Button, Modal, ModalProps, Paper, Stack, Typography } from "@mui/material";
+
+interface CloseIntelligenceModalProps extends Omit {
+ onCloseIntelligence: () => void;
+}
+
+function CloseIntelligenceModal(props: CloseIntelligenceModalProps) {
+ const { onCloseIntelligence, ...modalProps } = props;
+
+ const handleCloseModal = () => {
+ modalProps?.onClose?.(new Event("Close Modal"), "escapeKeyDown");
+ };
+
+ const handleDiscard = () => {
+ onCloseIntelligence();
+ handleCloseModal();
+ };
+
+ return (
+
+
+
+
+
+
+ Do you want to discard
+
+ the Yorkie response?
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default CloseIntelligenceModal;
diff --git a/frontend/src/constants/intelligence.ts b/frontend/src/constants/intelligence.ts
new file mode 100644
index 00000000..fc278995
--- /dev/null
+++ b/frontend/src/constants/intelligence.ts
@@ -0,0 +1,7 @@
+export const INTELLIGENCE_HEADER_ID = "yorkie-intelligence-header";
+export const INTELLIGENCE_FOOTER_ID = "yorkie-intelligence-footer";
+
+export enum IntelligenceFeature {
+ GITHUB_ISSUE = "github-issue",
+ GITHUB_PR = "github-pr",
+}
diff --git a/frontend/src/hooks/api/intelligence.ts b/frontend/src/hooks/api/intelligence.ts
new file mode 100644
index 00000000..57e9e588
--- /dev/null
+++ b/frontend/src/hooks/api/intelligence.ts
@@ -0,0 +1,127 @@
+import { useCallback, useState } from "react";
+import { useSelector } from "react-redux";
+import { selectAuth } from "../../store/authSlice";
+import { selectDocument } from "../../store/documentSlice";
+import { IntelligenceFeature } from "../../constants/intelligence";
+
+export const useIntelligenceFeatureStream = (feature: IntelligenceFeature) => {
+ const authStore = useSelector(selectAuth);
+ const documentSotre = useSelector(selectDocument);
+ const [data, setData] = useState(null);
+ const [memoryKey, setMemoryKey] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [isComplete, setIsComplete] = useState(false);
+
+ const mutateAsync = useCallback(
+ async (content: string) => {
+ setIsLoading(true);
+ setIsComplete(false);
+ setMemoryKey(null);
+ setData(null);
+ const response = await fetch(
+ `${import.meta.env.VITE_API_ADDR}/intelligence/${feature}`,
+ {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${authStore.accessToken}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ documentId: documentSotre.data?.id,
+ content,
+ }),
+ }
+ );
+ const reader = response.body?.getReader();
+ let isFirst = true;
+ let result = "";
+
+ while (reader) {
+ const { done, value } = await reader.read();
+ setIsLoading(false);
+
+ if (done) {
+ break;
+ }
+
+ let text = new TextDecoder().decode(value);
+
+ if (isFirst) {
+ const splitted = text.split("\n");
+ setMemoryKey(splitted[0]);
+ isFirst = false;
+ text = splitted.slice(1).join("\n");
+ }
+
+ result += text;
+ setData(result);
+ }
+ setIsComplete(true);
+ },
+ [authStore.accessToken, documentSotre.data?.id, feature]
+ );
+
+ return {
+ data,
+ memoryKey,
+ isLoading,
+ isComplete,
+ mutateAsync,
+ };
+};
+
+export const useIntelligenceStream = (memoryKey: string | null) => {
+ const authStore = useSelector(selectAuth);
+ const documentSotre = useSelector(selectDocument);
+ const [data, setData] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [isComplete, setIsComplete] = useState(false);
+
+ const mutateAsync = useCallback(
+ async (content: string) => {
+ if (!memoryKey) return;
+
+ setIsLoading(true);
+ setIsComplete(false);
+ setData(null);
+ const response = await fetch(`${import.meta.env.VITE_API_ADDR}/intelligence`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${authStore.accessToken}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ documentId: documentSotre.data?.id,
+ memoryKey,
+ content,
+ }),
+ });
+ const reader = response.body?.getReader();
+ let result = "";
+
+ while (reader) {
+ const { done, value } = await reader.read();
+ setIsLoading(false);
+
+ if (done) {
+ break;
+ }
+
+ const text = new TextDecoder().decode(value);
+
+ result += text;
+ setData(result);
+ }
+ setIsComplete(true);
+ },
+ [authStore.accessToken, documentSotre.data?.id, memoryKey]
+ );
+
+ return {
+ data,
+ memoryKey,
+ isLoading,
+ isComplete,
+ mutateAsync,
+ };
+};
diff --git a/frontend/src/hooks/useYorkieDocument.ts b/frontend/src/hooks/useYorkieDocument.ts
index d15ffecb..ae3e80b6 100644
--- a/frontend/src/hooks/useYorkieDocument.ts
+++ b/frontend/src/hooks/useYorkieDocument.ts
@@ -7,6 +7,8 @@ import { useSearchParams } from "react-router-dom";
import { useSelector } from "react-redux";
import { selectAuth } from "../store/authSlice";
+yorkie.setLogLevel(4);
+
export const useYorkieDocument = (
yorkieDocuentId?: string | null,
presenceName?: string | null
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 5643c740..99c45664 100755
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -2,3 +2,8 @@ body {
margin: 0;
min-height: 100vh;
}
+
+#yorkie-intelligence-footer .wmde-markdown {
+ -webkit-user-modify: read-only;
+ white-space: initial !important;
+}
diff --git a/frontend/src/pages/workspace/document/Index.tsx b/frontend/src/pages/workspace/document/Index.tsx
index b4aaa2b0..c707744a 100644
--- a/frontend/src/pages/workspace/document/Index.tsx
+++ b/frontend/src/pages/workspace/document/Index.tsx
@@ -8,6 +8,7 @@ import { useGetDocumentQuery } from "../../../hooks/api/workspaceDocument";
import { useGetWorkspaceQuery } from "../../../hooks/api/workspace";
import DocumentView from "../../../components/editor/DocumentView";
import { useYorkieDocument } from "../../../hooks/useYorkieDocument";
+import YorkieIntelligence from "../../../components/editor/YorkieIntelligence";
function DocumentIndex() {
const dispatch = useDispatch();
@@ -36,6 +37,7 @@ function DocumentIndex() {
return (
+
);
}
diff --git a/frontend/src/store/editorSlice.ts b/frontend/src/store/editorSlice.ts
index 659120d1..0cd1e7c5 100644
--- a/frontend/src/store/editorSlice.ts
+++ b/frontend/src/store/editorSlice.ts
@@ -4,6 +4,7 @@ import { RootState } from "./store";
import * as yorkie from "yorkie-js-sdk";
import { YorkieCodeMirrorDocType, YorkieCodeMirrorPresenceType } from "../utils/yorkie/yorkieSync";
import { ShareRole } from "../utils/share";
+import { EditorView } from "codemirror";
export type EditorModeType = "edit" | "both" | "read";
export type CodePairDocType = yorkie.Document<
@@ -16,6 +17,7 @@ export interface EditorState {
shareRole: ShareRole | null;
doc: CodePairDocType | null;
client: yorkie.Client | null;
+ cmView: EditorView | null;
}
const initialState: EditorState = {
@@ -23,6 +25,7 @@ const initialState: EditorState = {
shareRole: null,
doc: null,
client: null,
+ cmView: null,
};
export const editorSlice = createSlice({
@@ -41,10 +44,13 @@ export const editorSlice = createSlice({
setClient: (state, action: PayloadAction) => {
state.client = action.payload;
},
+ setCmView: (state, action: PayloadAction) => {
+ state.cmView = action.payload;
+ },
},
});
-export const { setMode, setDoc, setClient, setShareRole } = editorSlice.actions;
+export const { setMode, setDoc, setClient, setShareRole, setCmView } = editorSlice.actions;
export const selectEditor = (state: RootState) => state.editor;
diff --git a/frontend/src/store/store.ts b/frontend/src/store/store.ts
index 9cd5faac..79710107 100644
--- a/frontend/src/store/store.ts
+++ b/frontend/src/store/store.ts
@@ -36,11 +36,12 @@ export const store = configureStore({
"persist/PERSIST", // redux-persist
"editor/setDoc",
"editor/setClient",
+ "editor/setCmView",
],
- ignoredPaths: ["editor.doc", "editor.client"],
+ ignoredPaths: ["editor.doc", "editor.client", "editor.cmView"],
},
immutableCheck: {
- ignoredPaths: ["editor.doc", "editor.client"],
+ ignoredPaths: ["editor.doc", "editor.client", "editor.cmView"],
},
}),
});
diff --git a/frontend/src/utils/document.ts b/frontend/src/utils/document.ts
index 152f64aa..0b219944 100644
--- a/frontend/src/utils/document.ts
+++ b/frontend/src/utils/document.ts
@@ -1,3 +1,10 @@
export function createDocumentKey() {
return Math.random().toString(36).substring(7);
}
+
+export function addSoftLineBreak(text: string) {
+ return text
+ .split("\n")
+ .map((line) => line + " ")
+ .join("\n");
+}
diff --git a/frontend/src/utils/intelligence/intelligencePivot.ts b/frontend/src/utils/intelligence/intelligencePivot.ts
new file mode 100644
index 00000000..e41bb893
--- /dev/null
+++ b/frontend/src/utils/intelligence/intelligencePivot.ts
@@ -0,0 +1,98 @@
+import * as cmView from "@codemirror/view";
+
+import * as cmState from "@codemirror/state";
+import * as dom from "lib0/dom";
+import * as pair from "lib0/pair";
+import { INTELLIGENCE_FOOTER_ID, INTELLIGENCE_HEADER_ID } from "../../constants/intelligence";
+
+class IntelligencePivotWidget extends cmView.WidgetType {
+ id: string;
+ content: string;
+ selectionRange: cmState.SelectionRange | null;
+
+ constructor(id: string, content: string, selectionRange: cmState.SelectionRange | null) {
+ super();
+ this.id = id;
+ this.content = content;
+ this.selectionRange = selectionRange;
+ }
+
+ toDOM() {
+ return dom.element("span", [
+ pair.create("id", this.id),
+ pair.create("content", this.content),
+ pair.create("style", `position: relaitve;`),
+ ]) as HTMLElement;
+ }
+
+ eq(widget: IntelligencePivotWidget) {
+ return widget.selectionRange === this.selectionRange;
+ }
+
+ compare(widget: IntelligencePivotWidget) {
+ return widget.selectionRange === this.selectionRange;
+ }
+
+ updateDOM() {
+ return false;
+ }
+
+ get estimatedHeight() {
+ return -1;
+ }
+
+ ignoreEvent() {
+ return true;
+ }
+}
+
+export class IntelligencePivotPluginValue {
+ decorations: cmView.DecorationSet;
+
+ constructor() {
+ this.decorations = cmState.RangeSet.of([]);
+ }
+
+ update(update: cmView.ViewUpdate) {
+ const decorations: Array> = [];
+ const selectionRange = update.state.selection.main;
+ const isDragged = selectionRange?.from !== selectionRange?.to;
+
+ if (isDragged && selectionRange) {
+ const selectedContent = update.state.sliceDoc(selectionRange.from, selectionRange.to);
+ decorations.push({
+ from: selectionRange.from,
+ to: selectionRange.from,
+ value: cmView.Decoration.widget({
+ side: 1,
+ block: false,
+ widget: new IntelligencePivotWidget(
+ INTELLIGENCE_HEADER_ID,
+ selectedContent,
+ selectionRange
+ ),
+ }),
+ });
+
+ decorations.push({
+ from: selectionRange.to,
+ to: selectionRange.to,
+ value: cmView.Decoration.widget({
+ side: 1,
+ block: false,
+ widget: new IntelligencePivotWidget(
+ INTELLIGENCE_FOOTER_ID,
+ selectedContent,
+ selectionRange
+ ),
+ }),
+ });
+ }
+
+ this.decorations = cmView.Decoration.set(decorations, true);
+ }
+}
+
+export const intelligencePivot = cmView.ViewPlugin.fromClass(IntelligencePivotPluginValue, {
+ decorations: (v) => v.decorations,
+});