import { NgComponentOutlet, AsyncPipe } from '@angular/common';
import * as i0 from '@angular/core';
import { Injectable, inject, input, computed, output, signal, viewChildren, Component, Injector, Input } from '@angular/core';
import { GainsightService, DatePipe, ModalService, AlertService, C8Y_PLUGIN_CONTEXT_PATH, AppStateService, Status, LoadingComponent, EmptyStateComponent, C8yTranslateDirective, GuideDocsComponent, GuideHrefDirective, MarkdownToHtmlPipe, C8yTranslatePipe, ContextRouteService } from '@c8y/ngx-components';
import { TranslateService } from '@ngx-translate/core';
import { AIService, defaultPruneMessagesForAgent } from '@c8y/ngx-components/ai';
import { gettext } from '@c8y/ngx-components/gettext';
import { AiChatAssistantMessageComponent, AiChatComponent, AiChatSuggestionComponent, AiChatMessageComponent, AiChatMessageActionComponent } from '@c8y/ngx-components/ai/ai-chat';
import { CollapseModule } from 'ngx-bootstrap/collapse';
import { WidgetConfigService, WidgetConfigFeedbackComponent } from '@c8y/ngx-components/context-dashboard';
import { of, isObservable } from 'rxjs';

/** A stateless service used by agent-chat for managing chat history. Not public. */
class ChatHistoryService {
    /**
     * Save messages and suggestions to an opaque, versioned, JSON-serializable snapshot for persistence.
     *
     * Only recent tool results and reasoning are included to save space.
     * Additional options for compressing the history can be provided using the config parameters.
     *
     * @param messages The current conversation messages to save
     * @param suggestions The current suggestions to include in the snapshot
     * @param config Optional configuration to limit what gets saved
     */
    save(messages, suggestions, config) {
        let msgs = messages;
        // Apply maxMessages limit - keep most recent messages
        if (config?.maxMessages && msgs.length > config.maxMessages) {
            msgs = msgs.slice(-config.maxMessages);
        }
        const transientToolNames = new Set(config?.transientToolNames || []);
        // NB: in future we could use tool metadata from the MCP servers to identify transient tools automatically
        // Only keep tool results and reasoning for the most recent N messages (=N/2 assistant messages)
        const recentMessageStartIndex = msgs.length - Math.min(10, msgs.length);
        const history = {
            messages: msgs.map((msg, msgIndex) => {
                const isRecentMessage = msgIndex >= recentMessageStartIndex;
                return {
                    role: msg.role,
                    content: msg.content,
                    ...(msg.timestamp ? { timestamp: msg.timestamp } : {}),
                    ...(msg.steps
                        ? {
                            steps: msg.steps.map(step => ({
                                // Specifically include just the fields we're interested in
                                type: step.type,
                                text: step.text,
                                aborted: step.aborted,
                                // Strip out ALL toolCalls, since these are anyway transient
                                toolCalls: undefined,
                                // Keep recent reasoning only
                                reasoning: isRecentMessage ? step.reasoning : undefined,
                                // Keep toolResults only if: in recent 10 messages AND not transient
                                toolResults: isRecentMessage && step.toolResults
                                    ? step.toolResults.filter(result => !transientToolNames.has(result.toolName))
                                    : undefined
                            }))
                        }
                        : {})
                };
            }),
            suggestions: suggestions,
            chatHistoryVersion: 1
        };
        //console.debug("Saving chat history as: ", history);
        return history;
    }
    /**
     * Restore a previously saved chat history snapshot.
     *
     * Validates the snapshot format and returns the messages and suggestions.
     * Throws an error if the snapshot is invalid.
     *
     * @param history A snapshot previously returned by `save()`
     */
    restore(history) {
        // Validate history structure and messages
        if (typeof history !== 'object' ||
            history === null ||
            !('messages' in history) ||
            !Array.isArray(history['messages'])) {
            throw new Error('Invalid chat history format');
        }
        if (history['chatHistoryVersion'] !== 1) {
            throw new Error(`Cannot load chat history - expected version 1 but got ${history['chatHistoryVersion']}`);
        }
        const messagesArray = history['messages'];
        if (messagesArray.some((msg, _) => !msg || typeof msg !== 'object' || !msg.role || typeof msg.content !== 'string')) {
            throw new Error('Invalid message format in chat history');
        }
        const suggestions = Array.isArray(history['suggestions'])
            ? history['suggestions']
            : [];
        return {
            messages: messagesArray,
            suggestions
        };
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ChatHistoryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ChatHistoryService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ChatHistoryService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root'
                }]
        }] });

/** A service used to send product experience data to GainSight PX. Not public. */
class UserAnalyticsService {
    constructor() {
        this.gainsightService = inject(GainsightService);
        /** If needed we could set this to false in production to only include hashes of the messages,
         * if there's a decision that customer data shouldn't be uploaded to GainSight. */
        this.includeCustomerSensitiveDataInAnalytics = true;
    }
    /** Create a dictionary of context from the agent-chat component that is useful to include in all GainSight messages */
    async getAnalyticsMetadataContext(agentName, agentDefinition, userAnalyticsContext, model, systemPrompt) {
        let systemPromptHash = undefined;
        if (systemPrompt) {
            systemPromptHash = await this._computeHash(systemPrompt.map(s => s.text).join('\n\n'));
        }
        else if (agentDefinition && agentDefinition.definition) {
            // Fall back to getting it from the agent definition (if provided)
            systemPromptHash = await this._computeHash(agentDefinition.definition.agent.system);
        }
        return {
            // User-defined context first
            ...(userAnalyticsContext || {}),
            model: model,
            agent: agentName,
            // Since we don't currently have any other version indicator available, a hash of the system prompt is useful to correlate feedback with the exact prompt used
            systemPromptHash: systemPromptHash
        };
    }
    /** Compute SHA-256 hash of a string for correlation purposes (assumes a secure context where crypto.subtle.digest is available) */
    async _computeHash(message) {
        const encoder = new TextEncoder();
        const data = encoder.encode(message);
        const hashBuffer = await crypto.subtle.digest('SHA-256', data);
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    }
    /** Summarize message metrics for GainSight analytics (without including any proprietary data) */
    async _summarizeMessagesForGainSight(userMessage, assistantMessage, detailed = false) {
        // NB: Do NOT send any message content to GainSight servers because that's proprietary/personal customer-owned data that we must protect
        // except when includeCustomerSensitiveDataInAnalytics is set;
        // include hashes of messages so we can always correlate with the history stored on our own servers if available
        const allAssistantText = assistantMessage.steps
            ? assistantMessage.steps
                ?.filter(s => s.text)
                .map(s => s.text || '')
                .join('\n---\n') || ''
            : assistantMessage.content;
        // Collect all unique tool names from each step's toolResults
        const toolNames = assistantMessage.steps
            ?.flatMap(s => s.toolResults?.map(tr => tr.toolName) || [])
            ?.filter((name, index, self) => self.indexOf(name) === index) || [];
        return {
            assistantMessageTimestamp: assistantMessage.timestamp,
            // Track how long we're spending waiting for the AI
            assistantDurationSecs: assistantMessage.timestamp && userMessage.timestamp
                ? (new Date(assistantMessage.timestamp).getTime() -
                    new Date(userMessage.timestamp).getTime()) /
                    1000
                : undefined,
            assistantMessageHash: await this._computeHash(allAssistantText),
            assistantMessage: this.includeCustomerSensitiveDataInAnalytics ? allAssistantText : undefined,
            // although harder to read, it's useful to include the full message including tool calls and steps the feedback is due to bugs etc
            assistantMessageJSON: this.includeCustomerSensitiveDataInAnalytics && detailed
                ? JSON.stringify(assistantMessage)
                : undefined,
            userMessageHash: userMessage ? await this._computeHash(userMessage.content) : '',
            userMessage: this.includeCustomerSensitiveDataInAnalytics ? userMessage.content : undefined,
            assistantMessageSteps: assistantMessage.steps?.length || 0,
            // nb: toolCalls here refers to completed calls (i.e. toolResults), we shouldn't have any calls still in progress at this point
            assistantMessageToolCallsCount: assistantMessage.steps?.reduce((sum, s) => sum + (s.toolResults?.length || 0), 0) || 0,
            assistantMessageToolNames: toolNames.length === 0 ? undefined : toolNames.join(','),
            assistantMessageLines: allAssistantText.split('\n').length,
            userMessageLength: userMessage.content.length,
            userMessageLines: userMessage.content.split('\n').length
        };
    }
    async sendAssistantMessageComplete(assistantMessage, allMessages, analyticsMetadataContext) {
        // Can't use lastIndexOf(assistantMessage) here (as we for for feedback) because allMessages may not yet have been updated with this assistantMessage item
        const userMessage = [...allMessages].reverse().find(m => m.role === 'user');
        if (!userMessage) {
            console.warn('No user message found for assistant message, skipping analytics event');
            return;
        }
        this.gainsightService.triggerEvent('ai.agent.message', {
            messageCount: allMessages.length,
            ...(await this._summarizeMessagesForGainSight(userMessage, assistantMessage)),
            ...analyticsMetadataContext
        });
    }
    /** Sent when there is a streaming error from the agent */
    async sendAssistantMessageError(error, allMessages, analyticsMetadataContext) {
        this.gainsightService.triggerEvent('ai.agent.error', {
            errorMessage: error,
            messageCount: allMessages.length,
            ...analyticsMetadataContext
        });
    }
    /** Sent when the agent is unavailable, for example because the microservice is not present or has insufficient permissions.
     * This is useful to find out what kinds of problems users are mostly having to get access.
     */
    async sendAgentUnavailable(errorType) {
        this.gainsightService.triggerEvent('ai.agent.unavailable', {
            errorType: errorType
        });
    }
    /** Rates an AI message as positive/negative, sending the feedback to GainSight */
    async sendFeedbackOnAssistantMessage(assistantMessage, allMessages, positive, analyticsMetadataContext) {
        const userMessage = allMessages[allMessages.lastIndexOf(assistantMessage) - 1];
        const detailedFeedback = !positive;
        this.gainsightService.triggerEvent('ai.agent.feedback', {
            positive,
            messageCount: allMessages.length,
            ...(await this._summarizeMessagesForGainSight(userMessage, assistantMessage, detailedFeedback)),
            ...analyticsMetadataContext
        });
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: UserAnalyticsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: UserAnalyticsService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: UserAnalyticsService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root'
                }]
        }] });

class AgentChatComponent {
    constructor() {
        this.translateService = inject(TranslateService);
        this.datePipe = inject(DatePipe);
        this.modalService = inject(ModalService);
        /** Debug logging function for AI messages. Can be enabled at runtime with localStorage.setItem('c8y-debug-log.agent-chat', 'true'); */
        this.debugLog = (category => {
            const isDebugEnabled = localStorage.getItem('c8y-debug-log.' + category) === 'true';
            return (message, ...args) => isDebugEnabled && console.debug(`[${category} DEBUG] ${message}`, ...args);
        })('agent-chat');
        /**
         * Identifies which AI agent from the AI Agent Manager service will be used by this component.
         *
         * Where possible, provide a `ClientAgentDefinition` rather than just a name.
         *
         * It is recommended to define a `toolCallConfig` in the same place you define `ClientAgentDefinition`,
         * and to use the keys from `assistantMessageDisplayConfig.toolCallConfig`
         * in the `ClientAgentDefinition` tools list.
         */
        this.agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
        /**
         * Clickable suggestions buttons to display just above the chat, for likely next user actions.
         * These may be hardcoded or be set by the AI agent (e.g. in a tool call).
         * This input should be `undefined` to indicate that there are no new suggestions yet, and that any restored from the chat history should be used instead.
         * You can use the `isWelcoming` output to show different suggestions while the initial welcome message is displayed.
         */
        this.suggestions = input([], ...(ngDevMode ? [{ debugName: "suggestions" }] : []));
        /** ChatConfig to customize the ai-chat component's configuration if desired. */
        this.chatConfig = input(...(ngDevMode ? [undefined, { debugName: "chatConfig" }] : []));
        /** Template for customizing the welcome view using `ng-template`. */
        this.welcomeTemplate = input(...(ngDevMode ? [undefined, { debugName: "welcomeTemplate" }] : []));
        /** Indicates whether the component has zero messages and is showing the welcome page. */
        this.isWelcoming = computed(() => this.messages().length === 0, ...(ngDevMode ? [{ debugName: "isWelcoming" }] : []));
        /** Configures whether this component should automatically create the provided `agent: ClientAgentDefinition`
         * if an agent of that name doesn't already exist. This is an alternative to the usual mechanism of configuring
         * agents using a `.agent.c8y.ts` file.
         */
        this.autoCreateAgents = input(true, ...(ngDevMode ? [{ debugName: "autoCreateAgents" }] : []));
        /** Variables to pass to the AI agent for placeholders specified in the agent's system prompt. */
        this.variables = input({}, ...(ngDevMode ? [{ debugName: "variables" }] : []));
        /**
         * Allows overriding the component used to render messages from the AI assistant, if the default
         * AiChatAssistantMessageComponent does not do what you need.
         *
         * The component must accept a single input `assistantMessageContext: AssistantMessageContext`.
         * This object contains all data needed to render the assistant message and is forward-compatible with future additions.
         *
         * Example usage:
         *   <c8y-agent-chat [assistantMessageComponent]="MyCustomAssistantMessageComponent"></c8y-agent-chat>
         */
        this.assistantMessageComponent = input(AiChatAssistantMessageComponent, ...(ngDevMode ? [{ debugName: "assistantMessageComponent" }] : []));
        /**
         * Configuration passed to `AiChatAssistantMessageComponent` for controlling how messages from the assistant
         * are rendered, including tool calls.
         *
         * If you are performing any tool calls it is recommended to configure the `toolCallConfig`
         * to provide localized display names for the tools used in your `AgentDefinitionConfig`.
         */
        this.assistantMessageDisplayConfig = input({}, ...(ngDevMode ? [{ debugName: "assistantMessageDisplayConfig" }] : []));
        /**
         * Optional function to preprocess messages from the assistant before they are displayed, for example if you need to remove special
         * tagged sections from the content to move into simulated tool calls.
         *
         * This function may be called many times for each message, as more steps, tool calls and content are streamed from the agent.
         *
         * If you modify any part of the message other than the `changedPart`, you should replace it with a new shallow copy of the object so
         * that change detection works correctly. This is not necessary for changedPart.
         *
         * @param message The message from the AI assistant.
         * @param changedPart The part of the message that has changed, which can be modified in-place if desired. Currently only tool calls are included here.
         */
        this.preprocessAgentMessage = input(undefined, ...(ngDevMode ? [{ debugName: "preprocessAgentMessage" }] : []));
        /** Optional function to override how the message history is prepared and compacted ready for sending with
         * agent requests. This overrides the default behaviour from `defaultPruneMessagesForAgent`.
         *
         * For example this can be used to remove unnecessary information to minimize tokens, reduce the number of messages,
         * or to make what is sent to the agent different from what is rendered in the UI.
         */
        this.pruneMessagesForAgent = input(undefined, ...(ngDevMode ? [{ debugName: "pruneMessagesForAgent" }] : []));
        /** Input that provides a previously-saved chat history snapshot to restore. */
        this.initialChatHistory = input(...(ngDevMode ? [undefined, { debugName: "initialChatHistory" }] : []));
        /** Suggestions that were restored from the initial chat history; used if `suggestions` input is not yet set. */
        this._restoredSuggestions = [];
        /**
         * An optional function that returns a message sent in AI agent requests just before each new user message to provide context or "grounding" to
         * anchor the AI's response - such as the current state of the document the AI is helping with.
         *
         * Typically this string would begin with a special prefix (e.g. "[STATE_SNAPSHOT]") that is described by the system prompt.
         *
         * This message does not form part of the chat history, to avoid confusing the AI agent with older grounding state.
         * It is injected into the message history just before the latest user message as per standard recommendations,
         * since AI gives more attention to the recent content.
         */
        this.groundingContextProvider = input(...(ngDevMode ? [undefined, { debugName: "groundingContextProvider" }] : []));
        /**
         * Provides application-defined fields to enrich the user feedback tracking data for this component.
         *
         * Do not include Intellectual Property or Personal Identifiable Information in this object, as it may
         * be included in feedback data sent to third-party services.
         */
        this.userAnalyticsContext = input(...(ngDevMode ? [undefined, { debugName: "userAnalyticsContext" }] : []));
        this.onMessageFinish = output();
        /**
         * Notified when a tool result is received from the agent.
         *
         * This is called before it is added to the message displayed in the component.
         *
         * Tools with a client/UI implementation may provide `output` for the tool
         * (e.g. data to render in the UI) by calling `addToolOutput`.
         */
        this.onToolResult = output();
        /** Notified when the user presses the positive or negative feedback buttons for an assistant message.*/
        this.onFeedback = output();
        this._isLoading = signal(true, ...(ngDevMode ? [{ debugName: "_isLoading" }] : [])); // initially true while we do our initial ping of the agent health - prevents message flashing up before ready
        this.aiAgentManagerApplicationName = gettext('AI Agent Manager');
        /** Stores any error with the agent or backend microservice. This error message is already translated. */
        this._agentHealthError = signal(this.translateService.instant(gettext('Connecting to AI agent...')), ...(ngDevMode ? [{ debugName: "_agentHealthError" }] : []));
        /** Stores the most recent agent health check response, or `undefined` while the initial check is in progress. */
        this._agentHealth = signal(undefined, ...(ngDevMode ? [{ debugName: "_agentHealth" }] : []));
        /** Stores any detailed error messages from the backend. */
        this.agentHealthDetailedMessages = signal(undefined, ...(ngDevMode ? [{ debugName: "agentHealthDetailedMessages" }] : []));
        /**
         * Stores any error that occurred during streaming the current message from the agent.
         *
         * If possible this string is translated already or registered with `gettext`.
         * In practice, this may not be translated if it comes from the backend.
         */
        this.agentStreamError = signal(undefined, ...(ngDevMode ? [{ debugName: "agentStreamError" }] : []));
        // A stream of AI messages representing the conversation.
        // NB: this is NOT part of the API of this component and may change in future
        // (e.g. we may improve AIMessage class)
        this.messages = signal([], ...(ngDevMode ? [{ debugName: "messages" }] : []));
        /** If the create agent button should be shown to the user. */
        this.canCreate = false;
        this.prompt = '';
        this.aiService = inject(AIService);
        this.userAnalyticsService = inject(UserAnalyticsService);
        this.alertService = inject(AlertService);
        this.chatHistoryService = inject(ChatHistoryService);
        this.pluginContextPath = inject(C8Y_PLUGIN_CONTEXT_PATH, { optional: true });
        this.appState = inject(AppStateService);
        this.assistantMessageComponents = viewChildren(AiChatAssistantMessageComponent, ...(ngDevMode ? [{ debugName: "assistantMessageComponents" }] : []));
        /**
         * Stores client-supplied tool outputs to override tool results for a given toolCallId in the current assistant message.
         * Cleared at the start of each new message.
         */
        this._clientToolOutputs = {};
    }
    get agentName() {
        const value = this.agent();
        return typeof value === 'string' ? value : value.definition.name;
    }
    get agentDefinition() {
        const value = this.agent();
        if (!value || typeof value === 'string')
            return undefined;
        return value;
    }
    /** Is an AI agent request currently in progress. This can be used to disable UI elements while a reply (which might change the state) is being loaded. */
    get isLoadingAiResponse() {
        return this._isLoading.asReadonly();
    }
    /** Returns the most recent agent health check status such as `ready`, or an error. */
    get agentHealthStatus() {
        return this._agentHealth()?.status || 'loading';
    }
    get currentContextPath() {
        return this.pluginContextPath || this.appState.currentApplication?.value?.contextPath || '';
    }
    async ngOnInit() {
        // Restore chat history first, if provided
        this._restoreChatHistory(this.initialChatHistory());
        // Set the loading indicator to prevent interactions while we establish whether the agent is useable
        this._isLoading.set(true);
        try {
            const agentHealth = await this.aiService.getAgentHealth(this.agentName, this.currentContextPath);
            this._agentHealth.set(agentHealth);
            this.agentHealthDetailedMessages.set(agentHealth.messages?.join('\n'));
            this._agentHealthError.set(this.handleAgentError(agentHealth));
            if (this.agentDefinition && this.agentDefinition.snapshot) {
                this.createAgent();
            }
        }
        catch (ex) {
            console.warn('Error getting agent manager health:', ex);
            this.userAnalyticsService.sendAgentUnavailable('agent-health-check-failed');
            this._agentHealthError.set(this.translateService.instant(gettext('This tenant does not have the {{agentManager}} microservice. Please contact your administrator.'), { agentManager: this.translateService.instant(this.aiAgentManagerApplicationName) }));
        }
        this._isLoading.set(false);
    }
    /**
     * Save the current chat history and suggestions to an opaque, versioned, JSON-serializable snapshot of the chat history for persistence.
     *
     * To save space, only recent tool results and reasoning are included.
     * Additional options for compressing the history can be provided using the config parameters.
     *
     * @param config Optional configuration to limit what gets saved (e.g., filter out specific tools, limit total message count)
     */
    saveChatHistory(config) {
        return this.chatHistoryService.save(this.messages(), this.suggestions(), config);
    }
    /** Sends a message to the AI */
    async sendMessage(message) {
        if (this.isLoadingAiResponse()) {
            // Probably not worth a user-facing error message, but since we assume everywhere that we don't have parallel chats going on at once,
            // don't allow this (e.g. if called from a chat button or other part of the application's UI)
            console.log('Ignoring chat message because previous AI response is still loading');
            return;
        }
        // Collapse the thinking section of the previous assistant message before adding a new one
        const components = this.assistantMessageComponents();
        if (components.length > 0) {
            components[components.length - 1].setThinkingExpanded(false);
        }
        this.messages.set([...this.messages(), message]);
        this._isLoading.set(true);
        this.agentStreamError.set(undefined); // Clear any previous stream errors
        this.abortController = new AbortController();
        this._clientToolOutputs = {};
        const currentAssistantMessage = {
            role: 'assistant',
            content: ''
        };
        // Prepare messages for AI service, optionally including grounding message
        const pruneMessagesForAgent = this.pruneMessagesForAgent() || defaultPruneMessagesForAgent;
        const messagesForAI = pruneMessagesForAgent(this.messages());
        const groundingProvider = this.groundingContextProvider();
        if (groundingProvider) {
            // Insert grounding message before the last user message
            const groundingMsg = {
                role: 'user',
                content: groundingProvider()
            };
            messagesForAI.splice(messagesForAI.length - 1, 0, groundingMsg);
        }
        this.messages.set([...this.messages(), currentAssistantMessage]);
        const stream = await this.aiService.stream$(this.agentDefinition || this.agentName, messagesForAI, this.variables(), this.abortController, this.currentContextPath);
        if (this.assistantSubscription) {
            this.assistantSubscription.unsubscribe();
        }
        this.assistantSubscription = stream.subscribe({
            next: (response) => {
                if (response?.changedPart?.type === 'response-metadata') {
                    if (response.changedPart.model) {
                        this.model = response.changedPart.model;
                    }
                    if (response.changedPart.systemPrompt) {
                        this.systemPrompt = response.changedPart.systemPrompt;
                    }
                }
                else {
                    this.processAgentMessage(currentAssistantMessage, response.message, response.changedPart);
                }
            },
            complete: () => {
                if (this._isLoading()) {
                    // Should never happen, but worth knowing if it does (indicates bug in processAgentMessage)
                    console.error('AI response stream completed but message loading indicator was not reset');
                }
            },
            error: async (error) => {
                // Currently errors from the AIService are often a big JSON with no human-friendly message anywhere
                // (e.g. {error:{error:{statusCode:400, name:"AI-APICallError", ...}},
                // so we can only show the detail in the console and have to give a non-informative error to the user;
                // but if the backend could add a user-friendly "message" we could render that
                console.error('Error in AI stream:', error);
                const errorMessage = typeof error === 'string'
                    ? error
                    : error?.message || gettext('An error occurred while communicating with the AI agent.');
                this.agentStreamError.set(errorMessage);
                // Remove the empty assistant message that was added
                const messages = this.messages();
                if (messages[messages.length - 1] === currentAssistantMessage) {
                    this.messages.set(messages.slice(0, -1));
                }
                this._isLoading.set(false);
                this.userAnalyticsService.sendAssistantMessageError(errorMessage, this.messages(), await this.getAnalyticsMetadataContext());
            }
        });
    }
    ngOnDestroy() {
        this.cancel();
    }
    async reprompt(userMessage) {
        const index = this.messages().indexOf(userMessage);
        if (index > -1) {
            const formattedTimestamp = userMessage.timestamp
                ? this.datePipe.transform(userMessage.timestamp, 'adaptiveDate')
                : gettext('(unknown time)');
            try {
                await this.modalService.confirm(gettext('Edit this prompt and delete the following messages'), this.translateService.instant(gettext('Are you sure you want to rewrite the conversation by editing your message from {{timestamp}}, and deleting the following {{messageCount}} assistant and user messages?'), {
                    timestamp: formattedTimestamp,
                    messageCount: this.messages().length - index - 1
                }), Status.DANGER, { cancel: gettext('Cancel'), ok: gettext('Delete and replace') });
                this.messages.set(this.messages().slice(0, index));
                this.prompt = userMessage.content;
            }
            catch (e) {
                if (!e)
                    return; // User dismissed the dialog, do nothing
                throw e; // Unexpected error, rethrow to propagate
            }
        }
    }
    /** Rates an AI message as positive/negative, sending the feedback to GainSight */
    async rate(assistantMessage, positive) {
        this.userAnalyticsService.sendFeedbackOnAssistantMessage(assistantMessage, this.messages(), positive, await this.getAnalyticsMetadataContext());
        this.onFeedback.emit({
            userMessage: this.messages()[this.messages().lastIndexOf(assistantMessage) - 1],
            assistantMessage,
            feedbackPositive: positive
        });
        this.alertService.success(gettext('Thank you for the feedback!'));
    }
    reload(assistantMessage) {
        const index = this.messages().indexOf(assistantMessage);
        const userMessage = this.messages()[index - 1];
        if (index > -1) {
            this.messages.set(this.messages().slice(0, index - 1));
            this.sendMessage({
                role: 'user',
                content: userMessage.content,
                timestamp: new Date().toISOString()
            });
        }
    }
    cancel() {
        if (this.abortController) {
            this.abortController.abort();
        }
        if (this.assistantSubscription) {
            this.assistantSubscription.unsubscribe();
        }
        this._isLoading.set(false);
    }
    /**
     * Overrides the output of a specific tool result (in the current assistant message), including optionally indicating that an error occurred.
     *
     * This can be used to provide output for tool calls that are implemented on the client,
     * and is typically called from `onToolResult`.
     *
     */
    addToolOutput(toolCallId, output, error) {
        // No validation of the id is performed here; validation is done when applying the override in processAgentMessage
        this.debugLog(`Adding client-side tool ${error ? 'error' : 'output'} for ${toolCallId}: `, output);
        this._clientToolOutputs[toolCallId] = { output: output, error: error ?? false };
    }
    async createAgent() {
        this._isLoading.set(true);
        try {
            if (!this.agentDefinition) {
                throw new Error('No agent definition provided');
            }
            await this.aiService.createOrUpdateAgent(this.agentDefinition.definition);
            this._agentHealthError.set(undefined);
        }
        catch (ex) {
            // TODO: Distinguish permissions errors to provide a more specific error message.
            console.error('Error configuring AI agent:', ex);
            this.alertService.danger(gettext('Failed to configure the agent.'));
            this._agentHealthError.set(this.translateService.instant(gettext('Failed to configure the agent.')));
        }
        this._isLoading.set(false);
    }
    // Private methods go here:
    /** Restore chat history from a previously-saved snapshot. Does nothing if the component already has some messages. */
    _restoreChatHistory(history) {
        if (!history)
            return;
        // Ignore updates to history once we have messages
        if (this.messages().length > 0) {
            return;
        }
        const restored = this.chatHistoryService.restore(history);
        this.messages.set(restored.messages);
        this._restoredSuggestions = restored.suggestions;
    }
    async getAnalyticsMetadataContext() {
        return await this.userAnalyticsService.getAnalyticsMetadataContext(this.agentName, this.agentDefinition, this.userAnalyticsContext(), this.model, this.systemPrompt);
    }
    handleAgentError(agentHealth) {
        this.canCreate = false;
        const agentManager = this.translateService.instant(this.aiAgentManagerApplicationName);
        if (agentHealth.status != 'ready')
            this.userAnalyticsService.sendAgentUnavailable(agentHealth.status || 'unknown-error');
        // All of these errors are displayed under a headline banner stating "AI agent is not available"
        // From end-user perspective it makes sense to recommend opening AI Agent Manager to configure their provider/agent
        // (or, implicitly, to ask their admin to do that if they don't have access themselves).
        // If they're the application author they could create a `.agent.c8y.ts` file instead, but we don't want to
        // expose such low-level details in the UI.
        switch (agentHealth.status) {
            case 'ready':
                return undefined;
            case 'missing-provider':
                return this.translateService.instant(gettext('Configure a provider in the {{agentManager}} to get started.'), { agentManager });
            case 'missing-permissions':
                return this.translateService.instant(gettext('Add AI Agent permissions to the current user or role to get started.'));
            case 'missing-microservice':
                return this.translateService.instant(gettext('This tenant does not have the {{agentManager}} microservice. Please contact your administrator.'), { agentManager });
        }
        // Otherwise it's a missing agent definition
        if (agentHealth.canCreate && this.agentDefinition) {
            this.canCreate = true;
            if (this.autoCreateAgents() && this.agentDefinition) {
                this.createAgent();
                return this.translateService.instant(gettext('Creating the "{{agentName}}" AI agent now...'), { agentName: this.agentName });
            }
            // In this case we show a "Create agent" button
            return this.translateService.instant(gettext('Create the "{{agentName}}" AI agent to get started.'), { agentName: this.agentName });
        }
        return this.translateService.instant(gettext('Configure the "{{agentName}}" agent using the {{agentManager}} to get started.'), { agentName: this.agentName, agentManager });
    }
    makeChangedPartNewInstance(updatedAssistantMsg, changedPart) {
        if (!changedPart || !updatedAssistantMsg.steps)
            return changedPart;
        const newInstance = { ...changedPart };
        // For tool calls we often want to modify the output in-place based on client-side information,
        // so we need to ensure changedPart is a new instance to avoid change detection issues
        if (typeof changedPart.type === 'string' && changedPart.type.startsWith('tool-')) {
            for (const step of updatedAssistantMsg.steps) {
                if (step.toolCalls) {
                    const toolCallIndex = step.toolCalls.findIndex(tool => tool === changedPart);
                    if (toolCallIndex !== -1) {
                        step.toolCalls[toolCallIndex] = newInstance;
                        return newInstance;
                    }
                }
                if (step.toolResults) {
                    const toolResultIndex = step.toolResults.findIndex(tool => tool === changedPart);
                    if (toolResultIndex !== -1) {
                        step.toolResults[toolResultIndex] = newInstance;
                        return newInstance;
                    }
                }
            }
        }
        // Should never happen; if we does we need to know
        console.error('Internal error - changed part not found in updated assistant message steps', changedPart, updatedAssistantMsg);
        return changedPart;
    }
    /**
     * Called as responses are incrementally streamed from the agent.
     * @param currentAssistantMsg The message already added to the messages array rendered by the UI,
     * and dynamically updated by this function as more output comes from the agent.
     * @param updatedAssistantMsg contains the latest data received from the agent
     * @param changedPart if provided, indicates which part of the message was changed in this update
     */
    async processAgentMessage(currentAssistantMsg, updatedAssistantMsg, changedPart) {
        // Apply preprocessing if provided
        const preprocess = this.preprocessAgentMessage();
        if (preprocess) {
            updatedAssistantMsg = preprocess(updatedAssistantMsg, changedPart);
        }
        // Must ensure changedPart is a new instance, otherwise change detection can fail
        // For now this only applies to tools
        changedPart = this.makeChangedPartNewInstance(updatedAssistantMsg, changedPart);
        currentAssistantMsg.content = updatedAssistantMsg.content;
        // Invoke tool callback - which may call addToolOutput
        if (changedPart?.type === 'tool-result') {
            this.debugLog(`Received tool result: ${changedPart.toolName}`, changedPart);
            this.onToolResult.emit(changedPart);
        }
        if (updatedAssistantMsg.steps) {
            currentAssistantMsg.steps = updatedAssistantMsg.steps; // don't need a deep copy for this use case, and must avoid that so pre-processor can add steps
            // Apply any pending tool output overrides to the incoming steps - and check that none of them refers to a non-existent item
            if (currentAssistantMsg.steps && Object.keys(this._clientToolOutputs).length > 0) {
                for (const [toolCallId, override] of Object.entries(this._clientToolOutputs)) {
                    const toolResult = currentAssistantMsg.steps
                        ?.flatMap(step => step.toolResults || [])
                        .find(result => result.toolCallId === toolCallId);
                    if (toolResult) {
                        toolResult.output = override.output;
                        toolResult.error = override.error;
                    }
                    else {
                        throw new Error(`addToolOutput was called with a toolCallId '${toolCallId}' that does not exist in the current assistant message`);
                    }
                }
            }
        }
        if (updatedAssistantMsg.finishReason) {
            this.debugLog(`Received assistant message with ${updatedAssistantMsg.steps?.length || 0} steps: `, updatedAssistantMsg);
            try {
                this.handleMessageFinish(currentAssistantMsg, updatedAssistantMsg);
            }
            finally {
                this._isLoading.set(false);
            }
        }
        // as we are mutating the currentAssistantMessage object
        // which is already in the messages array we need to emit a new array reference
        this.messages.set([...this.messages()]);
    }
    handleMessageFinish(currentAssistantMsg, updatedAssistantMsg) {
        currentAssistantMsg.timestamp = new Date().toISOString();
        if (updatedAssistantMsg.finishReason === 'stop') {
            this.onMessageFinish.emit(currentAssistantMsg);
            void this.getAnalyticsMetadataContext()
                .then(analyticsContext => {
                this.userAnalyticsService.sendAssistantMessageComplete(updatedAssistantMsg, this.messages(), analyticsContext);
            })
                .catch(error => {
                console.error('Error sending analytics:', error);
            });
            if (currentAssistantMsg.content.trim().length === 0) {
                // Should be impossible, but adding this as a debugging aid, as it was seen once by a user
                console.warn('Received finish reason "stop" but assistant message content is empty. This may indicate an issue with the AI service or agent configuration.', currentAssistantMsg);
            }
        }
        else if (updatedAssistantMsg.finishReason === 'error') {
            if (currentAssistantMsg.content.trim().length === 0) {
                // Should be impossible, but adding this as a debugging aid, as it was seen once by a user
                console.warn('Received finish reason "error" but assistant message content is empty. This may indicate an issue with the AI service or agent configuration.', currentAssistantMsg);
            }
        }
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AgentChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AgentChatComponent, isStandalone: true, selector: "c8y-agent-chat", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null }, chatConfig: { classPropertyName: "chatConfig", publicName: "chatConfig", isSignal: true, isRequired: false, transformFunction: null }, welcomeTemplate: { classPropertyName: "welcomeTemplate", publicName: "welcomeTemplate", isSignal: true, isRequired: false, transformFunction: null }, autoCreateAgents: { classPropertyName: "autoCreateAgents", publicName: "autoCreateAgents", isSignal: true, isRequired: false, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: false, transformFunction: null }, assistantMessageComponent: { classPropertyName: "assistantMessageComponent", publicName: "assistantMessageComponent", isSignal: true, isRequired: false, transformFunction: null }, assistantMessageDisplayConfig: { classPropertyName: "assistantMessageDisplayConfig", publicName: "assistantMessageDisplayConfig", isSignal: true, isRequired: false, transformFunction: null }, preprocessAgentMessage: { classPropertyName: "preprocessAgentMessage", publicName: "preprocessAgentMessage", isSignal: true, isRequired: false, transformFunction: null }, pruneMessagesForAgent: { classPropertyName: "pruneMessagesForAgent", publicName: "pruneMessagesForAgent", isSignal: true, isRequired: false, transformFunction: null }, initialChatHistory: { classPropertyName: "initialChatHistory", publicName: "initialChatHistory", isSignal: true, isRequired: false, transformFunction: null }, groundingContextProvider: { classPropertyName: "groundingContextProvider", publicName: "groundingContextProvider", isSignal: true, isRequired: false, transformFunction: null }, userAnalyticsContext: { classPropertyName: "userAnalyticsContext", publicName: "userAnalyticsContext", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onMessageFinish: "onMessageFinish", onToolResult: "onToolResult", onFeedback: "onFeedback" }, viewQueries: [{ propertyName: "assistantMessageComponents", predicate: AiChatAssistantMessageComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@let agentHealthErrorMsg = _agentHealthError();\n@if (agentHealthErrorMsg) {\n  @if (isLoadingAiResponse()) {\n    <c8y-loading class=\"m-auto\"></c8y-loading>\n  } @else {\n    <c8y-ui-empty-state\n      class=\"m-auto\"\n      [icon]=\"'settings'\"\n      [title]=\"'AI agent is not available.' | translate\"\n    >\n      <span>{{ agentHealthErrorMsg }}</span>\n\n      @if (canCreate) {\n        <div class=\"text-center m-t-16 m-b-16\">\n          <button\n            class=\"btn btn-primary\"\n            (click)=\"createAgent()\"\n          >\n            {{ 'Create agent' | translate }}\n          </button>\n        </div>\n      } @else {\n        <p\n          class=\"text-pre-wrap m-t-8\"\n          data-cy=\"agent-health-detailed-messages\"\n        >\n          <small>{{ agentHealthDetailedMessages() }}</small>\n        </p>\n      }\n\n      <p c8y-guide-docs>\n        <small\n          translate\n          ngNonBindable\n        >\n          Find out more in the\n          <a c8y-guide-href=\"/docs/ai\">user documentation</a>.\n        </small>\n      </p>\n    </c8y-ui-empty-state>\n  }\n}\n\n@if (!agentHealthErrorMsg) {\n  <c8y-ai-chat\n    (onMessage)=\"sendMessage($event)\"\n    [isLoading]=\"isLoadingAiResponse()\"\n    (onCancel)=\"cancel()\"\n    [config]=\"chatConfig() ?? {}\"\n    [prompt]=\"prompt\"\n    [suggestionsTemplate]=\"suggestionsRef\"\n    [welcomeTemplate]=\"welcomeTemplate()\"\n  >\n    @let messages$ = messages();\n    @for (message of messages$; track $index; let i = $index) {\n      <c8y-ai-chat-message [message]=\"message\">\n        @if (message.role !== 'user' && model) {\n          <!-- Visually hidden label included when users copy-paste from the chat, e.g. to report issues. It's quite helpful to know the model. -->\n          <span\n            class=\"hidden-copy-label\"\n            aria-hidden=\"true\"\n          >\n            {{ `(Using model: ${model})` }}</span\n          >\n        }\n\n        @if (message.role === 'user') {\n          <div\n            class=\"message-content\"\n            data-cy=\"user-message-content\"\n            [innerHTML]=\"message.content | markdownToHtml | async\"\n          ></div>\n        } @else {\n          <ng-container\n            [ngComponentOutlet]=\"assistantMessageComponent()\"\n            [ngComponentOutletInputs]=\"{\n              assistantMessageContext: {\n                message: message,\n                config: assistantMessageDisplayConfig(),\n                isMessageLoading: isLoadingAiResponse() && i === messages$.length - 1,\n                messageDisplayIndex: messages$.length - 1 - i\n              }\n            }\"\n          ></ng-container>\n        }\n\n        @let isLastMessage = i === messages$.length - 1;\n\n        @if (message.role === 'user') {\n          <c8y-ai-chat-message-action\n            icon=\"pencil\"\n            [tooltip]=\"'Edit and resend this message' | translate\"\n            [disabled]=\"isLoadingAiResponse()\"\n            (click)=\"reprompt(message)\"\n          ></c8y-ai-chat-message-action>\n        }\n\n        @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n          <c8y-ai-chat-message-action\n            icon=\"thumbs-up\"\n            [tooltip]=\"'This is useful' | translate\"\n            [disabled]=\"isLoadingAiResponse()\"\n            (click)=\"rate(message, true)\"\n          ></c8y-ai-chat-message-action>\n        }\n\n        @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n          <c8y-ai-chat-message-action\n            icon=\"thumbs-down\"\n            [tooltip]=\"'This is not useful' | translate\"\n            [disabled]=\"isLoadingAiResponse()\"\n            (click)=\"rate(message, false)\"\n          ></c8y-ai-chat-message-action>\n        }\n\n        <!-- Only allow regenerating the last message, otherwise we could be deleting a lot of useful message history -->\n        @if (message.role === 'assistant' && i > 0 && isLastMessage && !isLoadingAiResponse()) {\n          <c8y-ai-chat-message-action\n            icon=\"refresh\"\n            [tooltip]=\"'Regenerate this response' | translate\"\n            [disabled]=\"isLoadingAiResponse()\"\n            (click)=\"reload(message)\"\n          ></c8y-ai-chat-message-action>\n        }\n      </c8y-ai-chat-message>\n    }\n\n    @let agentErrorMsg = agentStreamError();\n    @if (agentErrorMsg) {\n      <c8y-ai-chat-message [message]=\"{ role: 'assistant', content: agentErrorMsg }\">\n        <div\n          class=\"alert alert-danger d-flex a-i-center gap-8\"\n          role=\"alert\"\n        >\n          <i\n            class=\"c8y-icon c8y-icon-warning text-danger\"\n            aria-hidden=\"true\"\n          ></i>\n          <!-- Since errors come from the backend the only translation is for the fallback error message supplied by the UI. -->\n          <div class=\"flex-grow text-pre-wrap\">{{ agentErrorMsg | translate }}</div>\n        </div>\n      </c8y-ai-chat-message>\n    }\n\n    <ng-template #suggestionsRef>\n      @if (!isLoadingAiResponse()) {\n        <!-- As soon as we have any suggestions (even empty) these take priority over what we restored from history -->\n        @let activeSuggestions = suggestions() === undefined ? _restoredSuggestions : suggestions();\n        @for (suggestion of activeSuggestions; track $index) {\n          <c8y-ai-chat-suggestion\n            [icon]=\"suggestion.icon || 'c8y-bulb'\"\n            [useAiButtons]=\"true\"\n            [prompt]=\"suggestion.prompt\"\n            [label]=\"suggestion.label ?? suggestion.prompt\"\n            (suggestionClicked)=\"sendMessage($event)\"\n            [disabled]=\"isLoadingAiResponse()\"\n          ></c8y-ai-chat-suggestion>\n        }\n      }\n    </ng-template>\n  </c8y-ai-chat>\n}\n", styles: ["@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fade-in-out{animation:fadeIn .2s ease-in}.fade-in-out.ng-leave-active{animation:fadeOut .2s ease-out}.hidden-copy-label{display:block;font-size:0;line-height:0;-webkit-user-select:text;user-select:text}\n"], dependencies: [{ kind: "component", type: AiChatComponent, selector: "c8y-ai-chat", inputs: ["isLoading", "disabled", "prompt", "suggestionsTemplate", "welcomeTemplate", "config"], outputs: ["onMessage", "onCancel"] }, { kind: "component", type: AiChatSuggestionComponent, selector: "c8y-ai-chat-suggestion", inputs: ["label", "prompt", "icon", "useAiButtons", "disabled"], outputs: ["suggestionClicked"] }, { kind: "component", type: AiChatMessageComponent, selector: "c8y-ai-chat-message", inputs: ["role", "message"] }, { kind: "component", type: AiChatMessageActionComponent, selector: "c8y-ai-chat-message-action", inputs: ["custom", "disabled", "tooltip", "icon"], outputs: ["actionClicked"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "ngmodule", type: CollapseModule }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: GuideDocsComponent, selector: "[c8y-guide-docs]" }, { kind: "directive", type: GuideHrefDirective, selector: "[c8y-guide-href]", inputs: ["c8y-guide-href"] }, { kind: "pipe", type: MarkdownToHtmlPipe, name: "markdownToHtml" }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AgentChatComponent, decorators: [{
            type: Component,
            args: [{ selector: 'c8y-agent-chat', imports: [
                        AiChatComponent,
                        AiChatSuggestionComponent,
                        AiChatMessageComponent,
                        AiChatMessageActionComponent,
                        LoadingComponent,
                        MarkdownToHtmlPipe,
                        AsyncPipe,
                        NgComponentOutlet,
                        C8yTranslatePipe,
                        EmptyStateComponent,
                        CollapseModule,
                        C8yTranslateDirective,
                        GuideDocsComponent,
                        GuideHrefDirective
                    ], template: "@let agentHealthErrorMsg = _agentHealthError();\n@if (agentHealthErrorMsg) {\n  @if (isLoadingAiResponse()) {\n    <c8y-loading class=\"m-auto\"></c8y-loading>\n  } @else {\n    <c8y-ui-empty-state\n      class=\"m-auto\"\n      [icon]=\"'settings'\"\n      [title]=\"'AI agent is not available.' | translate\"\n    >\n      <span>{{ agentHealthErrorMsg }}</span>\n\n      @if (canCreate) {\n        <div class=\"text-center m-t-16 m-b-16\">\n          <button\n            class=\"btn btn-primary\"\n            (click)=\"createAgent()\"\n          >\n            {{ 'Create agent' | translate }}\n          </button>\n        </div>\n      } @else {\n        <p\n          class=\"text-pre-wrap m-t-8\"\n          data-cy=\"agent-health-detailed-messages\"\n        >\n          <small>{{ agentHealthDetailedMessages() }}</small>\n        </p>\n      }\n\n      <p c8y-guide-docs>\n        <small\n          translate\n          ngNonBindable\n        >\n          Find out more in the\n          <a c8y-guide-href=\"/docs/ai\">user documentation</a>.\n        </small>\n      </p>\n    </c8y-ui-empty-state>\n  }\n}\n\n@if (!agentHealthErrorMsg) {\n  <c8y-ai-chat\n    (onMessage)=\"sendMessage($event)\"\n    [isLoading]=\"isLoadingAiResponse()\"\n    (onCancel)=\"cancel()\"\n    [config]=\"chatConfig() ?? {}\"\n    [prompt]=\"prompt\"\n    [suggestionsTemplate]=\"suggestionsRef\"\n    [welcomeTemplate]=\"welcomeTemplate()\"\n  >\n    @let messages$ = messages();\n    @for (message of messages$; track $index; let i = $index) {\n      <c8y-ai-chat-message [message]=\"message\">\n        @if (message.role !== 'user' && model) {\n          <!-- Visually hidden label included when users copy-paste from the chat, e.g. to report issues. It's quite helpful to know the model. -->\n          <span\n            class=\"hidden-copy-label\"\n            aria-hidden=\"true\"\n          >\n            {{ `(Using model: ${model})` }}</span\n          >\n        }\n\n        @if (message.role === 'user') {\n          <div\n            class=\"message-content\"\n            data-cy=\"user-message-content\"\n            [innerHTML]=\"message.content | markdownToHtml | async\"\n          ></div>\n        } @else {\n          <ng-container\n            [ngComponentOutlet]=\"assistantMessageComponent()\"\n            [ngComponentOutletInputs]=\"{\n              assistantMessageContext: {\n                message: message,\n                config: assistantMessageDisplayConfig(),\n                isMessageLoading: isLoadingAiResponse() && i === messages$.length - 1,\n                messageDisplayIndex: messages$.length - 1 - i\n              }\n            }\"\n          ></ng-container>\n        }\n\n        @let isLastMessage = i === messages$.length - 1;\n\n        @if (message.role === 'user') {\n          <c8y-ai-chat-message-action\n            icon=\"pencil\"\n            [tooltip]=\"'Edit and resend this message' | translate\"\n            [disabled]=\"isLoadingAiResponse()\"\n            (click)=\"reprompt(message)\"\n          ></c8y-ai-chat-message-action>\n        }\n\n        @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n          <c8y-ai-chat-message-action\n            icon=\"thumbs-up\"\n            [tooltip]=\"'This is useful' | translate\"\n            [disabled]=\"isLoadingAiResponse()\"\n            (click)=\"rate(message, true)\"\n          ></c8y-ai-chat-message-action>\n        }\n\n        @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n          <c8y-ai-chat-message-action\n            icon=\"thumbs-down\"\n            [tooltip]=\"'This is not useful' | translate\"\n            [disabled]=\"isLoadingAiResponse()\"\n            (click)=\"rate(message, false)\"\n          ></c8y-ai-chat-message-action>\n        }\n\n        <!-- Only allow regenerating the last message, otherwise we could be deleting a lot of useful message history -->\n        @if (message.role === 'assistant' && i > 0 && isLastMessage && !isLoadingAiResponse()) {\n          <c8y-ai-chat-message-action\n            icon=\"refresh\"\n            [tooltip]=\"'Regenerate this response' | translate\"\n            [disabled]=\"isLoadingAiResponse()\"\n            (click)=\"reload(message)\"\n          ></c8y-ai-chat-message-action>\n        }\n      </c8y-ai-chat-message>\n    }\n\n    @let agentErrorMsg = agentStreamError();\n    @if (agentErrorMsg) {\n      <c8y-ai-chat-message [message]=\"{ role: 'assistant', content: agentErrorMsg }\">\n        <div\n          class=\"alert alert-danger d-flex a-i-center gap-8\"\n          role=\"alert\"\n        >\n          <i\n            class=\"c8y-icon c8y-icon-warning text-danger\"\n            aria-hidden=\"true\"\n          ></i>\n          <!-- Since errors come from the backend the only translation is for the fallback error message supplied by the UI. -->\n          <div class=\"flex-grow text-pre-wrap\">{{ agentErrorMsg | translate }}</div>\n        </div>\n      </c8y-ai-chat-message>\n    }\n\n    <ng-template #suggestionsRef>\n      @if (!isLoadingAiResponse()) {\n        <!-- As soon as we have any suggestions (even empty) these take priority over what we restored from history -->\n        @let activeSuggestions = suggestions() === undefined ? _restoredSuggestions : suggestions();\n        @for (suggestion of activeSuggestions; track $index) {\n          <c8y-ai-chat-suggestion\n            [icon]=\"suggestion.icon || 'c8y-bulb'\"\n            [useAiButtons]=\"true\"\n            [prompt]=\"suggestion.prompt\"\n            [label]=\"suggestion.label ?? suggestion.prompt\"\n            (suggestionClicked)=\"sendMessage($event)\"\n            [disabled]=\"isLoadingAiResponse()\"\n          ></c8y-ai-chat-suggestion>\n        }\n      }\n    </ng-template>\n  </c8y-ai-chat>\n}\n", styles: ["@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fade-in-out{animation:fadeIn .2s ease-in}.fade-in-out.ng-leave-active{animation:fadeOut .2s ease-out}.hidden-copy-label{display:block;font-size:0;line-height:0;-webkit-user-select:text;user-select:text}\n"] }]
        }], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], chatConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "chatConfig", required: false }] }], welcomeTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "welcomeTemplate", required: false }] }], autoCreateAgents: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCreateAgents", required: false }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: false }] }], assistantMessageComponent: [{ type: i0.Input, args: [{ isSignal: true, alias: "assistantMessageComponent", required: false }] }], assistantMessageDisplayConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "assistantMessageDisplayConfig", required: false }] }], preprocessAgentMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "preprocessAgentMessage", required: false }] }], pruneMessagesForAgent: [{ type: i0.Input, args: [{ isSignal: true, alias: "pruneMessagesForAgent", required: false }] }], initialChatHistory: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialChatHistory", required: false }] }], groundingContextProvider: [{ type: i0.Input, args: [{ isSignal: true, alias: "groundingContextProvider", required: false }] }], userAnalyticsContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "userAnalyticsContext", required: false }] }], onMessageFinish: [{ type: i0.Output, args: ["onMessageFinish"] }], onToolResult: [{ type: i0.Output, args: ["onToolResult"] }], onFeedback: [{ type: i0.Output, args: ["onFeedback"] }], assistantMessageComponents: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => AiChatAssistantMessageComponent), { isSignal: true }] }] } });

class WidgetAiChatSectionComponent {
    constructor() {
        this._componentConfig = signal({}, ...(ngDevMode ? [{ debugName: "_componentConfig" }] : []));
        this.suggestions = [];
        /** Provide the specified `contextVariableName` as a variable that can be used in the prompt. */
        this.useContextAsVariable = true;
        this.contextVariableName = 'c8yContext';
        this.variables$ = of({});
        this.injector = inject(Injector);
        this.widgetConfigService = inject(WidgetConfigService);
        this.alertService = inject(AlertService);
        this.contextRouteService = inject(ContextRouteService);
        /**
         * A callback that is invoked when a tool result is returned. The function can return either:
         * - `undefined` or `void`: No action is taken.
         * - an object which is stored to the widget configuration.
         */
        this.onToolResult = () => undefined;
    }
    /**
     * If set, the variables will be loaded async on each round-trip.
     * This is useful if the variables depend on some async data source or if they need to be updated on each round-trip.
     *
     * @example
     * ```ts
     * variables = combineLatest([
     *   this.someService.getData(),
     *   this.anotherService.getOtherData()
     * ]).pipe(
     *   map(([data, otherData]) => ({
     *     myVarOne: data,
     *     myVarTwo: otherData
     *   }))
     * );
     * ```
     */
    set variables(val) {
        if (isObservable(val)) {
            this.variables$ = val;
        }
        else {
            this.variables$ = of(val);
        }
    }
    get variables() {
        return this.variables$;
    }
    /**
     * @ignore
     */
    async ngOnInit() {
        if (!this.variables && this.useContextAsVariable) {
            const { contextData } = this.contextRouteService.activatedContextData;
            this.variables = {
                [this.contextVariableName]: contextData || this.widgetConfigService.currentConfig.device
            };
        }
        if (this.loadComponentConfig) {
            const config = await this.loadComponentConfig(this.injector);
            this._componentConfig.set(config);
        }
    }
    /**
     * Handles the tool result returned from the AI agent.
     * @param tool The tool result returned from the AI agent.
     */
    toolResultHandler(tool) {
        try {
            const parsedResult = this.onToolResult(tool);
            if (!parsedResult) {
                return;
            }
            this.widgetConfigService.updateConfig({
                config: {
                    ...this.widgetConfigService.currentConfig,
                    ...parsedResult
                }
            }, true);
        }
        catch (e) {
            this.alertService.danger(gettext('There was an error processing the AI response.'));
        }
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WidgetAiChatSectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: WidgetAiChatSectionComponent, isStandalone: true, selector: "c8y-widget-ai-chat-section", inputs: { loadComponentConfig: "loadComponentConfig", suggestions: "suggestions", agent: "agent", chatConfig: "chatConfig", useContextAsVariable: "useContextAsVariable", contextVariableName: "contextVariableName", variables: "variables", onToolResult: "onToolResult" }, ngImport: i0, template: "<c8y-widget-config-feedback>\n  <div\n    class=\"m-l-4 btn-ai btn-ai-hint btn-sm\"\n    [title]=\"'AI code assistant' | translate\"\n  >\n    <span></span>\n  </div>\n</c8y-widget-config-feedback>\n\n<c8y-agent-chat\n  #agentChat\n  [chatConfig]=\"chatConfig\"\n  [suggestions]=\"agentChat.isWelcoming() ? suggestions : []\"\n  [agent]=\"agent\"\n  [variables]=\"variables | async\"\n  (onToolResult)=\"toolResultHandler($event)\"\n  [preprocessAgentMessage]=\"_componentConfig().preprocessAgentMessage\"\n  [pruneMessagesForAgent]=\"_componentConfig().pruneMessagesForAgent\"\n  [assistantMessageDisplayConfig]=\"_componentConfig().assistantMessageDisplayConfig || {}\"\n></c8y-agent-chat>\n", dependencies: [{ kind: "component", type: AgentChatComponent, selector: "c8y-agent-chat", inputs: ["agent", "suggestions", "chatConfig", "welcomeTemplate", "autoCreateAgents", "variables", "assistantMessageComponent", "assistantMessageDisplayConfig", "preprocessAgentMessage", "pruneMessagesForAgent", "initialChatHistory", "groundingContextProvider", "userAnalyticsContext"], outputs: ["onMessageFinish", "onToolResult", "onFeedback"] }, { kind: "component", type: WidgetConfigFeedbackComponent, selector: "c8y-widget-config-feedback" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WidgetAiChatSectionComponent, decorators: [{
            type: Component,
            args: [{ selector: 'c8y-widget-ai-chat-section', imports: [AgentChatComponent, C8yTranslatePipe, WidgetConfigFeedbackComponent, AsyncPipe], standalone: true, template: "<c8y-widget-config-feedback>\n  <div\n    class=\"m-l-4 btn-ai btn-ai-hint btn-sm\"\n    [title]=\"'AI code assistant' | translate\"\n  >\n    <span></span>\n  </div>\n</c8y-widget-config-feedback>\n\n<c8y-agent-chat\n  #agentChat\n  [chatConfig]=\"chatConfig\"\n  [suggestions]=\"agentChat.isWelcoming() ? suggestions : []\"\n  [agent]=\"agent\"\n  [variables]=\"variables | async\"\n  (onToolResult)=\"toolResultHandler($event)\"\n  [preprocessAgentMessage]=\"_componentConfig().preprocessAgentMessage\"\n  [pruneMessagesForAgent]=\"_componentConfig().pruneMessagesForAgent\"\n  [assistantMessageDisplayConfig]=\"_componentConfig().assistantMessageDisplayConfig || {}\"\n></c8y-agent-chat>\n" }]
        }], propDecorators: { loadComponentConfig: [{
                type: Input
            }], suggestions: [{
                type: Input
            }], agent: [{
                type: Input
            }], chatConfig: [{
                type: Input
            }], useContextAsVariable: [{
                type: Input
            }], contextVariableName: [{
                type: Input
            }], variables: [{
                type: Input
            }], onToolResult: [{
                type: Input
            }] } });

/**
 * Generated bundle index. Do not edit.
 */

export { AgentChatComponent, WidgetAiChatSectionComponent };
//# sourceMappingURL=c8y-ngx-components-ai-agent-chat.mjs.map
