floating-copilot-widget 1.4.4 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +472 -16
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +809 -1
- package/dist/index.js +829 -0
- package/dist/services/copilot.d.ts +91 -0
- package/dist/services/copilot.js +503 -0
- package/dist/services/integrations.d.ts +62 -0
- package/dist/services/integrations.js +281 -0
- package/dist/types/copilot.d.ts +207 -0
- package/dist/types/copilot.js +7 -0
- package/dist/types.d.ts +7 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -257,4 +257,812 @@ const FloatingCopilot = ({ config = {} }) => {
|
|
|
257
257
|
finalConfig.resizable && !isMaximized && (React.createElement("div", { className: "copilot-resize-handle", onMouseDown: handleMouseDownResize, title: "Drag to resize" }))));
|
|
258
258
|
};
|
|
259
259
|
|
|
260
|
-
|
|
260
|
+
/******************************************************************************
|
|
261
|
+
Copyright (c) Microsoft Corporation.
|
|
262
|
+
|
|
263
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
264
|
+
purpose with or without fee is hereby granted.
|
|
265
|
+
|
|
266
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
267
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
268
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
269
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
270
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
271
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
272
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
273
|
+
***************************************************************************** */
|
|
274
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
278
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
279
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
280
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
281
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
282
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
283
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
288
|
+
var e = new Error(message);
|
|
289
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* GitHub Copilot Integration Service
|
|
294
|
+
*
|
|
295
|
+
* Provides AI-powered intent analysis and API integration for the floating chat widget.
|
|
296
|
+
* Uses GitHub's Models API (backed by OpenAI GPT-4) for intelligent message understanding.
|
|
297
|
+
*
|
|
298
|
+
* To use this service, you need a GitHub Personal Access Token with appropriate scopes.
|
|
299
|
+
*/
|
|
300
|
+
const GITHUB_COPILOT_MODELS = 'https://models.inference.ai.azure.com/chat/completions';
|
|
301
|
+
/**
|
|
302
|
+
* Get GitHub token from config or environment variables
|
|
303
|
+
*/
|
|
304
|
+
function getGithubToken(configToken) {
|
|
305
|
+
var _a, _b, _c;
|
|
306
|
+
// 1. Use explicitly provided token from config
|
|
307
|
+
if (configToken) {
|
|
308
|
+
return configToken;
|
|
309
|
+
}
|
|
310
|
+
// 2. Check common environment variable names
|
|
311
|
+
const token = (typeof process !== 'undefined' && ((_a = process.env) === null || _a === void 0 ? void 0 : _a.GITHUB_TOKEN)) ||
|
|
312
|
+
(typeof process !== 'undefined' && ((_b = process.env) === null || _b === void 0 ? void 0 : _b.VITE_GITHUB_TOKEN)) ||
|
|
313
|
+
(typeof process !== 'undefined' && ((_c = process.env) === null || _c === void 0 ? void 0 : _c.REACT_APP_GITHUB_TOKEN)) ||
|
|
314
|
+
(typeof window !== 'undefined' && window.__GITHUB_TOKEN__);
|
|
315
|
+
if (!token) {
|
|
316
|
+
throw new Error('GitHub token is required for Copilot integration. ' +
|
|
317
|
+
'Provide it via:\n' +
|
|
318
|
+
'1. CopilotConfig.githubToken parameter\n' +
|
|
319
|
+
'2. Environment variable: GITHUB_TOKEN, VITE_GITHUB_TOKEN, or REACT_APP_GITHUB_TOKEN\n' +
|
|
320
|
+
'3. Global variable: window.__GITHUB_TOKEN__\n\n' +
|
|
321
|
+
'Get token from: https://github.com/settings/tokens (scopes: copilot, read:user)');
|
|
322
|
+
}
|
|
323
|
+
return token;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Analyze user message to identify intent and extract parameters
|
|
327
|
+
*/
|
|
328
|
+
function analyzeUserIntent(request, config, availableApis) {
|
|
329
|
+
var _a, _b, _c;
|
|
330
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
331
|
+
const { userMessage, context, customInstructions } = request;
|
|
332
|
+
const { model = 'gpt-4o', maxTokens = 2000 } = config;
|
|
333
|
+
const githubToken = getGithubToken(config.githubToken);
|
|
334
|
+
// Build system prompt
|
|
335
|
+
const systemPrompt = buildIntentSystemPrompt(availableApis, customInstructions);
|
|
336
|
+
// Build user prompt
|
|
337
|
+
const userPrompt = buildIntentUserPrompt(userMessage, context);
|
|
338
|
+
try {
|
|
339
|
+
const response = yield fetch(GITHUB_COPILOT_MODELS, {
|
|
340
|
+
method: 'POST',
|
|
341
|
+
headers: {
|
|
342
|
+
'Authorization': `Bearer ${githubToken}`,
|
|
343
|
+
'Content-Type': 'application/json',
|
|
344
|
+
'X-GitHub-Api-Version': '2022-11-28'
|
|
345
|
+
},
|
|
346
|
+
body: JSON.stringify({
|
|
347
|
+
model: model,
|
|
348
|
+
messages: [
|
|
349
|
+
{ role: 'system', content: systemPrompt },
|
|
350
|
+
{ role: 'user', content: userPrompt }
|
|
351
|
+
],
|
|
352
|
+
max_tokens: maxTokens,
|
|
353
|
+
temperature: 0.3
|
|
354
|
+
})
|
|
355
|
+
});
|
|
356
|
+
if (!response.ok) {
|
|
357
|
+
const errorText = yield response.text();
|
|
358
|
+
throw new Error(`Copilot API error (${response.status}): ${errorText}`);
|
|
359
|
+
}
|
|
360
|
+
const data = yield response.json();
|
|
361
|
+
const content = (_c = (_b = (_a = data.choices) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.content;
|
|
362
|
+
if (!content) {
|
|
363
|
+
throw new Error('No response received from Copilot');
|
|
364
|
+
}
|
|
365
|
+
// Parse the intent response
|
|
366
|
+
return parseIntentResponse(content, userMessage);
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
throw new Error(`Failed to analyze intent: ${error.message}`);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Call an API integration based on intent and parameters
|
|
375
|
+
*/
|
|
376
|
+
function callAPI(request, integration) {
|
|
377
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
378
|
+
try {
|
|
379
|
+
// Build the request
|
|
380
|
+
const apiRequest = integration.buildRequest(request.parameters, request.context);
|
|
381
|
+
// Prepare fetch options
|
|
382
|
+
const fetchOptions = {
|
|
383
|
+
method: integration.method,
|
|
384
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, integration.headers)
|
|
385
|
+
};
|
|
386
|
+
// Add body for non-GET requests
|
|
387
|
+
if (integration.method !== 'GET' && apiRequest) {
|
|
388
|
+
fetchOptions.body = typeof apiRequest === 'string' ? apiRequest : JSON.stringify(apiRequest);
|
|
389
|
+
}
|
|
390
|
+
// Make the API call
|
|
391
|
+
const response = yield fetch(integration.endpoint, fetchOptions);
|
|
392
|
+
if (!response.ok) {
|
|
393
|
+
const errorText = yield response.text();
|
|
394
|
+
return {
|
|
395
|
+
success: false,
|
|
396
|
+
error: `API error (${response.status}): ${errorText}`,
|
|
397
|
+
apiName: integration.name
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
const responseData = yield response.json();
|
|
401
|
+
// Format the response
|
|
402
|
+
const formattedData = integration.formatResponse(responseData);
|
|
403
|
+
return {
|
|
404
|
+
success: true,
|
|
405
|
+
data: formattedData,
|
|
406
|
+
rawData: responseData,
|
|
407
|
+
apiName: integration.name
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
return {
|
|
412
|
+
success: false,
|
|
413
|
+
error: `Failed to call API: ${error.message}`,
|
|
414
|
+
apiName: integration.name
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Process user message: analyze intent and call appropriate API
|
|
421
|
+
*/
|
|
422
|
+
function processUserMessage(userMessage, config, availableApis, context) {
|
|
423
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
424
|
+
// Validate token is available early
|
|
425
|
+
getGithubToken(config.githubToken);
|
|
426
|
+
// Step 1: Analyze intent
|
|
427
|
+
const intentResponse = yield analyzeUserIntent({ userMessage, context }, config, availableApis);
|
|
428
|
+
// Step 2: Determine which API to call
|
|
429
|
+
let apiResponse;
|
|
430
|
+
if (intentResponse.suggestedApis.length > 0) {
|
|
431
|
+
const apiId = intentResponse.suggestedApis[0];
|
|
432
|
+
const api = availableApis.find(a => a.id === apiId);
|
|
433
|
+
if (api) {
|
|
434
|
+
// Check if API should be called for this intent
|
|
435
|
+
const shouldCall = api.shouldCall
|
|
436
|
+
? api.shouldCall(intentResponse.intent, intentResponse.parameters)
|
|
437
|
+
: true;
|
|
438
|
+
if (shouldCall) {
|
|
439
|
+
apiResponse = yield callAPI({
|
|
440
|
+
apiId,
|
|
441
|
+
parameters: intentResponse.parameters,
|
|
442
|
+
context
|
|
443
|
+
}, api);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
intent: intentResponse,
|
|
449
|
+
apiResponse
|
|
450
|
+
};
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Validate that GitHub token is available before processing
|
|
455
|
+
* Useful for early validation in applications
|
|
456
|
+
*/
|
|
457
|
+
function validateGithubToken(config) {
|
|
458
|
+
try {
|
|
459
|
+
const token = getGithubToken(config.githubToken);
|
|
460
|
+
return { valid: true, token };
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
return { valid: false, error: error.message };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Generate a user-friendly response from intent and API data
|
|
468
|
+
*/
|
|
469
|
+
function generateCopilotResponse(userMessage, intentResponse, apiResponse, config) {
|
|
470
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
471
|
+
// If we have API response data, return it
|
|
472
|
+
if ((apiResponse === null || apiResponse === void 0 ? void 0 : apiResponse.success) && apiResponse.data) {
|
|
473
|
+
return apiResponse.data;
|
|
474
|
+
}
|
|
475
|
+
// If API call failed, explain the error
|
|
476
|
+
if (apiResponse === null || apiResponse === void 0 ? void 0 : apiResponse.error) {
|
|
477
|
+
return `I encountered an error while processing your request: ${apiResponse.error}`;
|
|
478
|
+
}
|
|
479
|
+
// If no API was called but we have intent analysis
|
|
480
|
+
if (intentResponse.intent && intentResponse.confidence > 0.5) {
|
|
481
|
+
return `I understand you want to: ${intentResponse.intent}
|
|
482
|
+
|
|
483
|
+
Parameters identified:
|
|
484
|
+
${Object.entries(intentResponse.parameters)
|
|
485
|
+
.map(([key, value]) => `- ${key}: ${JSON.stringify(value)}`)
|
|
486
|
+
.join('\n')}
|
|
487
|
+
|
|
488
|
+
However, no API integration is available for this intent. Please check the available integrations.`;
|
|
489
|
+
}
|
|
490
|
+
// Fallback response
|
|
491
|
+
return `I'm not sure what you're asking. Could you provide more details?`;
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Build system prompt for intent analysis
|
|
496
|
+
*/
|
|
497
|
+
function buildIntentSystemPrompt(availableApis, customInstructions) {
|
|
498
|
+
let prompt = `You are an intelligent intent analyzer integrated into a chat application.
|
|
499
|
+
Your role is to understand user messages and identify the user's intent, extract relevant parameters, and suggest which API integrations to use.
|
|
500
|
+
|
|
501
|
+
RESPONSE FORMAT:
|
|
502
|
+
You MUST respond ONLY with a valid JSON object in this exact format:
|
|
503
|
+
{
|
|
504
|
+
"intent": "description of what the user wants",
|
|
505
|
+
"confidence": 0.95,
|
|
506
|
+
"parameters": {
|
|
507
|
+
"param1": "value1",
|
|
508
|
+
"param2": "value2"
|
|
509
|
+
},
|
|
510
|
+
"suggestedApis": ["api_id_1", "api_id_2"]
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
IMPORTANT:
|
|
514
|
+
- intent: A clear description of what the user wants to accomplish
|
|
515
|
+
- confidence: A number between 0 and 1 indicating how confident you are
|
|
516
|
+
- parameters: Key-value pairs of information extracted from the message
|
|
517
|
+
- suggestedApis: Array of API IDs that should be called (in order of preference)
|
|
518
|
+
|
|
519
|
+
Be conversational, helpful, and focus on understanding the user's actual need.`;
|
|
520
|
+
if (availableApis && availableApis.length > 0) {
|
|
521
|
+
prompt += `\n\nAVAILABLE API INTEGRATIONS:\n`;
|
|
522
|
+
availableApis.forEach(api => {
|
|
523
|
+
prompt += `\n- ID: ${api.id}\n Name: ${api.name}\n Description: ${api.name}`;
|
|
524
|
+
if (api.intents) {
|
|
525
|
+
prompt += `\n Intended for intents: ${api.intents.join(', ')}`;
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
if (customInstructions) {
|
|
530
|
+
prompt += `\n\nCUSTOM INSTRUCTIONS:\n${customInstructions}`;
|
|
531
|
+
}
|
|
532
|
+
return prompt;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Build user prompt for intent analysis
|
|
536
|
+
*/
|
|
537
|
+
function buildIntentUserPrompt(userMessage, context, availableApis) {
|
|
538
|
+
let prompt = `Analyze this user message and respond with ONLY a valid JSON object (no markdown, no extra text):
|
|
539
|
+
|
|
540
|
+
User message: "${userMessage}"`;
|
|
541
|
+
if (context && Object.keys(context).length > 0) {
|
|
542
|
+
prompt += `\n\nContext information:\n`;
|
|
543
|
+
Object.entries(context).forEach(([key, value]) => {
|
|
544
|
+
prompt += `- ${key}: ${JSON.stringify(value)}\n`;
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
prompt += `\n\nRespond with ONLY the JSON object, no other text.`;
|
|
548
|
+
return prompt;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Parse intent response from Copilot
|
|
552
|
+
*/
|
|
553
|
+
function parseIntentResponse(content, userMessage) {
|
|
554
|
+
try {
|
|
555
|
+
// Extract JSON from potential markdown code blocks
|
|
556
|
+
let jsonContent = content.trim();
|
|
557
|
+
const jsonMatch = content.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
|
|
558
|
+
if (jsonMatch) {
|
|
559
|
+
jsonContent = jsonMatch[1];
|
|
560
|
+
}
|
|
561
|
+
const parsed = JSON.parse(jsonContent);
|
|
562
|
+
return {
|
|
563
|
+
intent: parsed.intent || 'unknown',
|
|
564
|
+
confidence: typeof parsed.confidence === 'number' ? parsed.confidence : 0.5,
|
|
565
|
+
parameters: parsed.parameters || {},
|
|
566
|
+
suggestedApis: Array.isArray(parsed.suggestedApis) ? parsed.suggestedApis : [],
|
|
567
|
+
rawResponse: content
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
catch (error) {
|
|
571
|
+
// Fallback parsing if JSON parsing fails
|
|
572
|
+
return {
|
|
573
|
+
intent: 'user_query',
|
|
574
|
+
confidence: 0.3,
|
|
575
|
+
parameters: { query: userMessage },
|
|
576
|
+
suggestedApis: [],
|
|
577
|
+
rawResponse: content
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Validate API integration configuration
|
|
583
|
+
*/
|
|
584
|
+
function validateAPIIntegration(api) {
|
|
585
|
+
const errors = [];
|
|
586
|
+
if (!api.id)
|
|
587
|
+
errors.push('API must have an id');
|
|
588
|
+
if (!api.name)
|
|
589
|
+
errors.push('API must have a name');
|
|
590
|
+
if (!api.endpoint)
|
|
591
|
+
errors.push('API must have an endpoint');
|
|
592
|
+
if (!api.method)
|
|
593
|
+
errors.push('API must have a method');
|
|
594
|
+
if (!api.buildRequest || typeof api.buildRequest !== 'function') {
|
|
595
|
+
errors.push('API must have a buildRequest function');
|
|
596
|
+
}
|
|
597
|
+
if (!api.formatResponse || typeof api.formatResponse !== 'function') {
|
|
598
|
+
errors.push('API must have a formatResponse function');
|
|
599
|
+
}
|
|
600
|
+
return {
|
|
601
|
+
valid: errors.length === 0,
|
|
602
|
+
errors
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Merge multiple API integrations from different sources
|
|
607
|
+
*/
|
|
608
|
+
function mergeAPIIntegrations(...integrationSets) {
|
|
609
|
+
const merged = new Map();
|
|
610
|
+
integrationSets.forEach(set => {
|
|
611
|
+
set.forEach(api => {
|
|
612
|
+
const validation = validateAPIIntegration(api);
|
|
613
|
+
if (validation.valid) {
|
|
614
|
+
merged.set(api.id, api);
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
console.warn(`Invalid API integration ${api.id}:`, validation.errors);
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
return Array.from(merged.values());
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Get missing required parameters for an API
|
|
625
|
+
*/
|
|
626
|
+
function getMissingParameters(api, providedParams) {
|
|
627
|
+
if (!api.requiredParameters || api.requiredParameters.length === 0) {
|
|
628
|
+
return [];
|
|
629
|
+
}
|
|
630
|
+
return api.requiredParameters
|
|
631
|
+
.filter(param => param.required !== false && !providedParams[param.name])
|
|
632
|
+
.map(param => param.name);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Get the next missing parameter to ask for
|
|
636
|
+
*/
|
|
637
|
+
function getNextMissingParameter(api, providedParams) {
|
|
638
|
+
if (!api.requiredParameters || api.requiredParameters.length === 0) {
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
const missing = api.requiredParameters.find(param => param.required !== false && !providedParams[param.name]);
|
|
642
|
+
if (!missing) {
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
parameter: missing.name,
|
|
647
|
+
description: missing.description,
|
|
648
|
+
example: missing.example,
|
|
649
|
+
type: missing.type
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Validate a parameter value against its definition
|
|
654
|
+
*/
|
|
655
|
+
function validateParameter(paramName, value, definition) {
|
|
656
|
+
if (!value) {
|
|
657
|
+
return { valid: false, error: `${paramName} cannot be empty` };
|
|
658
|
+
}
|
|
659
|
+
// Type validation
|
|
660
|
+
if (definition.type) {
|
|
661
|
+
switch (definition.type) {
|
|
662
|
+
case 'email':
|
|
663
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
664
|
+
return { valid: false, error: `${paramName} must be a valid email address` };
|
|
665
|
+
}
|
|
666
|
+
break;
|
|
667
|
+
case 'phone':
|
|
668
|
+
if (!/^[\d\s\-+()]+$/.test(value) || value.replace(/\D/g, '').length < 10) {
|
|
669
|
+
return { valid: false, error: `${paramName} must be a valid phone number` };
|
|
670
|
+
}
|
|
671
|
+
break;
|
|
672
|
+
case 'number':
|
|
673
|
+
if (isNaN(Number(value))) {
|
|
674
|
+
return { valid: false, error: `${paramName} must be a number` };
|
|
675
|
+
}
|
|
676
|
+
break;
|
|
677
|
+
case 'date':
|
|
678
|
+
if (isNaN(Date.parse(value))) {
|
|
679
|
+
return { valid: false, error: `${paramName} must be a valid date` };
|
|
680
|
+
}
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
// Regex validation
|
|
685
|
+
if (definition.validation) {
|
|
686
|
+
const regex = new RegExp(definition.validation);
|
|
687
|
+
if (!regex.test(value)) {
|
|
688
|
+
return { valid: false, error: `${paramName} format is invalid` };
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return { valid: true };
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Check if all required parameters have been collected
|
|
695
|
+
*/
|
|
696
|
+
function areAllParametersCollected(api, providedParams) {
|
|
697
|
+
if (!api.requiredParameters || api.requiredParameters.length === 0) {
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
700
|
+
return !api.requiredParameters.some(param => param.required !== false && !providedParams[param.name]);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Collect parameters interactively
|
|
704
|
+
* Returns a system prompt asking for the next missing parameter
|
|
705
|
+
* OR triggers API call if all parameters are collected
|
|
706
|
+
*/
|
|
707
|
+
function generateParameterPromptOrCallAPI(api, providedParams, intent, context) {
|
|
708
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
709
|
+
// Check if all required parameters are collected
|
|
710
|
+
const allCollected = areAllParametersCollected(api, providedParams);
|
|
711
|
+
if (allCollected) {
|
|
712
|
+
// All parameters are collected - call the API
|
|
713
|
+
try {
|
|
714
|
+
const apiResponse = yield callAPI({
|
|
715
|
+
apiId: api.id,
|
|
716
|
+
parameters: providedParams,
|
|
717
|
+
context
|
|
718
|
+
}, api);
|
|
719
|
+
return {
|
|
720
|
+
apiResponse,
|
|
721
|
+
allParametersCollected: true
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
catch (error) {
|
|
725
|
+
return {
|
|
726
|
+
prompt: `Error executing request: ${error.message}`,
|
|
727
|
+
allParametersCollected: true
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
// Still missing parameters - ask for the next one
|
|
732
|
+
const nextParam = getNextMissingParameter(api, providedParams);
|
|
733
|
+
if (!nextParam) {
|
|
734
|
+
return {
|
|
735
|
+
allParametersCollected: true
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
const provided = Object.keys(providedParams)
|
|
739
|
+
.filter(key => providedParams[key])
|
|
740
|
+
.map(key => `- ${key}: ${providedParams[key]}`)
|
|
741
|
+
.join('\n');
|
|
742
|
+
let prompt = `To complete your request to ${intent}, I need the following information:\n\n`;
|
|
743
|
+
prompt += `**${nextParam.parameter}**: ${nextParam.description}`;
|
|
744
|
+
if (nextParam.type) {
|
|
745
|
+
prompt += ` (Type: ${nextParam.type})`;
|
|
746
|
+
}
|
|
747
|
+
if (nextParam.example) {
|
|
748
|
+
prompt += `\nExample: ${nextParam.example}`;
|
|
749
|
+
}
|
|
750
|
+
if (provided) {
|
|
751
|
+
prompt += `\n\nAlready provided:\n${provided}`;
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
prompt,
|
|
755
|
+
allParametersCollected: false
|
|
756
|
+
};
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Collect parameters interactively
|
|
761
|
+
* Returns a system prompt asking for the next missing parameter
|
|
762
|
+
*/
|
|
763
|
+
function generateParameterPrompt(api, providedParams) {
|
|
764
|
+
const nextParam = getNextMissingParameter(api, providedParams);
|
|
765
|
+
if (!nextParam) {
|
|
766
|
+
return '';
|
|
767
|
+
}
|
|
768
|
+
const provided = Object.keys(providedParams)
|
|
769
|
+
.filter(key => providedParams[key])
|
|
770
|
+
.map(key => `- ${key}: ${providedParams[key]}`)
|
|
771
|
+
.join('\n');
|
|
772
|
+
let prompt = `To complete your request, I need the following information:\n\n`;
|
|
773
|
+
prompt += `**${nextParam.parameter}**: ${nextParam.description}`;
|
|
774
|
+
if (nextParam.type) {
|
|
775
|
+
prompt += ` (Type: ${nextParam.type})`;
|
|
776
|
+
}
|
|
777
|
+
if (nextParam.example) {
|
|
778
|
+
prompt += `\nExample: ${nextParam.example}`;
|
|
779
|
+
}
|
|
780
|
+
if (provided) {
|
|
781
|
+
prompt += `\n\nAlready provided:\n${provided}`;
|
|
782
|
+
}
|
|
783
|
+
return prompt;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* API Integration Examples
|
|
788
|
+
*
|
|
789
|
+
* This file provides examples of how to create and configure API integrations
|
|
790
|
+
* for use with the Copilot chat widget.
|
|
791
|
+
*/
|
|
792
|
+
/**
|
|
793
|
+
* Example: Customer Creation API with Required Parameters
|
|
794
|
+
*
|
|
795
|
+
* This example shows how to integrate an API that creates a customer
|
|
796
|
+
* with interactive parameter collection
|
|
797
|
+
*/
|
|
798
|
+
const createCustomerAPIIntegration = (baseUrl) => {
|
|
799
|
+
const requiredParams = [
|
|
800
|
+
{
|
|
801
|
+
name: 'name',
|
|
802
|
+
description: 'Customer full name',
|
|
803
|
+
type: 'string',
|
|
804
|
+
required: true,
|
|
805
|
+
example: 'John Doe'
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
name: 'email',
|
|
809
|
+
description: 'Customer email address',
|
|
810
|
+
type: 'email',
|
|
811
|
+
required: true,
|
|
812
|
+
validation: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
|
|
813
|
+
example: 'john@example.com'
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
name: 'phone',
|
|
817
|
+
description: 'Customer phone number',
|
|
818
|
+
type: 'phone',
|
|
819
|
+
required: true,
|
|
820
|
+
example: '+1-555-123-4567'
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
name: 'company',
|
|
824
|
+
description: 'Company name (optional)',
|
|
825
|
+
type: 'string',
|
|
826
|
+
required: false,
|
|
827
|
+
example: 'Acme Corp'
|
|
828
|
+
}
|
|
829
|
+
];
|
|
830
|
+
return {
|
|
831
|
+
id: 'create_customer',
|
|
832
|
+
name: 'Create Customer',
|
|
833
|
+
endpoint: `${baseUrl}/api/customers`,
|
|
834
|
+
method: 'POST',
|
|
835
|
+
intents: ['create_customer', 'add_customer', 'new_customer'],
|
|
836
|
+
requiredParameters: requiredParams,
|
|
837
|
+
buildRequest: (params) => ({
|
|
838
|
+
name: params.name,
|
|
839
|
+
email: params.email,
|
|
840
|
+
phone: params.phone,
|
|
841
|
+
company: params.company || null,
|
|
842
|
+
createdAt: new Date().toISOString()
|
|
843
|
+
}),
|
|
844
|
+
formatResponse: (data) => {
|
|
845
|
+
return `✅ **Customer Created Successfully**\n\n` +
|
|
846
|
+
`**ID**: ${data.id}\n` +
|
|
847
|
+
`**Name**: ${data.name}\n` +
|
|
848
|
+
`**Email**: ${data.email}\n` +
|
|
849
|
+
`**Phone**: ${data.phone}\n` +
|
|
850
|
+
(data.company ? `**Company**: ${data.company}\n` : '') +
|
|
851
|
+
`**Created**: ${new Date(data.createdAt).toLocaleDateString()}`;
|
|
852
|
+
},
|
|
853
|
+
shouldCall: (intent, params) => {
|
|
854
|
+
// Call when all required params are present
|
|
855
|
+
return !!(params.name && params.email && params.phone);
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
};
|
|
859
|
+
/**
|
|
860
|
+
* Example: REST API Integration
|
|
861
|
+
*
|
|
862
|
+
* This example shows how to integrate a generic REST API that returns user data
|
|
863
|
+
*/
|
|
864
|
+
const createUserAPIIntegration = (baseUrl) => ({
|
|
865
|
+
id: 'user_api',
|
|
866
|
+
name: 'User Service API',
|
|
867
|
+
endpoint: `${baseUrl}/api/users`,
|
|
868
|
+
method: 'GET',
|
|
869
|
+
intents: ['get_user', 'find_user', 'lookup_user'],
|
|
870
|
+
buildRequest: (params) => {
|
|
871
|
+
// Transform intent parameters into API query string
|
|
872
|
+
const queryParams = new URLSearchParams();
|
|
873
|
+
if (params.userId)
|
|
874
|
+
queryParams.append('id', params.userId);
|
|
875
|
+
if (params.username)
|
|
876
|
+
queryParams.append('username', params.username);
|
|
877
|
+
if (params.email)
|
|
878
|
+
queryParams.append('email', params.email);
|
|
879
|
+
return queryParams.toString();
|
|
880
|
+
},
|
|
881
|
+
formatResponse: (data) => {
|
|
882
|
+
if (Array.isArray(data)) {
|
|
883
|
+
return data.map(user => `**${user.name}** (${user.email})\nID: ${user.id}\nRole: ${user.role}`).join('\n\n');
|
|
884
|
+
}
|
|
885
|
+
return `**${data.name}** (${data.email})\nID: ${data.id}\nRole: ${data.role}`;
|
|
886
|
+
},
|
|
887
|
+
shouldCall: (intent, params) => {
|
|
888
|
+
// Only call if we have identifying parameters
|
|
889
|
+
return !!(params.userId || params.username || params.email);
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
/**
|
|
893
|
+
* Example: Data Analysis API Integration
|
|
894
|
+
*
|
|
895
|
+
* This shows how to integrate an API that performs data analysis
|
|
896
|
+
*/
|
|
897
|
+
const createAnalysisAPIIntegration = (baseUrl) => ({
|
|
898
|
+
id: 'analysis_api',
|
|
899
|
+
name: 'Data Analysis Service',
|
|
900
|
+
endpoint: `${baseUrl}/api/analysis`,
|
|
901
|
+
method: 'POST',
|
|
902
|
+
intents: ['analyze_data', 'generate_report', 'data_insights'],
|
|
903
|
+
buildRequest: (params) => ({
|
|
904
|
+
dataSource: params.dataSource,
|
|
905
|
+
analysisType: params.analysisType || 'summary',
|
|
906
|
+
timeRange: params.timeRange,
|
|
907
|
+
filters: params.filters || {}
|
|
908
|
+
}),
|
|
909
|
+
formatResponse: (data) => {
|
|
910
|
+
let response = `## Analysis Results\n\n`;
|
|
911
|
+
if (data.summary) {
|
|
912
|
+
response += `### Summary\n${data.summary}\n\n`;
|
|
913
|
+
}
|
|
914
|
+
if (data.metrics) {
|
|
915
|
+
response += `### Key Metrics\n`;
|
|
916
|
+
Object.entries(data.metrics).forEach(([key, value]) => {
|
|
917
|
+
response += `- **${key}**: ${value}\n`;
|
|
918
|
+
});
|
|
919
|
+
response += '\n';
|
|
920
|
+
}
|
|
921
|
+
if (data.recommendations) {
|
|
922
|
+
response += `### Recommendations\n`;
|
|
923
|
+
data.recommendations.forEach((rec) => {
|
|
924
|
+
response += `- ${rec}\n`;
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
return response;
|
|
928
|
+
},
|
|
929
|
+
shouldCall: (intent, params) => {
|
|
930
|
+
return !!(params.dataSource && params.analysisType);
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
/**
|
|
934
|
+
* Example: Search API Integration
|
|
935
|
+
*
|
|
936
|
+
* Shows how to integrate a search/query API
|
|
937
|
+
*/
|
|
938
|
+
const createSearchAPIIntegration = (baseUrl) => ({
|
|
939
|
+
id: 'search_api',
|
|
940
|
+
name: 'Search Service',
|
|
941
|
+
endpoint: `${baseUrl}/api/search`,
|
|
942
|
+
method: 'POST',
|
|
943
|
+
intents: ['search', 'find', 'query'],
|
|
944
|
+
buildRequest: (params) => ({
|
|
945
|
+
query: params.query,
|
|
946
|
+
filters: {
|
|
947
|
+
type: params.type,
|
|
948
|
+
category: params.category,
|
|
949
|
+
dateRange: params.dateRange
|
|
950
|
+
},
|
|
951
|
+
limit: params.limit || 10,
|
|
952
|
+
offset: params.offset || 0
|
|
953
|
+
}),
|
|
954
|
+
formatResponse: (data) => {
|
|
955
|
+
if (!data.results || data.results.length === 0) {
|
|
956
|
+
return 'No results found matching your search criteria.';
|
|
957
|
+
}
|
|
958
|
+
let response = `Found **${data.results.length}** results:\n\n`;
|
|
959
|
+
response += data.results.map((result, index) => `${index + 1}. **${result.title}**\n ${result.description}\n URL: ${result.url}`).join('\n\n');
|
|
960
|
+
return response;
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
/**
|
|
964
|
+
* Example: Webhook/Action API Integration
|
|
965
|
+
*
|
|
966
|
+
* Shows how to integrate APIs that perform actions (create, update, delete)
|
|
967
|
+
*/
|
|
968
|
+
const createActionAPIIntegration = (baseUrl) => ({
|
|
969
|
+
id: 'action_api',
|
|
970
|
+
name: 'Action Service',
|
|
971
|
+
endpoint: `${baseUrl}/api/actions`,
|
|
972
|
+
method: 'POST',
|
|
973
|
+
intents: ['create', 'update', 'delete', 'perform_action'],
|
|
974
|
+
buildRequest: (params) => ({
|
|
975
|
+
action: params.action,
|
|
976
|
+
targetId: params.targetId,
|
|
977
|
+
targetType: params.targetType,
|
|
978
|
+
payload: params.payload || {},
|
|
979
|
+
metadata: params.metadata || {}
|
|
980
|
+
}),
|
|
981
|
+
formatResponse: (data) => {
|
|
982
|
+
if (data.success) {
|
|
983
|
+
return `✅ **Action Completed Successfully**\n\n${data.message || 'The requested action has been performed.'}\n\nDetails: ${JSON.stringify(data.result, null, 2)}`;
|
|
984
|
+
}
|
|
985
|
+
return `❌ **Action Failed**\n\n${data.message || 'The requested action could not be completed.'}`;
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
/**
|
|
989
|
+
* Example: Database Query Integration
|
|
990
|
+
*
|
|
991
|
+
* Shows how to integrate a database query service
|
|
992
|
+
*/
|
|
993
|
+
const createDatabaseAPIIntegration = (baseUrl) => ({
|
|
994
|
+
id: 'database_api',
|
|
995
|
+
name: 'Database Query Service',
|
|
996
|
+
endpoint: `${baseUrl}/api/query`,
|
|
997
|
+
method: 'POST',
|
|
998
|
+
intents: ['query_database', 'get_data', 'fetch_records'],
|
|
999
|
+
buildRequest: (params) => ({
|
|
1000
|
+
table: params.table,
|
|
1001
|
+
columns: params.columns || ['*'],
|
|
1002
|
+
where: params.where || {},
|
|
1003
|
+
orderBy: params.orderBy,
|
|
1004
|
+
limit: params.limit || 50
|
|
1005
|
+
}),
|
|
1006
|
+
formatResponse: (data) => {
|
|
1007
|
+
if (!data.rows || data.rows.length === 0) {
|
|
1008
|
+
return 'No records found matching your query.';
|
|
1009
|
+
}
|
|
1010
|
+
let response = `Retrieved **${data.rows.length}** records:\n\n`;
|
|
1011
|
+
// Format as table if we have column information
|
|
1012
|
+
if (data.columns && data.columns.length > 0) {
|
|
1013
|
+
response += `| ${data.columns.join(' | ')} |\n`;
|
|
1014
|
+
response += `|${data.columns.map(() => '-').join('|')}|\n`;
|
|
1015
|
+
data.rows.forEach((row) => {
|
|
1016
|
+
response += `| ${data.columns.map((col) => row[col] || '-').join(' | ')} |\n`;
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
// Fallback: display as JSON
|
|
1021
|
+
response += '```json\n' + JSON.stringify(data.rows, null, 2) + '\n```';
|
|
1022
|
+
}
|
|
1023
|
+
return response;
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
/**
|
|
1027
|
+
* Example: External Service Integration (Weather, News, etc.)
|
|
1028
|
+
*/
|
|
1029
|
+
const createWeatherAPIIntegration = (apiKey) => ({
|
|
1030
|
+
id: 'weather_api',
|
|
1031
|
+
name: 'Weather Service',
|
|
1032
|
+
endpoint: 'https://api.openweathermap.org/data/2.5/weather',
|
|
1033
|
+
method: 'GET',
|
|
1034
|
+
headers: {
|
|
1035
|
+
'User-Agent': 'FloatingCopilot/1.0'
|
|
1036
|
+
},
|
|
1037
|
+
intents: ['get_weather', 'weather', 'check_weather'],
|
|
1038
|
+
buildRequest: (params) => {
|
|
1039
|
+
// Note: For actual use, pass apiKey securely
|
|
1040
|
+
return {
|
|
1041
|
+
q: params.city,
|
|
1042
|
+
appid: apiKey,
|
|
1043
|
+
units: params.units || 'metric'
|
|
1044
|
+
};
|
|
1045
|
+
},
|
|
1046
|
+
formatResponse: (data) => {
|
|
1047
|
+
var _a, _b, _c;
|
|
1048
|
+
const weather = (_a = data.weather) === null || _a === void 0 ? void 0 : _a[0];
|
|
1049
|
+
const main = data.main;
|
|
1050
|
+
return `**Weather for ${data.name}, ${(_b = data.sys) === null || _b === void 0 ? void 0 : _b.country}**
|
|
1051
|
+
|
|
1052
|
+
Current: ${weather === null || weather === void 0 ? void 0 : weather.main} (${weather === null || weather === void 0 ? void 0 : weather.description})
|
|
1053
|
+
Temperature: ${main === null || main === void 0 ? void 0 : main.temp}°C
|
|
1054
|
+
Feels like: ${main === null || main === void 0 ? void 0 : main.feels_like}°C
|
|
1055
|
+
Humidity: ${main === null || main === void 0 ? void 0 : main.humidity}%
|
|
1056
|
+
Wind Speed: ${(_c = data.wind) === null || _c === void 0 ? void 0 : _c.speed} m/s
|
|
1057
|
+
|
|
1058
|
+
Updated: ${new Date(data.dt * 1000).toLocaleString()}`;
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
/**
|
|
1062
|
+
* Helper function to create a custom API integration
|
|
1063
|
+
*/
|
|
1064
|
+
function createCustomAPIIntegration(config) {
|
|
1065
|
+
return config;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
export { FloatingCopilot, analyzeUserIntent, areAllParametersCollected, callAPI, createActionAPIIntegration, createAnalysisAPIIntegration, createCustomAPIIntegration, createCustomerAPIIntegration, createDatabaseAPIIntegration, createSearchAPIIntegration, createUserAPIIntegration, createWeatherAPIIntegration, FloatingCopilot as default, defaultConfig, generateCopilotResponse, generateParameterPrompt, generateParameterPromptOrCallAPI, getMissingParameters, getNextMissingParameter, mergeAPIIntegrations, processUserMessage, validateAPIIntegration, validateGithubToken, validateParameter };
|