Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/backend/src/apps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import formsgApp from './formsg'
import gathersgApp from './gathersg'
import lettersgApp from './lettersg'
import m365ExcelApp from './m365-excel'
import pairApp from './pair'
import paysgApp from './paysg'
import postmanApp from './postman'
import postmanSmsApp from './postman-sms'
Expand Down Expand Up @@ -42,6 +43,7 @@ const apps: Record<string, IApp> = {
[webhookApp.key]: webhookApp,
[aisayApp.key]: aisayApp,
[gathersgApp.key]: gathersgApp,
[pairApp.key]: pairApp,
}

export default apps
84 changes: 84 additions & 0 deletions packages/backend/src/apps/pair/actions/call-pair/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { IRawAction } from '@plumber/types'

import { fromZodError } from 'zod-validation-error'

import generateText from '@/apps/pair/common/generateText'
import StepError, { GenericSolution } from '@/errors/step'

import { schema } from './schema'

const action: IRawAction = {
name: 'Pair',
key: 'callPair',
description:
'Enter a custom prompt to summarise, categorise or analyse data with Pair',
arguments: [
// TODO (kevinkim-ogp): each option should link to a different
// default value for the prompt. update when the default value is provided
{
label: 'What would you like to do?',
key: 'promptType',
type: 'dropdown' as const,
required: true,
options: [
{ label: 'Analyse', value: 'analyse' },
{ label: 'Categorise', value: 'categorise' },
{ label: 'Summarise', value: 'summarise' },
{ label: 'Write', value: 'write' },
{
label: 'Custom prompt',
value: 'custom',
description: 'Do it yourself',
},
],
showOptionValue: false,
},
{
label: 'Prompt',
key: 'prompt',
type: 'rich-text' as const,
required: true,
variables: true,
customRteMenuOptions: [
'Bold',
'Italic',
'Underline',
'divider', // specify when to show a divider
'Heading 1',
'Heading 2',
'Heading 3',
'Heading 4',
'Bullet List',
'Ordered List',
'divider',
'Undo',
'Redo',
],
returnMarkdown: true,
},
],

async run($) {
const validatedParameters = schema.safeParse($.step.parameters)

if (!validatedParameters.success) {
const firstError = fromZodError(validatedParameters.error).details[0]
throw new StepError(
firstError.message,
GenericSolution.ReconfigureInvalidField,
$.step.position,
$.app.name,
)
}

const { prompt } = validatedParameters.data

const response = await generateText(prompt, $)

$.setActionItem({
raw: { data: response },
})
},
}

export default action
9 changes: 9 additions & 0 deletions packages/backend/src/apps/pair/actions/call-pair/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import z from 'zod/v3'

export const schema = z.object({
promptType: z.enum(['analyse', 'categorise', 'summarise', 'write', 'custom']),
prompt: z.string().min(1),
responseFormat: z
.enum(['singleField', 'multipleFields'])
.default('singleField'),
})
3 changes: 3 additions & 0 deletions packages/backend/src/apps/pair/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import callPair from './call-pair'

export default [callPair]
4 changes: 4 additions & 0 deletions packages/backend/src/apps/pair/assets/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 63 additions & 0 deletions packages/backend/src/apps/pair/common/generateText.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { IGlobalVariable } from '@plumber/types'

import { startActiveObservation } from '@langfuse/tracing'
import { generateText as AiSdkGenerateText } from 'ai'

import appConfig from '@/config/app'
import logger from '@/helpers/logger'
import { model, MODEL_TYPE } from '@/helpers/pair'

async function generateText(prompt: string, $: IGlobalVariable) {
try {
const result = await startActiveObservation(
'pair-action-generate-text',
async (trace) => {
trace.updateTrace({
userId: $.user.email,
environment: appConfig.appEnv,
tags: ['pair', 'action', 'generate-text'],
})

trace.update({ input: prompt })

const generation = trace.startObservation(
'pair-action-generate-text',
{
model: MODEL_TYPE,
input: [{ role: 'user', content: prompt as string }],
},
{ asType: 'generation' },
)

const { text } = await AiSdkGenerateText({
model,
prompt: [{ role: 'user', content: prompt as string }],
experimental_telemetry: {
isEnabled: true,
functionId: 'pair-action-generate-text',
metadata: {
userId: $.user.email,
},
},
})

trace.update({ output: text })

generation.update({ output: text }).end()

return text
},
)

return result
} catch (error) {
logger.error('Failed to generate text', {
error: error,
user: $.user.email,
})
throw error
}
return ''
}

export default generateText
18 changes: 18 additions & 0 deletions packages/backend/src/apps/pair/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { IApp } from '@plumber/types'

import actions from './actions'

const app: IApp = {
name: 'Pair',
key: 'pair',
description: 'Summarise, categorise or analyse data with Pair',
iconUrl: '{BASE_URL}/apps/pair/assets/favicon.svg',
authDocUrl: '',
baseUrl: '',
apiBaseUrl: '',
primaryColor: '',
actions,
category: 'ai',
}

export default app
2 changes: 2 additions & 0 deletions packages/backend/src/graphql/queries/get-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import formsgApp from '@/apps/formsg/'
import gathersgApp from '@/apps/gathersg'
import lettersgApp from '@/apps/lettersg'
import m365ExcelApp from '@/apps/m365-excel'
import pairApp from '@/apps/pair'
import paysgApp from '@/apps/paysg'
import postmanApp from '@/apps/postman'
import postmanSmsApp from '@/apps/postman-sms'
Expand Down Expand Up @@ -52,6 +53,7 @@ export const ACTION_APPS_RANKING = [
postmanSmsApp.key,
telegramBotApp.key,
slackApp.key,
pairApp.key,
aisayApp.key,
gathersgApp.key,
customApiApp.key,
Expand Down