@rhizomatics/signalk-einklabel-genai-plugin 0.1.0 → 0.1.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/CHANGELOG.md +23 -0
- package/README.md +46 -24
- package/dist/cliExtension.js +12 -5
- package/dist/config.d.ts +4 -2
- package/dist/llmGateway.d.ts +9 -9
- package/dist/llmGateway.js +14 -10
- package/dist/llmProviderRegistry.d.ts +2 -1
- package/dist/llmProviderRegistry.js +2 -1
- package/dist/plugin.js +7 -6
- package/dist/templateProvider.js +3 -3
- package/docs/assets/icons/icon_tmp.png +0 -0
- package/docs/assets/screenshots/example_weather_openai.png +0 -0
- package/package.json +21 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# [1.0.1]
|
|
2
|
+
|
|
3
|
+
- Packaging fixes
|
|
4
|
+
- Simplified adding GenAI support to `esl-cli`
|
|
5
|
+
- Added `openrouter` as the default built-in AI provider
|
|
6
|
+
- Other providers are available by `npm install`
|
|
7
|
+
|
|
8
|
+
# [1.0.0]
|
|
9
|
+
|
|
10
|
+
- Initial release with support for major cloud GenAI services and local LLMs
|
|
11
|
+
- As an alternative to a template, an image can be generated by an LLM based on customized prompt
|
|
12
|
+
- Prompt will automatically include details of the ESL, including available colours, height, width, manufacturer and locally supported fonts
|
|
13
|
+
- A marine forecast prompt is provided as an example
|
|
14
|
+
- It automatically includes vessel length, type and position.
|
|
15
|
+
- The [Vercel AI SDK](https://ai-sdk.dev) is used, so a wide range of [providers](https://ai-sdk.dev/providers/ai-sdk-providers) are supported by the framework, although only a handful of the major ones imported to minimze bloat. These are all optional dependencies, so the plugin will not fail to load if they are unavailable, and they can be omitted if installing directly using `npm`
|
|
16
|
+
- [Anthropic](https://ai-sdk.dev/providers/ai-sdk-providers/anthropic) (Claude)
|
|
17
|
+
- [Google](https://ai-sdk.dev/providers/ai-sdk-providers/google) (Gemini)
|
|
18
|
+
- [OpenAI](https://ai-sdk.dev/providers/ai-sdk-providers/openai) (ChatGPT)
|
|
19
|
+
- [xAI](https://ai-sdk.dev/providers/ai-sdk-providers/xai) (Grok)
|
|
20
|
+
- [Moonshot AI](https://ai-sdk.dev/providers/ai-sdk-providers/moonshotai) (Kimi)
|
|
21
|
+
- [DeepSeek](https://ai-sdk.dev/providers/ai-sdk-providers/deepseek)
|
|
22
|
+
- [Ollama](https://ai-sdk.dev/providers/community-providers/ollama) - local and cloud using the offical API
|
|
23
|
+
- [OpenAI Compatible](https://ai-sdk.dev/providers/openai-compatible-providers) - including local LM Studio
|
package/README.md
CHANGED
|
@@ -2,21 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
A companion plugin for [`@rhizomatics/signalk-einklabel-plugin`](https://github.com/rhizomatics/signalk-einklabel-plugin) that generates a device's content from an LLM prompt, as an alternative to a hand-authored SVG template.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
By default it uses [OpenRouter](https://openrouter.ai), however it can use any of the [Vercel AI SDK providers](https://ai-sdk.dev/providers/ai-sdk-providers) directly, including local LLMs like Ollama.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Working Example
|
|
8
8
|
|
|
9
|
-
This plugin
|
|
9
|
+
This plugin comes with a marine weather forecast example prompt. When installed, and SignalK server restarted, you should see this appear as a template to choose in the main eInk Label plugin.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- All LLM provider/model/API-key configuration lives in **this** plugin's own config screen - the core plugin knows nothing about any of that.
|
|
13
|
-
- If the LLM call fails (network/API error, exhausted retries) or its response isn't a renderable SVG, the **core** plugin pushes its own generic fallback-warning template instead of leaving the previous, possibly now-wrong, content on screen - the same safety net a broken hand-authored template gets. The next scheduled repaint retries automatically.
|
|
11
|
+
### Customizing Prompts
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
Prompts have the same template syntax as the SVG images, so the weather forecast will give the current GPS position, and the length and type of boat.
|
|
14
|
+
|
|
15
|
+
All prompts also get a generated appendix to the prompt that gives details about the eInk label, including manufacturer, name, pixel height and width, colour range, and the description provided in the main plugin.
|
|
16
|
+
|
|
17
|
+
### Full Example
|
|
18
|
+
|
|
19
|
+
```markdown
|
|
20
|
+
Summarize the maritime weather outlook for the next 24 hours, appropriate for the sailor of a 13.6m Sailing craft positioned at latitude 40.689247, longitude -74.044502
|
|
21
|
+
|
|
22
|
+
Generate an SVG image for the result suitable for a ZhunyCo 4.13" eInk Electronic Shelf Label display with pixel height 240 and width 416. Available colours are black (#000000), white (#FFFFFF), red (#FF0000), yellow (#FFFF00). Fonts available are serif, sans-serif, monospace.
|
|
23
|
+
|
|
24
|
+
Use graphics to make it easier to read and use the space better, and use colour and layout to draw attention to safety critical information. Include in minimal size font the source of the information.
|
|
25
|
+
```
|
|
16
26
|
|
|
17
27
|
## Installation
|
|
18
28
|
|
|
19
|
-
Install both plugins from the SignalK AppStore (or `npm install @rhizomatics/signalk-einklabel-plugin @rhizomatics/signalk-einklabel-genai-plugin`), then install whichever LLM provider package(s) you'll actually use - these are `optionalDependencies`, not installed automatically, since a user only ever needs one:
|
|
29
|
+
Install both plugins from the SignalK AppStore (or `npm install @rhizomatics/signalk-einklabel-plugin @rhizomatics/signalk-einklabel-genai-plugin`), then install whichever LLM provider package(s) you'll actually use - these are `optionalDependencies`, not installed automatically, since a user only ever needs one. `openrouter` is the one exception: `@openrouter/ai-sdk-provider` is a regular `dependency`, so it's already installed and needs no extra step, as is the base `ai` package:
|
|
20
30
|
|
|
21
31
|
```bash
|
|
22
32
|
npm install ai @ai-sdk/openai # llmProvider: "openai"
|
|
@@ -33,24 +43,13 @@ If the package for your configured provider isn't installed, a GenAI-backed devi
|
|
|
33
43
|
|
|
34
44
|
## Configuration
|
|
35
45
|
|
|
36
|
-
- `llmProvider` - `openai`, `anthropic`, `google`, `xai`, `deepseek`, `moonshotai`, `ollama`, or `local` (any other OpenAI-compatible server)
|
|
46
|
+
- `llmProvider` - `openai`, `anthropic`, `google`, `xai`, `deepseek`, `moonshotai`, `openrouter`, `ollama`, or `local` (any other OpenAI-compatible server)
|
|
37
47
|
- `llmApiKey` - not needed for `ollama`/`local` unless your server itself checks one
|
|
38
48
|
- `llmModel` - provider-specific model id, e.g. `gpt-4o`, `claude-sonnet-4-5`, `gemini-2.5-flash`, `grok-4`, or an Ollama/local model tag
|
|
39
49
|
- `llmBaseUrl` - required for `local`; optional for `ollama` (defaults to `http://localhost:11434/v1`)
|
|
40
50
|
- `llmTimeoutSeconds`/`llmRetries` - how long to wait, and how many attempts, before giving up (the core plugin then shows its fallback warning)
|
|
41
51
|
- `promptsDir` - where to look for your own `.md` prompt files, same convention as the core plugin's `templatesDir` (empty for the default, a relative path resolves against `~/.signalk`, absolute used as-is)
|
|
42
52
|
|
|
43
|
-
### Adding another LLM provider
|
|
44
|
-
|
|
45
|
-
The provider list above is `resolveModel`'s (`src/llmGateway.ts`) built-in switch, but you're not limited to it: call `registerLlmProvider(name, factory)` (exported from this plugin, mirroring the core plugin's own extension points) from your own SignalK plugin's `start()` to add support for a provider this plugin doesn't bundle, without forking it. A registered name takes priority over the built-in switch, so it can even replace one of the names above.
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
import genai from "@rhizomatics/signalk-einklabel-genai-plugin";
|
|
49
|
-
genai.registerLlmProvider("my-provider", (settings) => myOwnModelFactory(settings));
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Declare this package as a regular npm `dependency` (**not** a `peerDependency` - see the core plugin's own README for why) plus `"signalk": { "requires": ["@rhizomatics/signalk-einklabel-genai-plugin"] }` in your extension's `package.json`.
|
|
53
|
-
|
|
54
53
|
## Writing Prompts
|
|
55
54
|
|
|
56
55
|
A prompt (`.md` file) is just prose, with the same `{...}` binding syntax as a template's `<desc>` in the core plugin (see its README, "Template Source Specification") - a bare path like `{design.length.overall}`, or the full `source=...,path=...,format=...` grammar, resolve exactly the way they would in a template. One more source is available here, `source=label`, giving facts about the physical label the LLM needs to design for:
|
|
@@ -71,8 +70,8 @@ Every prompt also has a bundled `.assets/target_guidance.md` fragment appended a
|
|
|
71
70
|
This plugin contributes `prompt`/`generate` subcommands to the core plugin's `esl-cli`, loaded via `-r`:
|
|
72
71
|
|
|
73
72
|
```bash
|
|
74
|
-
esl-cli -r
|
|
75
|
-
esl-cli -r
|
|
73
|
+
esl-cli -r @rhizomatics/signalk-einklabel-genai-plugin/cli prompt forecast -e examples
|
|
74
|
+
esl-cli -r @rhizomatics/signalk-einklabel-genai-plugin/cli generate forecast -e examples -o forecast.png --llm-provider ollama --llm-model llama3.2
|
|
76
75
|
```
|
|
77
76
|
|
|
78
77
|
- `prompt [name]` - resolve a prompt's placeholders against a live SignalK server (or example data) and print the exact text an LLM would be sent, with no LLM call
|
|
@@ -80,6 +79,29 @@ esl-cli -r node_modules/@rhizomatics/signalk-einklabel-genai-plugin/dist/cliExte
|
|
|
80
79
|
|
|
81
80
|
The optional `[name]` argument names a prompt the same way the config UI's picker does - a full path also works. Use `--help` on either for the full option list.
|
|
82
81
|
|
|
83
|
-
##
|
|
82
|
+
## Architecture
|
|
84
83
|
|
|
85
|
-
|
|
84
|
+
### Why 2 plugins?
|
|
85
|
+
|
|
86
|
+
This is deliberately a separate plugin, not a feature of the core one: the core plugin has nothing to do with any LLM SDK or API key, so installing it never pulls in GenAI dependencies unless you also explicitly install and enable this one - useful for anyone on a small/constrained install, or who'd simply rather not have any GenAI code in their install at all.
|
|
87
|
+
|
|
88
|
+
### How it fits together
|
|
89
|
+
|
|
90
|
+
This plugin registers a `TemplateProvider` with the core plugin (the same extension mechanism a new vendor's hardware driver uses - see the core plugin's README, "Extending"). Once both plugins are installed and enabled:
|
|
91
|
+
|
|
92
|
+
- Its prompts show up as ordinary entries in the core plugin's own "Template" picker, suffixed `(GenAI)` - pick one exactly like you'd pick a `.svg` file. No separate "render mode" setting.
|
|
93
|
+
- All LLM provider/model/API-key configuration lives in **this** plugin's own config screen - the core plugin knows nothing about any of that.
|
|
94
|
+
- If the LLM call fails (network/API error, exhausted retries) or its response isn't a renderable SVG, the **core** plugin pushes its own generic fallback-warning template instead of leaving the previous, possibly now-wrong, content on screen - the same safety net a broken hand-authored template gets. The next scheduled repaint retries automatically.
|
|
95
|
+
|
|
96
|
+
Since each repaint may call a (usually paid) LLM API, a GenAI-backed device should use Repaint Trigger `interval`, not `subscription`.
|
|
97
|
+
|
|
98
|
+
### Adding another LLM provider
|
|
99
|
+
|
|
100
|
+
The provider list above is `resolveModel`'s (`src/llmGateway.ts`) built-in switch, but you're not limited to it: call `registerLlmProvider(name, factory)` (exported from this plugin, mirroring the core plugin's own extension points) from your own SignalK plugin's `start()` to add support for a provider this plugin doesn't bundle, without forking it. A registered name takes priority over the built-in switch, so it can even replace one of the names above.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import genai from "@rhizomatics/signalk-einklabel-genai-plugin";
|
|
104
|
+
genai.registerLlmProvider("my-provider", (settings) => myOwnModelFactory(settings));
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Declare this package as a regular npm `dependency` (**not** a `peerDependency` - see the core plugin's own README for why) plus `"signalk": { "requires": ["@rhizomatics/signalk-einklabel-genai-plugin"] }` in your extension's `package.json`.
|
package/dist/cliExtension.js
CHANGED
|
@@ -5,10 +5,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
/**
|
|
7
7
|
* Contributes `prompt`/`generate` subcommands to the core plugin's `esl-cli`, for testing prompts
|
|
8
|
-
* without a device or a live SignalK server. Load via `esl-cli -r
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* without a device or a live SignalK server. Load via `esl-cli -r @rhizomatics/signalk-einklabel-genai-plugin/cli prompt ...`
|
|
9
|
+
* (the `./cli` subpath export in package.json, so callers don't need to know this compiles to
|
|
10
|
+
* `dist/cliExtension.js`) - the core CLI pre-scans `-r`/`--require` and loads those modules *before*
|
|
11
|
+
* building its own command tree (see its own README/source for why), which is what makes adding a
|
|
12
|
+
* brand-new subcommand from here possible at all, unlike a module that only registers a vendor
|
|
13
|
+
* driver/template provider.
|
|
14
|
+
*
|
|
15
|
+
* Deliberately NOT loaded from this package's main entry (`./index.ts`, what SignalK server actually
|
|
16
|
+
* requires to load the plugin) - the core CLI module this file requires below calls
|
|
17
|
+
* `program.parseAsync(process.argv)` at the top level as a side effect of being required, which is
|
|
18
|
+
* correct for a CLI invocation but would misfire if pulled into a running SignalK server process.
|
|
12
19
|
*
|
|
13
20
|
* A plain top-level `require()` (rather than a static `import`) of the core CLI module is deliberate -
|
|
14
21
|
* it makes the load-time side effect (adding commands to an already-running CLI process) obvious to a
|
|
@@ -95,7 +102,7 @@ addPromptOptions(cli.program
|
|
|
95
102
|
.description("Call an LLM with a resolved prompt and write the rendered SVG's PNG, without needing a device"))
|
|
96
103
|
.requiredOption("-o, --output <path>", "output PNG path")
|
|
97
104
|
.option("--save-svg <path>", "also write the LLM's raw SVG response to this path (feed it straight into the core plugin's `paint -t`/`render -t` afterwards)")
|
|
98
|
-
.option("--llm-provider <name>", "openai, anthropic, google, xai, deepseek, moonshotai, ollama, or local", "
|
|
105
|
+
.option("--llm-provider <name>", "openrouter, openai, anthropic, google, xai, deepseek, moonshotai, ollama, or local", "openrouter")
|
|
99
106
|
.option("--llm-api-key <key>", "LLM API key - falls back to the provider's usual environment variable if omitted")
|
|
100
107
|
.requiredOption("--llm-model <name>", "provider-specific model id/name, e.g. gpt-4o, claude-sonnet-4-5, gemini-2.5-flash, grok-4, or a local/ollama model tag")
|
|
101
108
|
.option("--llm-base-url <url>", 'OpenAI-compatible endpoint - required if --llm-provider is "local"; for "ollama" defaults to http://localhost:11434/v1')
|
package/dist/config.d.ts
CHANGED
|
@@ -3,9 +3,11 @@ export interface PluginConfig {
|
|
|
3
3
|
* Which LLM gateway a prompt is sent to - see `./llmGateway.ts`. `"ollama"`/`"local"` both go through
|
|
4
4
|
* the generic OpenAI-compatible provider; any other value not in this list is looked up in the
|
|
5
5
|
* `registerLlmProvider` registry (`./llmProviderRegistry.ts`) before giving up - so someone keen can
|
|
6
|
-
* add support for another provider entirely without forking this plugin. Defaults to `"
|
|
6
|
+
* add support for another provider entirely without forking this plugin. Defaults to `"openrouter"`,
|
|
7
|
+
* the only provider bundled as a regular `dependency` rather than an `optionalDependency` - see
|
|
8
|
+
* `./llmGateway.ts`'s top doc comment.
|
|
7
9
|
*/
|
|
8
|
-
llmProvider?: "openai" | "anthropic" | "google" | "xai" | "deepseek" | "moonshotai" | "ollama" | "local";
|
|
10
|
+
llmProvider?: "openrouter" | "openai" | "anthropic" | "google" | "xai" | "deepseek" | "moonshotai" | "ollama" | "local";
|
|
9
11
|
/** Ignored for `llmProvider: "ollama"`/`"local"` unless the server itself requires one. */
|
|
10
12
|
llmApiKey?: string;
|
|
11
13
|
/** Provider-specific model id, e.g. `"gpt-4o"`, `"claude-sonnet-4-5"`, `"gemini-2.5-flash"`, or an Ollama/local model tag. */
|
package/dist/llmGateway.d.ts
CHANGED
|
@@ -4,15 +4,15 @@ export type LlmSettings = RegistryLlmSettings & {
|
|
|
4
4
|
llmTimeoutSeconds?: number;
|
|
5
5
|
};
|
|
6
6
|
/**
|
|
7
|
-
* `ai` and every `@ai-sdk/*`/`ai-sdk-ollama` provider package
|
|
8
|
-
* package.json), not plain `dependencies` - a user picks
|
|
9
|
-
* force every one of them to install, and an install that
|
|
10
|
-
* network hiccup, `npm install --omit=optional`, an unsupported
|
|
11
|
-
* for everyone else. That means every reference to one of these
|
|
12
|
-
* only when `callLlm` actually needs it - a top-level `import`
|
|
13
|
-
* module loads, throwing before any config is even read. Types
|
|
14
|
-
* `typeof import(...)` at each call site, which - unlike a value `import` -
|
|
15
|
-
* compile time and leaves no runtime trace for `tsc` to eagerly require.
|
|
7
|
+
* `ai` and every `@ai-sdk/*`/`ai-sdk-ollama` provider package (other than `@openrouter/ai-sdk-provider`,
|
|
8
|
+
* see below) are `optionalDependencies` (see package.json), not plain `dependencies` - a user picks
|
|
9
|
+
* exactly one provider, so there's no reason to force every one of them to install, and an install that
|
|
10
|
+
* fails to fetch one it doesn't even use (a network hiccup, `npm install --omit=optional`, an unsupported
|
|
11
|
+
* platform) must not break this plugin for everyone else. That means every reference to one of these
|
|
12
|
+
* packages has to be a `require()` reached only when `callLlm` actually needs it - a top-level `import`
|
|
13
|
+
* would be resolved eagerly the moment this module loads, throwing before any config is even read. Types
|
|
14
|
+
* are still fully checked via `typeof import(...)` at each call site, which - unlike a value `import` -
|
|
15
|
+
* is erased entirely at compile time and leaves no runtime trace for `tsc` to eagerly require.
|
|
16
16
|
*/
|
|
17
17
|
export declare function loadOptional<M>(moduleName: string): M;
|
|
18
18
|
/**
|
package/dist/llmGateway.js
CHANGED
|
@@ -3,17 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.loadOptional = loadOptional;
|
|
4
4
|
exports.callLlm = callLlm;
|
|
5
5
|
exports.extractSvg = extractSvg;
|
|
6
|
+
const ai_sdk_provider_1 = require("@openrouter/ai-sdk-provider");
|
|
6
7
|
const llmProviderRegistry_1 = require("./llmProviderRegistry");
|
|
7
8
|
/**
|
|
8
|
-
* `ai` and every `@ai-sdk/*`/`ai-sdk-ollama` provider package
|
|
9
|
-
* package.json), not plain `dependencies` - a user picks
|
|
10
|
-
* force every one of them to install, and an install that
|
|
11
|
-
* network hiccup, `npm install --omit=optional`, an unsupported
|
|
12
|
-
* for everyone else. That means every reference to one of these
|
|
13
|
-
* only when `callLlm` actually needs it - a top-level `import`
|
|
14
|
-
* module loads, throwing before any config is even read. Types
|
|
15
|
-
* `typeof import(...)` at each call site, which - unlike a value `import` -
|
|
16
|
-
* compile time and leaves no runtime trace for `tsc` to eagerly require.
|
|
9
|
+
* `ai` and every `@ai-sdk/*`/`ai-sdk-ollama` provider package (other than `@openrouter/ai-sdk-provider`,
|
|
10
|
+
* see below) are `optionalDependencies` (see package.json), not plain `dependencies` - a user picks
|
|
11
|
+
* exactly one provider, so there's no reason to force every one of them to install, and an install that
|
|
12
|
+
* fails to fetch one it doesn't even use (a network hiccup, `npm install --omit=optional`, an unsupported
|
|
13
|
+
* platform) must not break this plugin for everyone else. That means every reference to one of these
|
|
14
|
+
* packages has to be a `require()` reached only when `callLlm` actually needs it - a top-level `import`
|
|
15
|
+
* would be resolved eagerly the moment this module loads, throwing before any config is even read. Types
|
|
16
|
+
* are still fully checked via `typeof import(...)` at each call site, which - unlike a value `import` -
|
|
17
|
+
* is erased entirely at compile time and leaves no runtime trace for `tsc` to eagerly require.
|
|
17
18
|
*/
|
|
18
19
|
function loadOptional(moduleName) {
|
|
19
20
|
try {
|
|
@@ -46,7 +47,7 @@ function resolveModel(settings) {
|
|
|
46
47
|
if (!model) {
|
|
47
48
|
throw new Error("no LLM model configured (llmModel)");
|
|
48
49
|
}
|
|
49
|
-
const provider = settings.llmProvider ?? "
|
|
50
|
+
const provider = settings.llmProvider ?? "openrouter";
|
|
50
51
|
const registered = (0, llmProviderRegistry_1.getLlmProvider)(provider);
|
|
51
52
|
if (registered)
|
|
52
53
|
return registered(settings);
|
|
@@ -71,6 +72,9 @@ function resolveModel(settings) {
|
|
|
71
72
|
const { createMoonshotAI } = loadOptional("@ai-sdk/moonshotai");
|
|
72
73
|
return createMoonshotAI({ apiKey: settings.llmApiKey })(model);
|
|
73
74
|
}
|
|
75
|
+
case "openrouter": {
|
|
76
|
+
return (0, ai_sdk_provider_1.createOpenRouter)({ apiKey: settings.llmApiKey })(model);
|
|
77
|
+
}
|
|
74
78
|
case "ollama": {
|
|
75
79
|
const { createOllama } = loadOptional("ai-sdk-ollama");
|
|
76
80
|
return createOllama({ baseURL: settings.llmBaseUrl || DEFAULT_OLLAMA_BASE_URL, apiKey: settings.llmApiKey })(model);
|
|
@@ -12,7 +12,8 @@ export type LlmProviderFactory = (settings: LlmSettings) => LlmModel;
|
|
|
12
12
|
* Public extension point mirroring the core `signalk-einklabel-plugin`'s own `registerVendorDriver`/
|
|
13
13
|
* `registerTemplateProvider` pattern, one level down: this plugin already bundles support for several
|
|
14
14
|
* providers (`./llmGateway.ts`'s built-in switch: openai/anthropic/google/xai/deepseek/moonshotai/
|
|
15
|
-
* ollama/local
|
|
15
|
+
* ollama/local as `optionalDependencies`, plus openrouter as a regular `dependency`), but someone
|
|
16
|
+
* wanting a provider not in that list (a bespoke
|
|
16
17
|
* wrapper, a provider with no official `@ai-sdk/*` package yet) can register one of their own without
|
|
17
18
|
* forking this plugin - `resolveModel` in `./llmGateway.ts` checks this registry first, by `llmProvider`
|
|
18
19
|
* name, before falling through to the built-in switch. Call this from your own SignalK plugin's
|
|
@@ -7,7 +7,8 @@ const providers = new Map();
|
|
|
7
7
|
* Public extension point mirroring the core `signalk-einklabel-plugin`'s own `registerVendorDriver`/
|
|
8
8
|
* `registerTemplateProvider` pattern, one level down: this plugin already bundles support for several
|
|
9
9
|
* providers (`./llmGateway.ts`'s built-in switch: openai/anthropic/google/xai/deepseek/moonshotai/
|
|
10
|
-
* ollama/local
|
|
10
|
+
* ollama/local as `optionalDependencies`, plus openrouter as a regular `dependency`), but someone
|
|
11
|
+
* wanting a provider not in that list (a bespoke
|
|
11
12
|
* wrapper, a provider with no official `@ai-sdk/*` package yet) can register one of their own without
|
|
12
13
|
* forking this plugin - `resolveModel` in `./llmGateway.ts` checks this registry first, by `llmProvider`
|
|
13
14
|
* name, before falling through to the built-in switch. Call this from your own SignalK plugin's
|
package/dist/plugin.js
CHANGED
|
@@ -24,11 +24,12 @@ function createPlugin(app) {
|
|
|
24
24
|
llmProvider: {
|
|
25
25
|
type: "string",
|
|
26
26
|
title: "LLM provider",
|
|
27
|
-
description:
|
|
28
|
-
|
|
29
|
-
'
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
description: 'Which LLM gateway to call - "OpenRouter" is bundled and needs no extra install; every other option ' +
|
|
28
|
+
"(other than local/ollama) needs its own optional npm package installed, e.g. `npm install ai @ai-sdk/anthropic` " +
|
|
29
|
+
'(see README). "Ollama" defaults to a local Ollama server with no further config; "Local" is any other ' +
|
|
30
|
+
'OpenAI-compatible endpoint (e.g. LM Studio) and needs "LLM base URL" below.',
|
|
31
|
+
enum: ["openrouter", "openai", "anthropic", "google", "xai", "deepseek", "moonshotai", "ollama", "local"],
|
|
32
|
+
default: "openrouter",
|
|
32
33
|
},
|
|
33
34
|
llmApiKey: {
|
|
34
35
|
type: "string",
|
|
@@ -73,7 +74,7 @@ function createPlugin(app) {
|
|
|
73
74
|
uiSchema: () => ({ llmApiKey: { "ui:widget": "password" } }),
|
|
74
75
|
start(config) {
|
|
75
76
|
currentConfig = { ...(0, config_1.defaultConfig)(), ...config };
|
|
76
|
-
app.debug(`eInk Label GenAI plugin started (llmProvider=${currentConfig.llmProvider ?? "
|
|
77
|
+
app.debug(`eInk Label GenAI plugin started (llmProvider=${currentConfig.llmProvider ?? "openrouter"})`);
|
|
77
78
|
},
|
|
78
79
|
stop() { },
|
|
79
80
|
};
|
package/dist/templateProvider.js
CHANGED
|
@@ -14,9 +14,9 @@ const llmGateway_1 = require("./llmGateway");
|
|
|
14
14
|
/** Appended to every prompt name this plugin offers in the core plugin's "Template" dropdown. */
|
|
15
15
|
exports.SUFFIX = "(GenAI)";
|
|
16
16
|
const GUIDANCE_NAME = (0, path_1.join)(".assets", "target_guidance.md");
|
|
17
|
-
/** Strips ` (GenAI)` back off a dropdown entry
|
|
17
|
+
/** Strips ` (GenAI)` back off a dropdown entry and restores the `.md` extension hidden from it, e.g. `"forecast (GenAI)"` -> `"forecast.md"`. */
|
|
18
18
|
function promptNameFor(templateName) {
|
|
19
|
-
return templateName.slice(0, templateName.length - ` ${exports.SUFFIX}`.length)
|
|
19
|
+
return `${templateName.slice(0, templateName.length - ` ${exports.SUFFIX}`.length)}.md`;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* The `esl.TemplateProvider` this plugin registers with the core plugin (`esl.registerTemplateProvider`,
|
|
@@ -38,7 +38,7 @@ function createTemplateProvider(getConfig) {
|
|
|
38
38
|
return {
|
|
39
39
|
suffix: exports.SUFFIX,
|
|
40
40
|
listTemplates() {
|
|
41
|
-
return (0, config_1.promptNameOptions)(promptsDir()).map((name) => `${name} ${exports.SUFFIX}`);
|
|
41
|
+
return (0, config_1.promptNameOptions)(promptsDir()).map((name) => `${name.replace(/\.md$/, "")} ${exports.SUFFIX}`);
|
|
42
42
|
},
|
|
43
43
|
describeBindings(templateName) {
|
|
44
44
|
const { promptPath, guidancePath } = promptPathFor(templateName);
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhizomatics/signalk-einklabel-genai-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "GenAI prompt generated labels for the eInk Electronic Shelf Label plugin. Contributes prompts as an alternative to hand-authored SVG templates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eink",
|
|
@@ -24,10 +24,22 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"dist",
|
|
26
26
|
"prompts",
|
|
27
|
-
"
|
|
27
|
+
"docs",
|
|
28
|
+
"README.md",
|
|
29
|
+
"CHANGELOG.md"
|
|
28
30
|
],
|
|
29
31
|
"main": "dist/index.js",
|
|
30
32
|
"types": "dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./cli": {
|
|
39
|
+
"types": "./dist/cliExtension.d.ts",
|
|
40
|
+
"default": "./dist/cliExtension.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
31
43
|
"scripts": {
|
|
32
44
|
"build": "tsc",
|
|
33
45
|
"watch": "tsc --watch",
|
|
@@ -39,7 +51,9 @@
|
|
|
39
51
|
"lint": "oxlint"
|
|
40
52
|
},
|
|
41
53
|
"dependencies": {
|
|
42
|
-
"@
|
|
54
|
+
"@openrouter/ai-sdk-provider": "^3.0.0",
|
|
55
|
+
"@rhizomatics/signalk-einklabel-plugin": "^1.0.0",
|
|
56
|
+
"ai": "^7.0.37"
|
|
43
57
|
},
|
|
44
58
|
"devDependencies": {
|
|
45
59
|
"@signalk/server-api": "^2.28.0",
|
|
@@ -59,7 +73,6 @@
|
|
|
59
73
|
"@ai-sdk/openai": "^4.0.20",
|
|
60
74
|
"@ai-sdk/openai-compatible": "^3.0.14",
|
|
61
75
|
"@ai-sdk/xai": "^4.0.18",
|
|
62
|
-
"ai": "^7.0.37",
|
|
63
76
|
"ai-sdk-ollama": "^4.1.0"
|
|
64
77
|
},
|
|
65
78
|
"engines": {
|
|
@@ -67,6 +80,10 @@
|
|
|
67
80
|
},
|
|
68
81
|
"signalk": {
|
|
69
82
|
"displayName": "eInk Label GenAI Rendering",
|
|
83
|
+
"appIcon": "docs/assets/icons/icon_tmp.png",
|
|
84
|
+
"screenshots": [
|
|
85
|
+
"docs/assets/screenshots/example_weather_openai.png"
|
|
86
|
+
],
|
|
70
87
|
"requires": [
|
|
71
88
|
"@rhizomatics/signalk-einklabel-plugin"
|
|
72
89
|
]
|