@miso.ai/doggoganger-api 0.0.1 → 0.10.0-beta.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/lib/api/api.js +21 -0
- package/lib/api/ask.js +25 -21
- package/lib/api/index.js +1 -17
- package/lib/api/interactions.js +2 -2
- package/lib/api/products.js +4 -4
- package/lib/api/recommendation.js +8 -6
- package/lib/api/search.js +8 -6
- package/lib/data/answers.js +63 -58
- package/lib/data/articles.js +38 -34
- package/lib/data/completions.js +38 -30
- package/lib/data/data.js +89 -0
- package/lib/data/facets.js +46 -40
- package/lib/data/images.js +19 -17
- package/lib/data/index.js +4 -8
- package/lib/data/products.js +25 -24
- package/lib/data/questions.js +7 -7
- package/lib/data/search.js +28 -21
- package/lib/index.js +2 -4
- package/lib/utils.js +13 -0
- package/package.json +5 -3
- package/test/prng.test.js +51 -0
- package/package.json.bak +0 -26
package/lib/api/api.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
|
|
7
|
+
export function api(options) {
|
|
8
|
+
return new Api(options);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class Api {
|
|
12
|
+
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.ask = new Ask(options);
|
|
15
|
+
this.search = new Search(options);
|
|
16
|
+
this.recommendation = new Recommendation(options);
|
|
17
|
+
this.interactions = new Interactions(options);
|
|
18
|
+
this.products = new Products(options);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
}
|
package/lib/api/ask.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { misoData } from '../data/index.js';
|
|
1
2
|
import { trimObj } from '../utils.js';
|
|
2
|
-
import { answer, searchResults, questions, completions, utils } from '../data/index.js';
|
|
3
3
|
|
|
4
4
|
const CPS = 100;
|
|
5
5
|
const ITEMS_LOADING_TIME = 3;
|
|
@@ -25,40 +25,43 @@ const STAGES = [
|
|
|
25
25
|
const MODE_QUESTION = 0;
|
|
26
26
|
const MODE_SEARCH = 1;
|
|
27
27
|
|
|
28
|
-
export
|
|
28
|
+
export class Ask {
|
|
29
29
|
|
|
30
30
|
constructor(options = {}) {
|
|
31
31
|
this._options = options;
|
|
32
32
|
this._answers = new Map();
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
questions(payload, options = {}) {
|
|
36
|
-
const
|
|
35
|
+
questions(payload, { seed, ...options } = {}) {
|
|
36
|
+
const data = misoData({ seed });
|
|
37
|
+
const answer = this._createAnswer(data, MODE_QUESTION, payload, options);
|
|
37
38
|
const { question_id } = answer;
|
|
38
39
|
return { question_id };
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
search(payload, options = {}) {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
42
|
+
search(payload, { seed, ...options } = {}) {
|
|
43
|
+
const data = misoData({ seed });
|
|
44
|
+
const miso_id = data._lorem.prng.uuid();
|
|
45
|
+
const result = { miso_id, ...data.searchResults(payload) };
|
|
44
46
|
if (payload.answer === undefined || payload.answer) {
|
|
45
|
-
result.question_id = this._createAnswer(MODE_SEARCH, payload, options).question_id;
|
|
47
|
+
result.question_id = this._createAnswer(data, MODE_SEARCH, payload, options).question_id;
|
|
46
48
|
}
|
|
47
49
|
return result;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
autocomplete({ q, completion_fields = ['title'], rows = 5 }) {
|
|
52
|
+
autocomplete({ q, completion_fields = ['title'], rows = 5, ...rest }, { seed } = {}) {
|
|
53
|
+
const data = misoData({ seed });
|
|
51
54
|
return {
|
|
52
|
-
completions: completions({ q, completion_fields, rows }),
|
|
55
|
+
completions: data.completions({ q, completion_fields, rows }),
|
|
53
56
|
};
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
search_autocomplete(args) {
|
|
57
|
-
return this.autocomplete(args);
|
|
59
|
+
search_autocomplete(args, options) {
|
|
60
|
+
return this.autocomplete(args, options);
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
_createAnswer(mode, payload, options = {}) {
|
|
61
|
-
const answer = new Answer(mode, payload, { ...this._options, ...options });
|
|
63
|
+
_createAnswer(data, mode, payload, options = {}) {
|
|
64
|
+
const answer = new Answer(data, mode, payload, { ...this._options, ...options });
|
|
62
65
|
this._answers.set(answer.question_id, answer);
|
|
63
66
|
return answer;
|
|
64
67
|
}
|
|
@@ -73,27 +76,28 @@ export default class Ask {
|
|
|
73
76
|
return answer.get();
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
related_questions(payload) {
|
|
77
|
-
const
|
|
79
|
+
related_questions(payload, { seed } = {}) {
|
|
80
|
+
const data = misoData({ seed });
|
|
81
|
+
const miso_id = data._lorem.prng.uuid();
|
|
78
82
|
return {
|
|
79
|
-
related_questions:
|
|
83
|
+
related_questions: data.questions(payload),
|
|
80
84
|
miso_id,
|
|
81
85
|
};
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
trending_questions(payload) {
|
|
85
|
-
return this.related_questions(payload);
|
|
88
|
+
trending_questions(payload, options) {
|
|
89
|
+
return this.related_questions(payload, options);
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
class Answer {
|
|
91
95
|
|
|
92
|
-
constructor(mode, payload, { answerFormat, answerSampling, answerLanguages, ...options } = {}) {
|
|
96
|
+
constructor(data, mode, payload, { answerFormat, answerSampling, answerLanguages, ...options } = {}) {
|
|
93
97
|
this._mode = mode;
|
|
94
98
|
this._options = Object.freeze(options);
|
|
95
99
|
const timestamp = this.timestamp = Date.now();
|
|
96
|
-
this._data = answer({ ...payload, timestamp }, { answerFormat, answerSampling, answerLanguages });
|
|
100
|
+
this._data = data.answer({ ...payload, timestamp }, { answerFormat, answerSampling, answerLanguages });
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
get question_id() {
|
package/lib/api/index.js
CHANGED
|
@@ -1,17 +1 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
7
|
-
export default class Api {
|
|
8
|
-
|
|
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
|
-
}
|
|
16
|
-
|
|
17
|
-
}
|
|
1
|
+
export * from './api.js';
|
package/lib/api/interactions.js
CHANGED
package/lib/api/products.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
export
|
|
1
|
+
export class Products {
|
|
2
2
|
|
|
3
3
|
constructor(options) {
|
|
4
4
|
this._options = options;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
upload(records) {
|
|
7
|
+
upload(records, { seed } = {}) {
|
|
8
8
|
return [];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
batchDelete(ids) {
|
|
11
|
+
batchDelete(ids, { seed } = {}) {
|
|
12
12
|
return [];
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
ids() {
|
|
15
|
+
ids({ seed } = {}) {
|
|
16
16
|
const ids = [];
|
|
17
17
|
for (let i = 0; i < 5000; i++) {
|
|
18
18
|
ids.push(mockProductId(i));
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { misoData } from '../data/index.js';
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export class Recommendation {
|
|
4
4
|
|
|
5
5
|
constructor(options) {
|
|
6
6
|
this._options = options;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
user_to_products({ rows = 5 }) {
|
|
9
|
+
user_to_products({ rows = 5, ...rest }, { seed } = {}) {
|
|
10
|
+
const data = misoData({ seed });
|
|
10
11
|
return {
|
|
11
|
-
products:
|
|
12
|
+
products: data.products({ rows }),
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
product_to_products({ rows = 5 }) {
|
|
16
|
+
product_to_products({ rows = 5, ...rest }, { seed } = {}) {
|
|
17
|
+
const data = misoData({ seed });
|
|
16
18
|
return {
|
|
17
|
-
products:
|
|
19
|
+
products: data.products({ rows }),
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
|
package/lib/api/search.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { misoData } from '../data/index.js';
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export class Search {
|
|
4
4
|
|
|
5
5
|
constructor(options) {
|
|
6
6
|
this._options = options;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
search({ rows = 5 }) {
|
|
9
|
+
search({ rows = 5, ...rest }, { seed } = {}) {
|
|
10
|
+
const data = misoData({ seed });
|
|
10
11
|
return {
|
|
11
|
-
products:
|
|
12
|
+
products: data.products({ rows }),
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
autocomplete({ q, completion_fields = ['title'], rows = 5 }) {
|
|
16
|
+
autocomplete({ q, completion_fields = ['title'], rows = 5, ...rest }, { seed } = {}) {
|
|
17
|
+
const data = misoData({ seed });
|
|
16
18
|
return {
|
|
17
|
-
completions: completions({ q, completion_fields, rows }),
|
|
19
|
+
completions: data.completions({ q, completion_fields, rows }),
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
|
package/lib/data/answers.js
CHANGED
|
@@ -1,69 +1,74 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { articles } from './articles.js';
|
|
3
|
-
import { images as _images } from './images.js';
|
|
4
|
-
import { questions } from './questions.js';
|
|
5
|
-
import { facets as generateFacetFields } from './facets.js';
|
|
1
|
+
import { formatDatetime, sample, excludeHtml } from '../utils.js';
|
|
6
2
|
|
|
7
|
-
|
|
3
|
+
export class Answers {
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
parent_question_id,
|
|
13
|
-
fl = ['cover_image', 'url'],
|
|
14
|
-
source_fl = ['cover_image', 'url'],
|
|
15
|
-
related_resource_fl = ['cover_image', 'url'],
|
|
16
|
-
cite_link = false,
|
|
17
|
-
cite_start = '[',
|
|
18
|
-
cite_end = ']',
|
|
19
|
-
rows = 10,
|
|
20
|
-
facets,
|
|
21
|
-
timestamp = Date.now(),
|
|
22
|
-
_meta: {
|
|
23
|
-
page = 0,
|
|
24
|
-
} = {},
|
|
25
|
-
}, { answerFormat = 'markdown', answerSampling, answerLanguages = [] } = {}) {
|
|
5
|
+
constructor(data) {
|
|
6
|
+
this._data = data;
|
|
7
|
+
}
|
|
26
8
|
|
|
27
|
-
|
|
28
|
-
|
|
9
|
+
_answer({
|
|
10
|
+
_ctrl = {},
|
|
11
|
+
question,
|
|
12
|
+
parent_question_id,
|
|
13
|
+
fl = ['cover_image', 'url'],
|
|
14
|
+
source_fl = ['cover_image', 'url'],
|
|
15
|
+
related_resource_fl = ['cover_image', 'url'],
|
|
16
|
+
cite_link = false,
|
|
17
|
+
cite_start = '[',
|
|
18
|
+
cite_end = ']',
|
|
19
|
+
rows = 10,
|
|
20
|
+
facets,
|
|
21
|
+
timestamp = Date.UTC(2026, 0, 1),
|
|
22
|
+
_meta: {
|
|
23
|
+
page = 0,
|
|
24
|
+
} = {},
|
|
25
|
+
}, { answerFormat = 'markdown', answerSampling, answerLanguages = [] } = {}) {
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
const data = this._data;
|
|
28
|
+
const { fields, prng, utils } = data._lorem;
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const sources = [...articles({ rows: sampleRandomInt(4, 6, sampling), fl: source_fl })].map(excludeHtml);
|
|
30
|
+
const question_id = prng.uuid();
|
|
31
|
+
const datetime = formatDatetime(timestamp);
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
const sampling = answerSampling !== undefined ? Math.max(0, Math.min(1, answerSampling)) : undefined;
|
|
34
|
+
const features = answerLanguages.length ? answerLanguages.map(language => `lang-${language}`) : undefined;
|
|
39
35
|
|
|
40
|
-
|
|
36
|
+
const related_resources = data.articles({ rows: this._sampleRandomInt(6, 8, sampling), fl: related_resource_fl }).map(excludeHtml);
|
|
37
|
+
const images = data.images({ rows: this._sampleRandomInt(2, 12, sampling) });
|
|
38
|
+
const sources = data.articles({ rows: this._sampleRandomInt(4, 6, sampling), fl: source_fl }).map(excludeHtml);
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
start: cite_start,
|
|
45
|
-
end: cite_end,
|
|
46
|
-
unused: shuffle([...Array(sources.length).keys()]),
|
|
47
|
-
};
|
|
48
|
-
const answer = fields.answer({ sources, citation, format: answerFormat, sampling, features });
|
|
49
|
-
const followup_questions = [...questions({ rows: sampleRandomInt(3, 6) })];
|
|
40
|
+
const total = _ctrl.total !== undefined ? _ctrl.total : prng.randomInt(1000, 10000);
|
|
41
|
+
const products = data.articles({ rows: Math.min(total - page * rows, rows), fl }).map(excludeHtml);
|
|
50
42
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
43
|
+
const facet_counts = facets ? { facet_fields: data.facets({ facets, _ctrl }) } : undefined;
|
|
44
|
+
|
|
45
|
+
const citation = {
|
|
46
|
+
link: cite_link !== '0' && !!cite_link,
|
|
47
|
+
start: cite_start,
|
|
48
|
+
end: cite_end,
|
|
49
|
+
unused: utils.shuffle([...Array(sources.length).keys()]),
|
|
50
|
+
};
|
|
51
|
+
const answer = fields.answer({ sources, citation, format: answerFormat, sampling, features });
|
|
52
|
+
const followup_questions = data.questions({ rows: this._sampleRandomInt(3, 6) });
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
question,
|
|
56
|
+
question_id,
|
|
57
|
+
...(parent_question_id && { parent_question_id }),
|
|
58
|
+
datetime,
|
|
59
|
+
answer,
|
|
60
|
+
images,
|
|
61
|
+
sources,
|
|
62
|
+
products,
|
|
63
|
+
total,
|
|
64
|
+
facet_counts,
|
|
65
|
+
related_resources,
|
|
66
|
+
followup_questions,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_sampleRandomInt(min, max, sampling) {
|
|
71
|
+
return this._data._lorem.prng.randomInt(sample(min, sampling), sample(max, sampling));
|
|
72
|
+
}
|
|
66
73
|
|
|
67
|
-
function sampleRandomInt(min, max, sampling) {
|
|
68
|
-
return randomInt(sample(min, sampling), sample(max, sampling));
|
|
69
74
|
}
|
package/lib/data/articles.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { fields, utils } from '@miso.ai/lorem';
|
|
2
|
-
|
|
3
1
|
const FIELDS = new Set([
|
|
4
2
|
'cover_image',
|
|
5
3
|
'url',
|
|
@@ -8,43 +6,49 @@ const FIELDS = new Set([
|
|
|
8
6
|
'published_at',
|
|
9
7
|
]);
|
|
10
8
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
export class Articles {
|
|
10
|
+
|
|
11
|
+
constructor(data) {
|
|
12
|
+
this._lorem = data._lorem;
|
|
14
13
|
}
|
|
15
|
-
}
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
15
|
+
_article({ html, fl = [] } = {}) {
|
|
16
|
+
const { fields, prng } = this._lorem;
|
|
17
|
+
|
|
18
|
+
const id = prng.shortId();
|
|
19
|
+
|
|
20
|
+
const article = {
|
|
21
|
+
product_id: id,
|
|
22
|
+
authors: fields.authors(),
|
|
23
|
+
categories: [],
|
|
24
|
+
tags: fields.tags(),
|
|
25
|
+
title: fields.title({ size: [4, 10] }),
|
|
26
|
+
snippet: fields.description({ size: [20, 40] }),
|
|
27
|
+
html: fields.html(html),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
for (const field of fl) {
|
|
31
|
+
if (FIELDS.has(field)) {
|
|
32
|
+
article[field] = this._property(article, field);
|
|
33
|
+
}
|
|
33
34
|
}
|
|
35
|
+
|
|
36
|
+
return article;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
}
|
|
39
|
+
_property({ product_id }, field) {
|
|
40
|
+
const { fields } = this._lorem;
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
switch (field) {
|
|
43
|
+
case 'cover_image':
|
|
44
|
+
return fields.image();
|
|
45
|
+
case 'url':
|
|
46
|
+
return `/products/${product_id}`;
|
|
47
|
+
case 'created_at':
|
|
48
|
+
case 'updated_at':
|
|
49
|
+
case 'published_at':
|
|
50
|
+
return fields.date();
|
|
51
|
+
}
|
|
49
52
|
}
|
|
53
|
+
|
|
50
54
|
}
|
package/lib/data/completions.js
CHANGED
|
@@ -1,37 +1,45 @@
|
|
|
1
|
-
|
|
1
|
+
export class Completions {
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const result = {};
|
|
6
|
-
for (const field of completion_fields) {
|
|
7
|
-
result[field] = completionsForField(field, q, index, rows);
|
|
8
|
-
index += rows;
|
|
3
|
+
constructor(data) {
|
|
4
|
+
this._lorem = data._lorem;
|
|
9
5
|
}
|
|
10
|
-
return result;
|
|
11
|
-
}
|
|
12
6
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
_completions({ q, completion_fields, rows } = {}) {
|
|
8
|
+
let index = 0;
|
|
9
|
+
const result = {};
|
|
10
|
+
for (const field of completion_fields) {
|
|
11
|
+
result[field] = this._completionsForField(field, q, index, rows);
|
|
12
|
+
index += rows;
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
17
15
|
}
|
|
18
|
-
return completions;
|
|
19
|
-
}
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
17
|
+
_completionsForField(field, q, index, rows) {
|
|
18
|
+
const completions = [];
|
|
19
|
+
for (let i = 0; i < rows; i++) {
|
|
20
|
+
completions.push(this._completion(field, q, index));
|
|
21
|
+
}
|
|
22
|
+
return completions;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_completion(field, q, index) {
|
|
26
|
+
const { fields, prng } = this._lorem;
|
|
27
|
+
|
|
28
|
+
const text = fields.title();
|
|
29
|
+
const i = prng.randomInt(0, text.length);
|
|
30
|
+
const prefix = text.substring(0, i);
|
|
31
|
+
const suffix = text.substring(i);
|
|
32
|
+
return {
|
|
33
|
+
text: `${prefix}${q}${suffix}`,
|
|
34
|
+
text_with_markups: `${this._marked(prefix)}${q}${this._marked(suffix)}`,
|
|
35
|
+
text_with_inverted_markups: `${prefix}${this._marked(q)}${suffix}`,
|
|
36
|
+
_field: field,
|
|
37
|
+
_index: index,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
_marked(text) {
|
|
42
|
+
return text.length > 0 ? `<mark>${text}</mark>` : '';
|
|
43
|
+
}
|
|
34
44
|
|
|
35
|
-
function marked(text) {
|
|
36
|
-
return text.length > 0 ? `<mark>${text}</mark>` : '';
|
|
37
45
|
}
|
package/lib/data/data.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { lorem } from '@miso.ai/lorem';
|
|
2
|
+
import { Products } from './products.js';
|
|
3
|
+
import { Articles } from './articles.js';
|
|
4
|
+
import { Questions } from './questions.js';
|
|
5
|
+
import { Images } from './images.js';
|
|
6
|
+
import { Completions } from './completions.js';
|
|
7
|
+
import { Facets } from './facets.js';
|
|
8
|
+
import { Search } from './search.js';
|
|
9
|
+
import { Answers } from './answers.js';
|
|
10
|
+
|
|
11
|
+
export function misoData(options) {
|
|
12
|
+
return new MisoData(options);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class MisoData {
|
|
16
|
+
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this._lorem = lorem(options);
|
|
19
|
+
|
|
20
|
+
// Initialize in dependency order
|
|
21
|
+
this._articles = new Articles(this);
|
|
22
|
+
this._products = new Products(this);
|
|
23
|
+
this._questions = new Questions(this);
|
|
24
|
+
this._images = new Images(this);
|
|
25
|
+
this._completions = new Completions(this);
|
|
26
|
+
this._facets = new Facets(this);
|
|
27
|
+
|
|
28
|
+
// These depend on the above
|
|
29
|
+
this._search = new Search(this);
|
|
30
|
+
this._answers = new Answers(this);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Articles
|
|
34
|
+
articles({ rows, ...options } = {}) {
|
|
35
|
+
const results = [];
|
|
36
|
+
for (let i = 0; i < rows; i++) {
|
|
37
|
+
results.push(this._articles._article({ ...options, index: i }));
|
|
38
|
+
}
|
|
39
|
+
return results;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Products
|
|
43
|
+
products({ rows, ...options } = {}) {
|
|
44
|
+
const results = [];
|
|
45
|
+
for (let i = 0; i < rows; i++) {
|
|
46
|
+
results.push(this._products._product({ ...options, index: i }));
|
|
47
|
+
}
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Questions
|
|
52
|
+
questions({ rows = 5, ...options } = {}) {
|
|
53
|
+
const results = [];
|
|
54
|
+
for (let i = 0; i < rows; i++) {
|
|
55
|
+
results.push(this._questions._question({ ...options, index: i }));
|
|
56
|
+
}
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Images
|
|
61
|
+
images({ rows, ...options } = {}) {
|
|
62
|
+
const results = [];
|
|
63
|
+
for (let i = 0; i < rows; i++) {
|
|
64
|
+
results.push(this._images._image({ ...options, index: i }));
|
|
65
|
+
}
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Completions
|
|
70
|
+
completions(options) {
|
|
71
|
+
return this._completions._completions(options);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Facets
|
|
75
|
+
facets(options) {
|
|
76
|
+
return this._facets._facets(options);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Search
|
|
80
|
+
searchResults(options) {
|
|
81
|
+
return this._search._searchResults(options);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Answers
|
|
85
|
+
answer(payload, options) {
|
|
86
|
+
return this._answers._answer(payload, options);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
}
|
package/lib/data/facets.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { fields } from '@miso.ai/lorem';
|
|
2
|
-
|
|
3
1
|
const DEFAULT_FACET_SIZE = 5;
|
|
4
2
|
const MAX_FACET_SIZE = 10;
|
|
5
3
|
const MIN_FACET_SIZE = 1;
|
|
@@ -7,52 +5,60 @@ const MIN_FACET_SIZE = 1;
|
|
|
7
5
|
const HIGHEST_FACET_COUNT_RATIO_RANGE = [1000, 5000];
|
|
8
6
|
const NEXT_FACET_COUNT_RATIO_RANGE = [0.75, 0.25];
|
|
9
7
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
export class Facets {
|
|
9
|
+
|
|
10
|
+
constructor(data) {
|
|
11
|
+
this._lorem = data._lorem;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_facets({ facets, ...options } = {}) {
|
|
15
|
+
const results = {};
|
|
16
|
+
for (let facet of facets) {
|
|
17
|
+
if (typeof facet === 'string') {
|
|
18
|
+
facet = { field: facet };
|
|
19
|
+
}
|
|
20
|
+
results[facet.field] = this._facetCountList(facet, options);
|
|
15
21
|
}
|
|
16
|
-
results
|
|
22
|
+
return results;
|
|
17
23
|
}
|
|
18
|
-
return results;
|
|
19
|
-
}
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
_facetCountList(facet, { _ctrl = {}, ...options } = {}) {
|
|
26
|
+
let { field, size = DEFAULT_FACET_SIZE } = facet;
|
|
27
|
+
size = _ctrl.total === 0 ? 0 : Math.max(MIN_FACET_SIZE, Math.min(size, MAX_FACET_SIZE));
|
|
28
|
+
let count = this._highestFacetCountValue(field, options);
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
const usedTerms = new Set();
|
|
31
|
+
const results = [];
|
|
32
|
+
for (let i = 0; i < size; i++) {
|
|
33
|
+
results.push([this._getTerm(field, usedTerms), count]);
|
|
34
|
+
count = this._nextFacetCountValue(count, field, options);
|
|
35
|
+
}
|
|
36
|
+
return results;
|
|
31
37
|
}
|
|
32
|
-
return results;
|
|
33
|
-
}
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
usedTerms.
|
|
43
|
-
|
|
39
|
+
_getTerm(field, usedTerms) {
|
|
40
|
+
const { fields } = this._lorem;
|
|
41
|
+
if (usedTerms.size > 50) {
|
|
42
|
+
return fields.term({ field });
|
|
43
|
+
}
|
|
44
|
+
while (true) {
|
|
45
|
+
const term = fields.term({ field });
|
|
46
|
+
if (!usedTerms.has(term)) {
|
|
47
|
+
usedTerms.add(term);
|
|
48
|
+
return term;
|
|
49
|
+
}
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
|
-
}
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
_highestFacetCountValue(field, options) {
|
|
54
|
+
const [min, max] = HIGHEST_FACET_COUNT_RATIO_RANGE;
|
|
55
|
+
return this._lorem.prng.randomInt(min, max);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_nextFacetCountValue(value, field, options) {
|
|
59
|
+
const [min, max] = NEXT_FACET_COUNT_RATIO_RANGE;
|
|
60
|
+
const ratio = min + this._lorem.prng.random() * (max - min);
|
|
61
|
+
return Math.ceil(value * ratio);
|
|
62
|
+
}
|
|
53
63
|
|
|
54
|
-
function nextFacetCountValue(value, field, options) {
|
|
55
|
-
const [min, max] = NEXT_FACET_COUNT_RATIO_RANGE;
|
|
56
|
-
const ratio = Math.random() * (max - min) + min;
|
|
57
|
-
return Math.ceil(value * ratio);
|
|
58
64
|
}
|
package/lib/data/images.js
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
export class Images {
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
yield image({ ...options, index: i });
|
|
3
|
+
constructor(data) {
|
|
4
|
+
this._lorem = data._lorem;
|
|
6
5
|
}
|
|
7
|
-
}
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
_image({ fl = [] } = {}) {
|
|
8
|
+
const { fields, prng } = this._lorem;
|
|
9
|
+
|
|
10
|
+
const id = prng.shortId();
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
product_id: id,
|
|
14
|
+
image_src: fields.image({ size: [1200, 400] }),
|
|
15
|
+
image_alt: fields.title({ size: [2, 4] }),
|
|
16
|
+
title: fields.title({ size: [4, 10] }),
|
|
17
|
+
url: `/products/${id}`,
|
|
18
|
+
created_at: fields.date(),
|
|
19
|
+
updated_at: fields.date(),
|
|
20
|
+
published_at: fields.date(),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
11
23
|
|
|
12
|
-
return {
|
|
13
|
-
product_id: id,
|
|
14
|
-
image_src: fields.image({ size: [1200, 400] }),
|
|
15
|
-
image_alt: fields.title({ size: [2, 4] }),
|
|
16
|
-
title: fields.title({ size: [4, 10] }),
|
|
17
|
-
url: `/products/${id}`,
|
|
18
|
-
created_at: fields.date(),
|
|
19
|
-
updated_at: fields.date(),
|
|
20
|
-
published_at: fields.date(),
|
|
21
|
-
};
|
|
22
24
|
}
|
package/lib/data/index.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
export * from '
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export * from './answers.js';
|
|
6
|
-
export * from './search.js';
|
|
7
|
-
export * from './completions.js';
|
|
8
|
-
export * from './images.js';
|
|
1
|
+
export * from './data.js';
|
|
2
|
+
|
|
3
|
+
// Re-export lorem
|
|
4
|
+
export { lorem } from '@miso.ai/lorem';
|
package/lib/data/products.js
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
export class Products {
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
yield product({ ...options, index: i });
|
|
3
|
+
constructor(data) {
|
|
4
|
+
this._lorem = data._lorem;
|
|
6
5
|
}
|
|
7
|
-
}
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
_product({} = {}) {
|
|
8
|
+
const { fields, prng, utils } = this._lorem;
|
|
9
|
+
|
|
10
|
+
const id = prng.shortId();
|
|
11
|
+
const prices = utils.repeat(() => fields.price(), [1, 2]);
|
|
12
|
+
prices.sort((a, b) => a - b);
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
product_id: id,
|
|
16
|
+
authors: fields.authors(),
|
|
17
|
+
categories: [],
|
|
18
|
+
tags: fields.tags(),
|
|
19
|
+
title: fields.title(),
|
|
20
|
+
description: fields.description(),
|
|
21
|
+
cover_image: fields.image(),
|
|
22
|
+
url: `https://dummy.miso.ai/products/${id}`,
|
|
23
|
+
sale_price: prices[0],
|
|
24
|
+
original_price: prices[prices.length - 1],
|
|
25
|
+
rating: fields.rating(),
|
|
26
|
+
availability: fields.availability(),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
13
29
|
|
|
14
|
-
return {
|
|
15
|
-
product_id: id,
|
|
16
|
-
authors: fields.authors(),
|
|
17
|
-
categories: [],
|
|
18
|
-
tags: fields.tags(),
|
|
19
|
-
title: fields.title(),
|
|
20
|
-
description: fields.description(),
|
|
21
|
-
//html,
|
|
22
|
-
cover_image: fields.image(),
|
|
23
|
-
url: `https://dummy.miso.ai/products/${id}`,
|
|
24
|
-
sale_price: prices[0],
|
|
25
|
-
original_price: prices[prices.length - 1],
|
|
26
|
-
rating: fields.rating(),
|
|
27
|
-
availability: fields.availability(),
|
|
28
|
-
};
|
|
29
30
|
}
|
package/lib/data/questions.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
export class Questions {
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
constructor(data) {
|
|
4
|
+
this._lorem = data._lorem;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
_question({} = {}) {
|
|
8
|
+
return this._lorem.fields.description({ size: [4, 8], punctuation: '?' });
|
|
6
9
|
}
|
|
7
|
-
}
|
|
8
10
|
|
|
9
|
-
function question({} = {}) {
|
|
10
|
-
return fields.description({ size: [4, 8], punctuation: '?' });
|
|
11
11
|
}
|
package/lib/data/search.js
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { articles } from './articles.js';
|
|
3
|
-
import { facets as generateFacetFields } from './facets.js';
|
|
1
|
+
import { excludeHtml } from '../utils.js';
|
|
4
2
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
export class Search {
|
|
4
|
+
|
|
5
|
+
constructor(data) {
|
|
6
|
+
this._data = data;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
_searchResults({
|
|
10
|
+
_ctrl = {},
|
|
11
|
+
q,
|
|
12
|
+
fl = ['cover_image', 'url'],
|
|
13
|
+
rows = 10,
|
|
14
|
+
facets,
|
|
15
|
+
_meta: {
|
|
16
|
+
page = 0,
|
|
17
|
+
} = {},
|
|
18
|
+
}) {
|
|
19
|
+
const { prng } = this._data._lorem;
|
|
20
|
+
const total = _ctrl.total !== undefined ? _ctrl.total : prng.randomInt(1000, 10000);
|
|
21
|
+
const products = this._data.articles({ rows: Math.min(total - page * rows, rows), fl }).map(excludeHtml);
|
|
22
|
+
const facet_counts = facets ? { facet_fields: this._data.facets({ facets, _ctrl }) } : undefined;
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
products,
|
|
26
|
+
total,
|
|
27
|
+
facet_counts,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
18
30
|
|
|
19
|
-
return {
|
|
20
|
-
products,
|
|
21
|
-
total,
|
|
22
|
-
facet_counts,
|
|
23
|
-
};
|
|
24
31
|
}
|
package/lib/index.js
CHANGED
package/lib/utils.js
CHANGED
|
@@ -10,3 +10,16 @@ export function trimObj(obj) {
|
|
|
10
10
|
}
|
|
11
11
|
return trimmed;
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
export function formatDatetime(timestamp) {
|
|
15
|
+
const str = new Date(timestamp).toISOString();
|
|
16
|
+
return str.endsWith('Z') ? str.slice(0, -1) : str;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function sample(size, sampling) {
|
|
20
|
+
return sampling !== undefined ? Math.ceil(size * sampling) : size;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function excludeHtml({ html, ...rest }) {
|
|
24
|
+
return rest;
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
9
|
-
"scripts": {
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "uvu test"
|
|
11
|
+
},
|
|
10
12
|
"repository": {
|
|
11
13
|
"type": "git",
|
|
12
14
|
"url": "git+https://github.com/MisoAI/doggoganger.git"
|
|
@@ -20,7 +22,7 @@
|
|
|
20
22
|
"url": "https://github.com/MisoAI/doggoganger/issues"
|
|
21
23
|
},
|
|
22
24
|
"dependencies": {
|
|
23
|
-
"@miso.ai/lorem": "0.0.
|
|
25
|
+
"@miso.ai/lorem": "0.10.0-beta.0"
|
|
24
26
|
},
|
|
25
|
-
"version": "0.0.
|
|
27
|
+
"version": "0.10.0-beta.0"
|
|
26
28
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { test } from 'uvu';
|
|
2
|
+
import * as assert from 'uvu/assert';
|
|
3
|
+
import { misoData } from '../lib/index.js';
|
|
4
|
+
|
|
5
|
+
const SEED = 42;
|
|
6
|
+
|
|
7
|
+
function generate(seed) {
|
|
8
|
+
const data = misoData({ seed });
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
// Products
|
|
12
|
+
products: data.products({ rows: 3 }),
|
|
13
|
+
|
|
14
|
+
// Articles
|
|
15
|
+
articles: data.articles({ rows: 3, fl: ['cover_image', 'url'] }),
|
|
16
|
+
|
|
17
|
+
// Questions
|
|
18
|
+
questions: data.questions({ rows: 3 }),
|
|
19
|
+
|
|
20
|
+
// Images
|
|
21
|
+
images: data.images({ rows: 3 }),
|
|
22
|
+
|
|
23
|
+
// Completions
|
|
24
|
+
completions: data.completions({ q: 'test', completion_fields: ['title'], rows: 3 }),
|
|
25
|
+
|
|
26
|
+
// Facets
|
|
27
|
+
facets: data.facets({ facets: ['category', 'brand'] }),
|
|
28
|
+
|
|
29
|
+
// Search results
|
|
30
|
+
searchResults: data.searchResults({ q: 'test', rows: 5 }),
|
|
31
|
+
|
|
32
|
+
// Answer
|
|
33
|
+
answer: data.answer({ question: 'What is this?' }, { answerFormat: 'markdown' }),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test('same seed produces identical output', () => {
|
|
38
|
+
const output1 = generate(SEED);
|
|
39
|
+
const output2 = generate(SEED);
|
|
40
|
+
|
|
41
|
+
assert.equal(output1, output2);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('different seeds produce different output', () => {
|
|
45
|
+
const output1 = generate(SEED);
|
|
46
|
+
const output2 = generate(SEED + 1);
|
|
47
|
+
|
|
48
|
+
assert.not.equal(output1, output2);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test.run();
|
package/package.json.bak
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@miso.ai/doggoganger-api",
|
|
3
|
-
"description": "A dummy Miso API module",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"main": "lib/index.js",
|
|
6
|
-
"publishConfig": {
|
|
7
|
-
"access": "public"
|
|
8
|
-
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
},
|
|
11
|
-
"repository": {
|
|
12
|
-
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/MisoAI/doggoganger.git"
|
|
14
|
-
},
|
|
15
|
-
"homepage": "https://github.com/MisoAI/doggoganger/",
|
|
16
|
-
"license": "MIT",
|
|
17
|
-
"contributors": [
|
|
18
|
-
"simonpai <simon.pai@askmiso.com>"
|
|
19
|
-
],
|
|
20
|
-
"bugs": {
|
|
21
|
-
"url": "https://github.com/MisoAI/doggoganger/issues"
|
|
22
|
-
},
|
|
23
|
-
"dependencies": {
|
|
24
|
-
"@miso.ai/lorem": "file:packages/lorem"
|
|
25
|
-
}
|
|
26
|
-
}
|