react-native-nitro-mlx 0.2.2 → 0.3.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/MLXReactNative.podspec +1 -1
- package/ios/Sources/HybridLLM.swift +250 -15
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/llm.js +33 -3
- package/lib/module/llm.js.map +1 -1
- package/lib/module/tool-utils.js +56 -0
- package/lib/module/tool-utils.js.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/llm.d.ts +14 -2
- package/lib/typescript/src/llm.d.ts.map +1 -1
- package/lib/typescript/src/specs/LLM.nitro.d.ts +30 -3
- package/lib/typescript/src/specs/LLM.nitro.d.ts.map +1 -1
- package/lib/typescript/src/tool-utils.d.ts +13 -0
- package/lib/typescript/src/tool-utils.d.ts.map +1 -0
- package/nitrogen/generated/ios/MLXReactNative+autolinking.rb +1 -1
- package/nitrogen/generated/ios/MLXReactNative-Swift-Cxx-Bridge.cpp +34 -1
- package/nitrogen/generated/ios/MLXReactNative-Swift-Cxx-Bridge.hpp +173 -1
- package/nitrogen/generated/ios/MLXReactNative-Swift-Cxx-Umbrella.hpp +8 -1
- package/nitrogen/generated/ios/MLXReactNativeAutolinking.mm +1 -1
- package/nitrogen/generated/ios/MLXReactNativeAutolinking.swift +1 -1
- package/nitrogen/generated/ios/c++/HybridLLMSpecSwift.cpp +1 -1
- package/nitrogen/generated/ios/c++/HybridLLMSpecSwift.hpp +10 -3
- package/nitrogen/generated/ios/c++/HybridModelManagerSpecSwift.cpp +1 -1
- package/nitrogen/generated/ios/c++/HybridModelManagerSpecSwift.hpp +1 -1
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__shared_ptr_AnyMap______std__shared_ptr_AnyMap_.swift +62 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +1 -1
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +1 -1
- package/nitrogen/generated/ios/swift/Func_void_double.swift +1 -1
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +1 -1
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_AnyMap_.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_std__shared_ptr_AnyMap___.swift +67 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +1 -1
- package/nitrogen/generated/ios/swift/Func_void_std__string_std__string.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +1 -1
- package/nitrogen/generated/ios/swift/GenerationStats.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridLLMSpec.swift +2 -2
- package/nitrogen/generated/ios/swift/HybridLLMSpec_cxx.swift +14 -2
- package/nitrogen/generated/ios/swift/HybridModelManagerSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridModelManagerSpec_cxx.swift +1 -1
- package/nitrogen/generated/ios/swift/LLMLoadOptions.swift +44 -2
- package/nitrogen/generated/ios/swift/LLMMessage.swift +1 -1
- package/nitrogen/generated/ios/swift/ToolDefinition.swift +113 -0
- package/nitrogen/generated/ios/swift/ToolParameter.swift +69 -0
- package/nitrogen/generated/shared/c++/GenerationStats.hpp +1 -1
- package/nitrogen/generated/shared/c++/HybridLLMSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridLLMSpec.hpp +2 -2
- package/nitrogen/generated/shared/c++/HybridModelManagerSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridModelManagerSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/LLMLoadOptions.hpp +10 -3
- package/nitrogen/generated/shared/c++/LLMMessage.hpp +1 -1
- package/nitrogen/generated/shared/c++/ToolDefinition.hpp +93 -0
- package/nitrogen/generated/shared/c++/ToolParameter.hpp +87 -0
- package/package.json +8 -7
- package/src/index.ts +10 -3
- package/src/llm.ts +42 -3
- package/src/specs/LLM.nitro.ts +37 -3
- package/src/tool-utils.ts +74 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// ToolParameter.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © 2026 Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#if __has_include(<NitroModules/JSIConverter.hpp>)
|
|
11
|
+
#include <NitroModules/JSIConverter.hpp>
|
|
12
|
+
#else
|
|
13
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
14
|
+
#endif
|
|
15
|
+
#if __has_include(<NitroModules/NitroDefines.hpp>)
|
|
16
|
+
#include <NitroModules/NitroDefines.hpp>
|
|
17
|
+
#else
|
|
18
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
19
|
+
#endif
|
|
20
|
+
#if __has_include(<NitroModules/JSIHelpers.hpp>)
|
|
21
|
+
#include <NitroModules/JSIHelpers.hpp>
|
|
22
|
+
#else
|
|
23
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
#include <string>
|
|
29
|
+
|
|
30
|
+
namespace margelo::nitro::mlxreactnative {
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A struct which can be represented as a JavaScript object (ToolParameter).
|
|
34
|
+
*/
|
|
35
|
+
struct ToolParameter {
|
|
36
|
+
public:
|
|
37
|
+
std::string name SWIFT_PRIVATE;
|
|
38
|
+
std::string type SWIFT_PRIVATE;
|
|
39
|
+
std::string description SWIFT_PRIVATE;
|
|
40
|
+
bool required SWIFT_PRIVATE;
|
|
41
|
+
|
|
42
|
+
public:
|
|
43
|
+
ToolParameter() = default;
|
|
44
|
+
explicit ToolParameter(std::string name, std::string type, std::string description, bool required): name(name), type(type), description(description), required(required) {}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
} // namespace margelo::nitro::mlxreactnative
|
|
48
|
+
|
|
49
|
+
namespace margelo::nitro {
|
|
50
|
+
|
|
51
|
+
// C++ ToolParameter <> JS ToolParameter (object)
|
|
52
|
+
template <>
|
|
53
|
+
struct JSIConverter<margelo::nitro::mlxreactnative::ToolParameter> final {
|
|
54
|
+
static inline margelo::nitro::mlxreactnative::ToolParameter fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
|
|
55
|
+
jsi::Object obj = arg.asObject(runtime);
|
|
56
|
+
return margelo::nitro::mlxreactnative::ToolParameter(
|
|
57
|
+
JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, "name")),
|
|
58
|
+
JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, "type")),
|
|
59
|
+
JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, "description")),
|
|
60
|
+
JSIConverter<bool>::fromJSI(runtime, obj.getProperty(runtime, "required"))
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::mlxreactnative::ToolParameter& arg) {
|
|
64
|
+
jsi::Object obj(runtime);
|
|
65
|
+
obj.setProperty(runtime, "name", JSIConverter<std::string>::toJSI(runtime, arg.name));
|
|
66
|
+
obj.setProperty(runtime, "type", JSIConverter<std::string>::toJSI(runtime, arg.type));
|
|
67
|
+
obj.setProperty(runtime, "description", JSIConverter<std::string>::toJSI(runtime, arg.description));
|
|
68
|
+
obj.setProperty(runtime, "required", JSIConverter<bool>::toJSI(runtime, arg.required));
|
|
69
|
+
return obj;
|
|
70
|
+
}
|
|
71
|
+
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
72
|
+
if (!value.isObject()) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
jsi::Object obj = value.getObject(runtime);
|
|
76
|
+
if (!nitro::isPlainObject(runtime, obj)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, "name"))) return false;
|
|
80
|
+
if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, "type"))) return false;
|
|
81
|
+
if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, "description"))) return false;
|
|
82
|
+
if (!JSIConverter<bool>::canConvert(runtime, obj.getProperty(runtime, "required"))) return false;
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
} // namespace margelo::nitro
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nitro-mlx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Nitro module package",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"module": "./lib/module/index.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"build": "rm -rf lib && bun typecheck && bob build",
|
|
12
12
|
"typecheck": "tsc --noEmit",
|
|
13
13
|
"clean": "rm -rf android/build node_modules/**/android/build lib android/.cxx node_modules/**/android/.cxx",
|
|
14
|
-
"release": "release-it",
|
|
14
|
+
"release": "release-it && npm publish",
|
|
15
15
|
"specs": "bun typecheck && nitrogen --logLevel=\\\"debug\\\" && bun run build",
|
|
16
16
|
"specs:pod": "bun specs && bun --cwd ../example pod"
|
|
17
17
|
},
|
|
@@ -68,13 +68,14 @@
|
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"react": "*",
|
|
70
70
|
"react-native": "*",
|
|
71
|
-
"react-native-nitro-modules": "*"
|
|
71
|
+
"react-native-nitro-modules": "*",
|
|
72
|
+
"zod": ">=4.0.0"
|
|
73
|
+
},
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"zod": "^4.3.5"
|
|
72
76
|
},
|
|
73
77
|
"release-it": {
|
|
74
|
-
"npm":
|
|
75
|
-
"publish": true,
|
|
76
|
-
"skipVersion": true
|
|
77
|
-
},
|
|
78
|
+
"npm": false,
|
|
78
79
|
"github": {
|
|
79
80
|
"release": true,
|
|
80
81
|
"releaseName": "v${version}"
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { LLM, type Message } from './llm'
|
|
1
|
+
export { LLM, type Message, type ToolCallInfo, type ToolCallUpdate } from './llm'
|
|
2
2
|
export { ModelManager } from './modelManager'
|
|
3
3
|
export {
|
|
4
4
|
MLXModel,
|
|
@@ -8,6 +8,13 @@ export {
|
|
|
8
8
|
ModelProvider,
|
|
9
9
|
type ModelQuantization,
|
|
10
10
|
} from './models'
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
export type {
|
|
12
|
+
GenerationStats,
|
|
13
|
+
LLM as LLMSpec,
|
|
14
|
+
LLMLoadOptions,
|
|
15
|
+
ToolDefinition,
|
|
16
|
+
ToolParameter,
|
|
17
|
+
ToolParameterType,
|
|
18
|
+
} from './specs/LLM.nitro'
|
|
13
19
|
export type { ModelManager as ModelManagerSpec } from './specs/ModelManager.nitro'
|
|
20
|
+
export { createTool, type TypeSafeToolDefinition } from './tool-utils'
|
package/src/llm.ts
CHANGED
|
@@ -8,6 +8,16 @@ export type Message = {
|
|
|
8
8
|
content: string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
export type ToolCallInfo = {
|
|
12
|
+
name: string
|
|
13
|
+
arguments: Record<string, unknown>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ToolCallUpdate = {
|
|
17
|
+
toolCall: ToolCallInfo
|
|
18
|
+
allToolCalls: ToolCallInfo[]
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
function getInstance(): LLMSpec {
|
|
12
22
|
if (!instance) {
|
|
13
23
|
instance = NitroModules.createHybridObject<LLMSpec>('LLM')
|
|
@@ -58,13 +68,42 @@ export const LLM = {
|
|
|
58
68
|
},
|
|
59
69
|
|
|
60
70
|
/**
|
|
61
|
-
* Stream a response token by token.
|
|
71
|
+
* Stream a response token by token with optional tool calling support.
|
|
72
|
+
* Tools must be provided when loading the model via `load()` options.
|
|
73
|
+
* Tools are automatically executed when the model calls them.
|
|
62
74
|
* @param prompt - The input text to generate a response for
|
|
63
75
|
* @param onToken - Callback invoked for each generated token
|
|
76
|
+
* @param onToolCall - Optional callback invoked when a tool is called.
|
|
77
|
+
* Receives the current tool call and an accumulated array of all tool calls so far.
|
|
64
78
|
* @returns The complete generated text
|
|
65
79
|
*/
|
|
66
|
-
stream(
|
|
67
|
-
|
|
80
|
+
stream(
|
|
81
|
+
prompt: string,
|
|
82
|
+
onToken: (token: string) => void,
|
|
83
|
+
onToolCall?: (update: ToolCallUpdate) => void,
|
|
84
|
+
): Promise<string> {
|
|
85
|
+
const accumulatedToolCalls: ToolCallInfo[] = []
|
|
86
|
+
|
|
87
|
+
return getInstance().stream(prompt, onToken, (name: string, argsJson: string) => {
|
|
88
|
+
if (onToolCall) {
|
|
89
|
+
try {
|
|
90
|
+
const args = JSON.parse(argsJson) as Record<string, unknown>
|
|
91
|
+
const toolCall = { name, arguments: args }
|
|
92
|
+
accumulatedToolCalls.push(toolCall)
|
|
93
|
+
onToolCall({
|
|
94
|
+
toolCall,
|
|
95
|
+
allToolCalls: [...accumulatedToolCalls],
|
|
96
|
+
})
|
|
97
|
+
} catch {
|
|
98
|
+
const toolCall = { name, arguments: {} }
|
|
99
|
+
accumulatedToolCalls.push(toolCall)
|
|
100
|
+
onToolCall({
|
|
101
|
+
toolCall,
|
|
102
|
+
allToolCalls: [...accumulatedToolCalls],
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
})
|
|
68
107
|
},
|
|
69
108
|
|
|
70
109
|
/**
|
package/src/specs/LLM.nitro.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HybridObject } from 'react-native-nitro-modules'
|
|
1
|
+
import type { AnyMap, HybridObject } from 'react-native-nitro-modules'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Statistics from the last text generation.
|
|
@@ -19,6 +19,26 @@ export interface LLMMessage {
|
|
|
19
19
|
content: string
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Parameter definition for a tool.
|
|
24
|
+
*/
|
|
25
|
+
export interface ToolParameter {
|
|
26
|
+
name: string
|
|
27
|
+
type: string
|
|
28
|
+
description: string
|
|
29
|
+
required: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Tool definition that can be called by the model.
|
|
34
|
+
*/
|
|
35
|
+
export interface ToolDefinition {
|
|
36
|
+
name: string
|
|
37
|
+
description: string
|
|
38
|
+
parameters: ToolParameter[]
|
|
39
|
+
handler: (args: AnyMap) => Promise<AnyMap>
|
|
40
|
+
}
|
|
41
|
+
|
|
22
42
|
/** Options for loading a model.
|
|
23
43
|
*/
|
|
24
44
|
export interface LLMLoadOptions {
|
|
@@ -28,6 +48,8 @@ export interface LLMLoadOptions {
|
|
|
28
48
|
additionalContext?: LLMMessage[]
|
|
29
49
|
/** Whether to automatically manage message history */
|
|
30
50
|
manageHistory?: boolean
|
|
51
|
+
/** Tools available for the model to call */
|
|
52
|
+
tools?: ToolDefinition[]
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
/**
|
|
@@ -50,12 +72,18 @@ export interface LLM extends HybridObject<{ ios: 'swift' }> {
|
|
|
50
72
|
generate(prompt: string): Promise<string>
|
|
51
73
|
|
|
52
74
|
/**
|
|
53
|
-
* Stream a response token by token.
|
|
75
|
+
* Stream a response token by token with optional tool calling support.
|
|
76
|
+
* Tools are automatically executed when the model calls them.
|
|
54
77
|
* @param prompt - The input text to generate a response for
|
|
55
78
|
* @param onToken - Callback invoked for each generated token
|
|
79
|
+
* @param onToolCall - Optional callback invoked when a tool is called (for UI feedback)
|
|
56
80
|
* @returns The complete generated text
|
|
57
81
|
*/
|
|
58
|
-
stream(
|
|
82
|
+
stream(
|
|
83
|
+
prompt: string,
|
|
84
|
+
onToken: (token: string) => void,
|
|
85
|
+
onToolCall?: (toolName: string, args: string) => void,
|
|
86
|
+
): Promise<string>
|
|
59
87
|
|
|
60
88
|
/**
|
|
61
89
|
* Stop the current generation.
|
|
@@ -96,3 +124,9 @@ export interface LLM extends HybridObject<{ ios: 'swift' }> {
|
|
|
96
124
|
/** System prompt used when loading the model */
|
|
97
125
|
systemPrompt: string
|
|
98
126
|
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Supported parameter types for tool definitions.
|
|
130
|
+
* Used for type safety in createTool().
|
|
131
|
+
*/
|
|
132
|
+
export type ToolParameterType = 'string' | 'number' | 'boolean' | 'array' | 'object'
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { AnyMap } from 'react-native-nitro-modules'
|
|
2
|
+
import type { z } from 'zod'
|
|
3
|
+
import type { ToolDefinition, ToolParameter, ToolParameterType } from './specs/LLM.nitro'
|
|
4
|
+
|
|
5
|
+
type ZodObjectSchema = z.ZodObject<z.core.$ZodShape>
|
|
6
|
+
type InferArgs<T extends ZodObjectSchema> = z.infer<T>
|
|
7
|
+
|
|
8
|
+
export interface TypeSafeToolDefinition<T extends ZodObjectSchema> {
|
|
9
|
+
name: string
|
|
10
|
+
description: string
|
|
11
|
+
arguments: T
|
|
12
|
+
handler: (args: InferArgs<T>) => Promise<Record<string, unknown>>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getZodTypeString(zodType: z.ZodType): ToolParameterType {
|
|
16
|
+
const typeName = zodType._zod.def.type
|
|
17
|
+
switch (typeName) {
|
|
18
|
+
case 'string':
|
|
19
|
+
return 'string'
|
|
20
|
+
case 'number':
|
|
21
|
+
case 'int':
|
|
22
|
+
return 'number'
|
|
23
|
+
case 'boolean':
|
|
24
|
+
return 'boolean'
|
|
25
|
+
case 'array':
|
|
26
|
+
return 'array'
|
|
27
|
+
case 'object':
|
|
28
|
+
return 'object'
|
|
29
|
+
case 'optional':
|
|
30
|
+
return getZodTypeString((zodType as z.ZodOptional<z.ZodType>)._zod.def.innerType)
|
|
31
|
+
case 'default':
|
|
32
|
+
return getZodTypeString((zodType as z.ZodDefault<z.ZodType>)._zod.def.innerType)
|
|
33
|
+
default:
|
|
34
|
+
return 'string'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isZodOptional(zodType: z.ZodType): boolean {
|
|
39
|
+
const typeName = zodType._zod.def.type
|
|
40
|
+
return typeName === 'optional' || typeName === 'default'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function zodSchemaToParameters(schema: ZodObjectSchema): ToolParameter[] {
|
|
44
|
+
const shape = schema._zod.def.shape
|
|
45
|
+
const parameters: ToolParameter[] = []
|
|
46
|
+
|
|
47
|
+
for (const [key, zodType] of Object.entries(shape)) {
|
|
48
|
+
const zType = zodType as z.ZodType
|
|
49
|
+
parameters.push({
|
|
50
|
+
name: key,
|
|
51
|
+
type: getZodTypeString(zType),
|
|
52
|
+
description: zType.description ?? '',
|
|
53
|
+
required: !isZodOptional(zType),
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return parameters
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createTool<T extends ZodObjectSchema>(
|
|
61
|
+
definition: TypeSafeToolDefinition<T>,
|
|
62
|
+
): ToolDefinition {
|
|
63
|
+
return {
|
|
64
|
+
name: definition.name,
|
|
65
|
+
description: definition.description,
|
|
66
|
+
parameters: zodSchemaToParameters(definition.arguments),
|
|
67
|
+
handler: async (args: AnyMap) => {
|
|
68
|
+
const argsObj = args as unknown as Record<string, unknown>
|
|
69
|
+
const parsedArgs = definition.arguments.parse(argsObj)
|
|
70
|
+
const result = await definition.handler(parsedArgs)
|
|
71
|
+
return result as unknown as AnyMap
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
}
|