@hilbras/angular 0.17.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.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @hilbras/angular — Minimal Angular-compatible signal types
3
+ *
4
+ * These are minimal type definitions that match Angular's signal API.
5
+ * When used with real Angular, the actual @angular/core provides these.
6
+ */
7
+ export interface WritableSignal<T> {
8
+ (): T;
9
+ set(value: T): void;
10
+ update(fn: (value: T) => T): void;
11
+ asReadonly(): Signal<T>;
12
+ }
13
+ export interface Signal<T> {
14
+ (): T;
15
+ }
16
+ export declare function signal<T>(initialValue: T): WritableSignal<T>;
17
+ export declare function computed<T>(fn: () => T): Signal<T>;
18
+ export declare function Injectable(options?: any): ClassDecorator;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @hilbras/angular — Minimal Angular-compatible signal types
3
+ *
4
+ * These are minimal type definitions that match Angular's signal API.
5
+ * When used with real Angular, the actual @angular/core provides these.
6
+ */
7
+ export function signal(initialValue) {
8
+ let value = initialValue;
9
+ const fn = () => value;
10
+ fn.set = (v) => { value = v; };
11
+ fn.update = (upd) => { value = upd(value); };
12
+ fn.asReadonly = () => fn;
13
+ return fn;
14
+ }
15
+ export function computed(fn) {
16
+ return fn;
17
+ }
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ export function Injectable(options) {
20
+ return () => { };
21
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @hilbras/angular — Chat Service
3
+ *
4
+ * Angular service for streaming chat conversations with an LLM backend.
5
+ * Uses Angular signals for reactive state management.
6
+ *
7
+ * Usage:
8
+ * @Component({ ... })
9
+ * export class ChatComponent {
10
+ * chat = inject(HilbrasChatService);
11
+ * messages = this.chat.messages;
12
+ * isLoading = this.chat.isLoading;
13
+ *
14
+ * send() {
15
+ * this.chat.submit({ api: '/api/chat', body: { model: 'gpt-4o' } });
16
+ * }
17
+ * }
18
+ */
19
+ export interface UIMessage {
20
+ id: string;
21
+ role: "user" | "assistant" | "system" | "tool";
22
+ content: string;
23
+ createdAt?: number;
24
+ provider?: string;
25
+ model?: string;
26
+ }
27
+ export interface ChatOptions {
28
+ api: string;
29
+ initialMessages?: UIMessage[];
30
+ provider?: string;
31
+ model?: string;
32
+ onFinish?: (messages: UIMessage[]) => void;
33
+ onError?: (error: Error) => void;
34
+ headers?: Record<string, string>;
35
+ body?: Record<string, unknown>;
36
+ }
37
+ export declare class HilbrasChatService {
38
+ private _messages;
39
+ private _input;
40
+ private _isLoading;
41
+ private _error;
42
+ private _abortController;
43
+ readonly messages: import("./angular-shim.js").Signal<UIMessage[]>;
44
+ readonly input: import("./angular-shim.js").Signal<string>;
45
+ readonly isLoading: import("./angular-shim.js").Signal<boolean>;
46
+ readonly error: import("./angular-shim.js").Signal<Error | null>;
47
+ readonly hasMessages: import("./angular-shim.js").Signal<boolean>;
48
+ setInput(value: string): void;
49
+ stop(): void;
50
+ clear(): void;
51
+ submit(options: ChatOptions): Promise<void>;
52
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * @hilbras/angular — Chat Service
3
+ *
4
+ * Angular service for streaming chat conversations with an LLM backend.
5
+ * Uses Angular signals for reactive state management.
6
+ *
7
+ * Usage:
8
+ * @Component({ ... })
9
+ * export class ChatComponent {
10
+ * chat = inject(HilbrasChatService);
11
+ * messages = this.chat.messages;
12
+ * isLoading = this.chat.isLoading;
13
+ *
14
+ * send() {
15
+ * this.chat.submit({ api: '/api/chat', body: { model: 'gpt-4o' } });
16
+ * }
17
+ * }
18
+ */
19
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
20
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
21
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
22
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
23
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
24
+ };
25
+ import { Injectable, signal, computed } from "./angular-shim.js";
26
+ let _idCounter = 0;
27
+ function generateId() {
28
+ return `msg_${Date.now()}_${_idCounter++}`;
29
+ }
30
+ let HilbrasChatService = class HilbrasChatService {
31
+ _messages = signal([]);
32
+ _input = signal("");
33
+ _isLoading = signal(false);
34
+ _error = signal(null);
35
+ _abortController = null;
36
+ messages = this._messages.asReadonly();
37
+ input = this._input.asReadonly();
38
+ isLoading = this._isLoading.asReadonly();
39
+ error = this._error.asReadonly();
40
+ hasMessages = computed(() => this._messages().length > 0);
41
+ setInput(value) {
42
+ this._input.set(value);
43
+ }
44
+ stop() {
45
+ this._abortController?.abort();
46
+ this._abortController = null;
47
+ this._isLoading.set(false);
48
+ }
49
+ clear() {
50
+ this._messages.set([]);
51
+ this._input.set("");
52
+ this._error.set(null);
53
+ this.stop();
54
+ }
55
+ async submit(options) {
56
+ const trimmed = this._input().trim();
57
+ if (!trimmed || this._isLoading())
58
+ return;
59
+ const userMessage = {
60
+ id: generateId(),
61
+ role: "user",
62
+ content: trimmed,
63
+ createdAt: Date.now(),
64
+ };
65
+ const assistantMessage = {
66
+ id: generateId(),
67
+ role: "assistant",
68
+ content: "",
69
+ createdAt: Date.now(),
70
+ provider: options.provider,
71
+ model: options.model,
72
+ };
73
+ this._messages.update((msgs) => [...msgs, userMessage, assistantMessage]);
74
+ this._input.set("");
75
+ this._isLoading.set(true);
76
+ this._error.set(null);
77
+ const controller = new AbortController();
78
+ this._abortController = controller;
79
+ try {
80
+ const res = await fetch(options.api, {
81
+ method: "POST",
82
+ headers: { "Content-Type": "application/json", ...options.headers },
83
+ body: JSON.stringify({
84
+ messages: this._messages().slice(0, -1).map((m) => ({
85
+ role: m.role,
86
+ content: m.content,
87
+ })),
88
+ stream: true,
89
+ ...options.body,
90
+ }),
91
+ signal: controller.signal,
92
+ });
93
+ if (!res.ok)
94
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
95
+ if (!res.body)
96
+ throw new Error("Response body is null");
97
+ const reader = res.body.getReader();
98
+ const decoder = new TextDecoder();
99
+ let buffer = "";
100
+ while (true) {
101
+ const { done, value } = await reader.read();
102
+ if (done)
103
+ break;
104
+ buffer += decoder.decode(value, { stream: true });
105
+ const parts = buffer.split("\n\n");
106
+ buffer = parts.pop() ?? "";
107
+ for (const part of parts) {
108
+ const lines = part.split("\n");
109
+ let rawData = "";
110
+ for (const line of lines) {
111
+ if (line.startsWith("data: "))
112
+ rawData = line.slice(6).trim();
113
+ }
114
+ if (!rawData || rawData === "[DONE]")
115
+ continue;
116
+ let data;
117
+ try {
118
+ data = JSON.parse(rawData);
119
+ }
120
+ catch {
121
+ continue;
122
+ }
123
+ if (data.type === "text" && typeof data.text === "string") {
124
+ this._messages.update((msgs) => {
125
+ const updated = [...msgs];
126
+ const last = updated[updated.length - 1];
127
+ if (last)
128
+ updated[updated.length - 1] = { ...last, content: last.content + data.text };
129
+ return updated;
130
+ });
131
+ }
132
+ }
133
+ }
134
+ const finalMessages = this._messages();
135
+ options.onFinish?.(finalMessages);
136
+ }
137
+ catch (err) {
138
+ if (err.name === "AbortError")
139
+ return;
140
+ const error = err instanceof Error ? err : new Error(String(err));
141
+ this._error.set(error);
142
+ options.onError?.(error);
143
+ }
144
+ finally {
145
+ this._abortController = null;
146
+ this._isLoading.set(false);
147
+ }
148
+ }
149
+ };
150
+ HilbrasChatService = __decorate([
151
+ Injectable({ providedIn: "root" })
152
+ ], HilbrasChatService);
153
+ export { HilbrasChatService };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @hilbras/angular — Completion Service
3
+ *
4
+ * Angular service for streaming text completions from an LLM backend.
5
+ * Uses Angular signals for reactive state management.
6
+ *
7
+ * Usage:
8
+ * @Component({ ... })
9
+ * export class CompletionComponent {
10
+ * completion = inject(HilbrasCompletionService);
11
+ * text = this.completion.text;
12
+ * }
13
+ */
14
+ export interface CompletionOptions {
15
+ api: string;
16
+ prompt?: string;
17
+ onFinish?: (text: string) => void;
18
+ onError?: (error: Error) => void;
19
+ headers?: Record<string, string>;
20
+ body?: Record<string, unknown>;
21
+ }
22
+ export declare class HilbrasCompletionService {
23
+ private _text;
24
+ private _isLoading;
25
+ private _error;
26
+ private _abortController;
27
+ readonly text: import("./angular-shim.js").Signal<string>;
28
+ readonly isLoading: import("./angular-shim.js").Signal<boolean>;
29
+ readonly error: import("./angular-shim.js").Signal<Error | null>;
30
+ stop(): void;
31
+ clear(): void;
32
+ submit(options: CompletionOptions): Promise<void>;
33
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * @hilbras/angular — Completion Service
3
+ *
4
+ * Angular service for streaming text completions from an LLM backend.
5
+ * Uses Angular signals for reactive state management.
6
+ *
7
+ * Usage:
8
+ * @Component({ ... })
9
+ * export class CompletionComponent {
10
+ * completion = inject(HilbrasCompletionService);
11
+ * text = this.completion.text;
12
+ * }
13
+ */
14
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
15
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
19
+ };
20
+ import { Injectable, signal } from "./angular-shim.js";
21
+ let HilbrasCompletionService = class HilbrasCompletionService {
22
+ _text = signal("");
23
+ _isLoading = signal(false);
24
+ _error = signal(null);
25
+ _abortController = null;
26
+ text = this._text.asReadonly();
27
+ isLoading = this._isLoading.asReadonly();
28
+ error = this._error.asReadonly();
29
+ stop() {
30
+ this._abortController?.abort();
31
+ this._abortController = null;
32
+ this._isLoading.set(false);
33
+ }
34
+ clear() {
35
+ this._text.set("");
36
+ this._error.set(null);
37
+ this.stop();
38
+ }
39
+ async submit(options) {
40
+ if (this._isLoading())
41
+ return;
42
+ this._text.set("");
43
+ this._isLoading.set(true);
44
+ this._error.set(null);
45
+ const controller = new AbortController();
46
+ this._abortController = controller;
47
+ try {
48
+ const res = await fetch(options.api, {
49
+ method: "POST",
50
+ headers: { "Content-Type": "application/json", ...options.headers },
51
+ body: JSON.stringify({
52
+ prompt: options.prompt ?? "",
53
+ stream: true,
54
+ ...options.body,
55
+ }),
56
+ signal: controller.signal,
57
+ });
58
+ if (!res.ok)
59
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
60
+ if (!res.body)
61
+ throw new Error("Response body is null");
62
+ const reader = res.body.getReader();
63
+ const decoder = new TextDecoder();
64
+ let buffer = "";
65
+ let fullText = "";
66
+ while (true) {
67
+ const { done, value } = await reader.read();
68
+ if (done)
69
+ break;
70
+ buffer += decoder.decode(value, { stream: true });
71
+ const parts = buffer.split("\n\n");
72
+ buffer = parts.pop() ?? "";
73
+ for (const part of parts) {
74
+ const lines = part.split("\n");
75
+ let rawData = "";
76
+ for (const line of lines) {
77
+ if (line.startsWith("data: "))
78
+ rawData = line.slice(6).trim();
79
+ }
80
+ if (!rawData || rawData === "[DONE]")
81
+ continue;
82
+ let data;
83
+ try {
84
+ data = JSON.parse(rawData);
85
+ }
86
+ catch {
87
+ continue;
88
+ }
89
+ if (data.type === "text" && typeof data.text === "string") {
90
+ fullText += data.text;
91
+ this._text.set(fullText);
92
+ }
93
+ }
94
+ }
95
+ options.onFinish?.(fullText);
96
+ }
97
+ catch (err) {
98
+ if (err.name === "AbortError")
99
+ return;
100
+ const error = err instanceof Error ? err : new Error(String(err));
101
+ this._error.set(error);
102
+ options.onError?.(error);
103
+ }
104
+ finally {
105
+ this._abortController = null;
106
+ this._isLoading.set(false);
107
+ }
108
+ }
109
+ };
110
+ HilbrasCompletionService = __decorate([
111
+ Injectable({ providedIn: "root" })
112
+ ], HilbrasCompletionService);
113
+ export { HilbrasCompletionService };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @hilbras/angular — Public API
3
+ *
4
+ * Angular services for building streaming LLM UIs with @hilbras/sdk.
5
+ */
6
+ export { HilbrasChatService } from "./chat.service.js";
7
+ export type { ChatOptions } from "./chat.service.js";
8
+ export { HilbrasCompletionService } from "./completion.service.js";
9
+ export type { CompletionOptions } from "./completion.service.js";
10
+ export { HilbrasObjectService } from "./object.service.js";
11
+ export type { ObjectOptions } from "./object.service.js";
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @hilbras/angular — Public API
3
+ *
4
+ * Angular services for building streaming LLM UIs with @hilbras/sdk.
5
+ */
6
+ export { HilbrasChatService } from "./chat.service.js";
7
+ export { HilbrasCompletionService } from "./completion.service.js";
8
+ export { HilbrasObjectService } from "./object.service.js";
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @hilbras/angular — Object Service
3
+ *
4
+ * Angular service for streaming structured JSON output from an LLM backend.
5
+ * Uses Angular signals for reactive state management.
6
+ *
7
+ * Usage:
8
+ * @Component({ ... })
9
+ * export class ObjectComponent {
10
+ * obj = inject(HilbrasObjectService);
11
+ * partial = this.obj.object;
12
+ * }
13
+ */
14
+ export interface ObjectOptions<T> {
15
+ api: string;
16
+ schemaName: string;
17
+ onFinish?: (object: T) => void;
18
+ onError?: (error: Error) => void;
19
+ headers?: Record<string, string>;
20
+ body?: Record<string, unknown>;
21
+ }
22
+ export declare class HilbrasObjectService {
23
+ private _object;
24
+ private _isLoading;
25
+ private _error;
26
+ private _abortController;
27
+ readonly object: import("./angular-shim.js").Signal<Partial<unknown> | null>;
28
+ readonly isLoading: import("./angular-shim.js").Signal<boolean>;
29
+ readonly error: import("./angular-shim.js").Signal<Error | null>;
30
+ stop(): void;
31
+ clear(): void;
32
+ submit<T>(options: ObjectOptions<T>): Promise<void>;
33
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @hilbras/angular — Object Service
3
+ *
4
+ * Angular service for streaming structured JSON output from an LLM backend.
5
+ * Uses Angular signals for reactive state management.
6
+ *
7
+ * Usage:
8
+ * @Component({ ... })
9
+ * export class ObjectComponent {
10
+ * obj = inject(HilbrasObjectService);
11
+ * partial = this.obj.object;
12
+ * }
13
+ */
14
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
15
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
19
+ };
20
+ import { Injectable, signal } from "./angular-shim.js";
21
+ let HilbrasObjectService = class HilbrasObjectService {
22
+ _object = signal(null);
23
+ _isLoading = signal(false);
24
+ _error = signal(null);
25
+ _abortController = null;
26
+ object = this._object.asReadonly();
27
+ isLoading = this._isLoading.asReadonly();
28
+ error = this._error.asReadonly();
29
+ stop() {
30
+ this._abortController?.abort();
31
+ this._abortController = null;
32
+ this._isLoading.set(false);
33
+ }
34
+ clear() {
35
+ this._object.set(null);
36
+ this._error.set(null);
37
+ this.stop();
38
+ }
39
+ async submit(options) {
40
+ if (this._isLoading())
41
+ return;
42
+ this._object.set(null);
43
+ this._isLoading.set(true);
44
+ this._error.set(null);
45
+ const controller = new AbortController();
46
+ this._abortController = controller;
47
+ try {
48
+ const res = await fetch(options.api, {
49
+ method: "POST",
50
+ headers: { "Content-Type": "application/json", ...options.headers },
51
+ body: JSON.stringify({
52
+ schema: options.schemaName,
53
+ stream: true,
54
+ ...options.body,
55
+ }),
56
+ signal: controller.signal,
57
+ });
58
+ if (!res.ok)
59
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
60
+ if (!res.body)
61
+ throw new Error("Response body is null");
62
+ const reader = res.body.getReader();
63
+ const decoder = new TextDecoder();
64
+ let buffer = "";
65
+ let partialArgs = "";
66
+ let result = {};
67
+ while (true) {
68
+ const { done, value } = await reader.read();
69
+ if (done)
70
+ break;
71
+ buffer += decoder.decode(value, { stream: true });
72
+ const parts = buffer.split("\n\n");
73
+ buffer = parts.pop() ?? "";
74
+ for (const part of parts) {
75
+ const lines = part.split("\n");
76
+ let rawData = "";
77
+ for (const line of lines) {
78
+ if (line.startsWith("data: "))
79
+ rawData = line.slice(6).trim();
80
+ }
81
+ if (!rawData || rawData === "[DONE]")
82
+ continue;
83
+ let data;
84
+ try {
85
+ data = JSON.parse(rawData);
86
+ }
87
+ catch {
88
+ continue;
89
+ }
90
+ if (data.type === "tool_call_delta" && data.args) {
91
+ partialArgs += data.args;
92
+ try {
93
+ result = JSON.parse(partialArgs);
94
+ this._object.set({ ...result });
95
+ }
96
+ catch { /* partial JSON */ }
97
+ }
98
+ }
99
+ }
100
+ options.onFinish?.(result);
101
+ }
102
+ catch (err) {
103
+ if (err.name === "AbortError")
104
+ return;
105
+ const error = err instanceof Error ? err : new Error(String(err));
106
+ this._error.set(error);
107
+ options.onError?.(error);
108
+ }
109
+ finally {
110
+ this._abortController = null;
111
+ this._isLoading.set(false);
112
+ }
113
+ }
114
+ };
115
+ HilbrasObjectService = __decorate([
116
+ Injectable({ providedIn: "root" })
117
+ ], HilbrasObjectService);
118
+ export { HilbrasObjectService };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@hilbras/angular",
3
+ "version": "0.17.0",
4
+ "description": "Angular services for @hilbras/sdk — chat, completion, object streaming",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "test": "vitest run"
20
+ },
21
+ "peerDependencies": {
22
+ "@angular/core": ">=19",
23
+ "@angular/common": ">=19",
24
+ "@hilbras/sdk": ">=0.16.0",
25
+ "rxjs": ">=7"
26
+ },
27
+ "devDependencies": {
28
+ "@angular/core": "^19.0.0",
29
+ "@angular/common": "^19.0.0",
30
+ "rxjs": "^7.8.0",
31
+ "typescript": "^7.0.2",
32
+ "vitest": "^4.1.11"
33
+ },
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/Hilbras/Hilbras-ai-sdk.git",
38
+ "directory": "packages/angular"
39
+ }
40
+ }