@miso.ai/doggoganger 0.9.0-beta.9 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish.yml +1 -0
- package/bin/data.js +15 -0
- package/cli/index.js +7 -6
- package/dist/umd/doggoganger-browser.min.js +1 -0
- package/package.json +14 -4
- package/rollup.config.dev.mjs +3 -0
- package/rollup.config.mjs +3 -0
- package/rollup.util.mjs +42 -0
- package/src/api/ask.js +47 -78
- package/src/api/index.js +14 -17
- package/src/api/interactions.js +10 -6
- package/src/api/products.js +28 -0
- package/src/api/recommendation.js +18 -6
- package/src/api/search.js +12 -5
- package/src/browser.js +5 -0
- package/src/data/answers.js +33 -0
- package/src/data/fields.js +19 -3
- package/src/data/index.js +2 -0
- package/src/data/lorem.js +18 -20
- package/src/data/markdown/index.js +49 -21
- package/src/data/markdown/languages.js +101 -0
- package/src/data/questions.js +11 -0
- package/src/data/utils.js +21 -0
- package/src/data/words.js +182 -0
- package/src/index.js +29 -3
- package/src/middleware/error.js +29 -0
- package/src/middleware/index.js +2 -0
- package/src/middleware/latency.js +39 -0
- package/src/route/ask.js +37 -0
- package/src/route/index.js +26 -0
- package/src/route/interactions.js +17 -0
- package/src/route/products.js +33 -0
- package/src/route/recommendation.js +22 -0
- package/src/route/search.js +15 -0
- package/src/route/utils.js +6 -0
- package/src/utils.js +17 -0
- package/src/api/handlers.js +0 -10
- /package/{src/data → data}/words.yaml +0 -0
|
@@ -23,6 +23,7 @@ jobs:
|
|
|
23
23
|
- run: git config --global user.name "${{ github.actor }}"
|
|
24
24
|
- run: git config --global user.email "github-action-${{ github.actor }}@users.noreply.github.com"
|
|
25
25
|
- run: npm run version ${{ steps.version.outputs.version }}
|
|
26
|
+
- run: npm run build
|
|
26
27
|
- run: npm publish
|
|
27
28
|
if: "!github.event.release.prerelease"
|
|
28
29
|
env:
|
package/bin/data.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import { dirname, resolve } from 'path';
|
|
3
|
+
import { writeFileSync, readFileSync } from 'fs';
|
|
4
|
+
import yaml from 'js-yaml';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
const WORDS_YAML = resolve(__dirname, '../data/words.yaml');
|
|
9
|
+
const WORDS_JS = resolve(__dirname, '../src/data/words.js');
|
|
10
|
+
|
|
11
|
+
const words = yaml.load(readFileSync(WORDS_YAML, 'utf8'));
|
|
12
|
+
writeFileSync(WORDS_JS, `
|
|
13
|
+
// genereated by bin/data.js
|
|
14
|
+
export default ${JSON.stringify(words, null, 2)};
|
|
15
|
+
`);
|
package/cli/index.js
CHANGED
|
@@ -10,6 +10,11 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
10
10
|
const SRC_DIR = join(__dirname, '../src');
|
|
11
11
|
|
|
12
12
|
let { watch, ...argv } = yargs(hideBin(process.argv))
|
|
13
|
+
.option('verbose', {
|
|
14
|
+
alias: 'v',
|
|
15
|
+
describe: 'Be verbose',
|
|
16
|
+
type: 'boolean',
|
|
17
|
+
})
|
|
13
18
|
.option('port', {
|
|
14
19
|
alias: 'p',
|
|
15
20
|
describe: 'Port of mock API endpoint',
|
|
@@ -21,10 +26,6 @@ let { watch, ...argv } = yargs(hideBin(process.argv))
|
|
|
21
26
|
describe: 'Watch files for changes',
|
|
22
27
|
type: 'boolean',
|
|
23
28
|
})
|
|
24
|
-
.option('answer-format', {
|
|
25
|
-
describe: 'Answer field format in answers API response',
|
|
26
|
-
type: 'string',
|
|
27
|
-
})
|
|
28
29
|
.option('serve', {
|
|
29
30
|
alias: 's',
|
|
30
31
|
describe: 'Serve static files as well',
|
|
@@ -32,8 +33,8 @@ let { watch, ...argv } = yargs(hideBin(process.argv))
|
|
|
32
33
|
})
|
|
33
34
|
.argv;
|
|
34
35
|
|
|
35
|
-
const { port, serve
|
|
36
|
-
argv = { port, serve
|
|
36
|
+
const { verbose, port, serve } = argv;
|
|
37
|
+
argv = { verbose, port, serve };
|
|
37
38
|
|
|
38
39
|
if (watch) {
|
|
39
40
|
const exec = `node ${resolve(__dirname, 'server.js')} ${JSON.stringify(JSON.stringify(argv))}`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t="undefined"!=typeof globalThis?globalThis:t||self).doggoganger=n()}(this,(function(){"use strict";function t(t,n){return null!=n&&n>t?t+Math.floor(Math.random()*(n-t)):t}function n(){return Math.random().toString(36).substring(2,10)}function e(t){return`https://picsum.photos/seed/${Math.floor(1e3*Math.random())}/${Array.isArray(t)?t.length>1?`${t[0]}/${t[1]}`:""+t[0]:""+t}`}function r(t,n){return void 0!==n?Math.ceil(t*n):t}function o(){return 2*Math.random()-1}var s=["lorem","ipsum","dolor","sit","amet","consectetur","adipiscing","elit","curabitur","vel","hendrerit","libero","eleifend","blandit","nunc","ornare","odio","ut","orci","gravida","imperdiet","nullam","purus","lacinia","a","pretium","quis","congue","praesent","sagittis","laoreet","auctor","mauris","non","velit","eros","dictum","proin","accumsan","sapien","nec","massa","volutpat","venenatis","sed","eu","molestie","lacus","quisque","porttitor","ligula","dui","mollis","tempus","at","magna","vestibulum","turpis","ac","diam","tincidunt","id","condimentum","enim","sodales","in","hac","habitasse","platea","dictumst","aenean","neque","fusce","augue","leo","eget","semper","mattis","tortor","scelerisque","nulla","interdum","tellus","malesuada","rhoncus","porta","sem","aliquet","et","nam","suspendisse","potenti","vivamus","luctus","fringilla","erat","donec","justo","vehicula","ultricies","varius","ante","primis","faucibus","ultrices","posuere","cubilia","curae","etiam","cursus","aliquam","quam","dapibus","nisl","feugiat","egestas","class","aptent","taciti","sociosqu","ad","litora","torquent","per","conubia","nostra","inceptos","himenaeos","phasellus","nibh","pulvinar","vitae","urna","iaculis","lobortis","nisi","viverra","arcu","morbi","pellentesque","metus","commodo","ut","facilisis","felis","tristique","ullamcorper","placerat","aenean","convallis","sollicitudin","integer","rutrum","duis","est","etiam","bibendum","donec","pharetra","vulputate","maecenas","mi","fermentum","consequat","suscipit","aliquam","habitant","senectus","netus","fames","quisque","euismod","curabitur","lectus","elementum","tempor","risus","cras"];function i({decorates:n=[],output:e="string",size:r,min:o,max:i,...a}={}){let c=function(n=[5,10]){const e="number"==typeof n?n:t(...n);return function*(t){let n=0;for(let r of t){if(n++>=e)break;yield r}}}(r||[o,i])(function*({words:t=s,fixedStarts:n=0}={}){const e=t.length;for(let r=0;;r++)yield t[n>r?r:Math.floor(Math.random()*e)]}(a));for(const t of n)c=u(t)(c);return u(e)(c)}const a={string:function({separator:t=" "}={}){return n=>[...n].join(t)},array:function(){return t=>[...t]},title:function({}={}){return function*(t){for(let n of t)yield c(n)}},description:function({wordsPerSentence:t={avg:24,std:5,min:1},punctuation:n="."}={}){return function*(e){let r,o=0;for(let s of e)r&&(yield r),r=s,0===o&&(r=c(r),o=l(t)),0==--o&&(r+=n);r&&(r.endsWith(n)||(r+=n),yield r)}},multiline:function({wordsPerLine:t={avg:10,std:3,min:1}}={}){return n=>{let e=l(t),r="";for(let o of n)r&&(0==e--?(r+="\n",e=l(t)):r+=" "),r+=o;return r}}};function u(t){switch(typeof t){case"string":return a[t]();case"function":return t;case"object":if(Array.isArray(t)){const[n,e={}]=t;return a[n](e)}}throw Error("Unrecognized decorator/output form: "+t)}function c(t){return t[0].toUpperCase()+t.substring(1)}function l(t){if("number"==typeof t)return Math.round(n);let{avg:n,std:e,min:r,max:s}=t;void 0===e&&(e=n/4);let i=(o()+o()+o())*e+n;return void 0!==r&&(i=Math.max(r,i)),void 0!==s&&(i=Math.min(s,i)),Math.round(i)}const f="// module\nimport { a, b } from './module';\nimport * as module from './module';\nimport module from './module';\nexport default module;\nexport const a = 0;\nexport * from './module';\nexport * as module from './module';\n\n// variables\nlet a = 10;\nconst b = 20;\n\n// function declaration\nfunction sum(x, y) {\n return x + y;\n}\n\n// generator function\nfunction* iterator() {\n yield 0;\n yield 1;\n}\n\n// arrow function\nconst multiply = (x, y) => x * y;\n\n// class\nclass Person {\n constructor(name, age) {\n this.name = name;\n this.age = age;\n }\n\n greet() {\n console.log('Hello, my name is Miso.');\n }\n}\n\n// primitive\nconst str = 'Hello, world!';\nconst num = 10.99;\n\n// object\nconst person = new Person('John', 30);\nperson.greet();\n\n// object literal\nconst object = {\n name: 'John',\n [x]: 10,\n ...props,\n};\n\n// array literal\nconst arr = [1, 2, 3, 4, 5, ...props];\n\n// regexp literal\nconst regexp = /\\w+/g;\n\n// operators\nconst sum = a + b;\nconst product = a * b;\nconst negation = -a;\nconst max = a > b ? a : b;\n\n// flow control\nlet i = 9;\nfor (const n of arr) {\n if (n > i) {\n console.log(n);\n }\n i++;\n}\n\n// async/await\n(async () => {\n const result = await asyncFunction();\n})();\n\n// try/catch\ntry {\n} catch (e) {\n}\n\n// destructuring\nconst { name, age, ...rest } = person;\nconst [ x, y, ...rest ] = arr;\n\n// template literals\nconsole.log(`The sum of ${a} and ${b} is ${sum(a, b)}.`);";var d=Object.freeze({__proto__:null,javascript:function(){return f},js:function(){return f}});function p({features:n,blocks:r=[8,12],sampling:o=1}={}){let s=[];return[s,n]=function(t=[]){const n=new Set,e=[];for(const r of t)if(r.startsWith("lang-")){let t=r.slice(5);"javascript"===t&&(t="js"),n.add(t)}else e.push(r);return[[...n],e]}(n),function(t,n){const e=[];for(const r of t)1>n&&Math.random()>=n||e.push(r());return e}([()=>m({features:n}),()=>g({features:n}),...s.map((t=>()=>h({lang:t,features:n}))),...s.length?[]:[()=>h({features:n})],()=>g({features:n}),()=>function({columns:n=[2,4],rows:e=[2,8]}){n=t(...n),e=t(...e);const r=[...$({size:1},n-1),{size:[3,8]}],o=i({size:n,output:"array"}),s=r.map((()=>"---")),a=[o,s];for(let t=0;e-1>t;t++)a.push(r.map((({size:t})=>i({size:t}))));return a.map(x).join("\n")}({features:n}),()=>function({url:t,imageSize:n=[400,250],...r}={}){return t=t||e(n),`![${function({size:t=[1,3],content:n}={}){return n||i({size:t})}(r)}](${t})`}(),()=>g({features:n}),()=>"*-_".charAt(t(0,2)).repeat(t(3,6)),()=>m({features:n}),()=>g({features:n}),()=>w({features:n}),()=>g({features:n})],o).join("\n\n")}function m({level:n=[1,6],size:e=[1,8],content:r}){const o=r||i({size:e});return`${"#".repeat(t(...n))} ${o}`}function h({lang:n,content:e,size:r,fenceChar:o="`"}){return"random"===o&&(o="`~".charAt(t(0,1))),e=e||function({lang:t,size:n=[10,30]}){return t&&d[t]?d[t]():i({output:"multiline",size:n})}({lang:n,size:r}),`${o.repeat(3)}${n||""}\n${e}\n${o.repeat(3)}`}function g({size:t=[20,50]}){return i({size:t,decorates:["description",z()]})}const _=["ordered","bullet","task"];function w({features:n,type:e="random",count:r=[1,8],size:o=[5,15]}){r="number"==typeof r?r:t(...r);const s="random"===e?_[Math.floor(3*Math.random())]:e,i=[];for(;r>0;){const a=t(1,r);let u=g({features:n,size:o});a>1&&(u+="\n"+w({features:n,type:e,count:a-1,size:o})),i.push(M(s,u)),r-=a}return i.join("\n")}const y={"code-span":()=>["`","`"],"emphasis-1":()=>$(b(1),2),"emphasis-2":()=>$(b(2),2),"emphasis-3":()=>$(b(3),2),strikethrough:()=>["~","~"],link:({url:t="https://miso.ai"}={})=>["[",`](${t})`]};function b(n=[1,3]){return n="number"==typeof n?n:t(...n),"_*".charAt(t(0,1)).repeat(n)}const q=Object.keys(y),v=new Set(q);function z({features:n=q,size:e=[1,3],rest:r=[0,8]}={}){n=n.filter((t=>v.has(t)));const o=function(t){for(let n=t.length-1;n>0;n--){const e=Math.floor(Math.random()*(n+1));[t[n],t[e]]=[t[e],t[n]]}return t}([...n]);let s=o.length-1;const i=()=>"number"==typeof r?r:t(...r);return function*(r){let a,u,c=i();for(const l of r)if(u&&(yield u),0===c)if(a)u=`${l}${a}`,a=void 0,c=i();else{const[r,i]=y[0>s?n[t(0,n.length-1)]:o[s--]]();u=`${r}${l}`,a=i,c="number"==typeof e?e:t(...e)}else u=l,c--;u&&(yield void 0!==a?`${u}${a}`:u)}}function M(t,n){const[e,r]=n.split("\n",2),o=`${function(t,n="random"){switch(t){case"ordered":return"1.";case"bullet":return"-";case"task":return`- [${("random"===n?.5>Math.random():n)?"x":" "}]`;default:throw Error("unknown list item type: "+t)}}(t)} ${e}`;return r?o+"\n"+function(t,n){return n.split("\n").map((n=>" ".repeat(t)+n)).join("\n")}("ordered"===t?3:2,r):o}function x(t){return`| ${t.join(" | ")} |`}function $(t,n){const e=[];for(let r=0;n>r;r++)e.push("function"==typeof t?t():t);return e}function j({size:t=300}={}){return e(t)}function S({size:t=[1,3]}={}){return i({size:t,decorates:["title"],output:"array"})}function k({size:t=[1,4]}={}){return i({size:t,output:"array"})}function O({size:t=[2,6]}={}){return i({size:t,decorates:["title"]})}function A({size:t=[10,20],...n}={}){return i({size:t,decorates:[Object.keys(n).length?["description",n]:"description"]})}function T(){return Math.floor(1e4*Math.random())/100}function*C({rows:t,...n}={}){for(let e=0;t>e;e++)yield F({...n,index:e})}function F({}={}){const e=n(),r=function(n,e){const r=t(...e),o=[];for(let t=0;r>t;t++)o.push(n());return o}(T,[1,2]);return r.sort(),{product_id:e,authors:S(),categories:[],tags:k(),title:O(),description:A(),cover_image:j(),url:"/products/"+e,sale_price:r[0],original_price:r[r.length-1],rating:Math.floor(500*Math.random())/100+1,availability:Math.random()>.3?"IN_STOCK":"OUT_OF_STOCK"}}function*D({rows:t,...n}={}){for(let e=0;t>e;e++)yield L({...n,index:e})}function L({}={}){const t=n();return{product_id:t,authors:S(),categories:[],tags:k(),title:O({size:[4,10]}),snippet:A({size:[20,40]}),cover_image:j(),url:"/products/"+t}}function*P({rows:t,...n}={}){for(let e=0;t>e;e++)yield U({...n,index:e})}function U({}={}){return A({size:[4,8],punctuation:"?"})}function W({question:t,parent_question_id:n,timestamp:e=Date.now()},{answerFormat:o="markdown",answerSampling:s,answerLanguages:a=[]}={}){const u="10000000-1000-4000-8000-100000000000".replace(/[018]/g,(t=>(t^16*Math.random()>>t/4).toString(16))),c=function(t){const n=new Date(t).toISOString();return n.endsWith("Z")?n.slice(0,-1):n}(e),l=void 0!==s?Math.max(0,Math.min(1,s)):void 0,f=function({format:t,sampling:n,features:e}){return"markdown"===t?p({sampling:n,features:e}):i({min:r(50,n),max:r(50,n),decorates:["description"]})}({format:o,sampling:l,features:a.length?a.map((t=>"lang-"+t)):void 0}),d=[...D({rows:E(6,8,l)})];return{question:t,question_id:u,...n&&{parent_question_id:n},datetime:c,answer:f,sources:[...D({rows:E(4,6,l)})],related_resources:d,followup_questions:[...P({rows:E(3,6)})]}}function E(n,e,o){return t(r(n,o),r(e,o))}const H=[{name:"fetch",duration:1.5,text:"Checking the question and fetching results... "},{name:"verify",duration:1.5,text:"Verifying results... "},{name:"generate",duration:1.5,text:"Generating answer... "}];class I{constructor(t={}){this._options=t,this._answers=new Map}questions(t,n={}){return new J(t,{...this._options,...n})}}class J{constructor({question:t,parent_question_id:n},{answerFormat:e,answerSampling:r,answerLanguages:o,...s}={}){this._options=Object.freeze(s);const i=this.timestamp=Date.now();this._data=W({question:t,parent_question_id:n,timestamp:i},{answerFormat:e,answerSampling:r,answerLanguages:o})}get question_id(){return this._data.question_id}get(){const t=(Date.now()-this.timestamp)*(this._options.speedRate||1)/1e3,[n,e,r]=this._answer(t),o=this._sources(t,r),s=this._related_resources(t,r),i=this._followup_questions(t,r),{question_id:a,question:u,datetime:c,parent_question_id:l}=this._data;return{answer:e,answer_stage:n,datetime:c,finished:r,parent_question_id:l,question:u,question_id:a,sources:o,related_resources:s,followup_questions:i}}_answer(t){for(const n of H)if(0>(t-=n.duration))return[n.name,n.text,!1];const{answer:n}=this._data,e=Math.floor(100*t),r=e>=n.length;return["result",r?n:n.slice(0,e),r]}_sources(t,n){const{sources:e}=this._data;if(n)return e;const{length:r}=e;return e.slice(0,Math.floor(r*t/3))}_related_resources(t,n){const{related_resources:e}=this._data;if(n)return e;const{length:r}=e;return e.slice(0,Math.floor(r*t/3))}_followup_questions(t,n){const{followup_questions:e}=this._data;if(n||!e)return e;const{length:r}=e;return e.slice(0,Math.floor(r*t/3))}}class K{constructor(t){this._options=t}search({rows:t=5}){return{products:[...C({rows:t})]}}}class G{constructor(t){this._options=t}user_to_products({rows:t=5}){return{products:[...C({rows:t})]}}product_to_products({rows:t=5}){return{products:[...C({rows:t})]}}}class N{constructor(t){this._options=t}upload(t){return[]}}class R{constructor(t){this._options=t}upload(t){return[]}ids(){const t=[];for(let n=0;5e3>n;n++)t.push(V(n));return{ids:t}}}function V(t){return`${10>t?"p_000":100>t?"p_00":1e3>t?"p_0":"p_"}${t}`}class Z{constructor(t){this.ask=new I(t),this.search=new K(t),this.recommendation=new G(t),this.interactions=new N(t),this.products=new R(t)}}return{buildApi:(...t)=>new Z(...t)}}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miso.ai/doggoganger",
|
|
3
|
-
"version": "0.9.0
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "A dummy miso endpoint for demo and testing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,12 @@
|
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"
|
|
13
|
+
"build:data": "node ./bin/data.js",
|
|
14
|
+
"build:js": "rollup -c",
|
|
15
|
+
"build": "npm run build:data && npm run build:js",
|
|
16
|
+
"start:js": "rollup -w -c rollup.config.dev.mjs",
|
|
17
|
+
"start": "nodemon cli/index.js",
|
|
18
|
+
"dev": "npm run start -- -- -v",
|
|
14
19
|
"version": "node ./bin/version.js"
|
|
15
20
|
},
|
|
16
21
|
"repository": {
|
|
@@ -32,8 +37,13 @@
|
|
|
32
37
|
"koa": "^2.14.1",
|
|
33
38
|
"koa-body": "^6.0.1",
|
|
34
39
|
"koa-static": "^5.0.0",
|
|
35
|
-
"nodemon": "^2.0.
|
|
36
|
-
"uuid": "^8.3.2",
|
|
40
|
+
"nodemon": "^2.0.22",
|
|
37
41
|
"yargs": "^17.5.1"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@rollup/plugin-terser": "^0.4.3",
|
|
45
|
+
"rollup": "^3.23.0",
|
|
46
|
+
"rollup-plugin-livereload": "^2.0.5",
|
|
47
|
+
"rollup-plugin-serve": "^2.0.2"
|
|
38
48
|
}
|
|
39
49
|
}
|
package/rollup.util.mjs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import terser from '@rollup/plugin-terser';
|
|
2
|
+
import serve from 'rollup-plugin-serve';
|
|
3
|
+
import livereload from 'rollup-plugin-livereload';
|
|
4
|
+
|
|
5
|
+
export function config(env) {
|
|
6
|
+
const prod = env === 'prod';
|
|
7
|
+
let plugins;
|
|
8
|
+
if (prod) {
|
|
9
|
+
plugins = [
|
|
10
|
+
terser({
|
|
11
|
+
compress: {
|
|
12
|
+
pure_getters: true,
|
|
13
|
+
unsafe: true,
|
|
14
|
+
unsafe_comps: true,
|
|
15
|
+
warnings: false
|
|
16
|
+
},
|
|
17
|
+
}),
|
|
18
|
+
];
|
|
19
|
+
} else {
|
|
20
|
+
plugins = [
|
|
21
|
+
serve({
|
|
22
|
+
port: 10098,
|
|
23
|
+
}),
|
|
24
|
+
livereload({
|
|
25
|
+
delay: 500,
|
|
26
|
+
watch: 'dist',
|
|
27
|
+
}),
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
input: `src/browser.js`,
|
|
32
|
+
output: {
|
|
33
|
+
file: prod ? `dist/umd/doggoganger-browser.min.js` : `dist/umd/doggoganger-browser.js`,
|
|
34
|
+
format: 'umd',
|
|
35
|
+
name: 'doggoganger',
|
|
36
|
+
exports: 'default',
|
|
37
|
+
indent: !prod,
|
|
38
|
+
},
|
|
39
|
+
watch: !prod,
|
|
40
|
+
plugins,
|
|
41
|
+
};
|
|
42
|
+
}
|
package/src/api/ask.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { v4 as uuid } from 'uuid';
|
|
3
|
-
import { lorem, md, articles, utils } from '../data/index.js';
|
|
4
|
-
|
|
5
|
-
const { randomInt } = utils;
|
|
1
|
+
import { answer } from '../data/index.js';
|
|
6
2
|
|
|
7
3
|
const CPS = 100;
|
|
8
4
|
const ITEMS_LOADING_TIME = 3;
|
|
@@ -24,67 +20,51 @@ const STAGES = [
|
|
|
24
20
|
},
|
|
25
21
|
];
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
const str = new Date(timestamp).toISOString();
|
|
29
|
-
return str.endsWith('Z') ? str.slice(0, -1) : str;
|
|
30
|
-
}
|
|
23
|
+
export default class Ask {
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return md.markdown({});
|
|
36
|
-
case 'plaintext':
|
|
37
|
-
default:
|
|
38
|
-
return lorem.lorem({
|
|
39
|
-
min: 50,
|
|
40
|
-
max: 100,
|
|
41
|
-
decorates: ['description'],
|
|
42
|
-
});
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
this._options = options;
|
|
27
|
+
this._answers = new Map();
|
|
43
28
|
}
|
|
44
|
-
}
|
|
45
29
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
30
|
+
questions(payload, options = {}) {
|
|
31
|
+
return new Answer(payload, { ...this._options, ...options });
|
|
32
|
+
}
|
|
49
33
|
|
|
50
|
-
|
|
51
|
-
this.question_id = uuid();
|
|
52
|
-
this.question = question;
|
|
53
|
-
this.previous_answer_id = previous_question_id;
|
|
54
|
-
this.timestamp = Date.now();
|
|
55
|
-
this.datetime = formatDatetime(this.timestamp);
|
|
34
|
+
}
|
|
56
35
|
|
|
57
|
-
|
|
58
|
-
this.relatedResources = [...articles({ rows: randomInt(6, 8) })];
|
|
59
|
-
this.sources = [...articles({ rows: randomInt(4, 6) })];
|
|
36
|
+
class Answer {
|
|
60
37
|
|
|
61
|
-
|
|
62
|
-
|
|
38
|
+
constructor({ question, parent_question_id }, { answerFormat, answerSampling, answerLanguages, ...options } = {}) {
|
|
39
|
+
this._options = Object.freeze(options);
|
|
40
|
+
const timestamp = this.timestamp = Date.now();
|
|
41
|
+
this._data = answer({ question, parent_question_id, timestamp }, { answerFormat, answerSampling, answerLanguages });
|
|
63
42
|
}
|
|
64
43
|
|
|
65
|
-
get
|
|
66
|
-
return this.question_id;
|
|
44
|
+
get question_id() {
|
|
45
|
+
return this._data.question_id;
|
|
67
46
|
}
|
|
68
47
|
|
|
69
48
|
get() {
|
|
70
49
|
const now = Date.now();
|
|
71
|
-
const elapsed = (now - this.timestamp) / 1000;
|
|
50
|
+
const elapsed = (now - this.timestamp) * (this._options.speedRate || 1) / 1000;
|
|
72
51
|
const [answer_stage, answer, finished] = this._answer(elapsed);
|
|
73
|
-
const sources = this._sources(elapsed);
|
|
74
|
-
const related_resources = this.
|
|
75
|
-
const
|
|
52
|
+
const sources = this._sources(elapsed, finished);
|
|
53
|
+
const related_resources = this._related_resources(elapsed, finished);
|
|
54
|
+
const followup_questions = this._followup_questions(elapsed, finished);
|
|
55
|
+
const { question_id, question, datetime, parent_question_id } = this._data;
|
|
76
56
|
|
|
77
57
|
return {
|
|
78
|
-
affiliation: undefined,
|
|
79
58
|
answer,
|
|
80
59
|
answer_stage,
|
|
81
60
|
datetime,
|
|
82
61
|
finished,
|
|
83
|
-
|
|
62
|
+
parent_question_id,
|
|
84
63
|
question,
|
|
85
64
|
question_id,
|
|
86
|
-
related_resources,
|
|
87
65
|
sources,
|
|
66
|
+
related_resources,
|
|
67
|
+
followup_questions,
|
|
88
68
|
};
|
|
89
69
|
}
|
|
90
70
|
|
|
@@ -95,52 +75,41 @@ class Answer {
|
|
|
95
75
|
return [stage.name, stage.text, false];
|
|
96
76
|
}
|
|
97
77
|
}
|
|
78
|
+
const { answer } = this._data;
|
|
98
79
|
const length = Math.floor(elapsed * CPS);
|
|
99
|
-
const finished = length >=
|
|
100
|
-
const text = finished ?
|
|
80
|
+
const finished = length >= answer.length;
|
|
81
|
+
const text = finished ? answer : answer.slice(0, length);
|
|
101
82
|
return ['result', text, finished];
|
|
102
83
|
}
|
|
103
84
|
|
|
104
|
-
_sources(elapsed) {
|
|
105
|
-
const { sources } = this;
|
|
85
|
+
_sources(elapsed, finished) {
|
|
86
|
+
const { sources } = this._data;
|
|
87
|
+
if (finished) {
|
|
88
|
+
return sources;
|
|
89
|
+
}
|
|
106
90
|
const { length } = sources;
|
|
107
91
|
const loaded = Math.floor(length * elapsed / ITEMS_LOADING_TIME);
|
|
108
92
|
return sources.slice(0, loaded);
|
|
109
93
|
}
|
|
110
94
|
|
|
111
|
-
|
|
112
|
-
const {
|
|
113
|
-
|
|
95
|
+
_related_resources(elapsed, finished) {
|
|
96
|
+
const { related_resources } = this._data;
|
|
97
|
+
if (finished) {
|
|
98
|
+
return related_resources;
|
|
99
|
+
}
|
|
100
|
+
const { length } = related_resources;
|
|
114
101
|
const loaded = Math.floor(length * elapsed / ITEMS_LOADING_TIME);
|
|
115
|
-
return
|
|
102
|
+
return related_resources.slice(0, loaded);
|
|
116
103
|
}
|
|
117
104
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const router = new Router();
|
|
123
|
-
|
|
124
|
-
router.post('/questions', (ctx) => {
|
|
125
|
-
const { question, previous_answer_id } = JSON.parse(ctx.request.body);
|
|
126
|
-
const answerFormat = ctx.get('x-answer-format') || options.answerFormat;
|
|
127
|
-
const answer = new Answer(question, previous_answer_id, { answerFormat });
|
|
128
|
-
const data = {
|
|
129
|
-
question_id: answer.id,
|
|
130
|
-
};
|
|
131
|
-
ctx.body = JSON.stringify({ data });
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
router.get('/questions/:id/answer', (ctx) => {
|
|
135
|
-
const { id } = ctx.params;
|
|
136
|
-
const answer = answers.get(id);
|
|
137
|
-
if (!answer) {
|
|
138
|
-
ctx.status = 404;
|
|
139
|
-
} else {
|
|
140
|
-
const data = answer.get();
|
|
141
|
-
ctx.body = JSON.stringify({ data });
|
|
105
|
+
_followup_questions(elapsed, finished) {
|
|
106
|
+
const { followup_questions } = this._data;
|
|
107
|
+
if (finished || !followup_questions) {
|
|
108
|
+
return followup_questions;
|
|
142
109
|
}
|
|
143
|
-
|
|
110
|
+
const { length } = followup_questions;
|
|
111
|
+
const loaded = Math.floor(length * elapsed / ITEMS_LOADING_TIME);
|
|
112
|
+
return followup_questions.slice(0, loaded);
|
|
113
|
+
}
|
|
144
114
|
|
|
145
|
-
return router;
|
|
146
115
|
}
|
package/src/api/index.js
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
import Ask from './ask.js';
|
|
2
|
+
import Search from './search.js';
|
|
3
|
+
import Recommendation from './recommendation.js';
|
|
4
|
+
import Interactions from './interactions.js';
|
|
5
|
+
import Products from './products.js';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
router.use(path, middleware.routes(), middleware.allowedMethods());
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export default function(options) {
|
|
12
|
-
const router = new Router();
|
|
7
|
+
export default class Api {
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.ask = new Ask(options);
|
|
11
|
+
this.search = new Search(options);
|
|
12
|
+
this.recommendation = new Recommendation(options);
|
|
13
|
+
this.interactions = new Interactions(options);
|
|
14
|
+
this.products = new Products(options);
|
|
15
|
+
}
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
};
|
|
17
|
+
}
|
package/src/api/interactions.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
export default class Interactions {
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this._options = options;
|
|
5
|
+
}
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
7
|
+
upload(records) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
// TODO: delete
|
|
12
|
+
|
|
13
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export default class Products {
|
|
2
|
+
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this._options = options;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
upload(records) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
ids() {
|
|
12
|
+
const ids = [];
|
|
13
|
+
for (let i = 0; i < 5000; i++) {
|
|
14
|
+
ids.push(mockProductId(i));
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
ids,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// TODO: delete (batch)
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function mockProductId(i) {
|
|
26
|
+
const prefix = i < 10 ? 'p_000' : i < 100 ? 'p_00' : i < 1000 ? 'p_0' : 'p_';
|
|
27
|
+
return `${prefix}${i}`;
|
|
28
|
+
}
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { products } from './handlers.js';
|
|
1
|
+
import { products } from '../data/index.js';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
export default class Recommendation {
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this._options = options;
|
|
7
|
+
}
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
user_to_products({ rows = 5 }) {
|
|
10
|
+
return {
|
|
11
|
+
products: [...products({ rows })],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
product_to_products({ rows = 5 }) {
|
|
16
|
+
return {
|
|
17
|
+
products: [...products({ rows })],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
}
|
package/src/api/search.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { products } from './handlers.js';
|
|
1
|
+
import { products } from '../data/index.js';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
export default class Search {
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this._options = options;
|
|
7
|
+
}
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
search({ rows = 5 }) {
|
|
10
|
+
return {
|
|
11
|
+
products: [...products({ rows })],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
}
|
package/src/browser.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { randomInt, formatDatetime, sample, uuid } from './utils.js';
|
|
2
|
+
import * as fields from './fields.js';
|
|
3
|
+
import { articles } from './articles.js';
|
|
4
|
+
import { questions } from './questions.js';
|
|
5
|
+
|
|
6
|
+
export function answer({ question, parent_question_id, timestamp = Date.now() }, { answerFormat = 'markdown', answerSampling, answerLanguages = [] } = {}) {
|
|
7
|
+
|
|
8
|
+
const question_id = uuid();
|
|
9
|
+
const datetime = formatDatetime(timestamp);
|
|
10
|
+
|
|
11
|
+
const sampling = answerSampling !== undefined ? Math.max(0, Math.min(1, answerSampling)) : undefined;
|
|
12
|
+
const features = answerLanguages.length ? answerLanguages.map(language => `lang-${language}`) : undefined;
|
|
13
|
+
|
|
14
|
+
const answer = fields.answer({ format: answerFormat, sampling, features });
|
|
15
|
+
const related_resources = [...articles({ rows: sampleRandomInt(6, 8, sampling) })];
|
|
16
|
+
const sources = [...articles({ rows: sampleRandomInt(4, 6, sampling) })];
|
|
17
|
+
const followup_questions = [...questions({ rows: sampleRandomInt(3, 6) })];
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
question,
|
|
21
|
+
question_id,
|
|
22
|
+
...(parent_question_id && { parent_question_id }),
|
|
23
|
+
datetime,
|
|
24
|
+
answer,
|
|
25
|
+
sources,
|
|
26
|
+
related_resources,
|
|
27
|
+
followup_questions,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function sampleRandomInt(min, max, sampling) {
|
|
32
|
+
return randomInt(sample(min, sampling), sample(max, sampling));
|
|
33
|
+
}
|
package/src/data/fields.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as lorem from './lorem.js';
|
|
2
|
-
import { imageUrl } from './utils.js';
|
|
2
|
+
import { imageUrl, sample } from './utils.js';
|
|
3
|
+
import * as md from './markdown/index.js';
|
|
3
4
|
|
|
4
5
|
export function image({ size = 300 } = {}) {
|
|
5
6
|
return imageUrl(size);
|
|
@@ -29,10 +30,11 @@ export function title({ size = [2, 6] } = {}) {
|
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
export function description({ size = [10, 20] } = {}) {
|
|
33
|
+
export function description({ size = [10, 20], ...options } = {}) {
|
|
34
|
+
const decorator = Object.keys(options).length ? ['description', options] : 'description';
|
|
33
35
|
return lorem.lorem({
|
|
34
36
|
size,
|
|
35
|
-
decorates: [
|
|
37
|
+
decorates: [decorator],
|
|
36
38
|
});
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -47,3 +49,17 @@ export function price() {
|
|
|
47
49
|
export function rating() {
|
|
48
50
|
return Math.floor(Math.random() * 500) / 100 + 1;
|
|
49
51
|
}
|
|
52
|
+
|
|
53
|
+
export function answer({ format, sampling, features }) {
|
|
54
|
+
switch (format) {
|
|
55
|
+
case 'markdown':
|
|
56
|
+
return md.markdown({ sampling, features });
|
|
57
|
+
case 'plaintext':
|
|
58
|
+
default:
|
|
59
|
+
return lorem.lorem({
|
|
60
|
+
min: sample(50, sampling),
|
|
61
|
+
max: sample(50, sampling),
|
|
62
|
+
decorates: ['description'],
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/data/index.js
CHANGED
package/src/data/lorem.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { dirname, resolve } from 'path';
|
|
4
|
-
import yaml from 'js-yaml';
|
|
5
|
-
import { randomInt } from './utils.js';
|
|
6
|
-
|
|
7
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const DEFAULT_WORDS = yaml.load(readFileSync(resolve(__dirname, './words.yaml'), 'utf8'));
|
|
1
|
+
import { randomInt, gaussRandom } from './utils.js';
|
|
2
|
+
import DEFAULT_WORDS from './words.js';
|
|
9
3
|
|
|
10
4
|
export function lorem({ decorates = [], output = 'string', size, min, max, ...options } = {}) {
|
|
11
5
|
let iterator = limit(size || [min, max])(base(options));
|
|
@@ -24,7 +18,18 @@ const FNS = {
|
|
|
24
18
|
}
|
|
25
19
|
|
|
26
20
|
function lookup(fn) {
|
|
27
|
-
|
|
21
|
+
switch (typeof fn) {
|
|
22
|
+
case 'string':
|
|
23
|
+
return FNS[fn]();
|
|
24
|
+
case 'function':
|
|
25
|
+
return fn;
|
|
26
|
+
case 'object':
|
|
27
|
+
if (Array.isArray(fn)) {
|
|
28
|
+
const [name, options = {}] = fn;
|
|
29
|
+
return FNS[name](options);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`Unrecognized decorator/output form: ${fn}`);
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
// base //
|
|
@@ -97,6 +102,7 @@ export function description({
|
|
|
97
102
|
std: 5,
|
|
98
103
|
min: 1,
|
|
99
104
|
},
|
|
105
|
+
punctuation = '.',
|
|
100
106
|
} = {}) {
|
|
101
107
|
return function *(iterator) {
|
|
102
108
|
let word;
|
|
@@ -111,12 +117,12 @@ export function description({
|
|
|
111
117
|
slen = gaussMS(wordsPerSentence);
|
|
112
118
|
}
|
|
113
119
|
if (--slen === 0) {
|
|
114
|
-
word +=
|
|
120
|
+
word += punctuation;
|
|
115
121
|
}
|
|
116
122
|
}
|
|
117
123
|
if (word) {
|
|
118
|
-
if (!word.endsWith(
|
|
119
|
-
word +=
|
|
124
|
+
if (!word.endsWith(punctuation)) {
|
|
125
|
+
word += punctuation;
|
|
120
126
|
}
|
|
121
127
|
yield word;
|
|
122
128
|
}
|
|
@@ -146,11 +152,3 @@ function gaussMS(args) {
|
|
|
146
152
|
}
|
|
147
153
|
return Math.round(n);
|
|
148
154
|
}
|
|
149
|
-
|
|
150
|
-
function gaussRandom() {
|
|
151
|
-
return uniformRandom() + uniformRandom() + uniformRandom();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function uniformRandom() {
|
|
155
|
-
return Math.random() * 2 - 1;
|
|
156
|
-
}
|