import _ from 'lodash'
import events from '../../../core/events'
import { MESSAGE_OWNER } from './constants'
const EVENTS = {
open: 'aiHelpBot:open',
close: 'aiHelpBot:close',
call: 'aiHelpBot:call',
showTooltip: 'aiHelpBot:showTooltip',
hideTooltip: 'aiHelpBot:hideTooltip',
register: 'aiHelpBot:register',
deregister: 'aiHelpBot:deregister',
showButton: 'aiHelpBot:showButton',
write: 'aiHelpBot:write',
input: 'aiHelpBot:input',
getContext: 'aiHelpBot:getContext',
ask: 'aiHelpBot:ask',
getHistory: 'aiHelpBot:getHistory',
showMenu: 'aiHelpBot:showMenu',
getLlmProxyDetails: 'aiHelpBot:getLlmProxyDetails'
}
const emit = events.create(EVENTS)
/**
* Codio IDE API object
* @exports window.codioIDE
* @type {object}
* @namespace codioIDE
* @global
*/
window.codioIDE = window.codioIDE || {}
/**
* Coach Bot API methods
* @memberof codioIDE
* @type {object}
* @namespace codioIDE.coachBot
*/
window.codioIDE.coachBot = {
/**
* Simple callback
* @callback codioIDE.coachBot.Callback
* @memberof codioIDE.coachBot
*/
/**
* @typedef codioIDE.coachBot.CoachBotCallOptions
* @memberof codioIDE.coachBot
* @type {Object}
* @property {string} id - action id to start
* @property {Object} [defaultValues] - default values for action
* @property {Object} [params] - additional params for action that will be passed to the action handler
*/
/**
* @typedef codioIDE.coachBot.CoachBotContext.GuidesPage
* @memberof codioIDE.coachBot.CoachBotContext
* @type {Object}
* @property {string} page - current guides page name
* @property {string} content - current guides page content as text
*/
/**
* @typedef codioIDE.coachBot.CoachBotContext.AssignmentData
* @memberof codioIDE.coachBot.CoachBotContext
* @type {Object}
* @property {string} courseName - course name
* @property {string} assignmentName - assignment name
* @property {string} userId - user id
* @property {string} userName - username
* @property {string} userLogin - user login
*/
/**
* @typedef codioIDE.coachBot.CoachBotContext.FileInfo
* @memberof codioIDE.coachBot.CoachBotContext
* @type {Object}
* @property {string} path - file path
* @property {string} content - file content
*/
/**
* @typedef codioIDE.coachBot.CoachBotContext.ErrorInfo
* @memberof codioIDE.coachBot.CoachBotContext
* @type {Object}
* @property {boolean} errorState - has error or not
* @property {string} text - error text
*/
/**
* @typedef codioIDE.coachBot.CoachBotContext
* @memberof codioIDE.coachBot
* @type {Object}
* @property {codioIDE.coachBot.CoachBotContext.GuidesPage} guidesPage - opened guides page info
* @property {codioIDE.coachBot.CoachBotContext.AssignmentData} assignmentData - assignment info
* @property {codioIDE.coachBot.CoachBotContext.FileInfo[]} files - opened files info
* @property {codioIDE.coachBot.CoachBotContext.ErrorInfo} error - current error info
*/
/**
* @typedef codioIDE.coachBot.CoachBotMessage
* @type {Object}
* @property {codioIDE.coachBot.MESSAGE_ROLES} role - message role
* @property {string} content - message text
*/
/**
* @typedef codioIDE.coachBot.LlmProxyDetails
* @type {Object}
* @property {string} provider - proxy provider name
* @property {string} endpoint - proxy endpoint
* @property {string} key - proxy key
*/
/**
* @typedef codioIDE.coachBot.CoachBotCustomRequestData
* @description Custom request data. If userPrompt was specified it will be sent as user data otherwise messages will be sent.
* @memberof codioIDE.coachBot
* @type {Object}
* @property {string} systemPrompt - system prompt
* @property {string} userPrompt - user prompt
* @property {codioIDE.coachBot.CoachBotMessage} [messages] - input messages
* - roles must alternate between USER and ASSISTANT
* - first message must use the USER role
*/
/**
* @typedef codioIDE.coachBot.CoachBotCustomProxyOptions
* @memberof codioIDE.coachBot
* @type {Object}
* @property {string} provider - proxy LLM provider
* @property {string} endpoint - optional LLM proxy https endpoint
* @property {string} key - optional LLM proxy access key
* @property {string} model - optional LLM proxy AI model
* @property {number} temperature - optional LLM proxy AI temperature. Range depend on a provider and a model.
* @property {number} maxTokens - optional LLM proxy maxTokens value. Range is 1-1024
* @property {string} apiVersion - optional LLM proxy AI model api version
*/
/**
* @typedef codioIDE.coachBot.CoachBotCustomRequestOptions
* @memberof codioIDE.coachBot
* @type {Object}
* @property {boolean} [stream=true] - stream response to coach bot
* @property {boolean} [preventMenu=false] - allow user to prevent menu
* @property {codioIDE.coachBot.CoachBotCustomProxyOptions} proxy - optional custom ask proxy configuration
*/
/**
* @typedef codioIDE.coachBot.CoachBotAskCallbackData
* @memberof codioIDE.coachBot
* @type {Object}
* @property {boolean} interrupted - was interrupted by user(user click 'back' button)
* @property {Object} error - request error
*/
/**
* Ask bot done callback
* @memberof codioIDE.coachBot
* @callback codioIDE.coachBot.AskBotDoneCallback
* @param {codioIDE.coachBot.CoachBotAskCallbackData} result - done result
*/
/**
* Opens Coach Bot
* @memberof codioIDE.coachBot
* @method open
* @param {codioIDE.coachBot.CoachBotCallOptions} [options] - optional call action options
*/
open: (options) => {
emit.open(options)
},
/**
* Closes Coach Bot
* @memberof codioIDE.coachBot
* @method close
*/
close: () => {
emit.close()
},
/**
* Opens Coach Bot and calls action
* @memberof codioIDE.coachBot
* @method call
* @param {codioIDE.coachBot.CoachBotCallOptions} [options] - optional call action options
*/
call: (options) => {
emit.call(options)
},
/**
* Show Coach bot tooltip
* @memberof codioIDE.coachBot
* @method showTooltip
* @param {string} message - tooltip message
* @param {codioIDE.coachBot.Callback} callback - executes when tooltip clicked
*/
showTooltip: (message, callback) => {
emit.showTooltip({
message,
callback: () => {
callback && callback()
}
})
},
/**
* Hide Coach bot tooltip
* @memberof codioIDE.coachBot
* @method hideTooltip
*/
hideTooltip: () => {
emit.hideTooltip()
},
/**
* Register coach bot menu item
* @memberof codioIDE.coachBot
* @method register
* @property {string} id - A unique ID.
* @property {string} name - Button text.
* @property {codioIDE.coachBot.Callback} callback - Called on button click
*/
register: (id, name, callback) => {
if (!_.isFunction(callback)) {
throw new Error('Callback should be a function')
}
emit.register({
id,
name,
callback
})
},
/**
* Deregister coach bot menu item
* @memberof codioIDE.coachBot
* @method deregister
* @param {string} id - action id to deregister
*/
deregister: (id) => {
emit.deregister(id)
},
/**
* Show custom message in the coach bot
* @memberof codioIDE.coachBot
* @method write
* @param {string} text - plain text
* @param {codioIDE.coachBot.MESSAGE_ROLES} [ownerType] - message type, default is ASSISTANT
*/
write: (text, ownerType) => {
emit.write({ text, ownerType })
},
/**
* Show custom button in the coach bot
* @memberof codioIDE.coachBot
* @method showButton
* @param {string} text - Button text.
* @param {codioIDE.coachBot.Callback} callback - button on click handler
*/
showButton: (text, callback) => {
if (!_.isFunction(callback)) {
throw new Error('Callback should be a function')
}
emit.showButton({ text, callback })
},
/**
* Get coach bot context
* @memberof codioIDE.coachBot
* @method getContext
* @async
* @return {Promise<codioIDE.coachBot.CoachBotContext>}
*/
getContext: async () => {
return new Promise((resolve, reject) => {
emit.getContext((result) =>
result.error ? reject(result.error) : resolve(result.context)
)
})
},
/**
* Ask bot
* @memberof codioIDE.coachBot
* @method ask
* @async
* @param {codioIDE.coachBot.CoachBotCustomRequestData} data - request data
* @param {codioIDE.coachBot.CoachBotCustomRequestOptions|AskBotDoneCallback} options - request options or done callback
* @param {codioIDE.coachBot.AskBotDoneCallback} [onDone] - on request done callback
* @return {Promise}
*/
ask: async (data, options, onDone) => {
if (data.messages) {
if (
data.messages.findIndex(
(message) => message.role === MESSAGE_OWNER.BOT
) === 0
) {
throw new Error('first message must use the USER role')
}
let currentMessageRole
data.messages.forEach((message) => {
if (message.role === currentMessageRole) {
throw new Error('roles must alternate between USER and ASSISTANT')
}
currentMessageRole = message.role
})
}
return new Promise((resolve, reject) => {
emit.ask({
data,
options,
onDone: (result) => {
onDone && onDone(result)
result?.error ? reject(result.error) : resolve(result)
}
})
})
},
/**
* Show user input
* @memberof codioIDE.coachBot
* @method input
* @async
* @param {string} text - text that would be shown before input
* @param {string} defaultText - prepopulate text for the input
* @return {Promise<string>} - user input
*/
input: async (text, defaultText) => {
return new Promise((resolve, reject) => {
emit.input({
text,
defaultText,
callback: ({ error, input }) => {
error ? reject(error) : resolve(input)
}
})
})
},
/**
* Get history messages
* @memberof codioIDE.coachBot
* @method getHistory
* @async
* @returns {Promise<codioIDE.coachBot.CoachBotMessage[]>}
*/
getHistory: async () => {
return new Promise((resolve, reject) => {
emit.getHistory((result) =>
result.error ? reject(result.error) : resolve(result.messages)
)
})
},
/**
* Show coach bot menu
* @memberof codioIDE.coachBot
* @method showMenu
*/
showMenu: () => {
emit.showMenu()
},
/**
* Get LLM proxy details
* @memberof codioIDE.coachBot
* @method getLlmProxyDetails
* @async
* @returns {Promise<codioIDE.coachBot.LlmProxyDetails[]>}
*/
getLlmProxyDetails: async () => {
return new Promise((resolve, reject) => {
emit.getLlmProxyDetails((result) => {
result.error ? reject(result.error) : resolve(result.detailsList)
})
})
},
/**
* @memberof codioIDE.coachBot
* @readonly
* @enum {string}
*/
MESSAGE_ROLES: {
USER: MESSAGE_OWNER.USER,
ASSISTANT: MESSAGE_OWNER.BOT
}
}