1
- import type {
1
+ import {
2
2
SearchAIAnswer ,
3
3
GitBookAPI ,
4
4
Revision ,
5
5
RevisionPage ,
6
6
RevisionPageGroup ,
7
7
SearchAIAnswerSource ,
8
+ IntegrationInstallation ,
8
9
} from '@gitbook/api' ;
9
10
10
11
import {
@@ -14,8 +15,14 @@ import {
14
15
} from '../configuration' ;
15
16
import { slackAPI } from '../slack' ;
16
17
import { QueryDisplayBlock , ShareTools , decodeSlackEscapeChars , Spacer , SourcesBlock } from '../ui' ;
17
- import { getInstallationApiClient , stripBotName , stripMarkdown } from '../utils' ;
18
+ import {
19
+ getInstallationApiClient ,
20
+ getIntegrationInstallationForTeam ,
21
+ stripBotName ,
22
+ stripMarkdown ,
23
+ } from '../utils' ;
18
24
import { Logger } from '@gitbook/runtime' ;
25
+ import { IntegrationTaskAskAI } from '../types' ;
19
26
20
27
const logger = Logger ( 'slack:queryAskAI' ) ;
21
28
@@ -72,10 +79,8 @@ const capitalizeFirstLetter = (text: string) =>
72
79
async function getRelatedSources ( params : {
73
80
sources ?: SearchAIAnswer [ 'sources' ] ;
74
81
client : GitBookAPI ;
75
- environment : SlackRuntimeEnvironment ;
76
- organization : string ;
77
82
} ) : Promise < RelatedSource [ ] > {
78
- const { sources, client, organization } = params ;
83
+ const { sources, client } = params ;
79
84
80
85
if ( ! sources || sources . length === 0 ) {
81
86
return [ ] ;
@@ -149,59 +154,120 @@ async function getRelatedSources(params: {
149
154
/*
150
155
* Queries GitBook AskAI via the GitBook API and posts the answer in the form of Slack UI Blocks back to the original channel/conversation/thread.
151
156
*/
152
- export async function queryAskAI ( {
153
- channelId,
154
- teamId,
155
- threadId,
156
- userId,
157
- text,
158
- messageType,
159
- context,
160
- authorization,
161
-
162
- responseUrl,
163
- channelName,
164
- } : IQueryAskAI ) {
165
- const { environment, api } = context ;
157
+ export async function queryAskAI ( params : IQueryAskAI ) {
158
+ const {
159
+ channelId,
160
+ teamId,
161
+ threadId,
162
+ userId,
163
+ text,
164
+ messageType,
165
+ context,
166
+ authorization,
167
+ responseUrl,
168
+ } = params ;
169
+ const { api } = context ;
166
170
167
171
const askText = `_Asking: ${ stripMarkdown ( text ) } _` ;
168
172
logger . info ( `${ askText } (channelId: ${ channelId } , teamId: ${ teamId } , userId: ${ userId } )` ) ;
169
173
170
- const { client , installation } = await getInstallationApiClient ( api , teamId ) ;
174
+ const installation = await getIntegrationInstallationForTeam ( context , teamId ) ;
171
175
if ( ! installation ) {
172
176
throw new Error ( 'Installation not found' ) ;
173
177
}
174
- // Authenticate as the installation
178
+ // Use the slack access token
175
179
const accessToken = ( installation . configuration as SlackInstallationConfiguration )
176
180
. oauth_credentials ?. access_token ;
177
181
178
182
// strip a bot name if the user_id from the request is present in the query itself (specifically for a bot mention)
179
183
// @ts -ignore
180
184
const parsedQuery = stripMarkdown ( stripBotName ( text , authorization ?. user_id ) ) ;
181
185
182
- // async acknowledge the request to the end user early
183
- slackAPI (
184
- context ,
185
- {
186
- method : 'POST' ,
187
- path : messageType === 'ephemeral' ? 'chat.postEphemeral' : 'chat.postMessage' ,
188
- responseUrl,
189
- payload : {
190
- channel : channelId ,
191
- text : askText ,
192
- ...( userId ? { user : userId } : { } ) , // actually shouldn't be optional
193
- ...( threadId ? { thread_ts : threadId } : { } ) ,
186
+ await Promise . all ( [
187
+ // acknowledge the ask query back to the user
188
+ slackAPI (
189
+ context ,
190
+ {
191
+ method : 'POST' ,
192
+ path : messageType === 'ephemeral' ? 'chat.postEphemeral' : 'chat.postMessage' ,
193
+ responseUrl,
194
+ payload : {
195
+ channel : channelId ,
196
+ text : askText ,
197
+ ...( userId ? { user : userId } : { } ) , // actually shouldn't be optional
198
+ ...( threadId ? { thread_ts : threadId } : { } ) ,
199
+ } ,
200
+ } ,
201
+ {
202
+ accessToken,
194
203
} ,
204
+ ) ,
205
+ // Queue a task to process the AskAI query asynchronously. Because workers have a 30s timeout on
206
+ // waitUntil which is not enough for scenarios where AskAI might take longer to respond.
207
+ queueQueryAskAI ( {
208
+ ...params ,
209
+ accessToken,
210
+ installation,
211
+ query : parsedQuery ,
212
+ } ) ,
213
+ ] ) ;
214
+ }
215
+
216
+ /**
217
+ * Queues an integration task to process the AskAI query asynchronously.
218
+ */
219
+ async function queueQueryAskAI (
220
+ params : IQueryAskAI & {
221
+ query : string ;
222
+ installation : IntegrationInstallation ;
223
+ accessToken : string | undefined ;
224
+ } ,
225
+ ) {
226
+ const { accessToken, installation, query, context, ...rest } = params ;
227
+
228
+ const task : IntegrationTaskAskAI = {
229
+ type : 'ask:ai' ,
230
+ payload : {
231
+ query,
232
+ organizationId : installation . target . organization ,
233
+ installationId : installation . id ,
234
+ accessToken,
235
+ ...rest ,
195
236
} ,
196
- {
237
+ } ;
238
+
239
+ logger . info ( `Queue task ${ task . type } for installation: ${ task . payload . installationId } )` ) ;
240
+
241
+ await context . api . integrations . queueIntegrationTask ( context . environment . integration . name , {
242
+ task,
243
+ } ) ;
244
+ }
245
+
246
+ /**
247
+ * Handle the integration task to process the AskAI query.
248
+ */
249
+ export async function handleAskAITask ( task : IntegrationTaskAskAI , context : SlackRuntimeContext ) {
250
+ const {
251
+ payload : {
252
+ channelName,
253
+ channelId,
254
+ userId,
255
+ text,
256
+ messageType,
257
+ responseUrl,
258
+ threadId,
259
+ query,
260
+ organizationId,
261
+ installationId,
197
262
accessToken,
198
263
} ,
199
- ) ;
264
+ } = task ;
200
265
266
+ const client = await getInstallationApiClient ( context , installationId ) ;
201
267
const result = await client . orgs . askInOrganization (
202
- installation . target . organization ,
268
+ organizationId ,
203
269
{
204
- query : parsedQuery ,
270
+ query,
205
271
} ,
206
272
{
207
273
format : 'markdown' ,
@@ -223,8 +289,6 @@ export async function queryAskAI({
223
289
const relatedSources = await getRelatedSources ( {
224
290
sources : answer . sources ,
225
291
client,
226
- environment,
227
- organization : installation . target . organization ,
228
292
} ) ;
229
293
230
294
const header = text . length > 150 ? `${ text . slice ( 0 , 140 ) } ...` : text ;
0 commit comments