Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated the actual inputs value and not their initial value in views.update #132

Open
mister-good-deal opened this issue Mar 9, 2024 · 0 comments
Labels
bug Something isn't working

Comments

@mister-good-deal
Copy link

Hi, I'm using your lib and it is working fine except for one case.

When I am loading modal inputs data from an existing model, the returned modal View correctly shows the loaded values in the GUI. But after submitting the modal, the payload has undefined values for the ones being loaded before.

I am using initialValue or initialOption to load the values from my model on TextInput or StaticSelect.

It is like I need to fill a "real" input value and not its initial value to fix my issue.

Do you know how can I deal with this?

Here is some code sample of my app

src/listeners/views/assistant-configuration-modal.ts

import logger from "../../logger/winston.ts";
import prisma from "../../prisma/client.ts";
import { functionMap } from "../../ai/functions-mapper.ts";
import { createPublicChannel } from "../../slack/assistants-channel.ts";
import { GptModel, MistralModel, AIServiceName } from "@prisma/client";
import { Modal, Blocks, TextInput, StaticMultiSelect, StaticSelect, Bits, Md } from "slack-block-builder";

import type { AllMiddlewareArgs, SlackViewMiddlewareArgs, ViewOutput, ViewErrorsResponseAction } from "@slack/bolt";
import type { WebClient } from "@slack/bolt/node_modules/@slack/web-api"; // @fixme: update to @slack/web-api when https://github.com/slackapi/bolt-js/pull/2036 is merged
import type { Assistant } from "@prisma/client";

type ViewEventArgs = AllMiddlewareArgs & SlackViewMiddlewareArgs;

interface ModalInputs {
    assistantId: number;
    name: string;
    description: string;
    instructions: string;
    slackChannelName: string;
    aiServiceName: AIServiceName;
    model?: string;
    functions: string[];
}

export const NONE_SELECTED = -1;

export async function assistantConfigurationModal(slackTeamId: string, inputs?: ModalInputs) {
    const newAssistantLabel = "Create new assistant";
    const assistantsList = await prisma.assistant.findMany({ where: { SlackTeamId: slackTeamId } });
    const assistantsOptions = [Bits.Option({ text: newAssistantLabel, value: NONE_SELECTED.toString() })];
    // Populate the assistant options with the existing assistants
    assistantsOptions.push(
        ...assistantsList.map(assistant =>
            Bits.Option({
                text: assistant.name,
                value: assistant.id.toString()
            })
        )
    );

    const functionOptions = Object.keys(functionMap).map(functionName =>
        Bits.Option({
            text: functionMap[functionName].attributes.name,
            value: functionName
        })
    );

    const assistantId = inputs?.assistantId ?? NONE_SELECTED;
    const assistantName = inputs?.name ?? newAssistantLabel;
    const aiServiceName = inputs?.aiServiceName ?? AIServiceName.OPENAI;
    const model = inputs?.model ?? getDefaultModelValue(aiServiceName);

    return Modal()
        .callbackId("assistantConfigurationModalCallback")
        .title("AI Assistant")
        .submit("Submit")
        .close("Cancel")
        .blocks(
            Blocks.Section({ text: "Create an AI assistant bound to a public channel." }),
            Blocks.Divider(),
            Blocks.Input({ label: "Create or edit Assistant" })
                .element(
                    StaticSelect({ placeholder: "Select assistant" })
                        .options(assistantsOptions)
                        .initialOption(Bits.Option({ text: assistantName, value: assistantId.toString() }))
                        .actionId("select_assistant_action")
                )
                .dispatchAction()
                .blockId("select_assistant_action_block_id"),
            Blocks.Input({ label: "Assistant name" })
                .element(
                    TextInput({ placeholder: "Enter the assistant name" })
                        .initialValue(inputs?.name ?? "")
                        .actionId("assistant_name_text_input_action")
                )
                .blockId("assistant_name_block_id"),
            Blocks.Input({ label: "Assistant description" })
                .element(
                    TextInput({ placeholder: "Enter the assistant description", multiline: true })
                        .initialValue(inputs?.description ?? "")
                        .actionId("assistant_description_text_input_action")
                )
                .blockId("assistant_description_block_id"),
            Blocks.Input({ label: "Assistant instructions" })
                .element(
                    TextInput({ placeholder: "Enter the assistant instructions", multiline: true })
                        .initialValue(inputs?.instructions ?? "")
                        .actionId("assistant_instructions_text_input_action")
                )
                .blockId("assistant_instructions_block_id"),
            Blocks.Input({ label: "Public channel name" })
                .element(
                    TextInput({ placeholder: "Enter the public channel name" })
                        .initialValue(inputs?.slackChannelName ?? "")
                        .actionId("assistant_channel_name_text_input_action")
                )
                .blockId("assistant_channel_name_block_id"),
            Blocks.Input({ label: "AI service" })
                .element(
                    StaticSelect({ placeholder: "Select AI service" })
                        .actionId("select_ai_service_action")
                        .options(
                            Object.values(AIServiceName).map(service => Bits.Option({ text: service, value: service }))
                        )
                        .initialOption(Bits.Option({ text: aiServiceName, value: aiServiceName }))
                )
                .blockId("select_ai_service_block_id")
                .dispatchAction(),
            Blocks.Input({ label: "Model" })
                .element(
                    StaticSelect({ placeholder: "Select AI model" })
                        .actionId("select_ai_model_action")
                        .options(
                            Object.values(getModelType(aiServiceName)).map(model =>
                                Bits.Option({ text: model, value: model })
                            )
                        )
                        .initialOption(Bits.Option({ text: model, value: model }))
                )
                .blockId("select_ai_model_block_id"),
            Blocks.Section({
                text: `_See different models purpose and features in ${getModelLinkDescription(aiServiceName)}_`
            }),
            Blocks.Input({ label: "Functions" })
                .element(
                    StaticMultiSelect({ placeholder: "Select the functions assistant can use" })
                        .actionId("select_assistant_functions_action")
                        .options(functionOptions)
                        .initialOptions(inputs?.functions.map(value => Bits.Option({ text: value, value })) ?? [])
                )
                .blockId("select_assistant_functions_block_id"),
            Blocks.Section({
                text: "_The selected functions will be available for the assistant._"
            })
        )
        .buildToObject();
}

export function loadModalInputsFromView(view: ViewOutput): ModalInputs {
    const assistantId = parseInt(
        view.state.values.select_assistant_action_block_id.select_assistant_action.selected_option!.value!
    );
    const name = view.state.values.assistant_name_block_id.assistant_name_text_input_action.value!;
    const description = view.state.values.assistant_description_block_id.assistant_description_text_input_action.value!;
    const instructions =
        view.state.values.assistant_instructions_block_id.assistant_instructions_text_input_action.value!;
    const channel = view.state.values.assistant_channel_name_block_id.assistant_channel_name_text_input_action.value!;
    const ai = view.state.values.select_ai_service_block_id.select_ai_service_action.selected_option!.value!;
    const model = view.state.values.select_ai_model_block_id.select_ai_model_action.selected_option!.value!;
    const functions =
        view.state.values.select_assistant_functions_block_id.select_assistant_functions_action.selected_options!.map(
            (option: any) => option.value
        );
    logger.debug("view.state.values", view.state.values);
    return {
        assistantId,
        name,
        description,
        instructions,
        slackChannelName: channel,
        aiServiceName: ai as AIServiceName,
        model,
        functions
    };
}

// ...

src/listeners/actions/assistant-configuration-modal-actions-callback.ts

import logger from "../../logger/winston.ts";
import prisma from "../../prisma/client.ts";
import { feedback } from "../../utilities/slack.ts";
import {
    assistantConfigurationModal,
    loadModalInputsFromView,
    loadModalInputsFromAssistant,
    getDefaultModelValue,
    NONE_SELECTED
} from "../views/assistant-configuration-modal.ts";

import type { Assistant } from "@prisma/client";
import type { AllMiddlewareArgs, BlockAction, SlackActionMiddlewareArgs, StaticSelectAction } from "@slack/bolt";

type ActionEventArgs = AllMiddlewareArgs & SlackActionMiddlewareArgs<BlockAction>;

export async function selectAiServiceActionCallback({ ack, body, client }: ActionEventArgs) {
    try {
        await ack();
        // Type guard for typescript completion
        if (body.type !== "block_actions" || !body.view) return;

        const userInputs = loadModalInputsFromView(body.view);
        // Reset the model value
        userInputs.model = getDefaultModelValue(userInputs.aiServiceName);
        // @todo - not working ofc, body is not forwared outside of the function but this is kind of what we want to do
        body.view.state.values.select_ai_model_block_id.select_ai_model_action.value = getDefaultModelValue(
            userInputs.aiServiceName
        );
        // Update the configuration modal with the selected AI service values and the user's inputs
        logger.debug("userInputs", userInputs);
        logger.debug("assistantConfigurationModal", await assistantConfigurationModal(body.user.team_id!, userInputs));
        await client.views.update({
            user_id: body.user.id,
            view_id: body.view.id,
            hash: body.view.hash,
            view: await assistantConfigurationModal(body.user.team_id!, userInputs)
        });
    } catch (error) {
        logger.error(error);
        await feedback(client, body, "An error occurred while selecting the AI service. Please try again.");
    }
}

async function loadAssistant(assistantId: number, teamId: string): Promise<Assistant | undefined> {
    if (assistantId === NONE_SELECTED) return undefined;
    return await prisma.assistant.findUniqueOrThrow({ where: { id: assistantId, SlackTeamId: teamId } });
}

export async function selectAssistantActionCallback({ ack, body, client, payload }: ActionEventArgs) {
    try {
        await ack();
        // Type guard for typescript completion
        if (body.type !== "block_actions" || !body.view) return;

        const payloadTyped = payload as StaticSelectAction;
        const assistantId = parseInt(payloadTyped.selected_option.value);
        // Update the configuration modal with the selected assistant values
        await client.views.update({
            user_id: body.user.id,
            view_id: body.view.id,
            hash: body.view.hash,
            view: await assistantConfigurationModal(
                body.user.team_id!,
                loadModalInputsFromAssistant(await loadAssistant(assistantId, body.user.team_id!))
            )
        });
    } catch (error) {
        logger.error(error);
        await feedback(client, body, "An error occurred while selecting the assistant. Please try again.");
    }
}
@mister-good-deal mister-good-deal added the bug Something isn't working label Mar 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant