From 7218585927335058b87c80b16eae657fdacc2741 Mon Sep 17 00:00:00 2001 From: Alexis JC Date: Wed, 19 Feb 2025 14:12:35 +0100 Subject: [PATCH 1/5] feat: add description and spec --- src/wwPlugin.js | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ ww-config.js | 62 ++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/src/wwPlugin.js b/src/wwPlugin.js index eec49b8..0a7b15a 100644 --- a/src/wwPlugin.js +++ b/src/wwPlugin.js @@ -26,6 +26,7 @@ export default { xanoManager: null, xanoClient: null, channels: {}, + fullSpec: [], /*=============================================m_ÔÔ_m=============================================\ Plugin API \================================================================================================*/ @@ -53,10 +54,16 @@ export default { Editor API \================================================================================================*/ /* wwEditor:start */ + _getCopilotContext() { + return { + apiDoc: formatSpec(this.fullSpec), + }; + }, async initManager(settings) { this.xanoManager = this.createManager(settings); try { await this.xanoManager.init(); + this.fullSpec = await this.xanoManager.fetchFullSpec(); } catch (error) { wwLib.wwNotification.open({ text: 'Failed to init Xano, please ensure your API key has the permission required.', @@ -275,3 +282,81 @@ function buildXanoHeaders( .reduce((curr, next) => ({ ...curr, [next.key]: next.value }), {}), }; } + +function formatSpec(fullSpec) { + return fullSpec.map(spec => { + const result = { + name: spec.info.title, + baseUrl: spec.servers[0].url, + endpoints: {}, + }; + + // Process all paths + for (const [path, methods] of Object.entries(spec.paths)) { + for (const [method, details] of Object.entries(methods)) { + const endpointKey = `${method}${path.replace(/\//g, '_')}`; + + // Create endpoint info + const endpoint = { + path, + method, + requiresAuth: details.security?.length > 0, + summary: details.summary, + }; + + // Add path parameters if any + if (details.parameters?.length) { + endpoint.pathParams = details.parameters + .filter(p => p.in === 'path') + .map(p => ({ + name: p.name, + type: p.schema.type, + required: p.required, + })); + } + + // Add query parameters if any + if (details.parameters?.length) { + endpoint.queryParams = details.parameters + .filter(p => p.in === 'query') + .map(p => ({ + name: p.name, + type: p.schema.type, + required: p.required, + })); + } + + // Add request body if exists + const requestBody = details.requestBody?.content['application/json']?.schema; + if (requestBody) { + endpoint.requestSchema = { + type: requestBody.type, + properties: Object.entries(requestBody.properties).map(([key, value]) => ({ + name: key, + type: value.type, + description: value.description, + enum: value.enum, + required: requestBody.required?.includes(key), + })), + }; + } + + // Add response schema if exists + const responseSchema = details.responses['200']?.content['application/json']?.schema; + if (responseSchema) { + endpoint.responseSchema = { + type: responseSchema.type, + properties: + responseSchema.type === 'array' + ? responseSchema.items.properties + : responseSchema.properties, + }; + } + + result.endpoints[endpointKey] = endpoint; + } + } + + return result; + }); +} diff --git a/ww-config.js b/ww-config.js index d0312b0..684367a 100644 --- a/ww-config.js +++ b/ww-config.js @@ -301,6 +301,68 @@ export default { isAsync: true, /* wwEditor:start */ edit: () => import('./src/components/Request.vue'), + copilot: { + description: + 'Make a request to a Xano API endpoint. Can handle both regular REST requests and streaming responses. Automatically includes Xano authentication token if available.', + returns: + 'For regular requests: Axios response object containing { data, status, headers, config }. For streaming requests: Array of accumulated stream data accessed through the specified streamVariableId.', + schema: { + apiGroupUrl: { + type: 'string', + description: + 'The base URL of the Xano API group (e.g., "https://x8ki-letl-twmt.n7.xano.io/api:abcdef")', + bindable: true, + }, + endpoint: { + type: 'object', + description: + 'The endpoint configuration object with required properties:\n- method: HTTP method (get, post, put, patch, delete)\n- path: Endpoint path with optional parameter placeholders (e.g., "/users/{userId}")', + bindable: true, + }, + headers: { + type: 'Array<{key: string, value: string}', + description: + 'Custom headers as key-value pairs, e.g., [{"Content-Type": "application/json"}]. Automatically includes Xano authentication token if available. key and value are bindable individually.', + bindable: true, + }, + parameters: { + type: 'object', + description: + 'URL parameters object serving two purposes: 1) Replace path placeholders (e.g., {userId} in path), 2) Add query parameters to URL. Example: {"userId": "123", "filter": "active"}. The values are bindable, but not the whole object.', + bindable: false, + }, + body: { + type: 'object', + description: + 'Request body data. Only used for non-GET requests. Should be a JSON-serializable object. The values are bindable, but not the whole object.', + bindable: false, + }, + dataType: { + type: 'string', + description: + 'Content type for the request. Set to "text/event-stream" for SSE streaming. Default is "application/json"', + bindable: true, + }, + withCredentials: { + type: 'boolean', + description: + 'Include credentials (cookies) with the request. Falls back to plugin settings if not specified.', + bindable: true, + }, + useStreaming: { + type: 'boolean', + description: + 'Enable Server-Sent Events (SSE) streaming mode. When true, responses will be accumulated in the specified streamVariableId.', + bindable: true, + }, + streamVariableId: { + type: 'string', + description: + 'Required when useStreaming is true. The ID of the variable where streaming responses will be accumulated as an array.', + bindable: true, + }, + }, + }, /* wwEditor:end */ }, { From eab8325a1a9ecce167e0e76cabb40af72ff16b8c Mon Sep 17 00:00:00 2001 From: Alexis JC Date: Wed, 19 Feb 2025 14:59:05 +0100 Subject: [PATCH 2/5] fix: description data --- ww-config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ww-config.js b/ww-config.js index 684367a..1ec0a8f 100644 --- a/ww-config.js +++ b/ww-config.js @@ -334,7 +334,7 @@ export default { body: { type: 'object', description: - 'Request body data. Only used for non-GET requests. Should be a JSON-serializable object. The values are bindable, but not the whole object.', + 'Request body data. Only used for non-GET requests. Should be a JSON-serializable object. The key values are bindable, but not the whole object. The object cannot be bind, you have to bind individual sub keys. eg. {email: , password: }', bindable: false, }, dataType: { From cbea6e5acd454b6c966decd1b7181645dc91427a Mon Sep 17 00:00:00 2001 From: Alexis JC Date: Wed, 19 Feb 2025 15:07:32 +0100 Subject: [PATCH 3/5] fix: formula exemple --- ww-config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ww-config.js b/ww-config.js index 1ec0a8f..a8db7d9 100644 --- a/ww-config.js +++ b/ww-config.js @@ -334,7 +334,7 @@ export default { body: { type: 'object', description: - 'Request body data. Only used for non-GET requests. Should be a JSON-serializable object. The key values are bindable, but not the whole object. The object cannot be bind, you have to bind individual sub keys. eg. {email: , password: }', + 'Request body data. Only used for non-GET requests. Should be a JSON-serializable object. The key values are bindable, but not the whole object. The object cannot be bind, you have to bind individual sub keys. eg. {email: {__wwType: "...", code: "..."}, password: {__wwType: "...", code: "..."}}', bindable: false, }, dataType: { From bee5f1d46c3fa4c2dcfd3feec63a181a092e0ca1 Mon Sep 17 00:00:00 2001 From: Alexis JC Date: Wed, 19 Feb 2025 15:25:59 +0100 Subject: [PATCH 4/5] fix: ai description --- ww-config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ww-config.js b/ww-config.js index a8db7d9..c6d368f 100644 --- a/ww-config.js +++ b/ww-config.js @@ -310,7 +310,7 @@ export default { apiGroupUrl: { type: 'string', description: - 'The base URL of the Xano API group (e.g., "https://x8ki-letl-twmt.n7.xano.io/api:abcdef")', + 'The base URL of the Xano API group (e.g., "https://x8ki-letl-twmt.n7.xano.io/api:abcdef"). CRITICAL: select the correct API group base URL depending of the endpoint you want to use.', bindable: true, }, endpoint: { From e5d335e28c434213840c4833adc709bb6d576b64 Mon Sep 17 00:00:00 2001 From: Alexis JC Date: Wed, 19 Feb 2025 15:39:00 +0100 Subject: [PATCH 5/5] fix: improve ai --- src/wwPlugin.js | 5 +++-- ww-config.js | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/wwPlugin.js b/src/wwPlugin.js index 0a7b15a..fcf3b35 100644 --- a/src/wwPlugin.js +++ b/src/wwPlugin.js @@ -286,8 +286,8 @@ function buildXanoHeaders( function formatSpec(fullSpec) { return fullSpec.map(spec => { const result = { - name: spec.info.title, - baseUrl: spec.servers[0].url, + apiGroupName: spec.info.title, + apiGroupUrl: spec.servers[0].url, endpoints: {}, }; @@ -298,6 +298,7 @@ function formatSpec(fullSpec) { // Create endpoint info const endpoint = { + apiGroupUrl: spec.servers[0].url, path, method, requiresAuth: details.security?.length > 0, diff --git a/ww-config.js b/ww-config.js index c6d368f..1bb1a4d 100644 --- a/ww-config.js +++ b/ww-config.js @@ -310,14 +310,14 @@ export default { apiGroupUrl: { type: 'string', description: - 'The base URL of the Xano API group (e.g., "https://x8ki-letl-twmt.n7.xano.io/api:abcdef"). CRITICAL: select the correct API group base URL depending of the endpoint you want to use.', - bindable: true, + 'The base URL of the Xano API group (e.g., "https://x8ki-letl-twmt.n7.xano.io/api:abcdef").', + bindable: false, }, endpoint: { type: 'object', description: 'The endpoint configuration object with required properties:\n- method: HTTP method (get, post, put, patch, delete)\n- path: Endpoint path with optional parameter placeholders (e.g., "/users/{userId}")', - bindable: true, + bindable: false, }, headers: { type: 'Array<{key: string, value: string}',