@retrivora-ai/rag-engine 1.4.7 → 1.4.9
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/dist/{chunk-YCL23GST.mjs → chunk-FHNGNXXS.mjs} +14 -11
- package/dist/handlers/index.js +14 -11
- package/dist/handlers/index.mjs +1 -1
- package/dist/index.js +15 -3
- package/dist/index.mjs +15 -3
- package/dist/server.js +14 -11
- package/dist/server.mjs +1 -1
- package/package.json +1 -1
- package/src/components/DynamicChart.tsx +15 -5
- package/src/components/MessageBubble.tsx +11 -0
- package/src/core/Pipeline.ts +13 -10
|
@@ -2257,22 +2257,25 @@ var Pipeline = class {
|
|
|
2257
2257
|
this.reranker = new Reranker();
|
|
2258
2258
|
}
|
|
2259
2259
|
async initialize() {
|
|
2260
|
-
var _a;
|
|
2260
|
+
var _a, _b;
|
|
2261
2261
|
if (this.initialised) return;
|
|
2262
|
+
const CHART_MARKER = "<!-- VIZ_PROTOCOL_V1 -->";
|
|
2262
2263
|
const chartInstruction = `
|
|
2263
2264
|
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2265
|
+
${CHART_MARKER}
|
|
2266
|
+
### MASTER DATA VISUALIZATION PROTOCOL:
|
|
2267
|
+
You are an expert data analyst. When asked to visualize or compare data retrieved from the context:
|
|
2268
|
+
1. DECIDE the best chart type: 'pie' (for parts-of-a-whole), 'bar' (for category comparisons), or 'line' (for trends).
|
|
2269
|
+
2. TRANSFORM arbitrary metadata from the context into a FLAT list of objects. Extract relevant keys dynamically.
|
|
2270
|
+
3. OUTPUT a \`\`\`chart\`\`\` code block using this JSON format:
|
|
2268
2271
|
{
|
|
2269
2272
|
"type": "bar" | "line" | "pie",
|
|
2270
|
-
"xAxisKey": "
|
|
2271
|
-
"dataKeys": ["
|
|
2272
|
-
"data": [{ "
|
|
2273
|
+
"xAxisKey": "key_for_labels",
|
|
2274
|
+
"dataKeys": ["key_for_values"],
|
|
2275
|
+
"data": [ { "key_for_labels": "Category A", "key_for_values": 100 }, ... ]
|
|
2273
2276
|
}
|
|
2274
|
-
|
|
2275
|
-
if (!this.config.llm.systemPrompt
|
|
2277
|
+
4. RULES: Valid JSON only. No math. No text-based charts. No markdown tables for graphs. If showing a chart, DO NOT output a "products" JSON carousel.`;
|
|
2278
|
+
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
2276
2279
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
|
|
2277
2280
|
}
|
|
2278
2281
|
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
@@ -2289,7 +2292,7 @@ When requested to show data visually (charts, graphs, or comparisons), you MUST
|
|
|
2289
2292
|
this.entityExtractor = new EntityExtractor(this.llmProvider);
|
|
2290
2293
|
}
|
|
2291
2294
|
await this.vectorDB.initialize();
|
|
2292
|
-
if (((
|
|
2295
|
+
if (((_b = this.config.rag) == null ? void 0 : _b.architecture) === "agentic") {
|
|
2293
2296
|
this.agent = new LangChainAgent(this, this.config);
|
|
2294
2297
|
await this.agent.initialize(this.llmProvider);
|
|
2295
2298
|
}
|
package/dist/handlers/index.js
CHANGED
|
@@ -3820,22 +3820,25 @@ var Pipeline = class {
|
|
|
3820
3820
|
this.reranker = new Reranker();
|
|
3821
3821
|
}
|
|
3822
3822
|
async initialize() {
|
|
3823
|
-
var _a;
|
|
3823
|
+
var _a, _b;
|
|
3824
3824
|
if (this.initialised) return;
|
|
3825
|
+
const CHART_MARKER = "<!-- VIZ_PROTOCOL_V1 -->";
|
|
3825
3826
|
const chartInstruction = `
|
|
3826
3827
|
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3828
|
+
${CHART_MARKER}
|
|
3829
|
+
### MASTER DATA VISUALIZATION PROTOCOL:
|
|
3830
|
+
You are an expert data analyst. When asked to visualize or compare data retrieved from the context:
|
|
3831
|
+
1. DECIDE the best chart type: 'pie' (for parts-of-a-whole), 'bar' (for category comparisons), or 'line' (for trends).
|
|
3832
|
+
2. TRANSFORM arbitrary metadata from the context into a FLAT list of objects. Extract relevant keys dynamically.
|
|
3833
|
+
3. OUTPUT a \`\`\`chart\`\`\` code block using this JSON format:
|
|
3831
3834
|
{
|
|
3832
3835
|
"type": "bar" | "line" | "pie",
|
|
3833
|
-
"xAxisKey": "
|
|
3834
|
-
"dataKeys": ["
|
|
3835
|
-
"data": [{ "
|
|
3836
|
+
"xAxisKey": "key_for_labels",
|
|
3837
|
+
"dataKeys": ["key_for_values"],
|
|
3838
|
+
"data": [ { "key_for_labels": "Category A", "key_for_values": 100 }, ... ]
|
|
3836
3839
|
}
|
|
3837
|
-
|
|
3838
|
-
if (!this.config.llm.systemPrompt
|
|
3840
|
+
4. RULES: Valid JSON only. No math. No text-based charts. No markdown tables for graphs. If showing a chart, DO NOT output a "products" JSON carousel.`;
|
|
3841
|
+
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
3839
3842
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
|
|
3840
3843
|
}
|
|
3841
3844
|
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
@@ -3852,7 +3855,7 @@ When requested to show data visually (charts, graphs, or comparisons), you MUST
|
|
|
3852
3855
|
this.entityExtractor = new EntityExtractor(this.llmProvider);
|
|
3853
3856
|
}
|
|
3854
3857
|
await this.vectorDB.initialize();
|
|
3855
|
-
if (((
|
|
3858
|
+
if (((_b = this.config.rag) == null ? void 0 : _b.architecture) === "agentic") {
|
|
3856
3859
|
this.agent = new LangChainAgent(this, this.config);
|
|
3857
3860
|
await this.agent.initialize(this.llmProvider);
|
|
3858
3861
|
}
|
package/dist/handlers/index.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -235,13 +235,21 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
|
|
|
235
235
|
Object.keys(subItem).forEach((sk) => {
|
|
236
236
|
const subVal = subItem[sk];
|
|
237
237
|
if (typeof subVal !== "object" || subVal === null) {
|
|
238
|
-
|
|
238
|
+
if (typeof subVal === "string" && !isNaN(Number(subVal)) && subVal.trim() !== "") {
|
|
239
|
+
newItem[sk] = Number(subVal);
|
|
240
|
+
} else {
|
|
241
|
+
newItem[sk] = subVal;
|
|
242
|
+
}
|
|
239
243
|
}
|
|
240
244
|
});
|
|
241
245
|
}
|
|
242
246
|
});
|
|
243
247
|
} else if (typeof val !== "object" || val === null) {
|
|
244
|
-
|
|
248
|
+
if (typeof val === "string" && !isNaN(Number(val)) && val.trim() !== "") {
|
|
249
|
+
newItem[k] = Number(val);
|
|
250
|
+
} else {
|
|
251
|
+
newItem[k] = val;
|
|
252
|
+
}
|
|
245
253
|
}
|
|
246
254
|
});
|
|
247
255
|
return newItem;
|
|
@@ -250,7 +258,7 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
|
|
|
250
258
|
if (!data || data.length === 0) {
|
|
251
259
|
return /* @__PURE__ */ import_react4.default.createElement("div", { className: "p-4 text-sm text-slate-500" }, "No data available for chart.");
|
|
252
260
|
}
|
|
253
|
-
const firstItem =
|
|
261
|
+
const firstItem = sanitizedData[0];
|
|
254
262
|
const allKeys = Object.keys(firstItem);
|
|
255
263
|
const resolvedXKey = config.xAxisKey && firstItem[config.xAxisKey] !== void 0 ? config.xAxisKey : allKeys.find((k) => k.toLowerCase() === "name" || k.toLowerCase() === "label" || k.toLowerCase() === "category") || allKeys.find((k) => typeof firstItem[k] === "string") || allKeys[0];
|
|
256
264
|
const resolvedDataKeys = config.dataKeys && config.dataKeys.every((k) => {
|
|
@@ -497,6 +505,10 @@ function MessageBubble({
|
|
|
497
505
|
}, [sources, isUser]);
|
|
498
506
|
const { productsFromContent, cleanContent } = import_react5.default.useMemo(() => {
|
|
499
507
|
if (isUser) return { productsFromContent: [], cleanContent: message.content };
|
|
508
|
+
const isVisualization = message.content.includes("```chart") || message.content.toLowerCase().includes("pie chart") || message.content.toLowerCase().includes("bar chart") || message.content.toLowerCase().includes("distribution");
|
|
509
|
+
if (isVisualization) {
|
|
510
|
+
return { productsFromContent: [], cleanContent: message.content };
|
|
511
|
+
}
|
|
500
512
|
const jsonRegex = /```json\s*([\s\S]*?)\s*```/g;
|
|
501
513
|
const products = [];
|
|
502
514
|
let content = message.content;
|
package/dist/index.mjs
CHANGED
|
@@ -198,13 +198,21 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
|
|
|
198
198
|
Object.keys(subItem).forEach((sk) => {
|
|
199
199
|
const subVal = subItem[sk];
|
|
200
200
|
if (typeof subVal !== "object" || subVal === null) {
|
|
201
|
-
|
|
201
|
+
if (typeof subVal === "string" && !isNaN(Number(subVal)) && subVal.trim() !== "") {
|
|
202
|
+
newItem[sk] = Number(subVal);
|
|
203
|
+
} else {
|
|
204
|
+
newItem[sk] = subVal;
|
|
205
|
+
}
|
|
202
206
|
}
|
|
203
207
|
});
|
|
204
208
|
}
|
|
205
209
|
});
|
|
206
210
|
} else if (typeof val !== "object" || val === null) {
|
|
207
|
-
|
|
211
|
+
if (typeof val === "string" && !isNaN(Number(val)) && val.trim() !== "") {
|
|
212
|
+
newItem[k] = Number(val);
|
|
213
|
+
} else {
|
|
214
|
+
newItem[k] = val;
|
|
215
|
+
}
|
|
208
216
|
}
|
|
209
217
|
});
|
|
210
218
|
return newItem;
|
|
@@ -213,7 +221,7 @@ function DynamicChart({ config, primaryColor = "#6366f1", accentColor = "#8b5cf6
|
|
|
213
221
|
if (!data || data.length === 0) {
|
|
214
222
|
return /* @__PURE__ */ React4.createElement("div", { className: "p-4 text-sm text-slate-500" }, "No data available for chart.");
|
|
215
223
|
}
|
|
216
|
-
const firstItem =
|
|
224
|
+
const firstItem = sanitizedData[0];
|
|
217
225
|
const allKeys = Object.keys(firstItem);
|
|
218
226
|
const resolvedXKey = config.xAxisKey && firstItem[config.xAxisKey] !== void 0 ? config.xAxisKey : allKeys.find((k) => k.toLowerCase() === "name" || k.toLowerCase() === "label" || k.toLowerCase() === "category") || allKeys.find((k) => typeof firstItem[k] === "string") || allKeys[0];
|
|
219
227
|
const resolvedDataKeys = config.dataKeys && config.dataKeys.every((k) => {
|
|
@@ -460,6 +468,10 @@ function MessageBubble({
|
|
|
460
468
|
}, [sources, isUser]);
|
|
461
469
|
const { productsFromContent, cleanContent } = React5.useMemo(() => {
|
|
462
470
|
if (isUser) return { productsFromContent: [], cleanContent: message.content };
|
|
471
|
+
const isVisualization = message.content.includes("```chart") || message.content.toLowerCase().includes("pie chart") || message.content.toLowerCase().includes("bar chart") || message.content.toLowerCase().includes("distribution");
|
|
472
|
+
if (isVisualization) {
|
|
473
|
+
return { productsFromContent: [], cleanContent: message.content };
|
|
474
|
+
}
|
|
463
475
|
const jsonRegex = /```json\s*([\s\S]*?)\s*```/g;
|
|
464
476
|
const products = [];
|
|
465
477
|
let content = message.content;
|
package/dist/server.js
CHANGED
|
@@ -3911,22 +3911,25 @@ var Pipeline = class {
|
|
|
3911
3911
|
this.reranker = new Reranker();
|
|
3912
3912
|
}
|
|
3913
3913
|
async initialize() {
|
|
3914
|
-
var _a;
|
|
3914
|
+
var _a, _b;
|
|
3915
3915
|
if (this.initialised) return;
|
|
3916
|
+
const CHART_MARKER = "<!-- VIZ_PROTOCOL_V1 -->";
|
|
3916
3917
|
const chartInstruction = `
|
|
3917
3918
|
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3919
|
+
${CHART_MARKER}
|
|
3920
|
+
### MASTER DATA VISUALIZATION PROTOCOL:
|
|
3921
|
+
You are an expert data analyst. When asked to visualize or compare data retrieved from the context:
|
|
3922
|
+
1. DECIDE the best chart type: 'pie' (for parts-of-a-whole), 'bar' (for category comparisons), or 'line' (for trends).
|
|
3923
|
+
2. TRANSFORM arbitrary metadata from the context into a FLAT list of objects. Extract relevant keys dynamically.
|
|
3924
|
+
3. OUTPUT a \`\`\`chart\`\`\` code block using this JSON format:
|
|
3922
3925
|
{
|
|
3923
3926
|
"type": "bar" | "line" | "pie",
|
|
3924
|
-
"xAxisKey": "
|
|
3925
|
-
"dataKeys": ["
|
|
3926
|
-
"data": [{ "
|
|
3927
|
+
"xAxisKey": "key_for_labels",
|
|
3928
|
+
"dataKeys": ["key_for_values"],
|
|
3929
|
+
"data": [ { "key_for_labels": "Category A", "key_for_values": 100 }, ... ]
|
|
3927
3930
|
}
|
|
3928
|
-
|
|
3929
|
-
if (!this.config.llm.systemPrompt
|
|
3931
|
+
4. RULES: Valid JSON only. No math. No text-based charts. No markdown tables for graphs. If showing a chart, DO NOT output a "products" JSON carousel.`;
|
|
3932
|
+
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
3930
3933
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
|
|
3931
3934
|
}
|
|
3932
3935
|
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
@@ -3943,7 +3946,7 @@ When requested to show data visually (charts, graphs, or comparisons), you MUST
|
|
|
3943
3946
|
this.entityExtractor = new EntityExtractor(this.llmProvider);
|
|
3944
3947
|
}
|
|
3945
3948
|
await this.vectorDB.initialize();
|
|
3946
|
-
if (((
|
|
3949
|
+
if (((_b = this.config.rag) == null ? void 0 : _b.architecture) === "agentic") {
|
|
3947
3950
|
this.agent = new LangChainAgent(this, this.config);
|
|
3948
3951
|
await this.agent.initialize(this.llmProvider);
|
|
3949
3952
|
}
|
package/dist/server.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.9",
|
|
4
4
|
"description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
|
|
5
5
|
"author": "Abhinav Alkuchi",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,6 +29,8 @@ export function DynamicChart({ config, primaryColor = '#6366f1', accentColor = '
|
|
|
29
29
|
// CRITICAL: Sanitize the data array to remove any non-primitive values (objects/arrays)
|
|
30
30
|
// This prevents the "Objects are not valid as a React child" error in Tooltips/Legends
|
|
31
31
|
// This hook MUST be called before any early returns to satisfy React rules
|
|
32
|
+
// CRITICAL: Sanitize the data array to remove any non-primitive values (objects/arrays)
|
|
33
|
+
// Also ensures numeric strings are converted to actual numbers for Recharts.
|
|
32
34
|
const sanitizedData = React.useMemo(() => {
|
|
33
35
|
if (!data) return [];
|
|
34
36
|
return data.map(item => {
|
|
@@ -36,19 +38,28 @@ export function DynamicChart({ config, primaryColor = '#6366f1', accentColor = '
|
|
|
36
38
|
Object.keys(item).forEach(k => {
|
|
37
39
|
const val = item[k];
|
|
38
40
|
if (Array.isArray(val)) {
|
|
39
|
-
// If the LLM output nested arrays, attempt to flatten them
|
|
40
41
|
val.forEach(subItem => {
|
|
41
42
|
if (typeof subItem === 'object' && subItem !== null) {
|
|
42
43
|
Object.keys(subItem).forEach(sk => {
|
|
43
44
|
const subVal = (subItem as Record<string, unknown>)[sk];
|
|
44
45
|
if (typeof subVal !== 'object' || subVal === null) {
|
|
45
|
-
|
|
46
|
+
// Auto-convert numeric strings
|
|
47
|
+
if (typeof subVal === 'string' && !isNaN(Number(subVal)) && subVal.trim() !== '') {
|
|
48
|
+
newItem[sk] = Number(subVal);
|
|
49
|
+
} else {
|
|
50
|
+
newItem[sk] = subVal as string | number | null;
|
|
51
|
+
}
|
|
46
52
|
}
|
|
47
53
|
});
|
|
48
54
|
}
|
|
49
55
|
});
|
|
50
56
|
} else if (typeof val !== 'object' || val === null) {
|
|
51
|
-
|
|
57
|
+
// Auto-convert numeric strings
|
|
58
|
+
if (typeof val === 'string' && !isNaN(Number(val)) && val.trim() !== '') {
|
|
59
|
+
newItem[k] = Number(val);
|
|
60
|
+
} else {
|
|
61
|
+
newItem[k] = val as string | number | null;
|
|
62
|
+
}
|
|
52
63
|
}
|
|
53
64
|
});
|
|
54
65
|
return newItem;
|
|
@@ -60,7 +71,7 @@ export function DynamicChart({ config, primaryColor = '#6366f1', accentColor = '
|
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
// Smart key resolution: if the provided keys don't exist in the data, try to guess them
|
|
63
|
-
const firstItem =
|
|
74
|
+
const firstItem = sanitizedData[0];
|
|
64
75
|
const allKeys = Object.keys(firstItem);
|
|
65
76
|
|
|
66
77
|
// Resolve xAxisKey: use provided, fallback to 'name', 'label', 'category', or the first string key
|
|
@@ -71,7 +82,6 @@ export function DynamicChart({ config, primaryColor = '#6366f1', accentColor = '
|
|
|
71
82
|
|| allKeys[0];
|
|
72
83
|
|
|
73
84
|
// Resolve dataKeys: use provided, fallback to 'value', 'count', 'amount', or all numeric keys
|
|
74
|
-
// CRITICAL: Filter out any keys that point to objects/arrays to prevent React render errors
|
|
75
85
|
const resolvedDataKeys = config.dataKeys && config.dataKeys.every(k => {
|
|
76
86
|
const val = firstItem[k];
|
|
77
87
|
return val !== undefined && typeof val !== 'object';
|
|
@@ -313,6 +313,17 @@ export function MessageBubble({
|
|
|
313
313
|
const { productsFromContent, cleanContent } = React.useMemo(() => {
|
|
314
314
|
if (isUser) return { productsFromContent: [] as Product[], cleanContent: message.content };
|
|
315
315
|
|
|
316
|
+
// If the message contains a chart or refers to visualization, suppress the product carousel
|
|
317
|
+
const isVisualization =
|
|
318
|
+
message.content.includes('```chart') ||
|
|
319
|
+
message.content.toLowerCase().includes('pie chart') ||
|
|
320
|
+
message.content.toLowerCase().includes('bar chart') ||
|
|
321
|
+
message.content.toLowerCase().includes('distribution');
|
|
322
|
+
|
|
323
|
+
if (isVisualization) {
|
|
324
|
+
return { productsFromContent: [] as Product[], cleanContent: message.content };
|
|
325
|
+
}
|
|
326
|
+
|
|
316
327
|
const jsonRegex = /```json\s*([\s\S]*?)\s*```/g;
|
|
317
328
|
const products: Product[] = [];
|
|
318
329
|
let content = message.content;
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -100,20 +100,23 @@ export class Pipeline {
|
|
|
100
100
|
if (this.initialised) return;
|
|
101
101
|
|
|
102
102
|
// Augment system prompt with a Universal Visualization Protocol
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
// We use a unique marker to ensure this is only injected once but is ALWAYS present.
|
|
104
|
+
const CHART_MARKER = '<!-- VIZ_PROTOCOL_V1 -->';
|
|
105
|
+
const chartInstruction = `\n\n${CHART_MARKER}
|
|
106
|
+
### MASTER DATA VISUALIZATION PROTOCOL:
|
|
107
|
+
You are an expert data analyst. When asked to visualize or compare data retrieved from the context:
|
|
108
|
+
1. DECIDE the best chart type: 'pie' (for parts-of-a-whole), 'bar' (for category comparisons), or 'line' (for trends).
|
|
109
|
+
2. TRANSFORM arbitrary metadata from the context into a FLAT list of objects. Extract relevant keys dynamically.
|
|
110
|
+
3. OUTPUT a \`\`\`chart\`\`\` code block using this JSON format:
|
|
107
111
|
{
|
|
108
112
|
"type": "bar" | "line" | "pie",
|
|
109
|
-
"xAxisKey": "
|
|
110
|
-
"dataKeys": ["
|
|
111
|
-
"data": [{ "
|
|
113
|
+
"xAxisKey": "key_for_labels",
|
|
114
|
+
"dataKeys": ["key_for_values"],
|
|
115
|
+
"data": [ { "key_for_labels": "Category A", "key_for_values": 100 }, ... ]
|
|
112
116
|
}
|
|
113
|
-
|
|
117
|
+
4. RULES: Valid JSON only. No math. No text-based charts. No markdown tables for graphs. If showing a chart, DO NOT output a "products" JSON carousel.`;
|
|
114
118
|
|
|
115
|
-
|
|
116
|
-
if (!this.config.llm.systemPrompt || !this.config.llm.systemPrompt.includes('chart')) {
|
|
119
|
+
if (!this.config.llm.systemPrompt?.includes(CHART_MARKER)) {
|
|
117
120
|
this.config.llm.systemPrompt = (this.config.llm.systemPrompt || '') + chartInstruction;
|
|
118
121
|
}
|
|
119
122
|
|