chat-agent-toolkit 1.2.1
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/README.md +234 -0
- package/package.json +101 -0
- package/src/config/config-manager.ts +319 -0
- package/src/config/config-types.ts +65 -0
- package/src/config/environment-variables.ts +8 -0
- package/src/config/index.ts +26 -0
- package/src/config/language-models-database.ts +1426 -0
- package/src/config/mcp-server-registry.ts +24 -0
- package/src/config/model-registry.ts +256 -0
- package/src/config/model-tester.ts +211 -0
- package/src/config/model-utils.ts +193 -0
- package/src/config/provider-ui-config.ts +196 -0
- package/src/connectors/composio.json +154 -0
- package/src/index.ts +22 -0
- package/src/memory/ARCHITECTURE.md +302 -0
- package/src/memory/MASTRA_INTEGRATION.md +106 -0
- package/src/memory/README.md +224 -0
- package/src/memory/agent-memory-manager.ts +416 -0
- package/src/memory/example.ts +343 -0
- package/src/memory/index.ts +47 -0
- package/src/memory/mastra-integration.ts +609 -0
- package/src/memory/storage/drizzle-storage.ts +205 -0
- package/src/memory/storage/in-memory-storage.ts +551 -0
- package/src/memory/storage/storage-interface.ts +68 -0
- package/src/memory/types.ts +125 -0
- package/src/models/providers.ts +5 -0
- package/src/models/registry.ts +4 -0
- package/src/models/types.ts +4 -0
- package/src/search.ts +5 -0
- package/src/tools/composio-mastra.ts +305 -0
- package/src/tools/composio-mcp.ts +205 -0
- package/src/tools/index.ts +13 -0
- package/src/tools/qwksearch-api-tools.ts +327 -0
- package/src/tools/search/doc-utils.ts +75 -0
- package/src/tools/search/document.ts +47 -0
- package/src/tools/search/index.ts +13 -0
- package/src/tools/search/link-summarizer.ts +112 -0
- package/src/tools/search/meta-search-types.ts +62 -0
- package/src/tools/search/metaSearchAgent.ts +454 -0
- package/src/tools/search/search-handlers.ts +85 -0
- package/src/tools/search/suggestionGeneratorAgent.ts +54 -0
- package/src/types.d.ts +137 -0
- package/src/utils/README.md +98 -0
- package/src/utils/chat-helpers.ts +18 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/markdown-to-html.ts +53 -0
- package/src/utils/outputParser.ts +73 -0
- package/src/utils/provider-image-cropper.ts +130 -0
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Global Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Shared TypeScript types for UI configuration, model providers,
|
|
5
|
+
* and MCP server configuration used throughout the toolkit.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Model } from "./config/config-types";
|
|
9
|
+
|
|
10
|
+
type BaseUIConfigField = {
|
|
11
|
+
name: string;
|
|
12
|
+
key: string;
|
|
13
|
+
required: boolean;
|
|
14
|
+
description: string;
|
|
15
|
+
scope: "client" | "server";
|
|
16
|
+
env?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type StringUIConfigField = BaseUIConfigField & {
|
|
20
|
+
type: "string";
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
default?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type SelectUIConfigFieldOptions = {
|
|
26
|
+
name: string;
|
|
27
|
+
value: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type SelectUIConfigField = BaseUIConfigField & {
|
|
31
|
+
type: "select";
|
|
32
|
+
default?: string;
|
|
33
|
+
options: SelectUIConfigFieldOptions[];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type PasswordUIConfigField = BaseUIConfigField & {
|
|
37
|
+
type: "password";
|
|
38
|
+
placeholder?: string;
|
|
39
|
+
default?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type TextareaUIConfigField = BaseUIConfigField & {
|
|
43
|
+
type: "textarea";
|
|
44
|
+
placeholder?: string;
|
|
45
|
+
default?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type SwitchUIConfigField = BaseUIConfigField & {
|
|
49
|
+
type: "switch";
|
|
50
|
+
default?: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type UIConfigField =
|
|
54
|
+
| StringUIConfigField
|
|
55
|
+
| SelectUIConfigField
|
|
56
|
+
| PasswordUIConfigField
|
|
57
|
+
| TextareaUIConfigField
|
|
58
|
+
| SwitchUIConfigField;
|
|
59
|
+
|
|
60
|
+
type ConfigModelProvider = {
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
type: string;
|
|
64
|
+
chatModels: Model[];
|
|
65
|
+
config: { [key: string]: any };
|
|
66
|
+
hash: string;
|
|
67
|
+
isEnvBased?: boolean; // True if provider was created from environment variables (global keys)
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type MCPServerConfig = {
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
type: string;
|
|
74
|
+
url?: string;
|
|
75
|
+
apiKey?: string;
|
|
76
|
+
config: { [key: string]: any };
|
|
77
|
+
enabled: boolean;
|
|
78
|
+
hash: string;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
type Config = {
|
|
82
|
+
version: number;
|
|
83
|
+
setupComplete: boolean;
|
|
84
|
+
preferences: {
|
|
85
|
+
[key: string]: any;
|
|
86
|
+
};
|
|
87
|
+
personalization: {
|
|
88
|
+
[key: string]: any;
|
|
89
|
+
};
|
|
90
|
+
modelProviders: ConfigModelProvider[];
|
|
91
|
+
mcpServers: MCPServerConfig[];
|
|
92
|
+
search: {
|
|
93
|
+
[key: string]: any;
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
type EnvMap = {
|
|
98
|
+
[key: string]: {
|
|
99
|
+
fieldKey: string;
|
|
100
|
+
providerKey: string;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
type ModelProviderUISection = {
|
|
105
|
+
name: string;
|
|
106
|
+
key: string;
|
|
107
|
+
fields: UIConfigField[];
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
type MCPServerUISection = {
|
|
111
|
+
name: string;
|
|
112
|
+
key: string;
|
|
113
|
+
fields: UIConfigField[];
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
type UIConfigSections = {
|
|
117
|
+
preferences: UIConfigField[];
|
|
118
|
+
personalization: UIConfigField[];
|
|
119
|
+
modelProviders: ModelProviderUISection[];
|
|
120
|
+
mcpServers: MCPServerUISection[];
|
|
121
|
+
search: UIConfigField[];
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export type {
|
|
125
|
+
UIConfigField,
|
|
126
|
+
Config,
|
|
127
|
+
EnvMap,
|
|
128
|
+
UIConfigSections,
|
|
129
|
+
SelectUIConfigField,
|
|
130
|
+
StringUIConfigField,
|
|
131
|
+
ModelProviderUISection,
|
|
132
|
+
MCPServerUISection,
|
|
133
|
+
ConfigModelProvider,
|
|
134
|
+
MCPServerConfig,
|
|
135
|
+
TextareaUIConfigField,
|
|
136
|
+
SwitchUIConfigField,
|
|
137
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Provider Image Cropper
|
|
2
|
+
|
|
3
|
+
Utility for extracting individual provider logos from a sprite sheet.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
### Basic Usage
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { cropProvider, cropProviderAsDataURL, getProviderImage } from 'agent-toolkit';
|
|
11
|
+
|
|
12
|
+
// Method 1: Load image first, then crop
|
|
13
|
+
const img = new Image();
|
|
14
|
+
img.src = '/providers-sprite.png';
|
|
15
|
+
await img.decode();
|
|
16
|
+
const canvas = await cropProvider(img, 'openai');
|
|
17
|
+
|
|
18
|
+
// Method 2: Get as data URL (for React/Vue components)
|
|
19
|
+
const dataUrl = await cropProviderAsDataURL(img, 'anthropic');
|
|
20
|
+
|
|
21
|
+
// Method 3: Load and crop in one step
|
|
22
|
+
const canvas = await getProviderImage('/providers-sprite.png', 'gemini');
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### React Component Example
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { useEffect, useState } from 'react';
|
|
29
|
+
import { cropProviderAsDataURL, Provider } from 'agent-toolkit';
|
|
30
|
+
|
|
31
|
+
function ProviderIcon({ providerType }: { providerType: Provider }) {
|
|
32
|
+
const [iconUrl, setIconUrl] = useState<string | null>(null);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const loadIcon = async () => {
|
|
36
|
+
const img = new Image();
|
|
37
|
+
img.src = '/providers-sprite.png';
|
|
38
|
+
await img.decode();
|
|
39
|
+
const url = await cropProviderAsDataURL(img, providerType);
|
|
40
|
+
setIconUrl(url);
|
|
41
|
+
};
|
|
42
|
+
loadIcon();
|
|
43
|
+
}, [providerType]);
|
|
44
|
+
|
|
45
|
+
return iconUrl ? <img src={iconUrl} alt={providerType} /> : null;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Sprite Sheet Layout
|
|
50
|
+
|
|
51
|
+
The sprite sheet must be organized in a 6×4 grid (6 columns, 4 rows) with the following layout:
|
|
52
|
+
|
|
53
|
+
| Row 0 | Col 0 | Col 1 | Col 2 | Col 3 | Col 4 | Col 5 |
|
|
54
|
+
|-------|-------|-------|-------|-------|-------|-------|
|
|
55
|
+
| **0** | openrouter | tongyi | ollama | huggingface | localai | openllm |
|
|
56
|
+
| **1** | zhipu | replicate | azure | anthropic | groq | sagemaker |
|
|
57
|
+
| **2** | 01ai | bedrock | openai | cohere | together | xorbits |
|
|
58
|
+
| **3** | wenxin | moonshot | gemini | mistral | jina | chatglm |
|
|
59
|
+
|
|
60
|
+
## Available Providers
|
|
61
|
+
|
|
62
|
+
Use `getProviderNames()` to get all supported providers:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { getProviderNames } from 'agent-toolkit';
|
|
66
|
+
|
|
67
|
+
const providers = getProviderNames();
|
|
68
|
+
// ['openrouter', 'tongyi', 'ollama', 'huggingface', ...]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## API Reference
|
|
72
|
+
|
|
73
|
+
### `cropProvider(image, provider)`
|
|
74
|
+
Returns a canvas element with the cropped provider logo.
|
|
75
|
+
|
|
76
|
+
### `cropProviderAsBlob(image, provider, type?, quality?)`
|
|
77
|
+
Returns a Blob of the cropped logo. Useful for file uploads.
|
|
78
|
+
|
|
79
|
+
### `cropProviderAsDataURL(image, provider, type?, quality?)`
|
|
80
|
+
Returns a data URL string. Best for inline images in HTML/React.
|
|
81
|
+
|
|
82
|
+
### `getProviderImage(spriteSheetUrl, provider)`
|
|
83
|
+
Loads the sprite sheet and returns the cropped canvas in one call.
|
|
84
|
+
|
|
85
|
+
### `getProviderNames()`
|
|
86
|
+
Returns an array of all supported provider names.
|
|
87
|
+
|
|
88
|
+
## Creating a Sprite Sheet
|
|
89
|
+
|
|
90
|
+
To create a compatible sprite sheet:
|
|
91
|
+
|
|
92
|
+
1. Collect 24 provider logos (one for each provider)
|
|
93
|
+
2. Arrange them in a 6×4 grid following the layout above
|
|
94
|
+
3. Ensure all logos are the same size
|
|
95
|
+
4. Export as PNG with transparency
|
|
96
|
+
5. Save to `public/images/providers-sprite.png`
|
|
97
|
+
|
|
98
|
+
Recommended tile size: 128×128px or 256×256px per provider.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat history formatting utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
type ChatHistoryMessage = {
|
|
6
|
+
content?: unknown;
|
|
7
|
+
role?: string;
|
|
8
|
+
type?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const formatChatHistoryAsString = (history: ChatHistoryMessage[]) => {
|
|
12
|
+
return history
|
|
13
|
+
.map(
|
|
14
|
+
(message) =>
|
|
15
|
+
`${message.role === 'assistant' || message.type === 'ai' ? 'AI' : 'User'}: ${String(message.content ?? '')}`,
|
|
16
|
+
)
|
|
17
|
+
.join('\n');
|
|
18
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module research/agents/markdown-to-html
|
|
3
|
+
* @description Converts Markdown to HTML with Highlight.js syntax highlighting.
|
|
4
|
+
*/
|
|
5
|
+
import { marked } from "marked";
|
|
6
|
+
import hljs from "highlight.js";
|
|
7
|
+
import { encode, decode } from "html-entities";
|
|
8
|
+
|
|
9
|
+
// Inline onclick: self-contained per button, works in dangerouslySetInnerHTML contexts
|
|
10
|
+
const COPY_ONCLICK = [
|
|
11
|
+
"(function(b){",
|
|
12
|
+
"var c=b.closest('figure.code-block')?.querySelector('pre code');",
|
|
13
|
+
"if(!c)return;",
|
|
14
|
+
"navigator.clipboard.writeText(c.innerText).then(function(){",
|
|
15
|
+
"var t=b.textContent;b.textContent='Copied!';",
|
|
16
|
+
"setTimeout(function(){b.textContent=t},2000)",
|
|
17
|
+
"})",
|
|
18
|
+
"})(this)",
|
|
19
|
+
].join("");
|
|
20
|
+
|
|
21
|
+
// Configure marked once at module load \u2014 stacking use() calls would duplicate extensions
|
|
22
|
+
marked.use({ breaks: true, gfm: true, async: true });
|
|
23
|
+
|
|
24
|
+
marked.use({
|
|
25
|
+
renderer: {
|
|
26
|
+
code({ text, lang }) {
|
|
27
|
+
const language = lang || "plaintext";
|
|
28
|
+
let highlighted: string;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
if (language && hljs.getLanguage(language)) {
|
|
32
|
+
highlighted = hljs.highlight(text, { language }).value;
|
|
33
|
+
} else {
|
|
34
|
+
highlighted = hljs.highlightAuto(text).value;
|
|
35
|
+
}
|
|
36
|
+
} catch (e) {
|
|
37
|
+
highlighted = encode(text);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return `<figure class="code-block"><button class="code-copy-btn" type="button" onclick="${COPY_ONCLICK}">Copy</button><pre><code class="hljs language-${language}">${highlighted}</code></pre></figure>`;
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Convert markdown text to HTML with Highlight.js syntax highlighting.
|
|
47
|
+
* Unescapes HTML entities like `&` \u2192 `&`.
|
|
48
|
+
*/
|
|
49
|
+
export async function convertMarkdownToHTMLEscaped(
|
|
50
|
+
markdown: string,
|
|
51
|
+
): Promise<string> {
|
|
52
|
+
return (await marked.parse(decode(markdown))).trim();
|
|
53
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module agent-toolkit/utils/outputParser
|
|
3
|
+
* @description Parsers that extract values from XML-tagged sections of LLM
|
|
4
|
+
* output (e.g. `<links>...</links>`, `<question>...</question>`).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const LIST_MARKER_REGEX = /^(\s*(-|\*|\d+\.\s|\d+\)\s|•)\s*)+/;
|
|
8
|
+
|
|
9
|
+
interface LineListOutputParserArgs {
|
|
10
|
+
key?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class LineListOutputParser {
|
|
14
|
+
private key = "questions";
|
|
15
|
+
|
|
16
|
+
constructor(args?: LineListOutputParserArgs) {
|
|
17
|
+
this.key = args?.key ?? this.key;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async parse(text: string): Promise<string[]> {
|
|
21
|
+
text = text.trim() || "";
|
|
22
|
+
|
|
23
|
+
const startKeyIndex = text.indexOf(`<${this.key}>`);
|
|
24
|
+
const endKeyIndex = text.indexOf(`</${this.key}>`);
|
|
25
|
+
|
|
26
|
+
if (startKeyIndex === -1 || endKeyIndex === -1) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const questionsStartIndex = startKeyIndex + `<${this.key}>`.length;
|
|
31
|
+
const lines = text
|
|
32
|
+
.slice(questionsStartIndex, endKeyIndex)
|
|
33
|
+
.trim()
|
|
34
|
+
.split("\n")
|
|
35
|
+
.filter((line) => line.trim() !== "")
|
|
36
|
+
.map((line) => line.replace(LIST_MARKER_REGEX, ""));
|
|
37
|
+
|
|
38
|
+
return lines;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface LineOutputParserArgs {
|
|
43
|
+
key?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class LineOutputParser {
|
|
47
|
+
private key = "questions";
|
|
48
|
+
|
|
49
|
+
constructor(args?: LineOutputParserArgs) {
|
|
50
|
+
this.key = args?.key ?? this.key;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async parse(text: string): Promise<string | undefined> {
|
|
54
|
+
text = text.trim() || "";
|
|
55
|
+
|
|
56
|
+
const startKeyIndex = text.indexOf(`<${this.key}>`);
|
|
57
|
+
const endKeyIndex = text.indexOf(`</${this.key}>`);
|
|
58
|
+
|
|
59
|
+
if (startKeyIndex === -1 || endKeyIndex === -1) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const questionsStartIndex = startKeyIndex + `<${this.key}>`.length;
|
|
64
|
+
const line = text
|
|
65
|
+
.slice(questionsStartIndex, endKeyIndex)
|
|
66
|
+
.trim()
|
|
67
|
+
.replace(LIST_MARKER_REGEX, "");
|
|
68
|
+
|
|
69
|
+
return line;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default LineOutputParser;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const PROVIDERS = {
|
|
2
|
+
openrouter: { row: 0, col: 0 },
|
|
3
|
+
tongyi: { row: 0, col: 1 },
|
|
4
|
+
ollama: { row: 0, col: 2 },
|
|
5
|
+
huggingface: { row: 0, col: 3 },
|
|
6
|
+
localai: { row: 0, col: 4 },
|
|
7
|
+
openllm: { row: 0, col: 5 },
|
|
8
|
+
zhipu: { row: 1, col: 0 },
|
|
9
|
+
replicate: { row: 1, col: 1 },
|
|
10
|
+
azure: { row: 1, col: 2 },
|
|
11
|
+
anthropic: { row: 1, col: 3 },
|
|
12
|
+
groq: { row: 1, col: 4 },
|
|
13
|
+
sagemaker: { row: 1, col: 5 },
|
|
14
|
+
"01ai": { row: 2, col: 0 },
|
|
15
|
+
bedrock: { row: 2, col: 1 },
|
|
16
|
+
openai: { row: 2, col: 2 },
|
|
17
|
+
cohere: { row: 2, col: 3 },
|
|
18
|
+
together: { row: 2, col: 4 },
|
|
19
|
+
xorbits: { row: 2, col: 5 },
|
|
20
|
+
wenxin: { row: 3, col: 0 },
|
|
21
|
+
moonshot: { row: 3, col: 1 },
|
|
22
|
+
gemini: { row: 3, col: 2 },
|
|
23
|
+
mistral: { row: 3, col: 3 },
|
|
24
|
+
jina: { row: 3, col: 4 },
|
|
25
|
+
chatglm: { row: 3, col: 5 },
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
export type Provider = keyof typeof PROVIDERS;
|
|
29
|
+
|
|
30
|
+
const COLS = 6;
|
|
31
|
+
const ROWS = 4;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Returns a cropped canvas containing just the provider box.
|
|
35
|
+
*/
|
|
36
|
+
export async function cropProvider(
|
|
37
|
+
image: HTMLImageElement | ImageBitmap,
|
|
38
|
+
provider: Provider
|
|
39
|
+
): Promise<HTMLCanvasElement> {
|
|
40
|
+
if (!(provider in PROVIDERS)) {
|
|
41
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { row, col } = PROVIDERS[provider];
|
|
45
|
+
|
|
46
|
+
const tileWidth = image.width / COLS;
|
|
47
|
+
const tileHeight = image.height / ROWS;
|
|
48
|
+
|
|
49
|
+
const canvas = document.createElement("canvas");
|
|
50
|
+
canvas.width = tileWidth;
|
|
51
|
+
canvas.height = tileHeight;
|
|
52
|
+
|
|
53
|
+
const ctx = canvas.getContext("2d");
|
|
54
|
+
if (!ctx) {
|
|
55
|
+
throw new Error("Failed to get 2D context");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
ctx.drawImage(
|
|
59
|
+
image,
|
|
60
|
+
col * tileWidth,
|
|
61
|
+
row * tileHeight,
|
|
62
|
+
tileWidth,
|
|
63
|
+
tileHeight,
|
|
64
|
+
0,
|
|
65
|
+
0,
|
|
66
|
+
tileWidth,
|
|
67
|
+
tileHeight
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return canvas;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Returns the provider image as a Blob.
|
|
75
|
+
*/
|
|
76
|
+
export async function cropProviderAsBlob(
|
|
77
|
+
image: HTMLImageElement | ImageBitmap,
|
|
78
|
+
provider: Provider,
|
|
79
|
+
type: "image/png" | "image/jpeg" | "image/webp" = "image/png",
|
|
80
|
+
quality?: number
|
|
81
|
+
): Promise<Blob> {
|
|
82
|
+
const canvas = await cropProvider(image, provider);
|
|
83
|
+
|
|
84
|
+
return new Promise<Blob>((resolve, reject) => {
|
|
85
|
+
canvas.toBlob(
|
|
86
|
+
(blob) => {
|
|
87
|
+
if (blob) {
|
|
88
|
+
resolve(blob);
|
|
89
|
+
} else {
|
|
90
|
+
reject(new Error("Failed to create blob"));
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
type,
|
|
94
|
+
quality
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Returns the provider image as a data URL.
|
|
101
|
+
*/
|
|
102
|
+
export async function cropProviderAsDataURL(
|
|
103
|
+
image: HTMLImageElement | ImageBitmap,
|
|
104
|
+
provider: Provider,
|
|
105
|
+
type: "image/png" | "image/jpeg" | "image/webp" = "image/png",
|
|
106
|
+
quality?: number
|
|
107
|
+
): Promise<string> {
|
|
108
|
+
const canvas = await cropProvider(image, provider);
|
|
109
|
+
return canvas.toDataURL(type, quality);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Helper to load and crop in one call.
|
|
114
|
+
*/
|
|
115
|
+
export async function getProviderImage(
|
|
116
|
+
spriteSheetUrl: string,
|
|
117
|
+
provider: Provider
|
|
118
|
+
): Promise<HTMLCanvasElement> {
|
|
119
|
+
const img = new Image();
|
|
120
|
+
img.src = spriteSheetUrl;
|
|
121
|
+
await img.decode();
|
|
122
|
+
return cropProvider(img, provider);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get all available provider names.
|
|
127
|
+
*/
|
|
128
|
+
export function getProviderNames(): Provider[] {
|
|
129
|
+
return Object.keys(PROVIDERS) as Provider[];
|
|
130
|
+
}
|