UNPKG

@hashbrownai/angular

Version:
679 lines (658 loc) 23.5 kB
import * as i0 from '@angular/core'; import { Injector, inject, runInInjectionContext, untracked, reflectComponentType, DestroyRef, signal, input, ApplicationRef, computed, ViewContainerRef, Component, InjectionToken, effect } from '@angular/core'; import { NgComponentOutlet, NgTemplateOutlet } from '@angular/common'; import { ɵcreateRuntimeFunctionImpl as _createRuntimeFunctionImpl, ɵcreateRuntimeImpl as _createRuntimeImpl, s, fryHashbrown, ɵcomponents as _components } from '@hashbrownai/core'; /* eslint-disable @typescript-eslint/no-explicit-any */ function createRuntimeFunction(cfg) { const injector = inject(Injector); return _createRuntimeFunctionImpl({ ...cfg, handler: (...args) => { return runInInjectionContext(injector, () => cfg.handler(...args)); }, }); } /* eslint-disable @typescript-eslint/no-explicit-any */ /** * Creates a new runtime. * * @param options - The options for creating the runtime. * @returns A reference to the runtime. */ function createRuntime(options) { return _createRuntimeImpl(options); } function createTool(input) { const injector = inject(Injector); if ('schema' in input) { const { name, description, schema, handler } = input; return { name, description, schema, handler: (args, abortSignal) => untracked(() => runInInjectionContext(injector, () => handler(args, abortSignal))), }; } else { const { name, description, handler } = input; return { name, description, schema: s.object('Empty Object', {}), handler: (_, abortSignal) => untracked(() => runInInjectionContext(injector, () => handler(abortSignal))), }; } } /** * Creates a tool that allows the LLM to run JavaScript code. It is run * in a stateful JavaScript environment, with no access to the internet, the DOM, * or any function that you have not explicitly defined. * * @param options - The options for creating the tool. * @returns The tool. */ function createToolJavaScript({ runtime }) { return createTool({ name: 'javascript', description: [ 'Whenever you send a message containing JavaScript code to javascript, it will be', 'executed in a stateful JavaScript environment. javascript will respond with the output', 'of the execution or time out after ${runtime.timeout / 1000} seconds. Internet access', 'for this session is disabled. Do not make external web requests or API calls as they', 'will fail.', '', 'Important: Prefer calling javascript once with a large amount of code, rather than calling it', 'multiple times with smaller amounts of code.', '', 'The following functions are available to you:', runtime.describe(), ].join('\n'), schema: s.streaming.object('The result', { code: s.streaming.string('The JavaScript code to run'), }), handler: async ({ code }, abortSignal) => { return runtime.run(code, abortSignal); }, }); } /* eslint-disable @typescript-eslint/no-explicit-any */ /** * Exposes a component by combining it with additional configuration details. * * @template T - The type of the Angular component. * @param {T} component - The Angular component to be exposed. * @param {Prettify<Omit<ExposedComponent<T>, 'component' | 'name'>>} config - The configuration object for the component, excluding the component itself. * @returns {ExposedComponent<T>} - An object representing the exposed component, including the component and its configuration. */ function exposeComponent(component, config) { const reflected = reflectComponentType(component); if (!reflected?.selector) { throw new Error(`Could not reflect component: ${component}`); } const { input, ...rest } = config; return { component, ...rest, props: input, name: reflected?.selector, }; } function readSignalLike(signalLike) { if (typeof signalLike === 'function') { return signalLike(); } return signalLike; } function toNgSignal(source, debugName) { const destroyRef = inject(DestroyRef); const _signal = signal(source(), { debugName, }); const teardown = source.subscribe((value) => { _signal.set(value); }); destroyRef.onDestroy(() => { teardown(); }); return _signal.asReadonly(); } const TAG_NAME_REGISTRY = Symbol('ɵtagNameRegistry'); const getTagNameRegistry = (message) => { if (TAG_NAME_REGISTRY in message) { return message[TAG_NAME_REGISTRY]; } return undefined; }; /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @angular-eslint/component-selector */ /** * Renders messages generated by the assistant from uiChatResource. * * @example * * ```html * <hb-render-message [message]="message" /> * ``` */ class RenderMessageComponent { message = input.required(); /** * @internal */ appRef = inject(ApplicationRef); /** * @internal */ content = computed(() => this.message().content?.ui ?? []); /** * @internal */ tagNameRegistry = computed(() => getTagNameRegistry(this.message())); /** * @internal */ viewContainerRef = inject(ViewContainerRef); /** * @internal */ rootNodesWeakMap = new WeakMap(); /** * @internal */ embeddedViewsWeakMap = new WeakMap(); /** * @internal */ getTagComponent(tagName) { return this.tagNameRegistry()?.[tagName]?.component ?? null; } /** * @internal */ getEmbeddedView(tpl) { if (this.embeddedViewsWeakMap.has(tpl)) { return this.embeddedViewsWeakMap.get(tpl); } const view = this.viewContainerRef.createEmbeddedView(tpl); this.embeddedViewsWeakMap.set(tpl, view); return view; } /** * @internal */ getRootNodes(tpl) { if (this.rootNodesWeakMap.has(tpl)) { return this.rootNodesWeakMap.get(tpl); } const view = this.getEmbeddedView(tpl); const nodes = [view.rootNodes]; this.rootNodesWeakMap.set(tpl, nodes); return nodes; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: RenderMessageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.5", type: RenderMessageComponent, isStandalone: true, selector: "hb-render-message", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: ` <ng-template #nodeTemplateRef let-node="node"> <ng-template #childrenTemplateRef> @for (child of node.$children; track $index) { <ng-container *ngTemplateOutlet="nodeTemplateRef; context: { node: child }" /> } </ng-template> @if (node) { <ng-container *ngComponentOutlet=" getTagComponent(node.$tagName); inputs: node.$props; content: getRootNodes(childrenTemplateRef) " ></ng-container> } </ng-template> @if (content()) { @for (node of content(); track $index) { <ng-template [ngTemplateOutlet]="nodeTemplateRef" [ngTemplateOutletContext]="node" > </ng-template> <ng-container *ngTemplateOutlet="nodeTemplateRef; context: { node: node }" /> } } `, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.5", ngImport: i0, type: RenderMessageComponent, decorators: [{ type: Component, args: [{ selector: 'hb-render-message', imports: [NgComponentOutlet, NgTemplateOutlet], template: ` <ng-template #nodeTemplateRef let-node="node"> <ng-template #childrenTemplateRef> @for (child of node.$children; track $index) { <ng-container *ngTemplateOutlet="nodeTemplateRef; context: { node: child }" /> } </ng-template> @if (node) { <ng-container *ngComponentOutlet=" getTagComponent(node.$tagName); inputs: node.$props; content: getRootNodes(childrenTemplateRef) " ></ng-container> } </ng-template> @if (content()) { @for (node of content(); track $index) { <ng-template [ngTemplateOutlet]="nodeTemplateRef" [ngTemplateOutletContext]="node" > </ng-template> <ng-container *ngTemplateOutlet="nodeTemplateRef; context: { node: node }" /> } } `, }] }] }); /** * @internal */ const ɵHASHBROWN_CONFIG_INJECTION_TOKEN = new InjectionToken('HashbrownConfig'); /** * Provides the Hashbrown configuration. * * @argument options - The Hashbrown configuration. * @returns The Hashbrown configuration. */ function provideHashbrown(options) { return { provide: ɵHASHBROWN_CONFIG_INJECTION_TOKEN, useValue: options, }; } /** * @internal */ function ɵinjectHashbrownConfig() { return inject(ɵHASHBROWN_CONFIG_INJECTION_TOKEN); } /* eslint-disable @typescript-eslint/no-explicit-any */ /** * Creates a chat resource for managing LLM-driven conversations. * * @template Tools * @param {ChatResourceOptions<Tools>} options - Configuration for the chat resource. * @returns {ChatResourceRef<Tools>} An object with reactive signals and methods for interacting with the chat. */ function chatResource(options) { const config = ɵinjectHashbrownConfig(); const injector = inject(Injector); const destroyRef = inject(DestroyRef); const hashbrown = fryHashbrown({ apiUrl: options.apiUrl ?? config.baseUrl, middleware: config.middleware?.map((m) => { return (requestInit) => runInInjectionContext(injector, () => m(requestInit)); }), system: readSignalLike(options.system), model: readSignalLike(options.model), tools: options.tools, emulateStructuredOutput: config.emulateStructuredOutput, debugName: options.debugName, }); const teardown = hashbrown.sizzle(); destroyRef.onDestroy(() => teardown()); const value = toNgSignal(hashbrown.messages, options.debugName && `${options.debugName}.value`); const isReceiving = toNgSignal(hashbrown.isReceiving, options.debugName && `${options.debugName}.isReceiving`); const isSending = toNgSignal(hashbrown.isSending, options.debugName && `${options.debugName}.isSending`); const isRunningToolCalls = toNgSignal(hashbrown.isRunningToolCalls, options.debugName && `${options.debugName}.isRunningToolCalls`); const error = toNgSignal(hashbrown.error, options.debugName && `${options.debugName}.error`); const lastAssistantMessage = toNgSignal(hashbrown.lastAssistantMessage, options.debugName && `${options.debugName}.lastAssistantMessage`); const status = computed(() => { if (isReceiving() || isSending() || isRunningToolCalls()) { return 'loading'; } if (error()) { return 'error'; } const hasAssistantMessage = value().some((message) => message.role === 'assistant'); if (hasAssistantMessage) { return 'resolved'; } return 'idle'; }, { debugName: options.debugName && `${options.debugName}.status` }); const isLoading = computed(() => { return isReceiving() || isSending() || isRunningToolCalls(); }, { debugName: options.debugName && `${options.debugName}.isLoading` }); function reload() { const lastMessage = value()[value().length - 1]; if (lastMessage.role === 'assistant') { hashbrown.setMessages(value().slice(0, -1)); return true; } return false; } function hasValue() { return value().some((message) => message.role === 'assistant'); } function sendMessage(message) { hashbrown.sendMessage(message); } function stop(clearStreamingMessage = false) { hashbrown.stop(clearStreamingMessage); } return { hasValue: hasValue, status, isLoading, reload, sendMessage, stop, value, error, lastAssistantMessage, }; } /* eslint-disable @typescript-eslint/no-explicit-any */ /** * Creates a completion resource. * * @param options - The options for the completion resource. * @returns The completion resource. */ function completionResource(options) { const { model, input, system } = options; const injector = inject(Injector); const destroyRef = inject(DestroyRef); const config = ɵinjectHashbrownConfig(); const hashbrown = fryHashbrown({ debugName: options.debugName, apiUrl: options.apiUrl ?? config.baseUrl, middleware: config.middleware?.map((m) => { return (requestInit) => runInInjectionContext(injector, () => m(requestInit)); }), model: readSignalLike(model), system: readSignalLike(system), messages: [], tools: [], retries: 3, }); const teardown = hashbrown.sizzle(); destroyRef.onDestroy(() => teardown()); const messages = toNgSignal(hashbrown.messages); const internalMessages = computed(() => { const _input = input(); if (!_input) { return []; } return [ { role: 'user', content: _input, }, ]; }); const error = toNgSignal(hashbrown.error, options.debugName && `${options.debugName}.error`); const exhaustedRetries = toNgSignal(hashbrown.exhaustedRetries, options.debugName && `${options.debugName}.exhaustedRetries`); effect(() => { const _messages = internalMessages(); hashbrown.setMessages(_messages); }); const value = computed(() => { const lastMessage = messages()[messages().length - 1]; if (lastMessage && lastMessage.role === 'assistant' && lastMessage.content && typeof lastMessage.content === 'string') { return lastMessage.content; } return null; }, { debugName: options.debugName && `${options.debugName}.value` }); const status = computed(() => { if (exhaustedRetries()) { return 'error'; } return 'idle'; }); const isLoading = signal(false); const reload = () => { return true; }; function hasValue() { return Boolean(value()); } function stop(clearStreamingMessage = false) { hashbrown.stop(clearStreamingMessage); } return { value, status, error, isLoading, reload, stop, hasValue: hasValue, }; } /* eslint-disable @typescript-eslint/no-explicit-any */ /** * Creates a structured chat resource. * * @param options - The options for the structured chat resource. * @returns The structured chat resource. */ function structuredChatResource(options) { const config = ɵinjectHashbrownConfig(); const injector = inject(Injector); const destroyRef = inject(DestroyRef); const hashbrown = fryHashbrown({ apiUrl: options.apiUrl ?? config.baseUrl, middleware: config.middleware?.map((m) => { return (requestInit) => runInInjectionContext(injector, () => m(requestInit)); }), system: readSignalLike(options.system), messages: [...(options.messages ?? [])], model: readSignalLike(options.model), tools: options.tools, responseSchema: options.schema, debugName: options.debugName, emulateStructuredOutput: config.emulateStructuredOutput, debounce: options.debounce, retries: options.retries, }); const teardown = hashbrown.sizzle(); destroyRef.onDestroy(() => teardown()); const value = toNgSignal(hashbrown.messages, options.debugName && `${options.debugName}.value`); const isReceiving = toNgSignal(hashbrown.isReceiving, options.debugName && `${options.debugName}.isReceiving`); const isSending = toNgSignal(hashbrown.isSending, options.debugName && `${options.debugName}.isSending`); const isRunningToolCalls = toNgSignal(hashbrown.isRunningToolCalls, options.debugName && `${options.debugName}.isRunningToolCalls`); const error = toNgSignal(hashbrown.error, options.debugName && `${options.debugName}.error`); const lastAssistantMessage = toNgSignal(hashbrown.lastAssistantMessage, options.debugName && `${options.debugName}.lastAssistantMessage`); const exhaustedRetries = toNgSignal(hashbrown.exhaustedRetries); const status = computed(() => { if (isReceiving() || isSending() || isRunningToolCalls()) { return 'loading'; } if (exhaustedRetries()) { return 'error'; } const hasAssistantMessage = value().some((message) => message.role === 'assistant'); if (hasAssistantMessage) { return 'resolved'; } return 'idle'; }, { debugName: options.debugName && `${options.debugName}.status` }); const isLoading = computed(() => { return isReceiving() || isSending() || isRunningToolCalls(); }, { debugName: options.debugName && `${options.debugName}.isLoading` }); function reload() { const lastMessage = value()[value().length - 1]; if (lastMessage.role === 'assistant') { hashbrown.setMessages(value().slice(0, -1)); return true; } return false; } function hasValue() { return value().some((message) => message.role === 'assistant'); } function sendMessage(message) { hashbrown.sendMessage(message); } function resendMessages() { hashbrown.resendMessages(); } function setMessages(messages) { hashbrown.setMessages(messages); } function stop(clearStreamingMessage = false) { hashbrown.stop(clearStreamingMessage); } return { hasValue: hasValue, status, isLoading, reload, sendMessage, resendMessages, stop, value, error, setMessages, lastAssistantMessage, }; } /* eslint-disable @typescript-eslint/no-explicit-any */ /** * Creates a structured completion resource. * * @param options - The options for the structured completion resource. * @returns The structured completion resource. */ function structuredCompletionResource(options) { const { model, input, schema, system, tools, debugName, apiUrl } = options; const resource = structuredChatResource({ model, system, schema, tools, debugName, retries: 3, apiUrl, }); effect(() => { const _input = input(); if (!_input) { return; } resource.setMessages([ { role: 'user', content: _input, }, ]); }); const value = computed(() => { const lastMessage = resource.value()[resource.value().length - 1]; if (lastMessage && lastMessage.role === 'assistant' && lastMessage.content && lastMessage.content !== null) { return lastMessage.content; } return null; }, { debugName: debugName && `${debugName}.value` }); const status = resource.status; const error = resource.error; const isLoading = resource.isLoading; const reload = resource.reload; const stop = resource.stop; function hasValue() { return Boolean(value()); } return { value, status, error, isLoading, reload, stop, hasValue: hasValue, }; } /* eslint-disable @typescript-eslint/no-explicit-any */ /** * Creates a UI chat resource. * * @param args - The arguments for the UI chat resource. * @returns The UI chat resource. */ function uiChatResource(args) { const flattenedComponents = computed(() => _components.flattenComponents(args.components)); const internalSchema = s.object('UI', { ui: s.streaming.array('List of elements', _components.createComponentSchema(args.components)), }); const chat = structuredChatResource({ model: args.model, schema: internalSchema, tools: [...(args.tools ?? [])], system: args.system, messages: [...(args.messages ?? [])], debugName: args.debugName, debounce: args.debounce, apiUrl: args.apiUrl, }); const value = computed(() => { const messages = chat.value(); return messages.map((message) => { if (message.role === 'assistant') { const content = message.content; if (!content) { return { ...message, [TAG_NAME_REGISTRY]: {}, }; } return { ...message, [TAG_NAME_REGISTRY]: Array.from(flattenedComponents().values()).reduce((acc, component) => { acc[component.name] = { props: component.props ?? {}, component: component.component, }; return acc; }, {}) ?? {}, }; } if (message.role === 'user') { return message; } if (message.role === 'error') { return message; } throw new Error(`Unknown message role`); }); }, { debugName: args.debugName && `${args.debugName}.value` }); const lastAssistantMessage = computed(() => { return value().findLast((message) => message.role === 'assistant'); }); return { ...chat, hasValue: chat.hasValue, value, lastAssistantMessage, }; } /** * Generated bundle index. Do not edit. */ export { RenderMessageComponent, chatResource, completionResource, createRuntime, createRuntimeFunction, createTool, createToolJavaScript, exposeComponent, provideHashbrown, structuredChatResource, structuredCompletionResource, uiChatResource }; //# sourceMappingURL=hashbrownai-angular.mjs.map