diff --git a/components/TopThree.tsx b/components/TopThree.tsx
new file mode 100644
index 0000000..f2ff1dd
--- /dev/null
+++ b/components/TopThree.tsx
@@ -0,0 +1,41 @@
+import { format } from 'date-fns';
+import Link from 'next/link';
+import useSWR from 'swr';
+import { Update, User } from '../utils/types';
+import { dateOnly } from '../utils/utils';
+
+export default function TopThree(props: {
+ user: User;
+ update: Update;
+}) {
+ const { data: top } = useSWR(
+ `/api/get-top-three?userId=${props.update.userId}&updateId=${props.update._id}`
+ );
+ if (top=== undefined) {
+ return;
+ }
+ console.log(top)
+ return (
+ <>
+ {top.data.map((update, index) => (
+
+ ))}
+ >
+ );
+}
diff --git a/models/models.ts b/models/models.ts
index 3c25399..0e2961c 100644
--- a/models/models.ts
+++ b/models/models.ts
@@ -46,10 +46,16 @@ const updateSchema: Schema = new Schema({
comments: [commentSchema],
mentionedUsers: [mongoose.Schema.Types.ObjectId],
published: {type: Boolean, required: true},
+ embedding: [mongoose.Schema.Types.Number]
}, {
timestamps: true,
});
+const embeddingSchema: Schema = new Schema({
+ updateId: mongoose.Schema.Types.ObjectId,
+ embedding: [mongoose.Schema.Types.Number]
+})
+
const userSchema: Schema = new Schema({
...authorObj,
private: {type: Boolean, required: true},
@@ -83,6 +89,7 @@ const likeSchema: Schema = new Schema({
export const userModel = (!!mongoose.models && mongoose.models.user) || mongoose.model("user", userSchema);
export const updateModel = (!!mongoose.models && mongoose.models.update) || mongoose.model("update", updateSchema);
+export const embeddingModel = (!!mongoose.models && mongoose.models.embedding) || mongoose.model("embedding", embeddingSchema);
export const commentModel = (!!mongoose.models && mongoose.models.comment) || mongoose.model("comment", commentSchema);
export const notificationModel: Model = (!!mongoose.models && mongoose.models.notification) || mongoose.model("notification", notificationSchema);
export const likeModel: Model = (!!mongoose.models && mongoose.models.like) || mongoose.model("like", likeSchema);
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 5e6ba02..e7fd43b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,8 +14,10 @@
"@types/mongoose": "^5.10.3",
"@types/node": "^14.14.9",
"axios": "^0.21.0",
+ "cohere-ai": "^5.0.2",
"date-fns": "^2.16.1",
"html-react-parser": "^0.14.2",
+ "mathjs": "^11.5.0",
"mongoose": "^5.10.16",
"next": "^12.1.6",
"next-auth": "^4.3.4",
@@ -46,11 +48,14 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.12.5",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
- "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
+ "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
"dependencies": {
- "regenerator-runtime": "^0.13.4"
+ "regenerator-runtime": "^0.13.11"
+ },
+ "engines": {
+ "node": ">=6.9.0"
}
},
"node_modules/@csstools/convert-colors": {
@@ -103,17 +108,6 @@
"hoist-non-react-statics": "^3.3.1"
}
},
- "node_modules/@emotion/react/node_modules/@babel/runtime": {
- "version": "7.14.6",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz",
- "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==",
- "dependencies": {
- "regenerator-runtime": "^0.13.4"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@emotion/serialize": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
@@ -981,6 +975,11 @@
"typo-js": "*"
}
},
+ "node_modules/cohere-ai": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/cohere-ai/-/cohere-ai-5.0.2.tgz",
+ "integrity": "sha512-Svt8VC20/GgwCBF2kHYZI3JZkfqEoG6wCbTT6tohNK8x/aBFyMxlBUYEF0gRGXH1055vQpBjj5ewHF8LpnSSOA=="
+ },
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -1010,6 +1009,18 @@
"node": ">=0.1.90"
}
},
+ "node_modules/complex.js": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz",
+ "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://www.patreon.com/infusion"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1191,6 +1202,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/decimal.js": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
+ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA=="
+ },
"node_modules/defined": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
@@ -1284,6 +1300,11 @@
"node": ">=6"
}
},
+ "node_modules/escape-latex": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
+ "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
+ },
"node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -1366,6 +1387,18 @@
"node": ">=4.0"
}
},
+ "node_modules/fraction.js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
+ "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://www.patreon.com/infusion"
+ }
+ },
"node_modules/fs-extra": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
@@ -1661,6 +1694,11 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
+ "node_modules/javascript-natural-sort": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
+ "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="
+ },
"node_modules/jose": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.8.1.tgz",
@@ -1796,6 +1834,28 @@
"node": ">= 8.16.2"
}
},
+ "node_modules/mathjs": {
+ "version": "11.5.0",
+ "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.5.0.tgz",
+ "integrity": "sha512-vJ/+SqWtxjW6/aeDRt8xL3TlOVKqwN15BIyTGVqGbIWuiqgY4SxZ0yLuna82YH9CB757iFP7uJ4m3KvVBX7Qcg==",
+ "dependencies": {
+ "@babel/runtime": "^7.20.6",
+ "complex.js": "^2.1.1",
+ "decimal.js": "^10.4.3",
+ "escape-latex": "^1.2.0",
+ "fraction.js": "^4.2.0",
+ "javascript-natural-sort": "^0.7.1",
+ "seedrandom": "^3.0.5",
+ "tiny-emitter": "^2.1.0",
+ "typed-function": "^4.1.0"
+ },
+ "bin": {
+ "mathjs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
@@ -2068,17 +2128,6 @@
}
}
},
- "node_modules/next-auth/node_modules/@babel/runtime": {
- "version": "7.17.9",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
- "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
- "dependencies": {
- "regenerator-runtime": "^0.13.4"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/next-response-helpers": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/next-response-helpers/-/next-response-helpers-0.2.0.tgz",
@@ -3525,9 +3574,9 @@
"integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
},
"node_modules/regenerator-runtime": {
- "version": "0.13.7",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
- "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/regexp-clone": {
"version": "1.0.0",
@@ -3649,6 +3698,11 @@
"loose-envify": "^1.1.0"
}
},
+ "node_modules/seedrandom": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
+ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
+ },
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -4096,6 +4150,11 @@
"node": ">=0.8"
}
},
+ "node_modules/tiny-emitter": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -4112,6 +4171,14 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
+ "node_modules/typed-function": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz",
+ "integrity": "sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg==",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/typeorm": {
"version": "0.2.39",
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.39.tgz",
@@ -4507,11 +4574,11 @@
},
"dependencies": {
"@babel/runtime": {
- "version": "7.12.5",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
- "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
+ "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
"requires": {
- "regenerator-runtime": "^0.13.4"
+ "regenerator-runtime": "^0.13.11"
}
},
"@csstools/convert-colors": {
@@ -4561,16 +4628,6 @@
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"hoist-non-react-statics": "^3.3.1"
- },
- "dependencies": {
- "@babel/runtime": {
- "version": "7.14.6",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz",
- "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==",
- "requires": {
- "regenerator-runtime": "^0.13.4"
- }
- }
}
},
"@emotion/serialize": {
@@ -5212,6 +5269,11 @@
"typo-js": "*"
}
},
+ "cohere-ai": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/cohere-ai/-/cohere-ai-5.0.2.tgz",
+ "integrity": "sha512-Svt8VC20/GgwCBF2kHYZI3JZkfqEoG6wCbTT6tohNK8x/aBFyMxlBUYEF0gRGXH1055vQpBjj5ewHF8LpnSSOA=="
+ },
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -5238,6 +5300,11 @@
"dev": true,
"optional": true
},
+ "complex.js": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz",
+ "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg=="
+ },
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -5371,6 +5438,11 @@
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
+ "decimal.js": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
+ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA=="
+ },
"defined": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
@@ -5446,6 +5518,11 @@
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
+ "escape-latex": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
+ "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
+ },
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -5512,6 +5589,11 @@
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
},
+ "fraction.js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
+ "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA=="
+ },
"fs-extra": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
@@ -5765,6 +5847,11 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
+ "javascript-natural-sort": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
+ "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="
+ },
"jose": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.8.1.tgz",
@@ -5878,6 +5965,22 @@
"resolved": "https://registry.npmjs.org/marked/-/marked-1.2.5.tgz",
"integrity": "sha512-2AlqgYnVPOc9WDyWu7S5DJaEZsfk6dNh/neatQ3IHUW4QLutM/VPSH9lG7bif+XjFWc9K9XR3QvR+fXuECmfdA=="
},
+ "mathjs": {
+ "version": "11.5.0",
+ "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.5.0.tgz",
+ "integrity": "sha512-vJ/+SqWtxjW6/aeDRt8xL3TlOVKqwN15BIyTGVqGbIWuiqgY4SxZ0yLuna82YH9CB757iFP7uJ4m3KvVBX7Qcg==",
+ "requires": {
+ "@babel/runtime": "^7.20.6",
+ "complex.js": "^2.1.1",
+ "decimal.js": "^10.4.3",
+ "escape-latex": "^1.2.0",
+ "fraction.js": "^4.2.0",
+ "javascript-natural-sort": "^0.7.1",
+ "seedrandom": "^3.0.5",
+ "tiny-emitter": "^2.1.0",
+ "typed-function": "^4.1.0"
+ }
+ },
"memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
@@ -6075,16 +6178,6 @@
"preact": "^10.6.3",
"preact-render-to-string": "^5.1.19",
"uuid": "^8.3.2"
- },
- "dependencies": {
- "@babel/runtime": {
- "version": "7.17.9",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
- "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
- "requires": {
- "regenerator-runtime": "^0.13.4"
- }
- }
}
},
"next-response-helpers": {
@@ -7260,9 +7353,9 @@
"integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
},
"regenerator-runtime": {
- "version": "0.13.7",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
- "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"regexp-clone": {
"version": "1.0.0",
@@ -7350,6 +7443,11 @@
"loose-envify": "^1.1.0"
}
},
+ "seedrandom": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
+ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
+ },
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -7692,6 +7790,11 @@
"thenify": ">= 3.1.0 < 4"
}
},
+ "tiny-emitter": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
+ },
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -7705,6 +7808,11 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
+ "typed-function": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz",
+ "integrity": "sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg=="
+ },
"typeorm": {
"version": "0.2.39",
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.39.tgz",
diff --git a/package.json b/package.json
index 6c77924..ecc5546 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"@types/mongoose": "^5.10.3",
"@types/node": "^14.14.9",
"axios": "^0.21.0",
+ "cohere-ai": "^5.0.2",
"date-fns": "^2.16.1",
"html-react-parser": "^0.14.2",
"mongoose": "^5.10.16",
diff --git a/pages/[username]/[updateUrl].tsx b/pages/[username]/[updateUrl].tsx
index 12fc6c0..13c6a29 100644
--- a/pages/[username]/[updateUrl].tsx
+++ b/pages/[username]/[updateUrl].tsx
@@ -1,6 +1,6 @@
import {GetServerSideProps} from "next";
import {getSession} from "next-auth/react";
-import {getCurrUserRequest, getUpdateRequest} from "../../utils/requests";
+import {getCurrUserRequest, getTopThree, getUpdateRequest} from "../../utils/requests";
import {format} from "date-fns";
import {cleanForJSON, dateOnly, fetcher} from "../../utils/utils";
import Link from "next/link";
@@ -20,6 +20,7 @@ import useSWR, {responseInterface} from "swr";
import {FiHeart} from "react-icons/fi";
import {notificationModel} from "../../models/models";
import {getMentionsAndBodySegments} from "../../components/UpdateCommentItem";
+import TopThree from "../../components/TopThree";
import { DeleteModal } from "../../components/Modal";
export default function UpdatePage(props: { data: {user: User, updates: (Update & {mentionedUsersArr: User[]})[]}, updateUrl: string, userData: User }) {
@@ -217,6 +218,8 @@ export default function UpdatePage(props: { data: {user: User, updates: (Update
{Parser(markdownConverter.makeHtml(bodyToParse))}
+ Similar
+
>
)}
@@ -250,10 +253,11 @@ export default function UpdatePage(props: { data: {user: User, updates: (Update
}
export const getServerSideProps: GetServerSideProps = async (context) => {
- if (Array.isArray(context.params.username) || Array.isArray(context.params.updateUrl) || context.params.username.substr(0, 1) !== "@") return { notFound: true };
- const username: string = context.params.username.substr(1);
+ if (Array.isArray(context.params.username) || Array.isArray(context.params.updateUrl) || context.params.username.substring(0, 1) !== "@") return { notFound: true };
+ const username: string = context.params.username.substring(1);
const updateUrl: string = context.params.updateUrl;
const data = await getUpdateRequest(username, updateUrl);
+ // const topThree = await getTopThree(username, updateUrl);
if (!data) return { notFound: true };
@@ -274,5 +278,5 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
if (userData) await notificationModel.updateMany({userId: userData._id, updateId: data.updates.find(d => d.url === encodeURIComponent(updateUrl))._id}, {read: true});
- return { props: { data: cleanForJSON(data), updateUrl: updateUrl, userData: cleanForJSON(userData), key: updateUrl }};
+ return { props: { data: cleanForJSON(data), updateUrl: updateUrl, userData: cleanForJSON(userData), key: updateUrl}};
};
\ No newline at end of file
diff --git a/pages/api/get-top-three.ts b/pages/api/get-top-three.ts
new file mode 100644
index 0000000..720ca6a
--- /dev/null
+++ b/pages/api/get-top-three.ts
@@ -0,0 +1,18 @@
+import {getSession} from "next-auth/react";
+import {NextApiRequest, NextApiResponse} from "next";
+import {getCurrUserRequest, getTopThree} from "../../utils/requests";
+
+export default async function getTopThreeHandler(req: NextApiRequest, res: NextApiResponse) {
+ if (req.method !== "GET") return res.status(405);
+ try {
+ const topThree = await getTopThree(req.query.userId as string, req.query.updateId as string);
+
+ res.status(200).json({data: topThree.map(update => ({
+ date: update.date,
+ title: update.title,
+ url: update.url
+ }))});
+ } catch (e) {
+ res.status(500).json({error: e});
+ }
+}
\ No newline at end of file
diff --git a/pages/api/update.ts b/pages/api/update.ts
index 8bd7c3e..d8f56ef 100644
--- a/pages/api/update.ts
+++ b/pages/api/update.ts
@@ -1,11 +1,12 @@
import {getSession} from "next-auth/react";
import {NextApiRequest, NextApiResponse} from "next";
import mongoose from "mongoose";
-import {userModel, updateModel, notificationModel} from "../../models/models";
+import {userModel, updateModel, notificationModel, embeddingModel} from "../../models/models";
import short from "short-uuid";
import {isEqual} from "date-fns";
import {dateOnly} from "../../utils/utils";
import {Update, User} from "../../utils/types";
+import { generateEmbedding } from "../../utils/requests";
function generateUrlName(title: string, date: string) {
let url: string = date;
@@ -61,7 +62,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const update = await updateModel.findOne({ "_id": req.body.id });
if (update === null) return res.status(500).json({message: "No update found for given username and ID"});
if (update.userId.toString() !== thisUser._id.toString()) return res.status(403).send("Unauthed");
-
+ const embedding = generateEmbedding(req.body.body);
+ await embeddingModel.findOneAndUpdate(
+ { updateId: req.body.id },
+ {
+ updateId: req.body.id,
+ embedding: embedding
+ },
+ { upsert: true }
+ )
switch(req.body.requestType) {
case "saveDraft": {
update["title"] = req.body.title;
diff --git a/utils/requests.ts b/utils/requests.ts
index ab9a70a..f74995b 100644
--- a/utils/requests.ts
+++ b/utils/requests.ts
@@ -1,9 +1,12 @@
import * as mongoose from "mongoose";
-import {updateModel, userModel} from "../models/models";
+import {embeddingModel, updateModel, userModel} from "../models/models";
import short from "short-uuid";
import axios from "axios";
+import cohere from "cohere-ai"
import {getSession} from "next-auth/react";
-import { SortBy } from "./types";
+import { SortBy, Update } from "./types";
+import { NextApiRequest } from "next";
+// import { dot, norm } from "mathjs";
export async function getUpdateRequest(username: string, url: string) {
await mongoose.connect(process.env.MONGODB_URL, {
@@ -204,4 +207,159 @@ export async function createAccount(user) {
private: false,
truePrivate: false,
});
+}
+
+
+// async function getUserUpdates (userId: string) {
+// await mongoose.connect(process.env.MONGODB_URL, {
+// useNewUrlParser: true,
+// useUnifiedTopology: true,
+// useFindAndModify: false,
+// });
+// let user = await userModel.findOne({ "urlName": username })
+// if (user === null) return null;
+
+// const updates: Update[] = await updateModel.find({
+// userId : user._id,
+// published: true
+// })
+// return updates
+// }
+
+export async function generateUserEmbeddings (updates: Update[]) {
+ cohere.init(process.env.COHERE_KEY)
+ const response = await cohere.embed({
+ texts: updates.map(update => update.body),
+ truncate: "RIGHT"
+ });
+ return response.body.embeddings
+}
+
+export async function generateEmbedding(text:string) {
+ cohere.init(process.env.COHERE_KEY)
+ const response = await cohere.embed({
+ texts: [text],
+ truncate: "RIGHT"
+ })
+ return response.body.embeddings[0]
+}
+
+
+async function getUpdate (url: string) {
+ await mongoose.connect(process.env.MONGODB_URL, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ useFindAndModify: false,
+ });
+
+ const update: Update = await updateModel.findOne({
+ url: url
+ });
+ return update
+}
+
+async function getUserEmbeddings(userId: string) {
+ await mongoose.connect(process.env.MONGODB_URL, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ useFindAndModify: false,
+ });
+ const updatesWithEmbeddings: (Update & { embedding: number[] })[] =
+ await updateModel.aggregate([
+ { $match: { userId: mongoose.Types.ObjectId(userId) } },
+ {
+ $lookup: {
+ from: 'embeddings',
+ localField: '_id',
+ foreignField: 'updateId',
+ as: 'fromEmbeddings',
+ },
+ },
+ { $match: { fromEmbeddings: { $ne: [] } } },
+ {
+ $replaceRoot: {
+ newRoot: {
+ $mergeObjects: [{ $arrayElemAt: ['$fromEmbeddings', 0] }, '$$ROOT'],
+ },
+ },
+ },
+ { $project: { fromEmbeddings: 0 } },
+ ]);
+ const updatesWithoutEmbeddings = await updateModel.aggregate([
+ { $match: { userId: mongoose.Types.ObjectId(userId) } },
+ {
+ $lookup: {
+ from: 'embeddings',
+ localField: '_id',
+ foreignField: 'updateId',
+ as: 'fromEmbeddings',
+ },
+ },
+ { $match: { fromEmbeddings: { $eq: [] } } },
+ { $project: { fromEmbeddings: 0 } },
+ ]);
+ console.log("computed embeddings for: ")
+ console.log(updatesWithoutEmbeddings.map(update => update.title));
+ if (updatesWithoutEmbeddings.length != 0) {
+ console.log('cache misses: recomputing embeddings');
+ const embeddings = await generateUserEmbeddings(updatesWithoutEmbeddings);
+ console.log(`computed ${embeddings.length} embeddings from cohere`);
+ await embeddingModel.insertMany(
+ embeddings.map((embedding, index) => ({
+ updateId: mongoose.Types.ObjectId(updatesWithoutEmbeddings[index]._id),
+ embedding: embedding,
+ }))
+ );
+
+ updatesWithEmbeddings.push(
+ ...updatesWithoutEmbeddings.map((update, index) => ({
+ embedding: embeddings[index],
+ ...update,
+ }))
+ );
+ }
+ // console.log(updatesWithEmbeddings)
+ return updatesWithEmbeddings;
+}
+
+
+export function semanticSimilarity(a: number[], b: number[]) {
+ const dot = (a: Float64Array, b: Float64Array) =>
+ a.reduce((prev, curr, index) => prev + curr * b[index]);
+
+ const norm = (x: Float64Array) =>
+ Math.sqrt(x.reduce((prev, curr) => prev + curr * curr));
+
+ const aEmbed = new Float64Array(a);
+ const bEmbed = new Float64Array(b);
+ return dot(aEmbed, bEmbed) / (norm(aEmbed) * norm(bEmbed));
+}
+
+export async function getTopThree(userId: string, updateId: string) {
+ console.log('GETTING TOP THREE');
+ cohere.init(process.env.COHERE_KEY);
+ await mongoose.connect(process.env.MONGODB_URL, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ useFindAndModify: false,
+ });
+
+ const update = await updateModel.findById(updateId);
+ const updatesWithEmbeddings = await getUserEmbeddings(userId);
+ const embedding = await embeddingModel.findOne({
+ updateId: update._id,
+ });
+ console.log('got embeddings');
+
+ console.time('computing similarity');
+ const similarities = updatesWithEmbeddings.map((x): [Update, number] => [
+ x,
+ semanticSimilarity(x.embedding, embedding.embedding),
+ ]);
+ console.timeEnd('computing similarity');
+ console.time('sorting');
+ const sortedSimilarities = similarities.sort((a, b) => b[1] - a[1]);
+ console.timeEnd('sorting');
+ // console.log(sortedSimilarities.map(x => `title: ${x[0].title} similarity: ${x[1]}`))
+ return sortedSimilarities.map(x => x[0]).slice(1, 4);
}
\ No newline at end of file
diff --git a/utils/types.ts b/utils/types.ts
index c150e47..adc2f60 100644
--- a/utils/types.ts
+++ b/utils/types.ts
@@ -37,6 +37,7 @@ export interface Update {
updatedAt: string, // date string
mentionedUsers: string[], // ids
published: boolean,
+ embedding?: number[]
}
export interface PrivateAggregation {