@lobb-js/lobb-ext-llm 0.1.16 → 0.3.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/.env ADDED
@@ -0,0 +1 @@
1
+ CHATGPT_API_KEY=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
package/.env.example ADDED
@@ -0,0 +1 @@
1
+ CHATGPT_API_KEY=<TOKEN>
package/CHANGELOG.md ADDED
@@ -0,0 +1,90 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.
3
+
4
+ - - -
5
+ ## llm-ext@0.3.1 - 2026-03-28
6
+ #### Bug Fixes
7
+ - adding readme to all packages - (3a9264a) - malik ben
8
+ #### Miscellaneous Chores
9
+ - add publishConfig and fix ext packages for npm publishing - (49747e9) - malik ben
10
+
11
+ - - -
12
+
13
+ ## llm-ext@0.3.0 - 2026-03-28
14
+ #### Features
15
+ - (**llm-ext**) add mock OpenAI server for tests and base_url config option - (ece56a3) - malik ben
16
+ - (**llm-ext**) migrate to Bun - (95cf023) - malik ben
17
+ #### Bug Fixes
18
+ - chaning $lib to relative path - (c6d9e8f) - malik ben
19
+ #### Miscellaneous Chores
20
+ - (**llm-ext**) move extension logic and tests to extensions/llm - (7026cf2) - malik ben
21
+ - (**mail-ext**) move extension logic and tests to extensions/mail - (529b0dc) - malik ben
22
+ - (**version**) 0.25.2 - (a62acb9) - Cocogitto Bot
23
+ - (**version**) 0.25.1 - (afe7e69) - Cocogitto Bot
24
+ - (**version**) 0.25.0 - (77a383c) - Cocogitto Bot
25
+ - (**version**) 0.24.0 - (a8cb605) - Cocogitto Bot
26
+ - (**version**) 0.23.0 - (60f357e) - Cocogitto Bot
27
+ - (**version**) 0.22.0 - (6510e32) - Cocogitto Bot
28
+ - (**version**) 0.21.0 - (c973aa9) - Cocogitto Bot
29
+ - (**version**) 0.20.0 - (06cc303) - Cocogitto Bot
30
+ - rename @lobb/ scope to @lobb-js/ across all packages and apps - (cce4ce0) - malik ben
31
+ - update CLAUDE.md to enforce no-commit-without-explicit-instruction rule - (6d63a42) - malik ben
32
+ - remove all deno.json and deno.lock files from the monorepo - (84ca5d4) - malik ben
33
+ - replace workspace:* with exact versions in all package.json files - (74fbdb7) - malik ben
34
+ - rename __studio to studio and remove unused studio dirs - (77fb932) - malik ben
35
+ - rename studio directory to __studio for faker-ext, llm-ext, mail-ext, reports-ext, storage-ext - (47e754a) - malik ben
36
+
37
+ - - -
38
+
39
+ ## llm-ext@0.2.0 - 2026-03-01
40
+ #### Features
41
+ - (**mindhar**) add storage extension integration - (bca6368) - malik ben
42
+ #### Miscellaneous Chores
43
+ - (**version**) 0.18.0 - (efc553f) - Cocogitto Bot
44
+ - (**version**) 0.17.0 - (4174f0c) - Cocogitto Bot
45
+ - (**version**) 0.16.0 - (9508655) - Cocogitto Bot
46
+ - (**version**) 0.14.11 - (ad92b61) - Cocogitto Bot
47
+ - (**version**) 0.14.8 - (0e6c1cb) - Cocogitto Bot
48
+ - (**version**) 0.14.3 - (76abe92) - Cocogitto Bot
49
+ - (**version**) 0.14.2 - (6ecdc1d) - Cocogitto Bot
50
+
51
+ - - -
52
+
53
+ ## llm-ext@0.1.13 - 2026-02-21
54
+ #### Bug Fixes
55
+ - using default export instead of named export for extensions - (37dd485) - malik ben
56
+ #### Miscellaneous Chores
57
+ - (**version**) 0.13.2 - (39b0145) - Cocogitto Bot
58
+ - (**version**) 0.12.3 - (cd06fc0) - Cocogitto Bot
59
+ - (**version**) 0.12.2 - (35b2ff3) - Cocogitto Bot
60
+ - (**version**) 0.12.1 - (c548105) - Cocogitto Bot
61
+ - (**version**) 0.11.1 - (659ebd3) - Cocogitto Bot
62
+ - (**version**) 0.11.0 - (3f4f47e) - Cocogitto Bot
63
+ - (**version**) 0.10.0 - (5d79b6e) - Cocogitto Bot
64
+ - (**version**) 0.8.0 - (fdee7ca) - Cocogitto Bot
65
+
66
+ - - -
67
+
68
+ ## llm-ext@0.1.12 - 2026-02-15
69
+ #### Bug Fixes
70
+ - fixed the name of the project for llm - (390f747) - malik ben
71
+
72
+ - - -
73
+
74
+ ## llm-ext@0.1.11 - 2026-02-15
75
+ #### Bug Fixes
76
+ - fix deno publish issue - (e8dcc4f) - malik ben
77
+ - issue fix - (63d66d3) - malik ben
78
+ #### Miscellaneous Chores
79
+ - (**version**) 0.5.5 - (d4dedeb) - Cocogitto Bot
80
+ - (**version**) 0.5.4 - (1ca3970) - Cocogitto Bot
81
+ - (**version**) 0.5.3 - (dcdb9cb) - Cocogitto Bot
82
+ - (**version**) 0.5.2 - (aa66e29) - Cocogitto Bot
83
+ - (**version**) 0.5.1 - (41b7c35) - Cocogitto Bot
84
+ - (**version**) 0.5.0 - (af63147) - Cocogitto Bot
85
+ - (**version**) 0.4.4 - (eaed3b4) - Cocogitto Bot
86
+ - (**version**) 0.4.3 - (ea9ec49) - Cocogitto Bot
87
+
88
+ - - -
89
+
90
+ Changelog generated by [cocogitto](https://github.com/cocogitto/cocogitto).
package/README.md CHANGED
@@ -1,27 +1 @@
1
- # LLM Extension - Studio
2
-
3
- This directory contains the frontend/dashboard interface for the LLM extension.
4
-
5
- ## Structure
6
-
7
- ```
8
- studio/
9
- ├── src/
10
- │ ├── index.ts # Extension entry point
11
- │ ├── main.ts # Vite app entry
12
- │ └── pages/ # UI pages (to be added)
13
- ├── public/ # Static assets
14
- ├── index.html # HTML entry point
15
- ├── vite.config.ts # Vite configuration
16
- ├── tailwind.config.ts # Tailwind CSS configuration
17
- └── tsconfig.json # TypeScript configuration
18
- ```
19
-
20
- ## Future Features
21
-
22
- The studio interface could include:
23
- - LLM provider configuration UI
24
- - Chat interface for testing models
25
- - Model selection and settings
26
- - API key management
27
- - Usage analytics and monitoring
1
+ # @lobb-js/lobb-ext-llm
@@ -0,0 +1,57 @@
1
+ import type { Lobb } from "@lobb-js/core";
2
+ import type { ChatArgs } from "./index.ts";
3
+ import type { ChatgptAdapterConfig, ExtensionConfig } from "../config/extensionConfigSchema.ts";
4
+
5
+ import OpenAI from "openai";
6
+ import { LLMAdapter } from "./index.ts";
7
+
8
+ export class ChatgptAdapter extends LLMAdapter {
9
+ constructor(lobb: Lobb, extensionConfig: ExtensionConfig) {
10
+ super(lobb, extensionConfig);
11
+ }
12
+
13
+ public async chat(
14
+ args: ChatArgs,
15
+ ): Promise<string | ReadableStream<Uint8Array>> {
16
+ const extensionConfig = this.extensionConfig as ChatgptAdapterConfig;
17
+
18
+ const client = new OpenAI({
19
+ apiKey: extensionConfig.settings.api_key,
20
+ baseURL: extensionConfig.settings.base_url,
21
+ });
22
+
23
+ if (args.stream === true) {
24
+ const completion = await client.chat.completions.create({
25
+ model: extensionConfig.settings.model,
26
+ stream: args.stream,
27
+ messages: args.messages,
28
+ response_format: args.format,
29
+ });
30
+
31
+ return new ReadableStream({
32
+ async start(controller) {
33
+ for await (const part of completion) {
34
+ const chunkContent = part.choices[0].delta.content;
35
+
36
+ if (chunkContent) {
37
+ const uint8Array = new TextEncoder().encode(chunkContent);
38
+ controller.enqueue(uint8Array);
39
+ }
40
+
41
+ if (part.choices[0].finish_reason) {
42
+ controller.close();
43
+ }
44
+ }
45
+ },
46
+ });
47
+ } else {
48
+ const completion = await client.chat.completions.create({
49
+ model: extensionConfig.settings.model,
50
+ stream: args.stream,
51
+ messages: args.messages,
52
+ });
53
+
54
+ return completion.choices[0].message.content ?? "";
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,27 @@
1
+ import type { Lobb } from "@lobb-js/core";
2
+ import type { ExtensionConfig } from "../config/extensionConfigSchema.ts";
3
+
4
+ export type Messages = {
5
+ role: "user" | "assistant" | "system";
6
+ content: string;
7
+ }[];
8
+
9
+ export interface ChatArgs {
10
+ messages: Messages;
11
+ stream?: boolean;
12
+ format: any;
13
+ }
14
+
15
+ export abstract class LLMAdapter {
16
+ protected lobb: Lobb;
17
+ protected extensionConfig: ExtensionConfig;
18
+
19
+ constructor(lobb: Lobb, extensionConfig: ExtensionConfig) {
20
+ this.lobb = lobb;
21
+ this.extensionConfig = extensionConfig;
22
+ }
23
+
24
+ public abstract chat(
25
+ agrs: ChatArgs,
26
+ ): Promise<string | ReadableStream<Uint8Array>>;
27
+ }
@@ -0,0 +1,61 @@
1
+ import type { Lobb } from "@lobb-js/core";
2
+ import type { ChatArgs } from "./index.ts";
3
+ import type { ExtensionConfig, OllamaAdapterConfig } from "../config/extensionConfigSchema.ts";
4
+
5
+ import { LobbError } from "@lobb-js/core";
6
+ import { LLMAdapter } from "./index.ts";
7
+
8
+ export class OllamaAdapter extends LLMAdapter {
9
+ constructor(lobb: Lobb, extensionConfig: ExtensionConfig) {
10
+ super(lobb, extensionConfig);
11
+ }
12
+
13
+ public async chat(
14
+ agrs: ChatArgs,
15
+ ): Promise<string | ReadableStream<Uint8Array>> {
16
+ const extensionConfig = this.extensionConfig as OllamaAdapterConfig;
17
+ const response = await fetch(
18
+ `${extensionConfig.settings.host}/api/chat`,
19
+ {
20
+ method: "POST",
21
+ body: JSON.stringify({
22
+ model: extensionConfig.settings.model,
23
+ messages: agrs.messages,
24
+ format: agrs.format,
25
+ stream: agrs.stream || false,
26
+ options: {
27
+ num_thread: extensionConfig.settings.num_thread,
28
+ seed: extensionConfig.settings.seed,
29
+ },
30
+ }),
31
+ },
32
+ );
33
+
34
+ if (agrs.stream === true) {
35
+ if (response.ok && response.body) {
36
+ const reader = response.body.getReader();
37
+ return new ReadableStream({
38
+ async start(controller) {
39
+ let done = false;
40
+ while (!done) {
41
+ const { value, done: readerDone } = await reader.read();
42
+ done = readerDone;
43
+ controller.enqueue(value);
44
+ }
45
+
46
+ controller.close();
47
+ },
48
+ });
49
+ } else {
50
+ throw new LobbError({
51
+ code: "INTERNAL_SERVER_ERROR",
52
+ message: `The response from the chat API was not successful`,
53
+ details: await response.text(),
54
+ });
55
+ }
56
+ } else {
57
+ const data: any = await response.json();
58
+ return data.message.content;
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,20 @@
1
+ import type { Lobb } from "@lobb-js/core";
2
+ import type { LLMAdapter } from "./index.ts";
3
+ import type { ExtensionConfig } from "../config/extensionConfigSchema.ts";
4
+
5
+ import { OllamaAdapter } from "./ollamaAdapter.ts";
6
+ import { LobbError } from "@lobb-js/core";
7
+ import { ChatgptAdapter } from "./chatgptAdapter.ts";
8
+
9
+ export function getLLMAdapter(lobb: Lobb, extensionConfig: ExtensionConfig): LLMAdapter {
10
+ if (extensionConfig.adapter === "chatgpt") {
11
+ return new ChatgptAdapter(lobb, extensionConfig);
12
+ } else if (extensionConfig.adapter === "ollama") {
13
+ return new OllamaAdapter(lobb, extensionConfig);
14
+ } else {
15
+ throw new LobbError({
16
+ code: "INTERNAL_SERVER_ERROR",
17
+ message: `The storage adapter is not implemented`,
18
+ });
19
+ }
20
+ }
@@ -0,0 +1,13 @@
1
+ import type { Hono } from "hono";
2
+ import type { ExtensionConfig } from "./config/extensionConfigSchema.ts";
3
+
4
+ import { LLMController } from "./controllers/llmController.ts";
5
+
6
+ export function getCollectionRoutes(extensionConfig: ExtensionConfig) {
7
+ return function collectionRoutes(route: Hono) {
8
+ route.post(
9
+ "/llm_chat",
10
+ (c) => LLMController.createOne(c, extensionConfig),
11
+ );
12
+ };
13
+ }
@@ -0,0 +1,10 @@
1
+ import type { CollectionConfig } from "@lobb-js/core";
2
+
3
+ export const chatCollection: CollectionConfig = {
4
+ "indexes": {},
5
+ "fields": {
6
+ "id": {
7
+ "type": "integer",
8
+ },
9
+ },
10
+ };
@@ -0,0 +1,10 @@
1
+ import type { CollectionConfig, Lobb } from "@lobb-js/core";
2
+ import { chatCollection } from "./chat.ts";
3
+
4
+ export function collections(
5
+ lobb: Lobb,
6
+ ): Record<string, CollectionConfig> {
7
+ const collectionsSchemas: Record<string, CollectionConfig> = {};
8
+ collectionsSchemas["llm_chat"] = chatCollection;
9
+ return collectionsSchemas;
10
+ }
@@ -0,0 +1,20 @@
1
+ export type ChatgptAdapterConfig = {
2
+ adapter: "chatgpt";
3
+ settings: {
4
+ model: string;
5
+ api_key: string;
6
+ base_url?: string; // override for testing (mock server)
7
+ };
8
+ };
9
+
10
+ export type OllamaAdapterConfig = {
11
+ adapter: "ollama";
12
+ settings: {
13
+ model: string;
14
+ host?: string;
15
+ num_thread?: number;
16
+ seed?: number;
17
+ };
18
+ };
19
+
20
+ export type ExtensionConfig = ChatgptAdapterConfig | OllamaAdapterConfig;
@@ -0,0 +1,49 @@
1
+ import type { Lobb } from "@lobb-js/core";
2
+ import type { Context } from "hono";
3
+ import type { ExtensionConfig } from "../config/extensionConfigSchema.ts";
4
+
5
+ import { LobbError } from "@lobb-js/core";
6
+ import { getLLMAdapter } from "../adapters/utils.ts";
7
+
8
+ export class LLMController {
9
+ public static async createOne(c: Context, extensionConfig: ExtensionConfig) {
10
+ const lobb = c.get("lobb") as Lobb;
11
+ const payload = await lobb.utils.getRequestBody(c);
12
+
13
+ if (!payload) {
14
+ throw new LobbError({
15
+ code: "BAD_REQUEST",
16
+ message: `You must send a payload`,
17
+ });
18
+ }
19
+
20
+ if (!payload.messages) {
21
+ throw new LobbError({
22
+ code: "BAD_REQUEST",
23
+ message: `You must send (messages) property`,
24
+ });
25
+ }
26
+
27
+ const adapter = getLLMAdapter(lobb, extensionConfig);
28
+ const response = await adapter.chat({
29
+ messages: payload.messages,
30
+ format: payload.format,
31
+ stream: payload.stream,
32
+ });
33
+
34
+ if (typeof response === "string") {
35
+ return c.json({
36
+ data: response,
37
+ }, 200);
38
+ } else {
39
+ return new Response(response, {
40
+ headers: {
41
+ "Content-Type": "text/event-stream",
42
+ "Cache-Control": "no-cache",
43
+ "Connection": "keep-alive",
44
+ "Transfer-Encoding": "chunked",
45
+ },
46
+ });
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,17 @@
1
+ import type { Extension } from "@lobb-js/core";
2
+ import type { ExtensionConfig } from "./config/extensionConfigSchema.ts";
3
+
4
+ import { getCollectionRoutes } from "./collectionRoutes.ts";
5
+ import { collections } from "./collections/collections.ts";
6
+ import { migrations } from "./migrations/migrations.ts";
7
+ import { openapi } from "./openapi.ts";
8
+
9
+ export function llm(extensionConfig: ExtensionConfig): Extension {
10
+ return {
11
+ name: "llm",
12
+ collectionRoutes: getCollectionRoutes(extensionConfig),
13
+ collections: collections,
14
+ migrations: migrations,
15
+ openapi: openapi as any,
16
+ };
17
+ }
@@ -0,0 +1,3 @@
1
+ import type { MigrationProps, Migrations } from "@lobb-js/core";
2
+
3
+ export const migrations: Migrations = {};
@@ -0,0 +1,88 @@
1
+ export const openapi = {
2
+ paths: {
3
+ "/api/collections/llm_chat": {
4
+ post: {
5
+ summary: "LLM chat",
6
+ description: "Talk to the llm using this endpoint",
7
+ requestBody: {
8
+ required: true,
9
+ content: {
10
+ "application/json": {
11
+ schema: {
12
+ type: "object",
13
+ required: ["messages"],
14
+ properties: {
15
+ stream: {
16
+ type: "boolean",
17
+ default: false,
18
+ example: false,
19
+ },
20
+ format: {
21
+ type: "string",
22
+ example: "json",
23
+ },
24
+ messages: {
25
+ type: "array",
26
+ items: {
27
+ type: "object",
28
+ properties: {
29
+ role: {
30
+ type: "string",
31
+ example: "system",
32
+ },
33
+ content: {
34
+ type: "string",
35
+ example:
36
+ "you are a good person that listens to me carefully",
37
+ },
38
+ },
39
+ },
40
+ example: [
41
+ {
42
+ "role": "system",
43
+ "content":
44
+ "you are a good person that listens to me carefully",
45
+ },
46
+ {
47
+ "role": "user",
48
+ "content": "generate a sentence of exactly five words",
49
+ },
50
+ ],
51
+ },
52
+ },
53
+ },
54
+ },
55
+ },
56
+ },
57
+ responses: {
58
+ "200": {
59
+ description: "Successful operation",
60
+ content: {
61
+ "application/json": {
62
+ schema: {
63
+ type: "object",
64
+ properties: {
65
+ data: {
66
+ type: "string",
67
+ example: "response text from the llm",
68
+ },
69
+ },
70
+ },
71
+ },
72
+ },
73
+ },
74
+ "500": {
75
+ description: "Internal Error",
76
+ content: {
77
+ "application/json": {
78
+ schema: {
79
+ $ref: `#/components/schemas/internalErrorResponse`,
80
+ },
81
+ },
82
+ },
83
+ },
84
+ },
85
+ },
86
+ },
87
+ },
88
+ };
@@ -0,0 +1,55 @@
1
+ import type { Config } from "@lobb-js/core";
2
+ import { llm } from "../../index.ts";
3
+
4
+ const API_KEY = Bun.env.CHATGPT_API_KEY;
5
+
6
+ if (!API_KEY) {
7
+ throw new Error("You need to specify the ChatGpt api_key");
8
+ }
9
+
10
+ export const simple: Config = {
11
+ project: {
12
+ name: "Lobb",
13
+ force_sync: true,
14
+ support_email: "support@lobb.com",
15
+ },
16
+ database: {
17
+ host: "localhost",
18
+ port: 5432,
19
+ username: "test",
20
+ password: "test",
21
+ database: "*",
22
+ },
23
+ web_server: {
24
+ host: "0.0.0.0",
25
+ port: 0,
26
+ },
27
+ extensions: [
28
+ llm({
29
+ adapter: "chatgpt",
30
+ settings: {
31
+ model: "gpt-4o-mini",
32
+ api_key: API_KEY,
33
+ },
34
+ }),
35
+ ],
36
+ collections: {
37
+ articles: {
38
+ indexes: {},
39
+ fields: {
40
+ id: { type: "integer" },
41
+ title: {
42
+ type: "string",
43
+ length: 255,
44
+ },
45
+ body: {
46
+ type: "string",
47
+ length: 255,
48
+ },
49
+ published: { type: "bool" },
50
+ number_of_likes: { type: "integer" },
51
+ user_id: { type: "integer" },
52
+ },
53
+ },
54
+ },
55
+ };
@@ -0,0 +1,96 @@
1
+ import { Lobb } from "@lobb-js/core";
2
+ import { expect, afterAll, beforeAll, describe, it } from "bun:test";
3
+ import { llm } from "../index.ts";
4
+ import type { Config } from "@lobb-js/core";
5
+
6
+ // A minimal mock server that mimics the OpenAI /v1/chat/completions endpoint.
7
+ // This lets tests run without a real API key.
8
+ function startMockOpenAIServer() {
9
+ return Bun.serve({
10
+ port: 0, // random available port
11
+ fetch(req) {
12
+ const url = new URL(req.url);
13
+
14
+ if (url.pathname === "/v1/chat/completions") {
15
+ return Response.json({
16
+ id: "mock-id",
17
+ object: "chat.completion",
18
+ choices: [
19
+ {
20
+ index: 0,
21
+ message: { role: "assistant", content: "hello world mock" },
22
+ finish_reason: "stop",
23
+ },
24
+ ],
25
+ usage: { prompt_tokens: 5, completion_tokens: 4, total_tokens: 9 },
26
+ });
27
+ }
28
+
29
+ return new Response("not found", { status: 404 });
30
+ },
31
+ });
32
+ }
33
+
34
+ describe("LLM extension (mock OpenAI server)", () => {
35
+ let lobb: Lobb;
36
+ let baseUrl: string;
37
+ let mockServer: ReturnType<typeof Bun.serve>;
38
+
39
+ beforeAll(async () => {
40
+ mockServer = startMockOpenAIServer();
41
+
42
+ const config: Config = {
43
+ project: { name: "Lobb", force_sync: true },
44
+ database: {
45
+ host: "localhost",
46
+ port: 5432,
47
+ username: "test",
48
+ password: "test",
49
+ database: "*",
50
+ },
51
+ web_server: { host: "0.0.0.0", port: 0 },
52
+ extensions: [
53
+ llm({
54
+ adapter: "chatgpt",
55
+ settings: {
56
+ model: "gpt-4o-mini",
57
+ api_key: "mock-key",
58
+ base_url: `http://localhost:${mockServer.port}/v1`,
59
+ },
60
+ }),
61
+ ],
62
+ collections: {
63
+ articles: {
64
+ indexes: {},
65
+ fields: {
66
+ id: { type: "integer" },
67
+ title: { type: "string", length: 255 },
68
+ },
69
+ },
70
+ },
71
+ };
72
+
73
+ lobb = await Lobb.init(config);
74
+ baseUrl = `http://127.0.0.1:${lobb.webServer.port}`;
75
+ });
76
+
77
+ afterAll(async () => {
78
+ await lobb.close();
79
+ mockServer.stop();
80
+ });
81
+
82
+ it("returns a response from the mock OpenAI server", async () => {
83
+ const response = await fetch(`${baseUrl}/api/collections/llm_chat`, {
84
+ method: "POST",
85
+ headers: { "Content-Type": "application/json" },
86
+ body: JSON.stringify({
87
+ messages: [{ role: "user", content: "say hello" }],
88
+ }),
89
+ });
90
+
91
+ const result = await response.json();
92
+ expect(response.status).toBe(200);
93
+ expect(typeof result.data).toBe("string");
94
+ expect(result.data).toBe("hello world mock");
95
+ });
96
+ });
package/lobb.ts ADDED
@@ -0,0 +1,51 @@
1
+ import { Lobb } from "@lobb-js/core";
2
+ import { llm } from "./extensions/llm/index.ts";
3
+
4
+ const CHATGPT_API_KEY = Deno.env.get("CHATGPT_API_KEY");
5
+
6
+ Lobb.init({
7
+ project: {
8
+ name: "LLM Project",
9
+ force_sync: true,
10
+ support_email: "support@lobb.com",
11
+ },
12
+ database: {
13
+ host: "localhost",
14
+ port: 5432,
15
+ username: "test",
16
+ password: "test",
17
+ database: "llm_ext_tests",
18
+ },
19
+ web_server: {
20
+ host: "0.0.0.0",
21
+ port: 3000,
22
+ },
23
+ extensions: [
24
+ llm({
25
+ adapter: "chatgpt",
26
+ settings: {
27
+ model: "gpt-4o-mini",
28
+ api_key: CHATGPT_API_KEY!,
29
+ },
30
+ }),
31
+ ],
32
+ collections: {
33
+ articles: {
34
+ indexes: {},
35
+ fields: {
36
+ id: { type: "integer" },
37
+ title: {
38
+ type: "string",
39
+ length: 255,
40
+ },
41
+ body: {
42
+ type: "string",
43
+ length: 255,
44
+ },
45
+ published: { type: "bool" },
46
+ number_of_likes: { type: "integer" },
47
+ user_id: { type: "integer" },
48
+ },
49
+ },
50
+ },
51
+ });
package/package.json CHANGED
@@ -1,37 +1,26 @@
1
1
  {
2
2
  "name": "@lobb-js/lobb-ext-llm",
3
- "version": "0.1.16",
3
+ "version": "0.3.1",
4
+ "license": "AGPL-3.0-only",
4
5
  "type": "module",
5
6
  "publishConfig": {
6
7
  "access": "public"
7
8
  },
8
- "files": [
9
- "src"
10
- ],
11
9
  "exports": {
12
- ".": "./src/index.ts"
10
+ ".": "./extensions/llm/index.ts"
13
11
  },
14
12
  "scripts": {
15
- "dev": "vite",
16
- "build": "vite build",
17
- "preview": "vite preview",
18
- "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
13
+ "test": "bun run test:lobb",
14
+ "test:lobb": "bun test extensions/llm/tests",
15
+ "dev": "bun run lobb.ts"
16
+ },
17
+ "dependencies": {
18
+ "@lobb-js/core": "0.13.1",
19
+ "hono": "^4.7.0",
20
+ "ollama": "^0.5.14",
21
+ "openai": "^4.87.3"
19
22
  },
20
23
  "devDependencies": {
21
- "@lobb-js/studio": "0.6.0",
22
- "@lucide/svelte": "^0.563.1",
23
- "@sveltejs/vite-plugin-svelte": "6.2.1",
24
- "@tailwindcss/vite": "^4.1.18",
25
- "@tsconfig/svelte": "^5.0.6",
26
- "@types/node": "^24.10.1",
27
- "clsx": "^2.1.1",
28
- "svelte": "^5.49.1",
29
- "svelte-check": "^4.3.4",
30
- "tailwind-merge": "^3.4.0",
31
- "tailwind-variants": "^3.2.2",
32
- "tailwindcss": "4.1.18",
33
- "tw-animate-css": "^1.4.0",
34
- "typescript": "~5.9.3",
35
- "vite": "6.3.3"
24
+ "bun-types": "latest"
36
25
  }
37
26
  }
package/todo.md ADDED
@@ -0,0 +1,7 @@
1
+ # high priority
2
+
3
+ - todo
4
+
5
+ # low priority
6
+
7
+ - todo
package/src/app.css DELETED
@@ -1,124 +0,0 @@
1
- @import "tailwindcss";
2
-
3
- @import "tw-animate-css";
4
-
5
- @source "../../../../packages";
6
- @source "../node_modules/@lobb-js";
7
-
8
- @custom-variant dark (&:is(.dark *));
9
-
10
- :root {
11
- --radius: 0.625rem;
12
- --background: oklch(1 0 0);
13
- --foreground: oklch(0.129 0.042 264.695);
14
- --card: oklch(1 0 0);
15
- --card-foreground: oklch(0.129 0.042 264.695);
16
- --popover: oklch(1 0 0);
17
- --popover-foreground: oklch(0.129 0.042 264.695);
18
- --primary: oklch(0.208 0.042 265.755);
19
- --primary-foreground: oklch(0.984 0.003 247.858);
20
- --secondary: oklch(0.968 0.007 247.896);
21
- --secondary-foreground: oklch(0.208 0.042 265.755);
22
- --muted: oklch(0.968 0.007 247.896);
23
- --muted-foreground: oklch(0.554 0.046 257.417);
24
- --accent: oklch(0.968 0.007 247.896);
25
- --accent-foreground: oklch(0.208 0.042 265.755);
26
- --destructive: oklch(0.577 0.245 27.325);
27
- --border: oklch(0.929 0.013 255.508);
28
- --input: oklch(0.929 0.013 255.508);
29
- --ring: oklch(0.704 0.04 256.788);
30
- --chart-1: oklch(0.646 0.222 41.116);
31
- --chart-2: oklch(0.6 0.118 184.704);
32
- --chart-3: oklch(0.398 0.07 227.392);
33
- --chart-4: oklch(0.828 0.189 84.429);
34
- --chart-5: oklch(0.769 0.188 70.08);
35
- --sidebar: oklch(0.984 0.003 247.858);
36
- --sidebar-foreground: oklch(0.129 0.042 264.695);
37
- --sidebar-primary: oklch(0.208 0.042 265.755);
38
- --sidebar-primary-foreground: oklch(0.984 0.003 247.858);
39
- --sidebar-accent: oklch(0.968 0.007 247.896);
40
- --sidebar-accent-foreground: oklch(0.208 0.042 265.755);
41
- --sidebar-border: oklch(0.929 0.013 255.508);
42
- --sidebar-ring: oklch(0.704 0.04 256.788);
43
- }
44
-
45
- .dark {
46
- --background: oklch(0.129 0.042 264.695);
47
- --foreground: oklch(0.984 0.003 247.858);
48
- --card: oklch(0.208 0.042 265.755);
49
- --card-foreground: oklch(0.984 0.003 247.858);
50
- --popover: oklch(0.208 0.042 265.755);
51
- --popover-foreground: oklch(0.984 0.003 247.858);
52
- --primary: oklch(0.929 0.013 255.508);
53
- --primary-foreground: oklch(0.208 0.042 265.755);
54
- --secondary: oklch(0.279 0.041 260.031);
55
- --secondary-foreground: oklch(0.984 0.003 247.858);
56
- --muted: oklch(0.279 0.041 260.031);
57
- --muted-foreground: oklch(0.704 0.04 256.788);
58
- --accent: oklch(0.279 0.041 260.031);
59
- --accent-foreground: oklch(0.984 0.003 247.858);
60
- --destructive: oklch(0.704 0.191 22.216);
61
- --border: oklch(1 0 0 / 10%);
62
- --input: oklch(1 0 0 / 15%);
63
- --ring: oklch(0.551 0.027 264.364);
64
- --chart-1: oklch(0.488 0.243 264.376);
65
- --chart-2: oklch(0.696 0.17 162.48);
66
- --chart-3: oklch(0.769 0.188 70.08);
67
- --chart-4: oklch(0.627 0.265 303.9);
68
- --chart-5: oklch(0.645 0.246 16.439);
69
- --sidebar: oklch(0.208 0.042 265.755);
70
- --sidebar-foreground: oklch(0.984 0.003 247.858);
71
- --sidebar-primary: oklch(0.488 0.243 264.376);
72
- --sidebar-primary-foreground: oklch(0.984 0.003 247.858);
73
- --sidebar-accent: oklch(0.279 0.041 260.031);
74
- --sidebar-accent-foreground: oklch(0.984 0.003 247.858);
75
- --sidebar-border: oklch(1 0 0 / 10%);
76
- --sidebar-ring: oklch(0.551 0.027 264.364);
77
- }
78
-
79
- @theme inline {
80
- --radius-sm: calc(var(--radius) - 4px);
81
- --radius-md: calc(var(--radius) - 2px);
82
- --radius-lg: var(--radius);
83
- --radius-xl: calc(var(--radius) + 4px);
84
- --color-background: var(--background);
85
- --color-foreground: var(--foreground);
86
- --color-card: var(--card);
87
- --color-card-foreground: var(--card-foreground);
88
- --color-popover: var(--popover);
89
- --color-popover-foreground: var(--popover-foreground);
90
- --color-primary: var(--primary);
91
- --color-primary-foreground: var(--primary-foreground);
92
- --color-secondary: var(--secondary);
93
- --color-secondary-foreground: var(--secondary-foreground);
94
- --color-muted: var(--muted);
95
- --color-muted-foreground: var(--muted-foreground);
96
- --color-accent: var(--accent);
97
- --color-accent-foreground: var(--accent-foreground);
98
- --color-destructive: var(--destructive);
99
- --color-border: var(--border);
100
- --color-input: var(--input);
101
- --color-ring: var(--ring);
102
- --color-chart-1: var(--chart-1);
103
- --color-chart-2: var(--chart-2);
104
- --color-chart-3: var(--chart-3);
105
- --color-chart-4: var(--chart-4);
106
- --color-chart-5: var(--chart-5);
107
- --color-sidebar: var(--sidebar);
108
- --color-sidebar-foreground: var(--sidebar-foreground);
109
- --color-sidebar-primary: var(--sidebar-primary);
110
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
111
- --color-sidebar-accent: var(--sidebar-accent);
112
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
113
- --color-sidebar-border: var(--sidebar-border);
114
- --color-sidebar-ring: var(--sidebar-ring);
115
- }
116
-
117
- @layer base {
118
- * {
119
- @apply border-border outline-ring/50;
120
- }
121
- body {
122
- @apply bg-background text-foreground;
123
- }
124
- }
package/src/index.ts DELETED
@@ -1,20 +0,0 @@
1
- import type { Extension, ExtensionUtils } from "../extension.types";
2
-
3
- export default function extension(utils: ExtensionUtils): Extension {
4
- return {
5
- name: "llm",
6
- // onStartup: onStartup,
7
- components: {
8
- // "pages.settings": Settings,
9
- },
10
- dashboardNavs: {
11
- middle: [
12
- // {
13
- // label: "LLM",
14
- // icon: utils.components.Icons.Bot,
15
- // href: "/extensions/llm/settings",
16
- // },
17
- ],
18
- },
19
- };
20
- }
package/src/lib/index.ts DELETED
@@ -1 +0,0 @@
1
- // place files you want to import through the `$lib` alias in this folder.
package/src/lib/utils.ts DELETED
@@ -1,13 +0,0 @@
1
- import { clsx, type ClassValue } from "clsx";
2
- import { twMerge } from "tailwind-merge";
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs));
6
- }
7
-
8
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
- export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T;
12
- export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
13
- export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
package/src/main.ts DELETED
@@ -1,14 +0,0 @@
1
- import "./app.css";
2
-
3
- import { mount } from "svelte";
4
- import { Studio } from "@lobb-js/studio";
5
- import { extension } from "./index";
6
-
7
- const app = mount(Studio, {
8
- target: document.getElementById("app")!,
9
- props: {
10
- extensions: [extension],
11
- },
12
- });
13
-
14
- export default app;