mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 08:01:35 +00:00
feat: more AI models (#5207)
* feat: more AI models * nits * groq + openrouter * nit
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value\n FROM resource\n WHERE path = $1 AND workspace_id = $2",
|
||||
"query": "SELECT value\n FROM resource\n WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -19,5 +19,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "286fa00c088df146c08c1934556f06a4243c66787ab8759a8045e60effd2fb77"
|
||||
"hash": "0a9dd1addaf48eeb46eed59abb6daf9819d07b08cf7ca442ea7ec78a9b2b63b2"
|
||||
}
|
||||
+6
-5
@@ -1,21 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT ai_resource, code_completion_enabled FROM workspace_settings WHERE workspace_id = $1",
|
||||
"query": "SELECT value, resource_type FROM resource WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "ai_resource",
|
||||
"name": "value",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "code_completion_enabled",
|
||||
"type_info": "Bool"
|
||||
"name": "resource_type",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
@@ -24,5 +25,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "0230bd64d2b6719c3536a106e18a68c7b43b3a9a9efa92b5517132e9c0d8a25b"
|
||||
"hash": "103ef3cf5cf4d25d780e4aefd5b290d810a5e8ea6458d9f9fd484ced549ea82e"
|
||||
}
|
||||
+23
-17
@@ -70,68 +70,73 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "code_completion_enabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "error_handler_extra_args",
|
||||
"type_info": "Json"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 14,
|
||||
"name": "error_handler_muted_on_cancel",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 15,
|
||||
"name": "large_file_storage",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 16,
|
||||
"name": "git_sync",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 17,
|
||||
"name": "default_app",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 18,
|
||||
"name": "auto_add",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 19,
|
||||
"name": "automatic_billing",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 20,
|
||||
"name": "default_scripts",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 21,
|
||||
"name": "deploy_ui",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 22,
|
||||
"name": "mute_critical_alerts",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 23,
|
||||
"name": "color",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 24,
|
||||
"name": "operator_settings",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "ai_models",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"name": "code_completion_model",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -153,7 +158,6 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
@@ -165,6 +169,8 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
+3
-3
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE workspace_settings SET ai_resource = NULL, code_completion_enabled = $1 WHERE workspace_id = $2",
|
||||
"query": "UPDATE workspace_settings SET ai_resource = NULL, code_completion_model = $1, ai_models = '{}' WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6940ddc5ee8d2a1048213d46f52d964b199604ba66dedbe9f89cea727d726a2d"
|
||||
"hash": "1bbef6baa5b8e2522d685df2979bb1e4b9022f5e841afd9eeb08a81688f6c0c8"
|
||||
}
|
||||
+4
-3
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE workspace_settings SET ai_resource = $1, code_completion_enabled = $2 WHERE workspace_id = $3",
|
||||
"query": "UPDATE workspace_settings SET ai_resource = $1, code_completion_model = $2, ai_models = $3 WHERE workspace_id = $4",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"VarcharArray",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1610c79b8d238e3a35d795f34a2a0100b933b7f199d41cc2f554d57c811d55f3"
|
||||
"hash": "4edf05cbf35325d444de0e74ee070aafd27ef61c940daba186e7e66f668c31ed"
|
||||
}
|
||||
+16
-10
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n -- slack_team_id, \n -- slack_name, \n -- slack_command_script, \n -- CASE WHEN slack_email = 'missing@email.xyz' THEN NULL ELSE slack_email END AS slack_email,\n auto_invite_domain IS NOT NULL AS \"auto_invite_enabled!\",\n CASE WHEN auto_invite_operator IS TRUE THEN 'operator' ELSE 'developer' END AS \"auto_invite_as!\", \n CASE WHEN auto_add IS TRUE THEN 'add' ELSE 'invite' END AS \"auto_invite_mode!\", \n webhook, \n deploy_to, \n error_handler, \n ai_resource, \n code_completion_enabled, \n error_handler_extra_args, \n error_handler_muted_on_cancel, \n large_file_storage, \n git_sync,\n default_app,\n default_scripts,\n workspace.name\n FROM workspace_settings\n LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id\n WHERE workspace_id = $1",
|
||||
"query": "SELECT\n -- slack_team_id, \n -- slack_name, \n -- slack_command_script, \n -- CASE WHEN slack_email = 'missing@email.xyz' THEN NULL ELSE slack_email END AS slack_email,\n auto_invite_domain IS NOT NULL AS \"auto_invite_enabled!\",\n CASE WHEN auto_invite_operator IS TRUE THEN 'operator' ELSE 'developer' END AS \"auto_invite_as!\", \n CASE WHEN auto_add IS TRUE THEN 'add' ELSE 'invite' END AS \"auto_invite_mode!\", \n webhook, \n deploy_to, \n error_handler, \n ai_resource, \n ai_models,\n code_completion_model,\n error_handler_extra_args, \n error_handler_muted_on_cancel, \n large_file_storage, \n git_sync,\n default_app,\n default_scripts,\n workspace.name\n FROM workspace_settings\n LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id\n WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -40,41 +40,46 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "code_completion_enabled",
|
||||
"type_info": "Bool"
|
||||
"name": "ai_models",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "code_completion_model",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "error_handler_extra_args",
|
||||
"type_info": "Json"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "error_handler_muted_on_cancel",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "large_file_storage",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "git_sync",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "default_app",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "default_scripts",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "name",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
@@ -94,6 +99,7 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
@@ -102,5 +108,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "0331a81262e2d3c1bcfaeb64617b11eb68ab4599d64e5e5af639a9ac5d791fd0"
|
||||
"hash": "51648e377d47815d0b15694572d5c9cc0a303d70980346e1f3c4096a8922d7d5"
|
||||
}
|
||||
+23
-17
@@ -70,68 +70,73 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "code_completion_enabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "error_handler_extra_args",
|
||||
"type_info": "Json"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 14,
|
||||
"name": "error_handler_muted_on_cancel",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 15,
|
||||
"name": "large_file_storage",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 16,
|
||||
"name": "git_sync",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 17,
|
||||
"name": "default_app",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 18,
|
||||
"name": "auto_add",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 19,
|
||||
"name": "automatic_billing",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 20,
|
||||
"name": "default_scripts",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 21,
|
||||
"name": "deploy_ui",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 22,
|
||||
"name": "mute_critical_alerts",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 23,
|
||||
"name": "color",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 24,
|
||||
"name": "operator_settings",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "ai_models",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"name": "code_completion_model",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -153,7 +158,6 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
@@ -165,6 +169,8 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT ai_resource, code_completion_model, ai_models FROM workspace_settings WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "ai_resource",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "code_completion_model",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "ai_models",
|
||||
"type_info": "VarcharArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b186efe51e7bb1924efbd7e9b36085502a1e77e30e59c7310c78976f77a810a3"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE workspace_settings ADD COLUMN code_completion_enabled BOOLEAN DEFAULT FALSE NOT NULL;
|
||||
|
||||
UPDATE workspace_settings SET code_completion_enabled = TRUE WHERE code_completion_model IS NOT NULL;
|
||||
|
||||
ALTER TABLE workspace_settings DROP COLUMN ai_models, DROP COLUMN code_completion_model;
|
||||
@@ -0,0 +1,24 @@
|
||||
ALTER TABLE workspace_settings
|
||||
ADD COLUMN ai_models varchar(255)[] DEFAULT '{}' NOT NULL,
|
||||
ADD COLUMN code_completion_model varchar(255);
|
||||
|
||||
UPDATE workspace_settings
|
||||
SET ai_models = CASE
|
||||
WHEN ai_resource->>'provider' = 'openai' THEN ARRAY['gpt-4o']
|
||||
WHEN ai_resource->>'provider' = 'anthropic' THEN ARRAY['claude-3-5-sonnet-latest']
|
||||
WHEN ai_resource->>'provider' = 'mistral' THEN ARRAY['codestral-latest']
|
||||
ELSE ai_models
|
||||
END
|
||||
WHERE ai_resource->>'path' IS NOT NULL;
|
||||
|
||||
UPDATE workspace_settings
|
||||
SET code_completion_model = CASE
|
||||
WHEN ai_resource->>'provider' = 'openai' THEN 'gpt-4o'
|
||||
WHEN ai_resource->>'provider' = 'anthropic' THEN 'claude-3-5-sonnet-latest'
|
||||
WHEN ai_resource->>'provider' = 'mistral' THEN 'codestral-latest'
|
||||
ELSE code_completion_model
|
||||
END
|
||||
WHERE code_completion_enabled IS TRUE;
|
||||
|
||||
|
||||
ALTER TABLE workspace_settings DROP COLUMN code_completion_enabled;
|
||||
@@ -1745,9 +1745,13 @@ paths:
|
||||
deploy_to:
|
||||
type: string
|
||||
ai_resource:
|
||||
$ref: "#/components/schemas/AiResource"
|
||||
code_completion_enabled:
|
||||
type: boolean
|
||||
$ref: "#/components/schemas/AIResource"
|
||||
code_completion_model:
|
||||
type: string
|
||||
ai_models:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
error_handler:
|
||||
type: string
|
||||
error_handler_extra_args:
|
||||
@@ -1771,7 +1775,7 @@ paths:
|
||||
operator_settings:
|
||||
$ref: "#/components/schemas/OperatorSettings"
|
||||
required:
|
||||
- code_completion_enabled
|
||||
- ai_models
|
||||
- automatic_billing
|
||||
- error_handler_muted_on_cancel
|
||||
|
||||
@@ -2084,12 +2088,16 @@ paths:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- code_completion_enabled
|
||||
- ai_models
|
||||
properties:
|
||||
ai_resource:
|
||||
$ref: "#/components/schemas/AiResource"
|
||||
code_completion_enabled:
|
||||
type: boolean
|
||||
$ref: "#/components/schemas/AIResource"
|
||||
code_completion_model:
|
||||
type: string
|
||||
ai_models:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
@@ -2116,15 +2124,18 @@ paths:
|
||||
type: object
|
||||
properties:
|
||||
ai_provider:
|
||||
type: string
|
||||
$ref: "#/components/schemas/AIProvider"
|
||||
exists_ai_resource:
|
||||
type: boolean
|
||||
code_completion_enabled:
|
||||
type: boolean
|
||||
code_completion_model:
|
||||
type: string
|
||||
ai_models:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- ai_provider
|
||||
- exists_ai_resource
|
||||
- code_completion_enabled
|
||||
- ai_models
|
||||
|
||||
/w/{workspace}/workspaces/edit_error_handler:
|
||||
post:
|
||||
@@ -11546,16 +11557,21 @@ components:
|
||||
schemas:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas"
|
||||
|
||||
AiResource:
|
||||
AIProvider:
|
||||
type: string
|
||||
enum: [openai, anthropic, mistral, deepseek, groq, openrouter, customai]
|
||||
|
||||
AIResource:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
provider:
|
||||
type: string
|
||||
$ref: "#/components/schemas/AIProvider"
|
||||
required:
|
||||
- path
|
||||
- provider
|
||||
|
||||
Script:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
+195
-115
@@ -4,27 +4,21 @@ use crate::{
|
||||
};
|
||||
|
||||
use anthropic::AnthropicCache;
|
||||
use axum::{
|
||||
body::Bytes,
|
||||
extract::{Path, Query},
|
||||
response::IntoResponse,
|
||||
routing::post,
|
||||
Extension, Router,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use axum::{body::Bytes, extract::Path, response::IntoResponse, routing::post, Extension, Router};
|
||||
use http::HeaderMap;
|
||||
use lazy_static::lazy_static;
|
||||
use mistral::MistralCache;
|
||||
use openai::OpenaiCache;
|
||||
use quick_cache::sync::Cache;
|
||||
use reqwest::{Client, RequestBuilder};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::error::{to_anyhow, Result};
|
||||
|
||||
use windmill_common::error::Error;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::{RawValue, Value};
|
||||
use std::collections::HashMap;
|
||||
use windmill_audit::{audit_ee::audit_log, ActionKind};
|
||||
use windmill_common::error::{to_anyhow, Error, Result};
|
||||
|
||||
use mistral::MistralCache;
|
||||
use openai::OpenaiCache;
|
||||
use openai_api_compatible::OpenaiApiCompatibleCache;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref HTTP_CLIENT: Client = reqwest::ClientBuilder::new()
|
||||
@@ -33,16 +27,63 @@ lazy_static::lazy_static! {
|
||||
.build().unwrap();
|
||||
}
|
||||
|
||||
trait AiRequest {
|
||||
fn prepare_request(self, path: &str, body: Bytes) -> Result<RequestBuilder>;
|
||||
mod openai_api_compatible {
|
||||
use super::*;
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct OpenaiApiCompatibleCache {
|
||||
pub base_url: String,
|
||||
pub api_key: Option<String>,
|
||||
}
|
||||
|
||||
impl OpenaiApiCompatibleCache {
|
||||
pub fn prepare_request(self, path: &str, body: Bytes) -> Result<RequestBuilder> {
|
||||
let url = format!("{}/{}", self.base_url, path);
|
||||
|
||||
let mut request = HTTP_CLIENT
|
||||
.post(url)
|
||||
.header("content-type", "application/json")
|
||||
.body(body);
|
||||
|
||||
if let Some(api_key) = self.api_key {
|
||||
request = request.header("Authorization", format!("Bearer {}", api_key));
|
||||
}
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_cached_value(
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
resource: Value,
|
||||
base_url: Option<String>,
|
||||
) -> Result<KeyCache> {
|
||||
let mut resource: OpenaiApiCompatibleCache = if let Some(base_url) = base_url {
|
||||
let api_key = match resource {
|
||||
Value::Object(mut obj) => obj
|
||||
.remove("api_key")
|
||||
.map(|v| serde_json::from_value::<String>(v.clone()).ok())
|
||||
.flatten(),
|
||||
_ => None,
|
||||
};
|
||||
OpenaiApiCompatibleCache { base_url, api_key }
|
||||
} else {
|
||||
serde_json::from_value(resource).with_context(|| "validating custom AI resource")?
|
||||
};
|
||||
|
||||
if let Some(api_key) = resource.api_key {
|
||||
resource.api_key = Some(get_variable_or_self(api_key, db, w_id).await?);
|
||||
}
|
||||
|
||||
Ok(KeyCache::OpenaiApiCompatible(resource))
|
||||
}
|
||||
}
|
||||
|
||||
mod openai {
|
||||
use super::*;
|
||||
|
||||
use super::{get_variable_or_self, KeyCache};
|
||||
|
||||
const API_VERSION: &str = "2023-05-15";
|
||||
const API_VERSION: &str = "2024-10-21";
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct OpenaiResource {
|
||||
@@ -94,8 +135,8 @@ mod openai {
|
||||
}
|
||||
|
||||
const BASE_URL: &str = "https://api.openai.com/v1";
|
||||
impl AiRequest for OpenaiCache {
|
||||
fn prepare_request(self, openai_path: &str, mut body: Bytes) -> Result<RequestBuilder> {
|
||||
impl OpenaiCache {
|
||||
pub fn prepare_request(self, openai_path: &str, mut body: Bytes) -> Result<RequestBuilder> {
|
||||
let OpenaiCache { api_key, azure_base_path, organization_id, user } = self;
|
||||
if user.is_some() {
|
||||
tracing::debug!("Adding user to request body");
|
||||
@@ -245,15 +286,9 @@ mod anthropic {
|
||||
|
||||
const API_VERSION: &str = "2023-06-01";
|
||||
|
||||
impl AnthropicCache {
|
||||
pub fn new(api_key: String) -> Self {
|
||||
Self { api_key }
|
||||
}
|
||||
}
|
||||
|
||||
const BASE_URL: &str = "https://api.anthropic.com";
|
||||
impl AiRequest for AnthropicCache {
|
||||
fn prepare_request(self, anthropic_path: &str, body: Bytes) -> Result<RequestBuilder> {
|
||||
impl AnthropicCache {
|
||||
pub fn prepare_request(self, anthropic_path: &str, body: Bytes) -> Result<RequestBuilder> {
|
||||
let AnthropicCache { api_key } = self;
|
||||
let url = format!("{}/{}", BASE_URL, anthropic_path);
|
||||
let request = HTTP_CLIENT
|
||||
@@ -270,8 +305,7 @@ mod anthropic {
|
||||
let mut resource: AnthropicCache = serde_json::from_value(resource)
|
||||
.map_err(|e| Error::InternalErr(format!("validating anthropic resource {e:#}")))?;
|
||||
resource.api_key = get_variable_or_self(resource.api_key, db, w_id).await?;
|
||||
let workspace_cache = AnthropicCache::new(resource.api_key);
|
||||
Ok(KeyCache::Anthropic(workspace_cache))
|
||||
Ok(KeyCache::Anthropic(resource))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,15 +317,9 @@ mod mistral {
|
||||
pub api_key: String,
|
||||
}
|
||||
|
||||
impl MistralCache {
|
||||
pub fn new(api_key: String) -> Self {
|
||||
Self { api_key }
|
||||
}
|
||||
}
|
||||
|
||||
const BASE_URL: &str = "https://api.mistral.ai";
|
||||
impl AiRequest for MistralCache {
|
||||
fn prepare_request(self, mistral_path: &str, body: Bytes) -> Result<RequestBuilder> {
|
||||
impl MistralCache {
|
||||
pub fn prepare_request(self, mistral_path: &str, body: Bytes) -> Result<RequestBuilder> {
|
||||
let MistralCache { api_key } = self;
|
||||
|
||||
let url = format!("{}/{}", BASE_URL, mistral_path);
|
||||
@@ -309,9 +337,7 @@ mod mistral {
|
||||
let mut resource: MistralCache = serde_json::from_value(resource)
|
||||
.map_err(|e| Error::InternalErr(format!("validating mistral resource {e:#}")))?;
|
||||
resource.api_key = get_variable_or_self(resource.api_key, db, w_id).await?;
|
||||
|
||||
let workspace_cache = MistralCache::new(resource.api_key);
|
||||
Ok(KeyCache::Mistral(workspace_cache))
|
||||
Ok(KeyCache::Mistral(resource))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,16 +346,17 @@ pub enum KeyCache {
|
||||
Openai(OpenaiCache),
|
||||
Anthropic(AnthropicCache),
|
||||
Mistral(MistralCache),
|
||||
OpenaiApiCompatible(OpenaiApiCompatibleCache),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AiCache {
|
||||
pub struct AICache {
|
||||
pub path: String,
|
||||
pub cached_key: KeyCache,
|
||||
pub expires_at: std::time::Instant,
|
||||
}
|
||||
|
||||
impl AiCache {
|
||||
impl AICache {
|
||||
pub fn new(path: String, cached_key: KeyCache) -> Self {
|
||||
Self {
|
||||
path,
|
||||
@@ -343,35 +370,57 @@ impl AiCache {
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref AI_KEY_CACHE: Cache<String, AiCache> = Cache::new(500);
|
||||
pub static ref AI_KEY_CACHE: Cache<String, AICache> = Cache::new(500);
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ProxyQueryParams {
|
||||
no_cache: Option<bool>,
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AIProvider {
|
||||
OpenAI,
|
||||
Anthropic,
|
||||
Mistral,
|
||||
DeepSeek,
|
||||
Groq,
|
||||
OpenRouter,
|
||||
CustomAI,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct AiResource {
|
||||
pub path: String,
|
||||
#[serde(deserialize_with = "check_if_valid_ai_provider")]
|
||||
pub provider: String,
|
||||
}
|
||||
|
||||
fn check_if_valid_ai_provider<'de, D>(provider: D) -> std::result::Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let provider = String::deserialize(provider)?;
|
||||
match provider.as_str() {
|
||||
"anthropic" | "openai" | "mistral" => Ok(provider),
|
||||
_ => Err(serde::de::Error::custom(
|
||||
"Only the following Ai providers are supported: openai, anthropic and mistral"
|
||||
.to_string(),
|
||||
)),
|
||||
impl AIProvider {
|
||||
pub fn get_openai_compatible_base_url(&self) -> Result<Option<String>> {
|
||||
match self {
|
||||
AIProvider::DeepSeek => Ok(Some("https://api.deepseek.com/v1".to_string())),
|
||||
AIProvider::Groq => Ok(Some("https://api.groq.com/openai/v1".to_string())),
|
||||
AIProvider::OpenRouter => Ok(Some("https://openrouter.ai/api/v1".to_string())),
|
||||
AIProvider::CustomAI => Ok(None),
|
||||
_ => Err(Error::BadRequest(
|
||||
"Please use the specific provider instead of the OpenAI compatible one".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for AIProvider {
|
||||
type Error = Error;
|
||||
fn try_from(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
"openai" => Ok(AIProvider::OpenAI),
|
||||
"anthropic" => Ok(AIProvider::Anthropic),
|
||||
"mistral" => Ok(AIProvider::Mistral),
|
||||
"groq" => Ok(AIProvider::Groq),
|
||||
"openrouter" => Ok(AIProvider::OpenRouter),
|
||||
"deepseek" => Ok(AIProvider::DeepSeek),
|
||||
"customai" => Ok(AIProvider::CustomAI),
|
||||
_ => Err(Error::BadRequest(format!("Invalid AI provider: {}", s))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct AIResource {
|
||||
pub path: String,
|
||||
pub provider: AIProvider,
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
let router = Router::new().route("/proxy/*ai", post(proxy));
|
||||
|
||||
@@ -382,74 +431,105 @@ async fn proxy(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, ai_path)): Path<(String, String)>,
|
||||
Query(query_params): Query<ProxyQueryParams>,
|
||||
headers: HeaderMap,
|
||||
body: Bytes,
|
||||
) -> impl IntoResponse {
|
||||
let workspace_cache = AI_KEY_CACHE.get(&w_id);
|
||||
let forced_resource_path = headers
|
||||
.get("X-Resource-Path")
|
||||
.map(|v| v.to_str().unwrap_or("").to_string());
|
||||
let ai_cache = match workspace_cache {
|
||||
Some(cache) if !cache.is_expired() && !query_params.no_cache.unwrap_or(false) => {
|
||||
cache.cached_key
|
||||
}
|
||||
Some(cache) if !cache.is_expired() && forced_resource_path.is_none() => cache.cached_key,
|
||||
_ => {
|
||||
let ai_resource = sqlx::query_scalar!(
|
||||
"SELECT ai_resource FROM workspace_settings WHERE workspace_id = $1",
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
let (resource, resource_path, ai_provider) = if let Some(resource_path) =
|
||||
forced_resource_path
|
||||
{
|
||||
// guess the provider from the resource type
|
||||
let record = sqlx::query!(
|
||||
"SELECT value, resource_type FROM resource WHERE path = $1 AND workspace_id = $2",
|
||||
&resource_path,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
Error::NotFound(format!(
|
||||
"Could not find the resource {}, update the resource path in the workspace settings", resource_path
|
||||
))
|
||||
})?;
|
||||
|
||||
if ai_resource.is_none() {
|
||||
return Err(Error::InternalErr("AI resource not configured".to_string()));
|
||||
}
|
||||
(
|
||||
record.value,
|
||||
resource_path,
|
||||
AIProvider::try_from(record.resource_type.as_str())?,
|
||||
)
|
||||
} else {
|
||||
let ai_resource = sqlx::query_scalar!(
|
||||
"SELECT ai_resource FROM workspace_settings WHERE workspace_id = $1",
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
|
||||
let ai_resource = serde_json::from_value::<AiResource>(ai_resource.unwrap())
|
||||
.map_err(|e| Error::BadRequest(e.to_string()))?;
|
||||
let ai_resource_path = ai_resource.path;
|
||||
if ai_resource.is_none() {
|
||||
return Err(Error::InternalErr("AI resource not configured".to_string()));
|
||||
}
|
||||
|
||||
let resource = sqlx::query_scalar!(
|
||||
"SELECT value
|
||||
FROM resource
|
||||
WHERE path = $1 AND workspace_id = $2",
|
||||
&ai_resource_path,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
Error::InternalErr(format!(
|
||||
"Could not find the {} resource at path {ai_resource_path}, update the resource path in the workspace settings", ai_resource.provider
|
||||
))
|
||||
})?;
|
||||
let ai_resource = serde_json::from_value::<AIResource>(ai_resource.unwrap())
|
||||
.map_err(|e| Error::BadRequest(e.to_string()))?;
|
||||
|
||||
let resource = sqlx::query_scalar!(
|
||||
"SELECT value
|
||||
FROM resource
|
||||
WHERE path = $1 AND workspace_id = $2",
|
||||
&ai_resource.path,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
Error::NotFound(format!(
|
||||
"Could not find the {:?} resource at path {}, update the resource path in the workspace settings", ai_resource.provider, ai_resource.path
|
||||
))
|
||||
})?;
|
||||
|
||||
(resource, ai_resource.path, ai_resource.provider)
|
||||
};
|
||||
|
||||
if resource.is_none() {
|
||||
return Err(Error::InternalErr(format!(
|
||||
"{} resource missing value",
|
||||
ai_resource.provider
|
||||
"{:?} resource missing value",
|
||||
ai_provider
|
||||
)));
|
||||
}
|
||||
|
||||
let resource = resource.unwrap();
|
||||
|
||||
let ai_cache = match ai_resource.provider.as_str() {
|
||||
"openai" => openai::get_cached_value(&db, &w_id, resource).await,
|
||||
"anthropic" => anthropic::get_cached_value(&db, &w_id, resource).await,
|
||||
"mistral" => mistral::get_cached_value(&db, &w_id, resource).await,
|
||||
provider => {
|
||||
return Err(Error::BadRequest(format!("{} is not supported", provider)))
|
||||
let ai_cache = match ai_provider {
|
||||
AIProvider::OpenAI => openai::get_cached_value(&db, &w_id, resource).await,
|
||||
AIProvider::Anthropic => anthropic::get_cached_value(&db, &w_id, resource).await,
|
||||
AIProvider::Mistral => mistral::get_cached_value(&db, &w_id, resource).await,
|
||||
_ => {
|
||||
openai_api_compatible::get_cached_value(
|
||||
&db,
|
||||
&w_id,
|
||||
resource,
|
||||
ai_provider.get_openai_compatible_base_url()?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
};
|
||||
let ai_cache = ai_cache?;
|
||||
AI_KEY_CACHE.insert(
|
||||
w_id.clone(),
|
||||
AiCache::new(ai_resource_path, ai_cache.clone()),
|
||||
);
|
||||
AI_KEY_CACHE.insert(w_id.clone(), AICache::new(resource_path, ai_cache.clone()));
|
||||
ai_cache
|
||||
}
|
||||
};
|
||||
let (path, request) = match ai_cache {
|
||||
KeyCache::Openai(cached) => ("openai_path", cached.prepare_request(&ai_path, body)),
|
||||
KeyCache::Anthropic(cached) => ("anthropic_path", cached.prepare_request(&ai_path, body)),
|
||||
KeyCache::Mistral(cached) => ("mistral_path", cached.prepare_request(&ai_path, body)),
|
||||
|
||||
let request = match ai_cache {
|
||||
KeyCache::Openai(cached) => cached.prepare_request(&ai_path, body),
|
||||
KeyCache::Anthropic(cached) => cached.prepare_request(&ai_path, body),
|
||||
KeyCache::Mistral(cached) => cached.prepare_request(&ai_path, body),
|
||||
KeyCache::OpenaiApiCompatible(cached) => cached.prepare_request(&ai_path, body),
|
||||
};
|
||||
|
||||
let response = request?.send().await.map_err(to_anyhow)?;
|
||||
@@ -463,7 +543,7 @@ async fn proxy(
|
||||
ActionKind::Execute,
|
||||
&w_id,
|
||||
Some(&authed.email),
|
||||
Some([(path, &format!("{:?}", ai_path)[..])].into()),
|
||||
Some([("ai_resource_path", &format!("{:?}", ai_path)[..])].into()),
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::ai::{AiResource, AI_KEY_CACHE};
|
||||
use crate::ai::{AIProvider, AIResource, AI_KEY_CACHE};
|
||||
use crate::db::ApiAuthed;
|
||||
use crate::users_ee::send_email_if_possible;
|
||||
use crate::utils::get_instance_username_or_create_pending;
|
||||
@@ -177,7 +177,9 @@ pub struct WorkspaceSettings {
|
||||
pub webhook: Option<String>,
|
||||
pub deploy_to: Option<String>,
|
||||
pub ai_resource: Option<serde_json::Value>,
|
||||
pub code_completion_enabled: bool,
|
||||
pub ai_models: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub code_completion_model: Option<String>,
|
||||
pub error_handler: Option<String>,
|
||||
pub error_handler_extra_args: Option<serde_json::Value>,
|
||||
pub error_handler_muted_on_cancel: Option<bool>,
|
||||
@@ -246,7 +248,8 @@ struct EditWebhook {
|
||||
#[derive(Deserialize)]
|
||||
struct EditCopilotConfig {
|
||||
ai_resource: Option<serde_json::Value>,
|
||||
code_completion_enabled: bool,
|
||||
code_completion_model: Option<String>,
|
||||
ai_models: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
@@ -660,27 +663,37 @@ async fn edit_copilot_config(
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
if let Some(ai_resource) = &eo.ai_resource {
|
||||
let path = serde_json::from_value::<AiResource>(ai_resource.clone())
|
||||
.map_err(|e| Error::BadRequest(e.to_string()))?
|
||||
.path;
|
||||
let parsed_ai_resource = serde_json::from_value::<AIResource>(ai_resource.clone())
|
||||
.map_err(|e| Error::BadRequest(e.to_string()))?;
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
{
|
||||
if matches!(parsed_ai_resource.provider, AIProvider::CustomAI) {
|
||||
return Err(Error::BadRequest(
|
||||
"Custom AI is only available on EE".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET ai_resource = $1, code_completion_enabled = $2 WHERE workspace_id = $3",
|
||||
"UPDATE workspace_settings SET ai_resource = $1, code_completion_model = $2, ai_models = $3 WHERE workspace_id = $4",
|
||||
ai_resource,
|
||||
eo.code_completion_enabled,
|
||||
eo.code_completion_model,
|
||||
eo.ai_models.as_slice(),
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
if let Some(cached) = AI_KEY_CACHE.get(&w_id) {
|
||||
if cached.path != path {
|
||||
if cached.path != parsed_ai_resource.path {
|
||||
AI_KEY_CACHE.remove(&w_id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET ai_resource = NULL, code_completion_enabled = $1 WHERE workspace_id = $2",
|
||||
eo.code_completion_enabled,
|
||||
"UPDATE workspace_settings SET ai_resource = NULL, code_completion_model = $1, ai_models = '{}' WHERE workspace_id = $2",
|
||||
eo.code_completion_model,
|
||||
&w_id,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
@@ -698,8 +711,8 @@ async fn edit_copilot_config(
|
||||
[
|
||||
("ai_resource", &format!("{:?}", eo.ai_resource)[..]),
|
||||
(
|
||||
"code_completion_enabled",
|
||||
&format!("{:?}", eo.code_completion_enabled)[..],
|
||||
"code_completion_model",
|
||||
&format!("{:?}", eo.code_completion_model)[..],
|
||||
),
|
||||
]
|
||||
.into(),
|
||||
@@ -713,9 +726,12 @@ async fn edit_copilot_config(
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CopilotInfo {
|
||||
pub ai_provider: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ai_provider: Option<AIProvider>,
|
||||
pub exists_ai_resource: bool,
|
||||
pub code_completion_enabled: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub code_completion_model: Option<String>,
|
||||
pub ai_models: Vec<String>,
|
||||
}
|
||||
async fn get_copilot_info(
|
||||
Extension(db): Extension<DB>,
|
||||
@@ -723,33 +739,26 @@ async fn get_copilot_info(
|
||||
) -> JsonResult<CopilotInfo> {
|
||||
let mut tx = db.begin().await?;
|
||||
let record = sqlx::query!(
|
||||
"SELECT ai_resource, code_completion_enabled FROM workspace_settings WHERE workspace_id = $1",
|
||||
"SELECT ai_resource, code_completion_model, ai_models FROM workspace_settings WHERE workspace_id = $1",
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("getting ai_resource and code_completion_enabled: {e:#}")))?;
|
||||
.map_err(|e| Error::InternalErr(format!("getting ai_resource and code_completion_model: {e:#}")))?;
|
||||
tx.commit().await?;
|
||||
|
||||
let (ai_provider, exists_ai_resource) = if let Some(ai_resource) = record.ai_resource {
|
||||
let ai_resource = serde_json::from_value::<AiResource>(ai_resource);
|
||||
let exist = ai_resource.is_ok();
|
||||
(
|
||||
if exist {
|
||||
ai_resource.unwrap().provider
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
exist,
|
||||
)
|
||||
let ai_resource = serde_json::from_value::<AIResource>(ai_resource)?;
|
||||
(Some(ai_resource.provider), true)
|
||||
} else {
|
||||
("".to_string(), false)
|
||||
(None, false)
|
||||
};
|
||||
|
||||
Ok(Json(CopilotInfo {
|
||||
ai_provider,
|
||||
exists_ai_resource,
|
||||
code_completion_enabled: record.code_completion_enabled,
|
||||
code_completion_model: record.code_completion_model,
|
||||
ai_models: record.ai_models,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -241,7 +241,9 @@ struct SimplifiedSettings {
|
||||
error_handler_extra_args: Option<Value>,
|
||||
error_handler_muted_on_cancel: bool,
|
||||
ai_resource: Option<serde_json::Value>,
|
||||
code_completion_enabled: bool,
|
||||
ai_models: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
code_completion_model: Option<String>,
|
||||
large_file_storage: Option<Value>,
|
||||
git_sync: Option<Value>,
|
||||
default_app: Option<String>,
|
||||
@@ -617,7 +619,8 @@ pub(crate) async fn tarball_workspace(
|
||||
deploy_to,
|
||||
error_handler,
|
||||
ai_resource,
|
||||
code_completion_enabled,
|
||||
ai_models,
|
||||
code_completion_model,
|
||||
error_handler_extra_args,
|
||||
error_handler_muted_on_cancel,
|
||||
large_file_storage,
|
||||
|
||||
@@ -54,7 +54,7 @@ export const OpenAPI: OpenAPIConfig = {
|
||||
PASSWORD: undefined,
|
||||
TOKEN: getEnv("WM_TOKEN"),
|
||||
USERNAME: undefined,
|
||||
VERSION: '1.447.5',
|
||||
VERSION: '1.454.1',
|
||||
WITH_CREDENTIALS: true,
|
||||
interceptors: {
|
||||
request: new Interceptors(),
|
||||
|
||||
+445
-1
File diff suppressed because one or more lines are too long
+422
-35
@@ -1,8 +1,10 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
export type AiResource = {
|
||||
export type AIProvider = 'openai' | 'anthropic' | 'mistral' | 'deepseek' | 'customai';
|
||||
|
||||
export type AIResource = {
|
||||
path: string;
|
||||
provider: string;
|
||||
provider: AIProvider;
|
||||
};
|
||||
|
||||
export type Script = {
|
||||
@@ -30,7 +32,7 @@ export type Script = {
|
||||
};
|
||||
lock?: string;
|
||||
lock_error_logs?: string;
|
||||
language: 'python3' | 'deno' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'oracledb' | 'graphql' | 'nativets' | 'bun' | 'php' | 'rust' | 'ansible' | 'csharp';
|
||||
language: ScriptLang;
|
||||
kind: 'script' | 'failure' | 'trigger' | 'command' | 'approval' | 'preprocessor';
|
||||
starred: boolean;
|
||||
tag?: string;
|
||||
@@ -54,8 +56,6 @@ export type Script = {
|
||||
on_behalf_of_email?: string;
|
||||
};
|
||||
|
||||
export type language = 'python3' | 'deno' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'oracledb' | 'graphql' | 'nativets' | 'bun' | 'php' | 'rust' | 'ansible' | 'csharp';
|
||||
|
||||
export type kind = 'script' | 'failure' | 'trigger' | 'command' | 'approval' | 'preprocessor';
|
||||
|
||||
export type NewScript = {
|
||||
@@ -69,7 +69,7 @@ export type NewScript = {
|
||||
};
|
||||
is_template?: boolean;
|
||||
lock?: string;
|
||||
language: 'python3' | 'deno' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'oracledb' | 'graphql' | 'nativets' | 'bun' | 'php' | 'rust' | 'ansible' | 'csharp';
|
||||
language: ScriptLang;
|
||||
kind?: 'script' | 'failure' | 'trigger' | 'command' | 'approval' | 'preprocessor';
|
||||
tag?: string;
|
||||
draft_only?: boolean;
|
||||
@@ -159,7 +159,7 @@ export type QueuedJob = {
|
||||
flow_status?: FlowStatus;
|
||||
raw_flow?: FlowValue;
|
||||
is_flow_step: boolean;
|
||||
language?: 'python3' | 'deno' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'oracledb' | 'graphql' | 'nativets' | 'bun' | 'php' | 'rust' | 'ansible' | 'csharp';
|
||||
language?: ScriptLang;
|
||||
email: string;
|
||||
visible_to_owner: boolean;
|
||||
mem_peak?: number;
|
||||
@@ -202,7 +202,7 @@ export type CompletedJob = {
|
||||
flow_status?: FlowStatus;
|
||||
raw_flow?: FlowValue;
|
||||
is_flow_step: boolean;
|
||||
language?: 'python3' | 'deno' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'oracledb' | 'graphql' | 'nativets' | 'bun' | 'php' | 'rust' | 'ansible' | 'csharp';
|
||||
language?: ScriptLang;
|
||||
is_skipped: boolean;
|
||||
email: string;
|
||||
visible_to_owner: boolean;
|
||||
@@ -372,11 +372,13 @@ export type MainArgSignature = {
|
||||
|
||||
export type type2 = 'Valid' | 'Invalid';
|
||||
|
||||
export type ScriptLang = 'python3' | 'deno' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'oracledb' | 'graphql' | 'nativets' | 'bun' | 'php' | 'rust' | 'ansible' | 'csharp';
|
||||
|
||||
export type Preview = {
|
||||
content?: string;
|
||||
path?: string;
|
||||
args: ScriptArgs;
|
||||
language?: 'python3' | 'deno' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'oracledb' | 'graphql' | 'nativets' | 'bun' | 'php' | 'rust' | 'ansible' | 'csharp';
|
||||
language?: ScriptLang;
|
||||
tag?: string;
|
||||
kind?: 'code' | 'identity' | 'http';
|
||||
dedicated_worker?: boolean;
|
||||
@@ -550,10 +552,18 @@ export type EditSchedule = {
|
||||
cron_version?: string;
|
||||
};
|
||||
|
||||
export type HttpTrigger = {
|
||||
path: string;
|
||||
export type TriggerExtraProperty = {
|
||||
email: string;
|
||||
extra_perms: {
|
||||
[key: string]: (boolean);
|
||||
};
|
||||
workspace_id: string;
|
||||
edited_by: string;
|
||||
edited_at: string;
|
||||
};
|
||||
|
||||
export type HttpTrigger = TriggerExtraProperty & {
|
||||
path: string;
|
||||
script_path: string;
|
||||
route_path: string;
|
||||
static_asset_config?: {
|
||||
@@ -562,11 +572,6 @@ export type HttpTrigger = {
|
||||
filename?: string;
|
||||
};
|
||||
is_flow: boolean;
|
||||
extra_perms: {
|
||||
[key: string]: (boolean);
|
||||
};
|
||||
email: string;
|
||||
workspace_id: string;
|
||||
http_method: 'get' | 'post' | 'put' | 'delete' | 'patch';
|
||||
is_async: boolean;
|
||||
requires_auth: boolean;
|
||||
@@ -613,22 +618,16 @@ export type TriggersCount = {
|
||||
webhook_count?: number;
|
||||
email_count?: number;
|
||||
websocket_count?: number;
|
||||
postgres_count?: number;
|
||||
kafka_count?: number;
|
||||
nats_count?: number;
|
||||
};
|
||||
|
||||
export type WebsocketTrigger = {
|
||||
export type WebsocketTrigger = TriggerExtraProperty & {
|
||||
path: string;
|
||||
edited_by: string;
|
||||
edited_at: string;
|
||||
script_path: string;
|
||||
url: string;
|
||||
is_flow: boolean;
|
||||
extra_perms: {
|
||||
[key: string]: (boolean);
|
||||
};
|
||||
email: string;
|
||||
workspace_id: string;
|
||||
server_id?: string;
|
||||
last_server_ping?: string;
|
||||
error?: string;
|
||||
@@ -639,6 +638,7 @@ export type WebsocketTrigger = {
|
||||
}>;
|
||||
initial_messages?: Array<WebsocketTriggerInitialMessage>;
|
||||
url_runnable_args?: ScriptArgs;
|
||||
can_return_message: boolean;
|
||||
};
|
||||
|
||||
export type NewWebsocketTrigger = {
|
||||
@@ -653,6 +653,7 @@ export type NewWebsocketTrigger = {
|
||||
}>;
|
||||
initial_messages?: Array<WebsocketTriggerInitialMessage>;
|
||||
url_runnable_args?: ScriptArgs;
|
||||
can_return_message: boolean;
|
||||
};
|
||||
|
||||
export type EditWebsocketTrigger = {
|
||||
@@ -666,6 +667,7 @@ export type EditWebsocketTrigger = {
|
||||
}>;
|
||||
initial_messages?: Array<WebsocketTriggerInitialMessage>;
|
||||
url_runnable_args?: ScriptArgs;
|
||||
can_return_message: boolean;
|
||||
};
|
||||
|
||||
export type WebsocketTriggerInitialMessage = {
|
||||
@@ -678,6 +680,74 @@ export type WebsocketTriggerInitialMessage = {
|
||||
};
|
||||
};
|
||||
|
||||
export type Slot = {
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export type SlotList = {
|
||||
slot_name?: string;
|
||||
active?: boolean;
|
||||
};
|
||||
|
||||
export type PublicationData = {
|
||||
table_to_track?: Array<Relations>;
|
||||
transaction_to_track: Array<(string)>;
|
||||
};
|
||||
|
||||
export type TableToTrack = Array<{
|
||||
table_name: string;
|
||||
columns_name?: Array<(string)>;
|
||||
where_clause?: string;
|
||||
}>;
|
||||
|
||||
export type Relations = {
|
||||
schema_name: string;
|
||||
table_to_track: TableToTrack;
|
||||
};
|
||||
|
||||
export type Language = 'Typescript';
|
||||
|
||||
export type TemplateScript = {
|
||||
postgres_resource_path: string;
|
||||
relations: Array<Relations>;
|
||||
language: Language;
|
||||
};
|
||||
|
||||
export type PostgresTrigger = TriggerExtraProperty & {
|
||||
path: string;
|
||||
script_path: string;
|
||||
is_flow: boolean;
|
||||
enabled: boolean;
|
||||
postgres_resource_path: string;
|
||||
publication_name: string;
|
||||
server_id?: string;
|
||||
replication_slot_name: string;
|
||||
error?: string;
|
||||
last_server_ping?: string;
|
||||
};
|
||||
|
||||
export type NewPostgresTrigger = {
|
||||
replication_slot_name?: string;
|
||||
publication_name?: string;
|
||||
path: string;
|
||||
script_path: string;
|
||||
is_flow: boolean;
|
||||
enabled: boolean;
|
||||
postgres_resource_path: string;
|
||||
publication?: PublicationData;
|
||||
};
|
||||
|
||||
export type EditPostgresTrigger = {
|
||||
replication_slot_name: string;
|
||||
publication_name: string;
|
||||
path: string;
|
||||
script_path: string;
|
||||
is_flow: boolean;
|
||||
enabled: boolean;
|
||||
postgres_resource_path: string;
|
||||
publication?: PublicationData;
|
||||
};
|
||||
|
||||
export type KafkaTrigger = {
|
||||
path: string;
|
||||
edited_by: string;
|
||||
@@ -817,6 +887,7 @@ export type UserWorkspaceList = {
|
||||
name: string;
|
||||
username: string;
|
||||
color: string;
|
||||
operator_settings?: OperatorSettings;
|
||||
}>;
|
||||
};
|
||||
|
||||
@@ -1111,7 +1182,7 @@ export type MetricDataPoint = {
|
||||
export type RawScriptForDependencies = {
|
||||
raw_code: string;
|
||||
path: string;
|
||||
language: 'python3' | 'deno' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'oracledb' | 'graphql' | 'nativets' | 'bun' | 'php' | 'rust' | 'ansible' | 'csharp';
|
||||
language: ScriptLang;
|
||||
};
|
||||
|
||||
export type ConcurrencyGroup = {
|
||||
@@ -1223,6 +1294,79 @@ export type CaptureConfig = {
|
||||
last_server_ping?: string;
|
||||
};
|
||||
|
||||
export type OperatorSettings = {
|
||||
/**
|
||||
* Whether operators can view runs
|
||||
*/
|
||||
runs: boolean;
|
||||
/**
|
||||
* Whether operators can view schedules
|
||||
*/
|
||||
schedules: boolean;
|
||||
/**
|
||||
* Whether operators can view resources
|
||||
*/
|
||||
resources: boolean;
|
||||
/**
|
||||
* Whether operators can view variables
|
||||
*/
|
||||
variables: boolean;
|
||||
/**
|
||||
* Whether operators can view audit logs
|
||||
*/
|
||||
audit_logs: boolean;
|
||||
/**
|
||||
* Whether operators can view triggers
|
||||
*/
|
||||
triggers: boolean;
|
||||
/**
|
||||
* Whether operators can view groups page
|
||||
*/
|
||||
groups: boolean;
|
||||
/**
|
||||
* Whether operators can view folders page
|
||||
*/
|
||||
folders: boolean;
|
||||
/**
|
||||
* Whether operators can view workers page
|
||||
*/
|
||||
workers: boolean;
|
||||
} | null;
|
||||
|
||||
export type TeamInfo = {
|
||||
/**
|
||||
* The unique identifier of the Microsoft Teams team
|
||||
*/
|
||||
team_id: string;
|
||||
/**
|
||||
* The display name of the Microsoft Teams team
|
||||
*/
|
||||
team_name: string;
|
||||
/**
|
||||
* List of channels within the team
|
||||
*/
|
||||
channels: Array<ChannelInfo>;
|
||||
};
|
||||
|
||||
export type ChannelInfo = {
|
||||
/**
|
||||
* The unique identifier of the channel
|
||||
*/
|
||||
channel_id: string;
|
||||
/**
|
||||
* The display name of the channel
|
||||
*/
|
||||
channel_name: string;
|
||||
/**
|
||||
* The Microsoft Teams tenant identifier
|
||||
*/
|
||||
tenant_id: string;
|
||||
/**
|
||||
* The service URL for the channel
|
||||
*/
|
||||
service_url: string;
|
||||
};
|
||||
|
||||
export type OpenFlow = {
|
||||
summary: string;
|
||||
description?: string;
|
||||
@@ -1331,7 +1475,7 @@ export type RawScript = {
|
||||
is_trigger?: boolean;
|
||||
};
|
||||
|
||||
export type language2 = 'deno' | 'bun' | 'python3' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'oracledb' | 'graphql' | 'nativets' | 'php';
|
||||
export type language = 'deno' | 'bun' | 'python3' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'oracledb' | 'graphql' | 'nativets' | 'php';
|
||||
|
||||
export type PathScript = {
|
||||
input_transforms: {
|
||||
@@ -1441,10 +1585,14 @@ export type FlowStatusModule = {
|
||||
|
||||
export type type4 = 'WaitingForPriorSteps' | 'WaitingForEvents' | 'WaitingForExecutor' | 'InProgress' | 'Success' | 'Failure';
|
||||
|
||||
export type ParameterId = string;
|
||||
|
||||
export type ParameterKey = string;
|
||||
|
||||
export type ParameterWorkspaceId = string;
|
||||
|
||||
export type ParameterPublicationName = string;
|
||||
|
||||
export type ParameterVersionId = number;
|
||||
|
||||
export type ParameterToken = string;
|
||||
@@ -2239,6 +2387,13 @@ export type WhoisData = {
|
||||
|
||||
export type WhoisResponse = (User);
|
||||
|
||||
export type UpdateOperatorSettingsData = {
|
||||
requestBody: OperatorSettings;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type UpdateOperatorSettingsResponse = (string);
|
||||
|
||||
export type ExistsEmailData = {
|
||||
email: string;
|
||||
};
|
||||
@@ -2285,8 +2440,9 @@ export type GetSettingsResponse = ({
|
||||
customer_id?: string;
|
||||
webhook?: string;
|
||||
deploy_to?: string;
|
||||
ai_resource?: AiResource;
|
||||
code_completion_enabled: boolean;
|
||||
ai_resource?: AIResource;
|
||||
code_completion_model?: string;
|
||||
ai_models: Array<(string)>;
|
||||
error_handler?: string;
|
||||
error_handler_extra_args?: ScriptArgs;
|
||||
error_handler_muted_on_cancel: boolean;
|
||||
@@ -2297,6 +2453,7 @@ export type GetSettingsResponse = ({
|
||||
default_scripts?: WorkspaceDefaultScripts;
|
||||
mute_critical_alerts?: boolean;
|
||||
color?: string;
|
||||
operator_settings?: OperatorSettings;
|
||||
});
|
||||
|
||||
export type GetDeployToData = {
|
||||
@@ -2427,8 +2584,9 @@ export type EditCopilotConfigData = {
|
||||
* WorkspaceCopilotConfig
|
||||
*/
|
||||
requestBody: {
|
||||
ai_resource?: AiResource;
|
||||
code_completion_enabled: boolean;
|
||||
ai_resource?: AIResource;
|
||||
code_completion_model?: string;
|
||||
ai_models: Array<(string)>;
|
||||
};
|
||||
workspace: string;
|
||||
};
|
||||
@@ -2440,9 +2598,10 @@ export type GetCopilotInfoData = {
|
||||
};
|
||||
|
||||
export type GetCopilotInfoResponse = ({
|
||||
ai_provider: string;
|
||||
ai_provider: AIProvider;
|
||||
exists_ai_resource: boolean;
|
||||
code_completion_enabled: boolean;
|
||||
code_completion_model?: string;
|
||||
ai_models: Array<(string)>;
|
||||
});
|
||||
|
||||
export type EditErrorHandlerData = {
|
||||
@@ -2586,6 +2745,7 @@ export type GetUsedTriggersResponse = ({
|
||||
websocket_used: boolean;
|
||||
kafka_used: boolean;
|
||||
nats_used: boolean;
|
||||
postgres_used: boolean;
|
||||
});
|
||||
|
||||
export type ListUsersData = {
|
||||
@@ -2916,6 +3076,8 @@ export type GetOauthConnectResponse = ({
|
||||
scopes?: Array<(string)>;
|
||||
});
|
||||
|
||||
export type SyncTeamsResponse = (Array<TeamInfo>);
|
||||
|
||||
export type CreateResourceData = {
|
||||
/**
|
||||
* new resource
|
||||
@@ -5490,6 +5652,20 @@ export type SetWebsocketTriggerEnabledData = {
|
||||
|
||||
export type SetWebsocketTriggerEnabledResponse = (string);
|
||||
|
||||
export type TestWebsocketConnectionData = {
|
||||
/**
|
||||
* test websocket connection
|
||||
*/
|
||||
requestBody: {
|
||||
url: string;
|
||||
url_runnable_args?: ScriptArgs;
|
||||
can_return_message: boolean;
|
||||
};
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type TestWebsocketConnectionResponse = (string);
|
||||
|
||||
export type CreateKafkaTriggerData = {
|
||||
/**
|
||||
* new kafka trigger
|
||||
@@ -5565,6 +5741,20 @@ export type SetKafkaTriggerEnabledData = {
|
||||
|
||||
export type SetKafkaTriggerEnabledResponse = (string);
|
||||
|
||||
export type TestKafkaConnectionData = {
|
||||
/**
|
||||
* test kafka connection
|
||||
*/
|
||||
requestBody: {
|
||||
connection: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type TestKafkaConnectionResponse = (string);
|
||||
|
||||
export type CreateNatsTriggerData = {
|
||||
/**
|
||||
* new nats trigger
|
||||
@@ -5640,6 +5830,195 @@ export type SetNatsTriggerEnabledData = {
|
||||
|
||||
export type SetNatsTriggerEnabledResponse = (string);
|
||||
|
||||
export type TestNatsConnectionData = {
|
||||
/**
|
||||
* test nats connection
|
||||
*/
|
||||
requestBody: {
|
||||
connection: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type TestNatsConnectionResponse = (string);
|
||||
|
||||
export type IsValidPostgresConfigurationData = {
|
||||
path: string;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type IsValidPostgresConfigurationResponse = (boolean);
|
||||
|
||||
export type CreateTemplateScriptData = {
|
||||
/**
|
||||
* template script
|
||||
*/
|
||||
requestBody: TemplateScript;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type CreateTemplateScriptResponse = (string);
|
||||
|
||||
export type GetTemplateScriptData = {
|
||||
id: string;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type GetTemplateScriptResponse = (string);
|
||||
|
||||
export type ListPostgresReplicationSlotData = {
|
||||
path: string;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type ListPostgresReplicationSlotResponse = (Array<SlotList>);
|
||||
|
||||
export type CreatePostgresReplicationSlotData = {
|
||||
path: string;
|
||||
/**
|
||||
* new slot for postgres
|
||||
*/
|
||||
requestBody: Slot;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type CreatePostgresReplicationSlotResponse = (string);
|
||||
|
||||
export type DeletePostgresReplicationSlotData = {
|
||||
path: string;
|
||||
/**
|
||||
* replication slot of postgres
|
||||
*/
|
||||
requestBody: Slot;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type DeletePostgresReplicationSlotResponse = (string);
|
||||
|
||||
export type ListPostgresPublicationData = {
|
||||
path: string;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type ListPostgresPublicationResponse = (Array<(string)>);
|
||||
|
||||
export type GetPostgresPublicationData = {
|
||||
path: string;
|
||||
publication: string;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type GetPostgresPublicationResponse = (PublicationData);
|
||||
|
||||
export type CreatePostgresPublicationData = {
|
||||
path: string;
|
||||
publication: string;
|
||||
/**
|
||||
* new publication for postgres
|
||||
*/
|
||||
requestBody: PublicationData;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type CreatePostgresPublicationResponse = (string);
|
||||
|
||||
export type UpdatePostgresPublicationData = {
|
||||
path: string;
|
||||
publication: string;
|
||||
/**
|
||||
* update publication for postgres
|
||||
*/
|
||||
requestBody: PublicationData;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type UpdatePostgresPublicationResponse = (string);
|
||||
|
||||
export type DeletePostgresPublicationData = {
|
||||
path: string;
|
||||
publication: string;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type DeletePostgresPublicationResponse = (string);
|
||||
|
||||
export type CreatePostgresTriggerData = {
|
||||
/**
|
||||
* new postgres trigger
|
||||
*/
|
||||
requestBody: NewPostgresTrigger;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type CreatePostgresTriggerResponse = (string);
|
||||
|
||||
export type UpdatePostgresTriggerData = {
|
||||
path: string;
|
||||
/**
|
||||
* updated trigger
|
||||
*/
|
||||
requestBody: EditPostgresTrigger;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type UpdatePostgresTriggerResponse = (string);
|
||||
|
||||
export type DeletePostgresTriggerData = {
|
||||
path: string;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type DeletePostgresTriggerResponse = (string);
|
||||
|
||||
export type GetPostgresTriggerData = {
|
||||
path: string;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type GetPostgresTriggerResponse = (PostgresTrigger);
|
||||
|
||||
export type ListPostgresTriggersData = {
|
||||
isFlow?: boolean;
|
||||
/**
|
||||
* which page to return (start at 1, default 1)
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* filter by path
|
||||
*/
|
||||
path?: string;
|
||||
pathStart?: string;
|
||||
/**
|
||||
* number of items to return for a given page (default 30, max 100)
|
||||
*/
|
||||
perPage?: number;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type ListPostgresTriggersResponse = (Array<PostgresTrigger>);
|
||||
|
||||
export type ExistsPostgresTriggerData = {
|
||||
path: string;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type ExistsPostgresTriggerResponse = (boolean);
|
||||
|
||||
export type SetPostgresTriggerEnabledData = {
|
||||
path: string;
|
||||
/**
|
||||
* updated postgres trigger enable
|
||||
*/
|
||||
requestBody: {
|
||||
enabled: boolean;
|
||||
};
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type SetPostgresTriggerEnabledResponse = (string);
|
||||
|
||||
export type ListInstanceGroupsResponse = (Array<InstanceGroup>);
|
||||
|
||||
export type GetInstanceGroupData = {
|
||||
@@ -5987,7 +6366,7 @@ export type ListAutoscalingEventsData = {
|
||||
export type ListAutoscalingEventsResponse = (Array<AutoscalingEvent>);
|
||||
|
||||
export type GetGranularAclsData = {
|
||||
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger' | 'kafka_trigger' | 'nats_trigger';
|
||||
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger' | 'kafka_trigger' | 'nats_trigger' | 'postgres_trigger';
|
||||
path: string;
|
||||
workspace: string;
|
||||
};
|
||||
@@ -5997,7 +6376,7 @@ export type GetGranularAclsResponse = ({
|
||||
});
|
||||
|
||||
export type AddGranularAclsData = {
|
||||
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger' | 'kafka_trigger' | 'nats_trigger';
|
||||
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger' | 'kafka_trigger' | 'nats_trigger' | 'postgres_trigger';
|
||||
path: string;
|
||||
/**
|
||||
* acl to add
|
||||
@@ -6012,7 +6391,7 @@ export type AddGranularAclsData = {
|
||||
export type AddGranularAclsResponse = (string);
|
||||
|
||||
export type RemoveGranularAclsData = {
|
||||
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger' | 'kafka_trigger' | 'nats_trigger';
|
||||
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger' | 'kafka_trigger' | 'nats_trigger' | 'postgres_trigger';
|
||||
path: string;
|
||||
/**
|
||||
* acl to add
|
||||
@@ -6076,6 +6455,13 @@ export type ListCapturesData = {
|
||||
|
||||
export type ListCapturesResponse = (Array<Capture>);
|
||||
|
||||
export type GetCaptureData = {
|
||||
id: number;
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type GetCaptureResponse = (Capture);
|
||||
|
||||
export type DeleteCaptureData = {
|
||||
id: number;
|
||||
workspace: string;
|
||||
@@ -6200,6 +6586,7 @@ export type DuckdbConnectionSettingsV2Data = {
|
||||
|
||||
export type DuckdbConnectionSettingsV2Response = ({
|
||||
connection_settings_str: string;
|
||||
azure_container_path?: string;
|
||||
});
|
||||
|
||||
export type PolarsConnectionSettingsData = {
|
||||
|
||||
+18
-18
@@ -1,15 +1,11 @@
|
||||
import { yamlStringify } from "./deps.ts";
|
||||
import { Confirm } from "./deps.ts";
|
||||
import { colors } from "./deps.ts";
|
||||
import { yamlParseFile } from "./deps.ts";
|
||||
import { log } from "./deps.ts";
|
||||
import process from "node:process";
|
||||
import { colors, Confirm, log, yamlParseFile, yamlStringify } from "./deps.ts";
|
||||
import * as wmill from "./gen/services.gen.ts";
|
||||
import { AiResource, Config, GlobalSetting } from "./gen/types.gen.ts";
|
||||
import { compareInstanceObjects, InstanceSyncOptions } from "./instance.ts";
|
||||
import { isSuperset } from "./types.ts";
|
||||
import { deepEqual } from "./utils.ts";
|
||||
import * as wmill from "./gen/services.gen.ts";
|
||||
import { AiResource, Config, GlobalSetting } from "./gen/types.gen.ts";
|
||||
import { removeWorkerPrefix } from "./worker_groups.ts";
|
||||
import process from "node:process";
|
||||
|
||||
export interface SimplifiedSettings {
|
||||
// slack_team_id?: string;
|
||||
@@ -25,7 +21,8 @@ export interface SimplifiedSettings {
|
||||
error_handler_extra_args?: any;
|
||||
error_handler_muted_on_cancel?: boolean;
|
||||
ai_resource?: AiResource;
|
||||
code_completion_enabled: boolean;
|
||||
code_completion_model?: string;
|
||||
ai_models: string[];
|
||||
large_file_storage?: any;
|
||||
git_sync?: any;
|
||||
default_app?: string;
|
||||
@@ -81,7 +78,8 @@ export async function pushWorkspaceSettings(
|
||||
error_handler_muted_on_cancel:
|
||||
remoteSettings.error_handler_muted_on_cancel,
|
||||
ai_resource: remoteSettings.ai_resource,
|
||||
code_completion_enabled: remoteSettings.code_completion_enabled,
|
||||
code_completion_model: remoteSettings.code_completion_model,
|
||||
ai_models: remoteSettings.ai_models,
|
||||
large_file_storage: remoteSettings.large_file_storage,
|
||||
git_sync: remoteSettings.git_sync,
|
||||
default_app: remoteSettings.default_app,
|
||||
@@ -153,15 +151,17 @@ export async function pushWorkspaceSettings(
|
||||
}
|
||||
}
|
||||
if (
|
||||
localSettings.ai_resource !== settings.ai_resource ||
|
||||
localSettings.code_completion_enabled !== settings.code_completion_enabled
|
||||
localSettings.ai_resource != settings.ai_resource ||
|
||||
localSettings.code_completion_model != settings.code_completion_model ||
|
||||
!deepEqual(localSettings.ai_models, settings.ai_models)
|
||||
) {
|
||||
log.debug(`Updating openai settings...`);
|
||||
log.debug(`Updating copilot settings...`);
|
||||
await wmill.editCopilotConfig({
|
||||
workspace,
|
||||
requestBody: {
|
||||
ai_resource: localSettings.ai_resource,
|
||||
code_completion_enabled: localSettings.code_completion_enabled,
|
||||
code_completion_model: localSettings.code_completion_model,
|
||||
ai_models: localSettings.ai_models,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -282,7 +282,9 @@ export async function readInstanceSettings(opts: InstanceSyncOptions) {
|
||||
await checkInstanceSettingsPath(opts);
|
||||
|
||||
try {
|
||||
localSettings = (await yamlParseFile(instanceSettingsPath)) as GlobalSetting[];
|
||||
localSettings = (await yamlParseFile(
|
||||
instanceSettingsPath
|
||||
)) as GlobalSetting[];
|
||||
} catch {
|
||||
log.warn(`No ${instanceSettingsPath} found`);
|
||||
}
|
||||
@@ -445,9 +447,7 @@ export async function pushInstanceSettings(
|
||||
}
|
||||
}
|
||||
|
||||
export async function readLocalConfigs(
|
||||
opts: InstanceSyncOptions
|
||||
) {
|
||||
export async function readLocalConfigs(opts: InstanceSyncOptions) {
|
||||
let localConfigs: Config[] = [];
|
||||
|
||||
await checkInstanceConfigPath(opts);
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
} from '$lib/relative_imports'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import type { ScheduleTrigger, TriggerContext } from './triggers'
|
||||
import { initAllAiWorkspace } from './copilot/lib'
|
||||
import { workspaceAIClients } from './copilot/lib'
|
||||
import type { FlowPropPickerConfig, PropPickerContext } from './prop_picker'
|
||||
import type { PickableProperties } from './flows/previousResults'
|
||||
$: token = $page.url.searchParams.get('wm_token') ?? undefined
|
||||
@@ -110,14 +110,19 @@
|
||||
|
||||
async function setCopilotInfo() {
|
||||
if (workspace) {
|
||||
initAllAiWorkspace(workspace)
|
||||
workspaceAIClients.init(workspace)
|
||||
try {
|
||||
copilotInfo.set(await WorkspaceService.getCopilotInfo({ workspace }))
|
||||
const info = await WorkspaceService.getCopilotInfo({ workspace })
|
||||
copilotInfo.set({
|
||||
...info,
|
||||
ai_provider: info.ai_provider ?? 'openai'
|
||||
})
|
||||
} catch (err) {
|
||||
copilotInfo.set({
|
||||
ai_provider: '',
|
||||
ai_provider: 'openai',
|
||||
exists_ai_resource: false,
|
||||
code_completion_enabled: false
|
||||
code_completion_model: undefined,
|
||||
ai_models: []
|
||||
})
|
||||
|
||||
console.error('Could not get copilot info')
|
||||
|
||||
@@ -172,7 +172,6 @@
|
||||
import { initVim } from './monaco_keybindings'
|
||||
import { buildWorkerDefinition } from '$lib/monaco_workers/build_workers'
|
||||
import { parseTypescriptDeps } from '$lib/relative_imports'
|
||||
import type { AiProviderTypes } from './copilot/lib'
|
||||
|
||||
// import EditorTheme from './EditorTheme.svelte'
|
||||
|
||||
@@ -640,7 +639,7 @@
|
||||
textAfterPosition,
|
||||
lang,
|
||||
abortController,
|
||||
aiProvider as AiProviderTypes
|
||||
aiProvider
|
||||
)
|
||||
if (insertText) {
|
||||
items = [
|
||||
@@ -668,7 +667,7 @@
|
||||
}
|
||||
|
||||
$: $copilotInfo.exists_ai_resource &&
|
||||
$copilotInfo.code_completion_enabled &&
|
||||
$copilotInfo.code_completion_model &&
|
||||
$codeCompletionSessionEnabled &&
|
||||
initialized &&
|
||||
addCopilotSuggestions()
|
||||
|
||||
@@ -86,7 +86,6 @@
|
||||
import type { FlowBuilderWhitelabelCustomUi } from './custom_ui'
|
||||
import FlowYamlEditor from './flows/header/FlowYamlEditor.svelte'
|
||||
import { type TriggerContext, type ScheduleTrigger } from './triggers'
|
||||
import type { AiProviderTypes } from './copilot/lib'
|
||||
|
||||
export let initialPath: string = ''
|
||||
export let pathStoreInit: string | undefined = undefined
|
||||
@@ -821,7 +820,7 @@
|
||||
try {
|
||||
push(history, $flowStore)
|
||||
let module = stepOnly ? $copilotModulesStore[0] : $copilotModulesStore[idx]
|
||||
const aiProvider = $copilotInfo.ai_provider as AiProviderTypes
|
||||
const aiProvider = $copilotInfo.ai_provider
|
||||
|
||||
copilotLoading = true
|
||||
copilotStatus = "Generating code for step '" + module.id + "'..."
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $copilotInfo.exists_ai_resource && $copilotInfo.code_completion_enabled}
|
||||
{#if $copilotInfo.exists_ai_resource && $copilotInfo.code_completion_model}
|
||||
<Toggle
|
||||
size="xs"
|
||||
bind:checked={$codeCompletionSessionEnabled}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { ExternalLink, Wand2 } from 'lucide-svelte'
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import { getNonStreamingCompletion, type AiProviderTypes } from './lib'
|
||||
import { getNonStreamingCompletion } from './lib'
|
||||
import Popup from '../common/popup/Popup.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { copilotInfo } from '$lib/stores'
|
||||
|
||||
import { base } from '$lib/base'
|
||||
import type { ChatCompletionMessageParam } from 'openai/resources/index.mjs'
|
||||
import type { AIProvider } from '$lib/gen'
|
||||
|
||||
export let schedule: string
|
||||
export let cronVersion: string
|
||||
@@ -56,7 +57,7 @@
|
||||
const response = await getNonStreamingCompletion(
|
||||
messages,
|
||||
abortController,
|
||||
aiProvider as AiProviderTypes
|
||||
aiProvider as AIProvider
|
||||
)
|
||||
|
||||
if (response.startsWith('ERROR:')) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Check, Loader2, Wand2 } from 'lucide-svelte'
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import { getNonStreamingCompletion, type AiProviderTypes } from './lib'
|
||||
import { getNonStreamingCompletion } from './lib'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import type { Flow, InputTransform } from '$lib/gen'
|
||||
import ManualPopover from '../ManualPopover.svelte'
|
||||
@@ -71,7 +71,7 @@ Only output the expression, do not explain or discuss.`
|
||||
}
|
||||
],
|
||||
abortController,
|
||||
aiProvider as AiProviderTypes
|
||||
aiProvider
|
||||
)
|
||||
} catch (err) {
|
||||
if (!abortController.signal.aborted) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { getCompletion, getResponseFromEvent, type AiProviderTypes } from './lib'
|
||||
import { getCompletion, getResponseFromEvent } from './lib'
|
||||
import { isInitialCode } from '$lib/script_helpers'
|
||||
import { Check, Loader2, Wand2 } from 'lucide-svelte'
|
||||
import { copilotInfo, metadataCompletionEnabled } from '$lib/stores'
|
||||
@@ -118,7 +118,7 @@ Generate a description for the flow below:
|
||||
content: config.user.replace(`{${config.placeholderName}}`, placeholderContent)
|
||||
}
|
||||
]
|
||||
const aiProvider = $copilotInfo.ai_provider as AiProviderTypes
|
||||
const aiProvider = $copilotInfo.ai_provider
|
||||
const response = await getCompletion(messages, abortController, aiProvider)
|
||||
generatedContent = ''
|
||||
for await (const chunk of response) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Wand2 } from 'lucide-svelte'
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import { getNonStreamingCompletion, type AiProviderTypes } from './lib'
|
||||
import { getNonStreamingCompletion } from './lib'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import type { FlowEditorContext } from '../flows/types'
|
||||
@@ -68,7 +68,7 @@ Only return the expression without any wrapper. Do not explain or discuss.`
|
||||
}
|
||||
],
|
||||
abortController,
|
||||
aiProvider as AiProviderTypes
|
||||
aiProvider
|
||||
)
|
||||
|
||||
dispatch('setExpr', result)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { base } from '$lib/base'
|
||||
import { Button } from '../common'
|
||||
|
||||
import { getNonStreamingCompletion, type AiProviderTypes } from './lib'
|
||||
import { getNonStreamingCompletion } from './lib'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import Popup from '../common/popup/Popup.svelte'
|
||||
import { copilotInfo } from '$lib/stores'
|
||||
@@ -40,7 +40,7 @@
|
||||
}
|
||||
],
|
||||
abortController,
|
||||
aiProvider as AiProviderTypes
|
||||
aiProvider
|
||||
)
|
||||
dispatch('gen', { res: res, prompt: funcDesc })
|
||||
funcDesc = ''
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { base } from '$lib/base'
|
||||
import { Button } from '../common'
|
||||
|
||||
import { SUPPORTED_LANGUAGES, copilot, type AiProviderTypes } from './lib'
|
||||
import { SUPPORTED_LANGUAGES, copilot } from './lib'
|
||||
import type { SupportedLanguage } from '$lib/common'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import type Editor from '../Editor.svelte'
|
||||
@@ -40,7 +40,7 @@
|
||||
try {
|
||||
genLoading = true
|
||||
abortController = new AbortController()
|
||||
const aiProvider = $copilotInfo.ai_provider as AiProviderTypes
|
||||
const aiProvider = $copilotInfo.ai_provider
|
||||
await copilot(
|
||||
{
|
||||
language: lang,
|
||||
@@ -53,7 +53,7 @@
|
||||
generatedCode,
|
||||
abortController,
|
||||
aiProvider,
|
||||
generatedExplanation,
|
||||
generatedExplanation
|
||||
)
|
||||
setupDiff()
|
||||
diffEditor?.setModified($generatedCode)
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
import { base } from '$lib/base'
|
||||
import { Button } from '../common'
|
||||
|
||||
import {
|
||||
MAX_SCHEMA_LENGTH,
|
||||
SUPPORTED_LANGUAGES,
|
||||
addThousandsSeparator,
|
||||
copilot,
|
||||
type AiProviderTypes
|
||||
} from './lib'
|
||||
import { MAX_SCHEMA_LENGTH, SUPPORTED_LANGUAGES, addThousandsSeparator, copilot } from './lib'
|
||||
import type { SupportedLanguage } from '$lib/common'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import type Editor from '../Editor.svelte'
|
||||
import Popup from '../common/popup/Popup.svelte'
|
||||
import { dbSchemas, copilotInfo, type DBSchema, workspaceStore } from '$lib/stores'
|
||||
import {
|
||||
dbSchemas,
|
||||
copilotInfo,
|
||||
type DBSchema,
|
||||
workspaceStore,
|
||||
copilotSessionModel
|
||||
} from '$lib/stores'
|
||||
import type DiffEditor from '../DiffEditor.svelte'
|
||||
import { scriptLangToEditorLang } from '$lib/scripts'
|
||||
import type SimpleEditor from '../SimpleEditor.svelte'
|
||||
@@ -25,16 +25,7 @@
|
||||
import LoadingIcon from '../apps/svelte-select/lib/LoadingIcon.svelte'
|
||||
import { sleep } from '$lib/utils'
|
||||
import { autoPlacement } from '@floating-ui/core'
|
||||
import {
|
||||
AlertTriangle,
|
||||
Ban,
|
||||
Bot,
|
||||
Check,
|
||||
ExternalLink,
|
||||
HistoryIcon,
|
||||
Wand2,
|
||||
X
|
||||
} from 'lucide-svelte'
|
||||
import { AlertTriangle, Ban, Check, ExternalLink, HistoryIcon, Wand2, X } from 'lucide-svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { isInitialCode } from '$lib/script_helpers'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
@@ -74,7 +65,7 @@
|
||||
return
|
||||
}
|
||||
savePrompt()
|
||||
const aiProvider = $copilotInfo.ai_provider as AiProviderTypes
|
||||
const aiProvider = $copilotInfo.ai_provider
|
||||
try {
|
||||
genLoading = true
|
||||
blockPopupOpen = true
|
||||
@@ -248,6 +239,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
function checkForInvalidModel() {
|
||||
if (
|
||||
!$copilotSessionModel ||
|
||||
($copilotSessionModel && !$copilotInfo.ai_models.includes($copilotSessionModel))
|
||||
) {
|
||||
$copilotSessionModel = $copilotInfo.ai_models[0]
|
||||
}
|
||||
}
|
||||
$: $copilotInfo && checkForInvalidModel()
|
||||
|
||||
onDestroy(() => {
|
||||
abortController?.abort()
|
||||
})
|
||||
@@ -399,21 +400,27 @@
|
||||
</div>
|
||||
{:else if $copilotInfo.exists_ai_resource}
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<div class="flex flex-row justify-between items-center w-96 gap-2">
|
||||
<ToggleButtonGroup class="w-auto shrink-0" bind:selected={mode}>
|
||||
<ToggleButton value={'gen'} label="Generate from scratch" small light />
|
||||
<ToggleButton value={'edit'} label="Edit existing code" small light />
|
||||
</ToggleButtonGroup>
|
||||
|
||||
<div class="text-[0.6rem] text-secondary opacity-60 flex flex-row items-center gap-0.5">
|
||||
{#if $copilotInfo.ai_provider === 'openai'}
|
||||
GPT-4o
|
||||
{:else if $copilotInfo.ai_provider === 'anthropic'}
|
||||
Claude-3.5
|
||||
{:else}
|
||||
Codestral
|
||||
<div class="min-w-0">
|
||||
{#if $copilotInfo.ai_models.length > 1}
|
||||
<select
|
||||
bind:value={$copilotSessionModel}
|
||||
class="!text-xs !pr-5 !bg-[right_center] overflow-ellipsis text-right !border-none !shadow-none"
|
||||
>
|
||||
{#each $copilotInfo.ai_models as model}
|
||||
<option value={model} class="pr-4">{model}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else if $copilotInfo.ai_models.length === 1}
|
||||
<div class="text-xs whitespace-nowrap overflow-hidden overflow-ellipsis">
|
||||
{$copilotInfo.ai_models[0]}
|
||||
</div>
|
||||
{/if}
|
||||
<Bot size={14} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-96 items-start">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Check, Loader2, Wand2 } from 'lucide-svelte'
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import { getNonStreamingCompletion, type AiProviderTypes } from './lib'
|
||||
import { getNonStreamingCompletion } from './lib'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import type { Flow, InputTransform } from '$lib/gen'
|
||||
import ManualPopover from '../ManualPopover.svelte'
|
||||
@@ -116,7 +116,7 @@ Only return the expression without any wrapper.`
|
||||
}
|
||||
],
|
||||
abortController,
|
||||
aiProvider as AiProviderTypes
|
||||
aiProvider
|
||||
)
|
||||
|
||||
if (
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import type { FlowEditorContext } from '../flows/types'
|
||||
import type { PickableProperties } from '../flows/previousResults'
|
||||
import { getContext } from 'svelte'
|
||||
import { getNonStreamingCompletion, type AiProviderTypes } from './lib'
|
||||
import { getNonStreamingCompletion } from './lib'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import type { FlowCopilotContext } from './flow'
|
||||
@@ -92,7 +92,7 @@ input_name2: expression2
|
||||
}
|
||||
],
|
||||
abortController,
|
||||
aiProvider as AiProviderTypes
|
||||
aiProvider
|
||||
)
|
||||
|
||||
parsedInputs = generatedContent.split('\n').map((x) => x.split(': '))
|
||||
|
||||
+16
-4
@@ -1,10 +1,13 @@
|
||||
<script lang="ts">
|
||||
import type { AIProvider } from '$lib/gen'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import { testKey, type AiProviderTypes } from './lib'
|
||||
import { testKey } from './lib'
|
||||
export let disabled = false
|
||||
export let apiKey: string | undefined = undefined
|
||||
export let aiProvider: AiProviderTypes
|
||||
export let resourcePath: string | undefined = undefined
|
||||
export let aiProvider: AIProvider
|
||||
export let model: string
|
||||
let loading = false
|
||||
</script>
|
||||
|
||||
@@ -21,8 +24,10 @@
|
||||
setTimeout(() => {
|
||||
abortController.abort()
|
||||
}, 10000)
|
||||
|
||||
await testKey({
|
||||
apiKey,
|
||||
resourcePath,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
@@ -30,7 +35,8 @@
|
||||
}
|
||||
],
|
||||
abortController,
|
||||
aiProvider
|
||||
aiProvider,
|
||||
model
|
||||
})
|
||||
sendUserToast('Valid key')
|
||||
} catch (err) {
|
||||
@@ -42,5 +48,11 @@
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}}>Test key</Button
|
||||
}}
|
||||
>
|
||||
{#if apiKey}
|
||||
Test key
|
||||
{:else}
|
||||
Test
|
||||
{/if}
|
||||
</Button>
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ChatCompletionMessageParam } from 'openai/resources/chat/index.mjs'
|
||||
import { getNonStreamingCompletion, type AiProviderTypes } from './lib'
|
||||
import type { AIProvider } from '$lib/gen'
|
||||
import { codeCompletionLoading } from '$lib/stores'
|
||||
import type { ChatCompletionMessageParam } from 'openai/resources/chat/index.mjs'
|
||||
import { getNonStreamingCompletion } from './lib'
|
||||
|
||||
const systemPrompt = `You are a code completion assistant, return the code that should go instead of the <completion_tokens>.
|
||||
|
||||
@@ -74,7 +75,7 @@ export async function editorCodeCompletion(
|
||||
after: string,
|
||||
lang: string,
|
||||
abortController: AbortController,
|
||||
aiProvider: AiProviderTypes
|
||||
aiProvider: AIProvider
|
||||
) {
|
||||
codeCompletionLoading.set(true)
|
||||
const messages: ChatCompletionMessageParam[] = [
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import {
|
||||
type FlowModule,
|
||||
ScriptService,
|
||||
type RawScript,
|
||||
type PathScript,
|
||||
type AIProvider,
|
||||
type FlowModule,
|
||||
type InputTransform,
|
||||
type PathScript,
|
||||
type RawScript,
|
||||
type Script
|
||||
} from '$lib/gen'
|
||||
import {
|
||||
addResourceTypes,
|
||||
deltaCodeCompletion,
|
||||
getNonStreamingCompletion,
|
||||
type AiProviderTypes
|
||||
} from './lib'
|
||||
import { scriptLangToEditorLang } from '$lib/scripts'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import type Editor from '../Editor.svelte'
|
||||
import type { Drawer } from '../common'
|
||||
import { scriptLangToEditorLang } from '$lib/scripts'
|
||||
import { addResourceTypes, deltaCodeCompletion, getNonStreamingCompletion } from './lib'
|
||||
|
||||
export type FlowCopilotModule = {
|
||||
id: string
|
||||
@@ -259,7 +255,7 @@ export async function stepCopilot(
|
||||
| undefined,
|
||||
isFirstInLoop: boolean,
|
||||
abortController: AbortController,
|
||||
aiProvider: AiProviderTypes
|
||||
aiProvider: AIProvider
|
||||
) {
|
||||
if (module.source !== 'custom') {
|
||||
throw new Error('Not a custom module')
|
||||
@@ -328,7 +324,7 @@ export async function glueCopilot(
|
||||
},
|
||||
isFirstInLoop: boolean,
|
||||
abortController: AbortController,
|
||||
aiProvider: AiProviderTypes
|
||||
aiProvider: AIProvider
|
||||
) {
|
||||
const { prevCode, prevLang } = await getPreviousStepContent(pastModule, workspace)
|
||||
|
||||
|
||||
@@ -1,56 +1,130 @@
|
||||
import { OpenAI } from 'openai'
|
||||
import { OpenAPI, ResourceService, type Script } from '../../gen'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import type { AIProvider } from '$lib/gen'
|
||||
import {
|
||||
copilotInfo,
|
||||
copilotSessionModel,
|
||||
type DBSchema,
|
||||
type GraphqlSchema,
|
||||
type SQLSchema
|
||||
} from '$lib/stores'
|
||||
import { Anthropic } from '@anthropic-ai/sdk'
|
||||
import type { DBSchema, GraphqlSchema, SQLSchema } from '$lib/stores'
|
||||
import { formatResourceTypes } from './utils'
|
||||
import { EDIT_CONFIG, FIX_CONFIG, GEN_CONFIG } from './prompts'
|
||||
import { Mistral } from '@mistralai/mistralai'
|
||||
import { buildClientSchema, printSchema } from 'graphql'
|
||||
import { OpenAI } from 'openai'
|
||||
import type {
|
||||
ChatCompletionCreateParamsStreaming,
|
||||
ChatCompletionMessageParam
|
||||
} from 'openai/resources/index.mjs'
|
||||
import { get, type Writable } from 'svelte/store'
|
||||
import { OpenAPI, ResourceService, type Script } from '../../gen'
|
||||
import { EDIT_CONFIG, FIX_CONFIG, GEN_CONFIG } from './prompts'
|
||||
import { formatResourceTypes } from './utils'
|
||||
|
||||
import type { MessageCreateParams, MessageParam } from '@anthropic-ai/sdk/resources/messages.mjs'
|
||||
import type { ChatCompletionRequest } from '@mistralai/mistralai/models/components/chatcompletionrequest'
|
||||
import type {
|
||||
SystemMessage,
|
||||
UserMessage,
|
||||
AssistantMessage,
|
||||
ToolMessage,
|
||||
CompletionEvent,
|
||||
ContentChunk
|
||||
ContentChunk,
|
||||
SystemMessage,
|
||||
ToolMessage,
|
||||
UserMessage
|
||||
} from '@mistralai/mistralai/models/components'
|
||||
import type { ChatCompletionRequest } from '@mistralai/mistralai/models/components/chatcompletionrequest'
|
||||
|
||||
export const SUPPORTED_LANGUAGES = new Set(Object.keys(GEN_CONFIG.prompts))
|
||||
|
||||
export type AiProviderTypes = 'openai' | 'anthropic' | 'mistral'
|
||||
|
||||
interface AiProvider {
|
||||
init: (workspace: string, updateClient: boolean, token?: string) => void
|
||||
export const AI_DEFAULT_MODELS: Record<AIProvider, string[]> = {
|
||||
openai: ['gpt-4o', 'gpt-4o-mini'],
|
||||
anthropic: ['claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest'],
|
||||
mistral: ['codestral-latest'],
|
||||
deepseek: ['deepseek-chat', 'deepseek-reasoner'],
|
||||
groq: ['llama-3.3-70b-versatile', 'llama-3.1-8b-instant'],
|
||||
openrouter: ['meta-llama/llama-3.2-3b-instruct:free'],
|
||||
customai: []
|
||||
}
|
||||
|
||||
class WorkspacedMistral implements AiProvider {
|
||||
private client: Mistral | undefined
|
||||
export const OPENAI_COMPATIBLE_BASE_URLS = {
|
||||
groq: 'https://api.groq.com/openai/v1',
|
||||
openrouter: 'https://openrouter.ai/api/v1',
|
||||
deepseek: 'https://api.deepseek.com/v1'
|
||||
} as const
|
||||
|
||||
init(workspace: string, updateClient: boolean, token?: string) {
|
||||
if (!this.client || updateClient) {
|
||||
this.client = initWorkspaceAiProvider(workspace, 'mistral', token) as unknown as Mistral
|
||||
}
|
||||
class WorkspacedAIClients {
|
||||
private openaiClient: OpenAI | undefined
|
||||
private anthropicClient: Anthropic | undefined
|
||||
private mistralClient: Mistral | undefined
|
||||
|
||||
init(workspace: string) {
|
||||
this.initOpenai(workspace)
|
||||
this.initAnthropic(workspace)
|
||||
this.initMistral(workspace)
|
||||
}
|
||||
|
||||
getClient() {
|
||||
if (!this.client) {
|
||||
throw new Error('AnthropicAi not initialized')
|
||||
private getBaseURL(workspace: string) {
|
||||
return `${location.origin}${OpenAPI.BASE}/w/${workspace}/ai/proxy`
|
||||
}
|
||||
|
||||
private initOpenai(workspace: string) {
|
||||
const baseURL = this.getBaseURL(workspace)
|
||||
this.openaiClient = new OpenAI({
|
||||
baseURL,
|
||||
apiKey: 'fake-key',
|
||||
defaultHeaders: {
|
||||
Authorization: '' // a non empty string will be unable to access Windmill backend proxy
|
||||
},
|
||||
dangerouslyAllowBrowser: true
|
||||
})
|
||||
}
|
||||
|
||||
private initAnthropic(workspace: string) {
|
||||
const baseURL = this.getBaseURL(workspace)
|
||||
this.anthropicClient = new Anthropic({
|
||||
baseURL,
|
||||
apiKey: 'fake-key',
|
||||
dangerouslyAllowBrowser: true
|
||||
})
|
||||
}
|
||||
|
||||
private initMistral(workspace: string) {
|
||||
const baseURL = this.getBaseURL(workspace)
|
||||
this.mistralClient = new Mistral({
|
||||
serverURL: baseURL
|
||||
})
|
||||
}
|
||||
|
||||
getOpenaiClient() {
|
||||
if (!this.openaiClient) {
|
||||
throw new Error('OpenAI not initialized')
|
||||
}
|
||||
return this.client
|
||||
return this.openaiClient
|
||||
}
|
||||
|
||||
getAnthropicClient() {
|
||||
if (!this.anthropicClient) {
|
||||
throw new Error('Anthropic not initialized')
|
||||
}
|
||||
return this.anthropicClient
|
||||
}
|
||||
|
||||
getMistralClient() {
|
||||
if (!this.mistralClient) {
|
||||
throw new Error('Mistral not initialized')
|
||||
}
|
||||
return this.mistralClient
|
||||
}
|
||||
}
|
||||
|
||||
export namespace MistralAi {
|
||||
export let workspace = new WorkspacedMistral()
|
||||
export const workspaceAIClients = new WorkspacedAIClients()
|
||||
|
||||
const DEFAULT_COMPLETION_CONFIG: ChatCompletionCreateParamsStreaming = {
|
||||
model: '',
|
||||
max_tokens: 8000, //TODO: make this dynamic
|
||||
temperature: 0,
|
||||
seed: 42,
|
||||
stream: true,
|
||||
messages: []
|
||||
}
|
||||
|
||||
namespace MistralAI {
|
||||
export const mistralConfig: ChatCompletionRequest = {
|
||||
temperature: 0,
|
||||
model: null,
|
||||
@@ -78,30 +152,11 @@ export namespace MistralAi {
|
||||
}
|
||||
}
|
||||
|
||||
class WorkspacedAnthropic implements AiProvider {
|
||||
private client: Anthropic | undefined
|
||||
|
||||
init(workspace: string, updateClient: boolean, token: string | undefined = undefined) {
|
||||
if (!this.client || updateClient) {
|
||||
this.client = initWorkspaceAiProvider(workspace, 'anthropic', token) as unknown as Anthropic
|
||||
}
|
||||
}
|
||||
|
||||
getClient() {
|
||||
if (!this.client) {
|
||||
throw new Error('AnthropicAi not initialized')
|
||||
}
|
||||
return this.client
|
||||
}
|
||||
}
|
||||
|
||||
export namespace AnthropicAi {
|
||||
export let workspace = new WorkspacedAnthropic()
|
||||
|
||||
export namespace AnthropicAI {
|
||||
export const config: MessageCreateParams = {
|
||||
temperature: 0,
|
||||
max_tokens: 8192,
|
||||
model: 'claude-3-5-sonnet-20241022',
|
||||
model: '',
|
||||
messages: []
|
||||
}
|
||||
|
||||
@@ -135,30 +190,11 @@ export namespace AnthropicAi {
|
||||
}
|
||||
}
|
||||
|
||||
class WorkspacedOpenai implements AiProvider {
|
||||
private client: OpenAI | undefined
|
||||
|
||||
init(workspace: string, updateClient: boolean, token: string | undefined = undefined) {
|
||||
if (!this.client || updateClient) {
|
||||
this.client = initWorkspaceAiProvider(workspace, 'openai', token) as unknown as OpenAI
|
||||
}
|
||||
}
|
||||
|
||||
getClient() {
|
||||
if (!this.client) {
|
||||
throw new Error('OpenAI not initialized')
|
||||
}
|
||||
return this.client
|
||||
}
|
||||
}
|
||||
|
||||
export namespace OpenAi {
|
||||
export let workspace = new WorkspacedOpenai()
|
||||
|
||||
namespace OpenAi {
|
||||
export const openaiConfig: ChatCompletionCreateParamsStreaming = {
|
||||
temperature: 0,
|
||||
max_tokens: 16384,
|
||||
model: 'gpt-4o-2024-08-06',
|
||||
model: '',
|
||||
seed: 42,
|
||||
stream: true,
|
||||
messages: []
|
||||
@@ -169,125 +205,38 @@ export namespace OpenAi {
|
||||
}
|
||||
}
|
||||
|
||||
export function initAllAiWorkspace(workspace: string, updateClient: boolean = false) {
|
||||
OpenAi.workspace.init(workspace, updateClient)
|
||||
AnthropicAi.workspace.init(workspace, updateClient)
|
||||
MistralAi.workspace.init(workspace, updateClient)
|
||||
}
|
||||
|
||||
function initWorkspaceAiProvider(
|
||||
workspace: string,
|
||||
aiProvider: AiProviderTypes,
|
||||
token: string | undefined = undefined
|
||||
): Anthropic | OpenAI | Mistral {
|
||||
const baseURL = `${location.origin}${OpenAPI.BASE}/w/${workspace}/ai/proxy`
|
||||
let client
|
||||
switch (aiProvider) {
|
||||
case 'openai': {
|
||||
client = new OpenAI({
|
||||
baseURL,
|
||||
apiKey: 'fake-key',
|
||||
defaultHeaders: {
|
||||
Authorization: token ? `Bearer ${token}` : ''
|
||||
},
|
||||
dangerouslyAllowBrowser: true
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'anthropic': {
|
||||
client = new Anthropic({
|
||||
baseURL,
|
||||
apiKey: 'fake-key',
|
||||
defaultHeaders: {
|
||||
Authorization: token ? `Bearer ${token}` : ''
|
||||
},
|
||||
dangerouslyAllowBrowser: true
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'mistral': {
|
||||
client = new Mistral({
|
||||
serverURL: baseURL
|
||||
})
|
||||
}
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
export async function testKey({
|
||||
apiKey,
|
||||
resourcePath,
|
||||
model,
|
||||
abortController,
|
||||
messages,
|
||||
aiProvider
|
||||
}: {
|
||||
apiKey?: string
|
||||
resourcePath?: string
|
||||
model: string | undefined
|
||||
messages: ChatCompletionMessageParam[]
|
||||
abortController: AbortController
|
||||
aiProvider: AiProviderTypes
|
||||
aiProvider: AIProvider
|
||||
}) {
|
||||
if (apiKey) {
|
||||
switch (aiProvider) {
|
||||
case 'openai': {
|
||||
const openai = new OpenAI({
|
||||
apiKey,
|
||||
dangerouslyAllowBrowser: true
|
||||
})
|
||||
await openai.chat.completions.create(
|
||||
{
|
||||
...OpenAi.openaiConfig,
|
||||
messages,
|
||||
stream: false
|
||||
},
|
||||
{
|
||||
signal: abortController.signal
|
||||
}
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'anthropic': {
|
||||
const anthropic = new Anthropic({
|
||||
apiKey,
|
||||
dangerouslyAllowBrowser: true
|
||||
})
|
||||
const [, anthropicMessages] = AnthropicAi.getSystemPromptAndArrayMessages(messages)
|
||||
await anthropic.messages.create(
|
||||
{
|
||||
...AnthropicAi.config,
|
||||
messages: anthropicMessages,
|
||||
stream: false
|
||||
},
|
||||
{
|
||||
signal: abortController.signal
|
||||
}
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'mistral': {
|
||||
const mistral = new Mistral({
|
||||
apiKey
|
||||
})
|
||||
await mistral.chat.complete(
|
||||
{
|
||||
...MistralAi.mistralConfig,
|
||||
model: 'codestral-latest',
|
||||
stream: false,
|
||||
messages: messages as MistralAi.MistralParamsMessage[]
|
||||
},
|
||||
{
|
||||
fetchOptions: {
|
||||
signal: abortController.signal,
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await getNonStreamingCompletion(messages, abortController, aiProvider, undefined, true)
|
||||
if (!apiKey && !resourcePath) {
|
||||
throw new Error('API key or resource path is required')
|
||||
}
|
||||
const modelToTest = model ?? AI_DEFAULT_MODELS[aiProvider][0]
|
||||
|
||||
if (!modelToTest) {
|
||||
throw new Error('Missing a model to test')
|
||||
}
|
||||
|
||||
await getNonStreamingCompletion(
|
||||
messages,
|
||||
abortController,
|
||||
aiProvider,
|
||||
apiKey,
|
||||
resourcePath,
|
||||
modelToTest
|
||||
)
|
||||
}
|
||||
|
||||
interface BaseOptions {
|
||||
@@ -410,7 +359,9 @@ function addDBSChema(scriptOptions: CopilotOptions, prompt: string) {
|
||||
const { dbSchema, language } = scriptOptions
|
||||
if (
|
||||
dbSchema &&
|
||||
['postgresql', 'mysql', 'snowflake', 'bigquery', 'mssql', 'graphql', 'oracledb'].includes(language) && // make sure we are using a SQL/query language
|
||||
['postgresql', 'mysql', 'snowflake', 'bigquery', 'mssql', 'graphql', 'oracledb'].includes(
|
||||
language
|
||||
) && // make sure we are using a SQL/query language
|
||||
language === dbSchema.lang // make sure we are using the same language as the schema
|
||||
) {
|
||||
let { stringified } = dbSchema
|
||||
@@ -467,66 +418,109 @@ const PROMPTS_CONFIGS = {
|
||||
export async function getNonStreamingCompletion(
|
||||
messages: ChatCompletionMessageParam[],
|
||||
abortController: AbortController,
|
||||
aiProvider: AiProviderTypes,
|
||||
model = OpenAi.openaiConfig.model,
|
||||
noCache?: boolean
|
||||
aiProvider: AIProvider,
|
||||
apiKey?: string, // testing API KEY directly from the frontend
|
||||
resourcePath?: string, // testing resource path passed as a header to the backend proxy
|
||||
forceModel?: string
|
||||
) {
|
||||
let response: string | undefined = ''
|
||||
const queryOptions = {
|
||||
query: {
|
||||
no_cache: noCache
|
||||
},
|
||||
let model = forceModel
|
||||
|
||||
if (!model) {
|
||||
model = get(copilotSessionModel)
|
||||
let info = get(copilotInfo)
|
||||
const { ai_models: aiModels } = info
|
||||
|
||||
if (!model || !aiModels.includes(model)) {
|
||||
console.warn('Invalid model, using default model:', aiModels[0])
|
||||
model = aiModels[0]
|
||||
}
|
||||
}
|
||||
|
||||
if (!model) {
|
||||
throw new Error('No model found')
|
||||
}
|
||||
|
||||
const fetchOptions: {
|
||||
signal: AbortSignal
|
||||
headers?: Record<string, string>
|
||||
} = {
|
||||
signal: abortController.signal
|
||||
}
|
||||
switch (aiProvider) {
|
||||
case 'openai': {
|
||||
const openaiClient = OpenAi.workspace.getClient()
|
||||
const completion = await openaiClient.chat.completions.create(
|
||||
{
|
||||
...OpenAi.openaiConfig,
|
||||
messages,
|
||||
stream: false,
|
||||
model
|
||||
},
|
||||
queryOptions
|
||||
)
|
||||
response = completion.choices[0]?.message.content || ''
|
||||
break
|
||||
if (resourcePath) {
|
||||
fetchOptions.headers = {
|
||||
'X-Resource-Path': resourcePath
|
||||
}
|
||||
}
|
||||
switch (aiProvider) {
|
||||
case 'anthropic': {
|
||||
const anthropicClient = AnthropicAi.workspace.getClient()
|
||||
const [system, anthropicMessages] = AnthropicAi.getSystemPromptAndArrayMessages(messages)
|
||||
const anthropicClient = apiKey
|
||||
? new Anthropic({
|
||||
apiKey,
|
||||
dangerouslyAllowBrowser: true
|
||||
})
|
||||
: workspaceAIClients.getAnthropicClient()
|
||||
const [system, anthropicMessages] = AnthropicAI.getSystemPromptAndArrayMessages(messages)
|
||||
const message = await anthropicClient.messages.create(
|
||||
{
|
||||
...AnthropicAi.config,
|
||||
...AnthropicAI.config,
|
||||
system,
|
||||
model,
|
||||
messages: anthropicMessages,
|
||||
stream: false
|
||||
},
|
||||
queryOptions
|
||||
fetchOptions
|
||||
)
|
||||
response = message.content[0].type === 'text' ? message.content[0].text : ''
|
||||
break
|
||||
}
|
||||
case 'mistral': {
|
||||
const mistralClient = MistralAi.workspace.getClient()
|
||||
const mistralClient = apiKey
|
||||
? new Mistral({
|
||||
apiKey
|
||||
})
|
||||
: workspaceAIClients.getMistralClient()
|
||||
const message = await mistralClient.chat.complete(
|
||||
{
|
||||
...MistralAi.mistralConfig,
|
||||
model: 'codestral-latest',
|
||||
...MistralAI.mistralConfig,
|
||||
model,
|
||||
stream: false,
|
||||
messages: messages as MistralAi.MistralParamsMessage[]
|
||||
messages: messages as MistralAI.MistralParamsMessage[]
|
||||
},
|
||||
{
|
||||
fetchOptions: {
|
||||
signal: abortController.signal,
|
||||
cache: 'no-store'
|
||||
}
|
||||
fetchOptions: fetchOptions
|
||||
}
|
||||
)
|
||||
response = MistralAi.retrieveTextValue(message.choices && message.choices[0].message.content)
|
||||
response = MistralAI.retrieveTextValue(message.choices && message.choices[0].message.content)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
if (aiProvider === 'customai' && apiKey) {
|
||||
throw new Error('Cannot test API key for Custom AI, only resource path is supported')
|
||||
}
|
||||
const baseURL = OPENAI_COMPATIBLE_BASE_URLS[aiProvider]
|
||||
|
||||
if (apiKey && aiProvider !== 'openai' && !baseURL) {
|
||||
throw new Error('No base URL for this provider: ' + aiProvider)
|
||||
}
|
||||
const openaiClient = apiKey
|
||||
? new OpenAI({
|
||||
apiKey,
|
||||
baseURL,
|
||||
dangerouslyAllowBrowser: true
|
||||
})
|
||||
: workspaceAIClients.getOpenaiClient()
|
||||
const completion = await openaiClient.chat.completions.create(
|
||||
{
|
||||
...(aiProvider === 'openai' ? OpenAi.openaiConfig : DEFAULT_COMPLETION_CONFIG),
|
||||
messages,
|
||||
model,
|
||||
stream: false
|
||||
},
|
||||
fetchOptions
|
||||
)
|
||||
response = completion.choices[0]?.message.content || ''
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
@@ -534,17 +528,30 @@ export async function getNonStreamingCompletion(
|
||||
export async function getCompletion(
|
||||
messages: ChatCompletionMessageParam[],
|
||||
abortController: AbortController,
|
||||
aiProvider: AiProviderTypes,
|
||||
model = OpenAi.openaiConfig.model
|
||||
aiProvider: AIProvider
|
||||
) {
|
||||
let model = get(copilotSessionModel)
|
||||
let info = get(copilotInfo)
|
||||
const { ai_models: aiModels } = info
|
||||
|
||||
if (!model || !aiModels.includes(model)) {
|
||||
console.warn('Invalid model, using default model:', aiModels[0])
|
||||
model = aiModels[0]
|
||||
}
|
||||
|
||||
if (!model) {
|
||||
throw new Error('No model found')
|
||||
}
|
||||
|
||||
switch (aiProvider) {
|
||||
case 'anthropic': {
|
||||
const anthropicClient = AnthropicAi.workspace.getClient()
|
||||
const [system, anthropicMessages] = AnthropicAi.getSystemPromptAndArrayMessages(messages)
|
||||
const anthropicClient = workspaceAIClients.getAnthropicClient()
|
||||
const [system, anthropicMessages] = AnthropicAI.getSystemPromptAndArrayMessages(messages)
|
||||
|
||||
const completion = await anthropicClient.messages.create(
|
||||
{
|
||||
...AnthropicAi.config,
|
||||
...AnthropicAI.config,
|
||||
model,
|
||||
system,
|
||||
messages: anthropicMessages,
|
||||
stream: true
|
||||
@@ -553,13 +560,29 @@ export async function getCompletion(
|
||||
)
|
||||
return completion
|
||||
}
|
||||
case 'openai': {
|
||||
const openaiClient = OpenAi.workspace.getClient()
|
||||
case 'mistral': {
|
||||
const mistralClient = workspaceAIClients.getMistralClient()
|
||||
const message = await mistralClient.chat.stream(
|
||||
{
|
||||
...MistralAI.mistralConfig,
|
||||
model,
|
||||
messages: messages as MistralAI.MistralParamsMessage[]
|
||||
},
|
||||
{
|
||||
fetchOptions: {
|
||||
signal: abortController.signal
|
||||
}
|
||||
}
|
||||
)
|
||||
return message
|
||||
}
|
||||
default: {
|
||||
const openaiClient = workspaceAIClients.getOpenaiClient()
|
||||
const completion = await openaiClient.chat.completions.create(
|
||||
{
|
||||
...OpenAi.openaiConfig,
|
||||
messages,
|
||||
model
|
||||
...(aiProvider === 'openai' ? OpenAi.openaiConfig : DEFAULT_COMPLETION_CONFIG),
|
||||
model,
|
||||
messages
|
||||
},
|
||||
{
|
||||
signal: abortController.signal
|
||||
@@ -567,23 +590,6 @@ export async function getCompletion(
|
||||
)
|
||||
return completion
|
||||
}
|
||||
case 'mistral': {
|
||||
const mistralClient = MistralAi.workspace.getClient()
|
||||
const message = await mistralClient.chat.stream(
|
||||
{
|
||||
...MistralAi.mistralConfig,
|
||||
model: 'codestral-latest',
|
||||
messages: messages as MistralAi.MistralParamsMessage[]
|
||||
},
|
||||
{
|
||||
fetchOptions: {
|
||||
signal: abortController.signal,
|
||||
cache: 'no-store'
|
||||
}
|
||||
}
|
||||
)
|
||||
return message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,21 +598,20 @@ export function getResponseFromEvent(
|
||||
| Anthropic.Messages.RawMessageStreamEvent
|
||||
| OpenAI.Chat.Completions.ChatCompletionChunk
|
||||
| CompletionEvent,
|
||||
aiProvider: AiProviderTypes
|
||||
aiProvider: AIProvider
|
||||
): string {
|
||||
switch (aiProvider) {
|
||||
case 'openai': {
|
||||
const messages = part as OpenAI.Chat.Completions.ChatCompletionChunk
|
||||
return OpenAi.retrieveTextValue(messages)
|
||||
}
|
||||
case 'anthropic': {
|
||||
const messages = part as Anthropic.Messages.RawMessageStreamEvent
|
||||
return AnthropicAi.retrieveTextValue(messages)
|
||||
return AnthropicAI.retrieveTextValue(messages)
|
||||
}
|
||||
case 'mistral': {
|
||||
const messages = part as CompletionEvent
|
||||
return MistralAi.retrieveTextValue(messages.data.choices[0].delta.content)
|
||||
return MistralAI.retrieveTextValue(messages.data.choices[0].delta.content)
|
||||
}
|
||||
default:
|
||||
const messages = part as OpenAI.Chat.Completions.ChatCompletionChunk
|
||||
return OpenAi.retrieveTextValue(messages)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,7 +619,7 @@ export async function copilot(
|
||||
scriptOptions: CopilotOptions,
|
||||
generatedCode: Writable<string>,
|
||||
abortController: AbortController,
|
||||
aiProvider: AiProviderTypes,
|
||||
aiProvider: AIProvider,
|
||||
generatedExplanation?: Writable<string>
|
||||
) {
|
||||
const { prompt, systemPrompt } = await getPrompts(scriptOptions)
|
||||
@@ -704,7 +709,7 @@ export async function deltaCodeCompletion(
|
||||
messages: ChatCompletionMessageParam[],
|
||||
generatedCodeDelta: Writable<string>,
|
||||
abortController: AbortController,
|
||||
aiProvider: AiProviderTypes
|
||||
aiProvider: AIProvider
|
||||
) {
|
||||
const completion = await getCompletion(messages, abortController, aiProvider)
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ export interface Setting {
|
||||
key: string
|
||||
// If value is not specified for first element, it will automatcally use undefined
|
||||
select_items?: {
|
||||
label: string,
|
||||
tooltip?: string,
|
||||
label: string
|
||||
tooltip?: string
|
||||
// If not specified, label will be used
|
||||
value?: any,
|
||||
}[],
|
||||
value?: any
|
||||
}[]
|
||||
fieldType:
|
||||
| 'text'
|
||||
| 'number'
|
||||
@@ -41,7 +41,7 @@ export interface Setting {
|
||||
isValid?: (value: any) => boolean
|
||||
error?: string
|
||||
defaultValue?: () => any
|
||||
codeAreaLang?: string,
|
||||
codeAreaLang?: string
|
||||
}
|
||||
|
||||
export type SettingStorage = 'setting'
|
||||
@@ -81,9 +81,9 @@ export const settings: Record<string, Setting[]> = {
|
||||
isValid: (value: string | undefined) =>
|
||||
value
|
||||
? value?.startsWith('http') &&
|
||||
value.includes('://') &&
|
||||
!value?.endsWith('/') &&
|
||||
!value?.endsWith(' ')
|
||||
value.includes('://') &&
|
||||
!value?.endsWith('/') &&
|
||||
!value?.endsWith(' ')
|
||||
: false
|
||||
},
|
||||
{
|
||||
@@ -182,7 +182,7 @@ export const settings: Record<string, Setting[]> = {
|
||||
{
|
||||
label: 'Azure OpenAI base path',
|
||||
description:
|
||||
'All Windmill AI features will run on the specified deployed model. Format: https://{your-resource-name}.openai.azure.com/openai/deployments/{deployment-id}. <a href="https://www.windmill.dev/docs/core_concepts/ai_generation#azure-openai-advanced-models">Learn more</a>',
|
||||
'All workspaces using an OpenAI resource for Windmill AI will run on the specified deployed model. Format: https://{your-resource-name}.openai.azure.com/openai/deployments/{deployment-id}. <a href="https://www.windmill.dev/docs/core_concepts/ai_generation#azure-openai-advanced-models">Learn more</a>',
|
||||
key: 'openai_azure_base_path',
|
||||
fieldType: 'text',
|
||||
storage: 'setting',
|
||||
@@ -236,24 +236,26 @@ export const settings: Record<string, Setting[]> = {
|
||||
// 2. Change LATEST_STABLE_PY in dockerfile
|
||||
// 3. Change #[default] annotation for PyVersion in backend
|
||||
placeholder: '3.10,3.11,3.12,3.13',
|
||||
select_items: [{
|
||||
label: "Latest Stable",
|
||||
value: "default",
|
||||
tooltip: "python-3.11",
|
||||
},
|
||||
{
|
||||
label: "3.10",
|
||||
},
|
||||
{
|
||||
label: "3.11",
|
||||
},
|
||||
{
|
||||
label: "3.12",
|
||||
},
|
||||
{
|
||||
label: "3.13",
|
||||
}],
|
||||
storage: 'setting',
|
||||
select_items: [
|
||||
{
|
||||
label: 'Latest Stable',
|
||||
value: 'default',
|
||||
tooltip: 'python-3.11'
|
||||
},
|
||||
{
|
||||
label: '3.10'
|
||||
},
|
||||
{
|
||||
label: '3.11'
|
||||
},
|
||||
{
|
||||
label: '3.12'
|
||||
},
|
||||
{
|
||||
label: '3.13'
|
||||
}
|
||||
],
|
||||
storage: 'setting'
|
||||
},
|
||||
{
|
||||
label: 'Pip index url',
|
||||
@@ -294,8 +296,7 @@ export const settings: Record<string, Setting[]> = {
|
||||
},
|
||||
{
|
||||
label: 'Nuget Config',
|
||||
description:
|
||||
'Write a nuget.config file to set custom package sources and credentials',
|
||||
description: 'Write a nuget.config file to set custom package sources and credentials',
|
||||
key: 'nuget_config',
|
||||
fieldType: 'codearea',
|
||||
codeAreaLang: 'xml',
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import MenuButton from './MenuButton.svelte'
|
||||
import { MenuItem } from '@rgossiaux/svelte-headlessui'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import { initAllAiWorkspace } from '../copilot/lib'
|
||||
import { workspaceAIClients } from '../copilot/lib'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let isCollapsed: boolean = false
|
||||
@@ -31,7 +31,7 @@
|
||||
if ($workspaceStore === id) {
|
||||
return
|
||||
}
|
||||
initAllAiWorkspace(id, true)
|
||||
workspaceAIClients.init(id)
|
||||
const editPages = [
|
||||
'/scripts/edit/',
|
||||
'/flows/edit/',
|
||||
@@ -82,8 +82,11 @@
|
||||
>
|
||||
<div class="flex items-center justify-between min-w-0 w-full">
|
||||
<div>
|
||||
<div class="text-primary pl-4 truncate text-left text-[1.2em]">{workspace.name}</div>
|
||||
<div class="text-tertiary font-mono pl-4 text-2xs whitespace-nowrap truncate text-left">
|
||||
<div class="text-primary pl-4 truncate text-left text-[1.2em]">{workspace.name}</div
|
||||
>
|
||||
<div
|
||||
class="text-tertiary font-mono pl-4 text-2xs whitespace-nowrap truncate text-left"
|
||||
>
|
||||
{workspace.id}
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,7 +94,7 @@
|
||||
<div
|
||||
class="w-5 h-5 mr-2 rounded border border-gray-300 dark:border-gray-600"
|
||||
style="background-color: {workspace.color}"
|
||||
></div>
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { BROWSER } from 'esm-env'
|
||||
import { derived, type Readable, writable } from 'svelte/store'
|
||||
|
||||
import type { IntrospectionQuery } from 'graphql'
|
||||
import {
|
||||
type WorkspaceDefaultScripts,
|
||||
type AIProvider,
|
||||
type OperatorSettings,
|
||||
type TokenResponse,
|
||||
type UserWorkspaceList,
|
||||
type OperatorSettings
|
||||
type WorkspaceDefaultScripts
|
||||
} from './gen'
|
||||
import type { IntrospectionQuery } from 'graphql'
|
||||
import { getLocalSetting } from './utils'
|
||||
|
||||
export interface UserExt {
|
||||
@@ -82,13 +83,14 @@ export const userWorkspaces: Readable<
|
||||
}
|
||||
})
|
||||
export const copilotInfo = writable<{
|
||||
ai_provider: string
|
||||
ai_provider: AIProvider
|
||||
exists_ai_resource: boolean
|
||||
code_completion_enabled: boolean
|
||||
code_completion_model?: string
|
||||
ai_models: string[]
|
||||
}>({
|
||||
ai_provider: '',
|
||||
ai_provider: 'openai',
|
||||
exists_ai_resource: false,
|
||||
code_completion_enabled: false
|
||||
ai_models: []
|
||||
})
|
||||
export const codeCompletionLoading = writable<boolean>(false)
|
||||
export const metadataCompletionEnabled = writable<boolean>(true)
|
||||
@@ -103,6 +105,9 @@ export const vimMode = writable<boolean>(getLocalSetting(VIM_MODE_SETTING_NAME)
|
||||
export const codeCompletionSessionEnabled = writable<boolean>(
|
||||
getLocalSetting(CODE_COMPLETION_SETTING_NAME) != 'false'
|
||||
)
|
||||
export const copilotSessionModel = writable<string | undefined>(
|
||||
getLocalSetting(CODE_COMPLETION_SETTING_NAME) ?? undefined
|
||||
)
|
||||
export const usedTriggerKinds = writable<string[]>([])
|
||||
|
||||
type SQLBaseSchema = {
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
import { syncTutorialsTodos } from '$lib/tutorialUtils'
|
||||
import { ArrowLeft, Search } from 'lucide-svelte'
|
||||
import { getUserExt } from '$lib/user'
|
||||
import { initAllAiWorkspace } from '$lib/components/copilot/lib'
|
||||
import { workspaceAIClients } from '$lib/components/copilot/lib'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import OperatorMenu from '$lib/components/sidebar/OperatorMenu.svelte'
|
||||
import GlobalSearchModal from '$lib/components/search/GlobalSearchModal.svelte'
|
||||
@@ -192,11 +192,9 @@
|
||||
async function loadUsedTriggerKinds() {
|
||||
let usedKinds: string[] = []
|
||||
const { http_routes_used, websocket_used, kafka_used, postgres_used, nats_used } =
|
||||
await WorkspaceService.getUsedTriggers(
|
||||
{
|
||||
workspace: $workspaceStore ?? ''
|
||||
}
|
||||
)
|
||||
await WorkspaceService.getUsedTriggers({
|
||||
workspace: $workspaceStore ?? ''
|
||||
})
|
||||
if (http_routes_used) {
|
||||
usedKinds.push('http')
|
||||
}
|
||||
@@ -242,15 +240,19 @@
|
||||
let devOnly = $page.url.pathname.startsWith(base + '/scripts/dev')
|
||||
|
||||
async function loadCopilot(workspace: string) {
|
||||
initAllAiWorkspace(workspace)
|
||||
workspaceAIClients.init(workspace)
|
||||
try {
|
||||
copilotInfo.set(await WorkspaceService.getCopilotInfo({ workspace }))
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
const info = await WorkspaceService.getCopilotInfo({ workspace })
|
||||
copilotInfo.set({
|
||||
ai_provider: '',
|
||||
...info,
|
||||
ai_provider: info.ai_provider ?? 'openai'
|
||||
})
|
||||
} catch (err) {
|
||||
copilotInfo.set({
|
||||
ai_provider: 'openai',
|
||||
exists_ai_resource: false,
|
||||
code_completion_enabled: false
|
||||
code_completion_model: undefined,
|
||||
ai_models: []
|
||||
})
|
||||
console.error('Could not get copilot info')
|
||||
}
|
||||
@@ -294,7 +296,13 @@
|
||||
setContext('openSearchWithPrefilledText', openSearchModal)
|
||||
|
||||
$: {
|
||||
if ($enterpriseLicense && $workspaceStore && $userStore && $devopsRole !== undefined && ($devopsRole || $userStore.is_admin)) {
|
||||
if (
|
||||
$enterpriseLicense &&
|
||||
$workspaceStore &&
|
||||
$userStore &&
|
||||
$devopsRole !== undefined &&
|
||||
($devopsRole || $userStore.is_admin)
|
||||
) {
|
||||
mountModal = true
|
||||
loadCriticalAlertsMuted()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
SettingService,
|
||||
UserService,
|
||||
VariableService,
|
||||
WorkspaceService
|
||||
WorkspaceService,
|
||||
type AIProvider
|
||||
} from '$lib/gen'
|
||||
import { validateUsername } from '$lib/utils'
|
||||
import { logoutWithRedirect } from '$lib/logout'
|
||||
@@ -18,12 +19,12 @@
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import TestAiKey from '$lib/components/copilot/TestAiKey.svelte'
|
||||
import TestAIKey from '$lib/components/copilot/TestAIKey.svelte'
|
||||
import { switchWorkspace } from '$lib/storeUtils'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
|
||||
import type { AiProviderTypes } from '$lib/components/copilot/lib'
|
||||
import { AI_DEFAULT_MODELS } from '$lib/components/copilot/lib'
|
||||
|
||||
const rd = $page.url.searchParams.get('rd')
|
||||
|
||||
@@ -41,8 +42,12 @@
|
||||
let colorEnabled = false
|
||||
|
||||
function generateRandomColor() {
|
||||
const randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
|
||||
workspaceColor = randomColor;
|
||||
const randomColor =
|
||||
'#' +
|
||||
Math.floor(Math.random() * 16777215)
|
||||
.toString(16)
|
||||
.padStart(6, '0')
|
||||
workspaceColor = randomColor
|
||||
}
|
||||
|
||||
$: id = name.toLowerCase().replace(/\s/gi, '-')
|
||||
@@ -111,7 +116,8 @@
|
||||
workspace: id,
|
||||
requestBody: {
|
||||
ai_resource: { path, provider: selected },
|
||||
code_completion_enabled: codeCompletionEnabled
|
||||
ai_models: aiKey ? AI_DEFAULT_MODELS[selected].slice(0, 1) : [],
|
||||
code_completion_model: codeCompletionEnabled ? AI_DEFAULT_MODELS[selected][0] : undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -178,7 +184,7 @@
|
||||
|
||||
let auto_invite = false
|
||||
let operatorOnly = false
|
||||
let selected: AiProviderTypes = 'openai'
|
||||
let selected: Exclude<AIProvider, 'customai'> = 'openai'
|
||||
</script>
|
||||
|
||||
<CenteredModal title="New Workspace">
|
||||
@@ -198,11 +204,23 @@
|
||||
</label>
|
||||
<label class="block pb-4">
|
||||
<span class="text-secondary text-sm">Workspace color</span>
|
||||
<span class="ml-5 text-tertiary text-xs">Color to identify the current workspace in the list of workspaces</span>
|
||||
<span class="ml-5 text-tertiary text-xs"
|
||||
>Color to identify the current workspace in the list of workspaces</span
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Toggle bind:checked={colorEnabled} options={{ right: 'Enable' }} />
|
||||
{#if colorEnabled}<input class="w-10" type="color" bind:value={workspaceColor} disabled={!colorEnabled} />{/if}
|
||||
<input type="text" class="w-24 text-sm" bind:value={workspaceColor} disabled={!colorEnabled} />
|
||||
{#if colorEnabled}<input
|
||||
class="w-10"
|
||||
type="color"
|
||||
bind:value={workspaceColor}
|
||||
disabled={!colorEnabled}
|
||||
/>{/if}
|
||||
<input
|
||||
type="text"
|
||||
class="w-24 text-sm"
|
||||
bind:value={workspaceColor}
|
||||
disabled={!colorEnabled}
|
||||
/>
|
||||
<Button on:click={generateRandomColor} size="xs" disabled={!colorEnabled}>Random</Button>
|
||||
</div>
|
||||
</label>
|
||||
@@ -230,9 +248,12 @@
|
||||
</span>
|
||||
<div class="pb-2">
|
||||
<ToggleButtonGroup bind:selected>
|
||||
<ToggleButton value="openai" label="OpenAi" />
|
||||
<ToggleButton value="openai" label="OpenAI" />
|
||||
<ToggleButton value="anthropic" label="Anthropic" />
|
||||
<ToggleButton value="mistral" label="Mistral" />
|
||||
<ToggleButton value="deepseek" label="DeepSeek" />
|
||||
<ToggleButton value="groq" label="Groq" />
|
||||
<ToggleButton value="openrouter" label="OpenRouter" />
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
@@ -243,7 +264,12 @@
|
||||
bind:value={aiKey}
|
||||
on:keyup={handleKeyUp}
|
||||
/>
|
||||
<TestAiKey apiKey={aiKey} disabled={!aiKey} aiProvider={selected} />
|
||||
<TestAIKey
|
||||
apiKey={aiKey}
|
||||
disabled={!aiKey}
|
||||
aiProvider={selected}
|
||||
model={AI_DEFAULT_MODELS[selected][0]}
|
||||
/>
|
||||
</div>
|
||||
{#if aiKey}
|
||||
<Toggle
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { page } from '$app/stores'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { Alert, Badge, Button, Tab, Tabs } from '$lib/components/common'
|
||||
import { Alert, Badge, Button, Skeleton, Tab, Tabs } from '$lib/components/common'
|
||||
|
||||
import DeployToSetting from '$lib/components/DeployToSetting.svelte'
|
||||
import ErrorOrRecoveryHandler from '$lib/components/ErrorOrRecoveryHandler.svelte'
|
||||
@@ -20,7 +20,8 @@
|
||||
WorkspaceService,
|
||||
JobService,
|
||||
ResourceService,
|
||||
SettingService
|
||||
SettingService,
|
||||
type AIProvider
|
||||
} from '$lib/gen'
|
||||
import {
|
||||
enterpriseLicense,
|
||||
@@ -50,7 +51,7 @@
|
||||
|
||||
import PremiumInfo from '$lib/components/settings/PremiumInfo.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import TestAiKey from '$lib/components/copilot/TestAiKey.svelte'
|
||||
import TestAIKey from '$lib/components/copilot/TestAIKey.svelte'
|
||||
import Portal from '$lib/components/Portal.svelte'
|
||||
|
||||
import { fade } from 'svelte/transition'
|
||||
@@ -66,8 +67,11 @@
|
||||
import { hubPaths } from '$lib/hub'
|
||||
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
|
||||
import type { AiProviderTypes } from '$lib/components/copilot/lib'
|
||||
import { AI_DEFAULT_MODELS } from '$lib/components/copilot/lib'
|
||||
import Description from '$lib/components/Description.svelte'
|
||||
import MultiSelect from 'svelte-multiselect'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
import ArgEnum from '$lib/components/ArgEnum.svelte'
|
||||
|
||||
type GitSyncTypeMap = {
|
||||
scripts: boolean
|
||||
@@ -113,8 +117,11 @@
|
||||
let errorHandlerMutedOnCancel: boolean | undefined = undefined
|
||||
let criticalAlertUIMuted: boolean | undefined = undefined
|
||||
let initialCriticalAlertUIMuted: boolean | undefined = undefined
|
||||
let aiResourceInitialPath: string | undefined = undefined
|
||||
let aiResourceInitialProvider: string | undefined = undefined
|
||||
|
||||
let aiResourcePath: string | undefined = undefined
|
||||
let aiProvider: AIProvider = 'openai'
|
||||
let aiModels: string[] = []
|
||||
let codeCompletionModel: string | undefined = undefined
|
||||
|
||||
let s3ResourceSettings: S3ResourceSettings = {
|
||||
resourceType: 's3',
|
||||
@@ -142,8 +149,6 @@
|
||||
let editedWorkspaceEncryptionKey: string | undefined = undefined
|
||||
let workspaceReencryptionInProgress: boolean = false
|
||||
let encryptionKeyRegex = /^[a-zA-Z0-9]{64}$/
|
||||
let codeCompletionEnabled: boolean = false
|
||||
let selected: AiProviderTypes = 'openai'
|
||||
let tab =
|
||||
($page.url.searchParams.get('tab') as
|
||||
| 'users'
|
||||
@@ -191,10 +196,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function editCopilotConfig(aiResourcePath: string, aiProvider: string): Promise<void> {
|
||||
// in JS, an empty string is also falsy
|
||||
aiResourceInitialPath = aiResourcePath
|
||||
aiResourceInitialProvider = aiProvider
|
||||
async function editCopilotConfig(): Promise<void> {
|
||||
if (aiResourcePath) {
|
||||
await WorkspaceService.editCopilotConfig({
|
||||
workspace: $workspaceStore!,
|
||||
@@ -203,26 +205,30 @@
|
||||
path: aiResourcePath,
|
||||
provider: aiProvider
|
||||
},
|
||||
code_completion_enabled: codeCompletionEnabled
|
||||
code_completion_model: codeCompletionModel,
|
||||
ai_models: aiModels
|
||||
}
|
||||
})
|
||||
copilotInfo.set({
|
||||
ai_provider: aiProvider,
|
||||
exists_ai_resource: true,
|
||||
code_completion_enabled: codeCompletionEnabled
|
||||
code_completion_model: codeCompletionModel,
|
||||
ai_models: aiModels
|
||||
})
|
||||
} else {
|
||||
await WorkspaceService.editCopilotConfig({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
ai_resource: undefined,
|
||||
code_completion_enabled: codeCompletionEnabled
|
||||
code_completion_model: codeCompletionModel,
|
||||
ai_models: []
|
||||
}
|
||||
})
|
||||
copilotInfo.set({
|
||||
ai_provider: '',
|
||||
ai_provider: 'openai',
|
||||
exists_ai_resource: false,
|
||||
code_completion_enabled: codeCompletionEnabled
|
||||
code_completion_model: codeCompletionModel,
|
||||
ai_models: []
|
||||
})
|
||||
}
|
||||
sendUserToast(`Copilot settings updated`)
|
||||
@@ -382,6 +388,7 @@
|
||||
}, 1000 - (timeEnd - timeStart))
|
||||
}
|
||||
|
||||
let loadedSettings = false
|
||||
async function loadSettings(): Promise<void> {
|
||||
const settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! })
|
||||
team_name = settings.slack_name
|
||||
@@ -395,9 +402,12 @@
|
||||
customer_id = settings.customer_id
|
||||
workspaceToDeployTo = settings.deploy_to
|
||||
webhook = settings.webhook
|
||||
aiResourceInitialPath = settings.ai_resource?.path
|
||||
aiResourceInitialProvider = settings.ai_resource?.provider
|
||||
selected = (aiResourceInitialProvider as AiProviderTypes) ?? 'openai'
|
||||
|
||||
aiResourcePath = settings.ai_resource?.path
|
||||
aiProvider = settings.ai_resource?.provider ?? 'openai'
|
||||
codeCompletionModel = settings.code_completion_model
|
||||
aiModels = settings.ai_models
|
||||
|
||||
errorHandlerItemKind = settings.error_handler?.split('/')[0] as 'flow' | 'script'
|
||||
errorHandlerScriptPath = (settings.error_handler ?? '').split('/').slice(1).join('/')
|
||||
errorHandlerInitialScriptPath = errorHandlerScriptPath
|
||||
@@ -415,7 +425,6 @@
|
||||
: 'custom'
|
||||
}
|
||||
errorHandlerExtraArgs = settings.error_handler_extra_args ?? {}
|
||||
codeCompletionEnabled = settings.code_completion_enabled
|
||||
workspaceDefaultAppPath = settings.default_app
|
||||
|
||||
s3ResourceSettings = convertBackendSettingsToFrontendSettings(settings.large_file_storage)
|
||||
@@ -508,6 +517,8 @@
|
||||
workspace: $workspaceStore!,
|
||||
path: 'openai_client_credentials_oauth'
|
||||
})
|
||||
|
||||
loadedSettings = true
|
||||
}
|
||||
|
||||
let deployUiSettings: {
|
||||
@@ -698,7 +709,9 @@
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
{#if tab == 'users'}
|
||||
{#if !loadedSettings}
|
||||
<Skeleton layout={[1, [40]]} />
|
||||
{:else if tab == 'users'}
|
||||
<WorkspaceUserSettings />
|
||||
{:else if tab == 'deploy_to'}
|
||||
<div class="flex flex-col gap-4 my-8">
|
||||
@@ -1044,43 +1057,104 @@
|
||||
</Description>
|
||||
</div>
|
||||
</div>
|
||||
<ToggleButtonGroup
|
||||
bind:selected
|
||||
on:selected={() => {
|
||||
aiResourceInitialPath = ''
|
||||
aiResourceInitialProvider = ''
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="openai" label="OpenAI" />
|
||||
<ToggleButton value="anthropic" label="Anthropic" />
|
||||
<ToggleButton value="mistral" label="Mistral" />
|
||||
</ToggleButtonGroup>
|
||||
<div class="mt-5 flex gap-1">
|
||||
{#key [aiResourceInitialPath, aiResourceInitialProvider, usingOpenaiClientCredentialsOauth, selected]}
|
||||
<ResourcePicker
|
||||
resourceType={usingOpenaiClientCredentialsOauth
|
||||
? 'openai_client_credentials_oauth'
|
||||
: selected}
|
||||
initialValue={aiResourceInitialPath}
|
||||
on:change={(ev) => {
|
||||
editCopilotConfig(ev.detail, selected)
|
||||
}}
|
||||
/>
|
||||
<TestAiKey
|
||||
disabled={!aiResourceInitialPath || aiResourceInitialProvider != selected}
|
||||
aiProvider={selected}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<Toggle
|
||||
class="mr-2"
|
||||
bind:checked={codeCompletionEnabled}
|
||||
options={{ right: 'Enable code completion' }}
|
||||
on:change={() => {
|
||||
editCopilotConfig(aiResourceInitialPath || '', aiResourceInitialProvider || '')
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<ToggleButtonGroup
|
||||
bind:selected={aiProvider}
|
||||
on:selected={() => {
|
||||
aiResourcePath = ''
|
||||
aiModels = []
|
||||
codeCompletionModel = undefined
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<ToggleButton value="openai" label="OpenAI" />
|
||||
<ToggleButton value="anthropic" label="Anthropic" />
|
||||
<ToggleButton value="mistral" label="Mistral" />
|
||||
<ToggleButton value="deepseek" label="DeepSeek" />
|
||||
<ToggleButton value="groq" label="Groq" />
|
||||
<ToggleButton value="openrouter" label="OpenRouter" />
|
||||
<ToggleButton
|
||||
value="customai"
|
||||
label={'Custom AI' + ($enterpriseLicense ? '' : ' (EE)')}
|
||||
disabled={!$enterpriseLicense}
|
||||
tooltip="Configure a custom AI provider that is OpenAI API compatible"
|
||||
showTooltipIcon
|
||||
/>
|
||||
</ToggleButtonGroup>
|
||||
<div class="flex gap-1">
|
||||
{#key aiProvider}
|
||||
<ResourcePicker
|
||||
resourceType={usingOpenaiClientCredentialsOauth
|
||||
? 'openai_client_credentials_oauth'
|
||||
: aiProvider}
|
||||
initialValue={aiResourcePath}
|
||||
bind:value={aiResourcePath}
|
||||
on:change={() => {
|
||||
if (aiResourcePath && aiModels.length === 0) {
|
||||
if (aiProvider !== 'customai') {
|
||||
aiModels = AI_DEFAULT_MODELS[aiProvider].slice(0, 1)
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<TestAIKey
|
||||
disabled={!aiResourcePath || (aiProvider === 'customai' && aiModels.length === 0)}
|
||||
resourcePath={aiResourcePath}
|
||||
{aiProvider}
|
||||
model={aiProvider === 'customai' ? aiModels[0] : AI_DEFAULT_MODELS[aiProvider][0]}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
{#if aiResourcePath}
|
||||
<Label label="Enabled models">
|
||||
<MultiSelect
|
||||
options={AI_DEFAULT_MODELS[aiProvider]}
|
||||
ulOptionsClass={'!bg-surface-secondary'}
|
||||
allowUserOptions="append"
|
||||
bind:selected={aiModels}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Toggle
|
||||
on:change={() => {
|
||||
if (codeCompletionModel != undefined) {
|
||||
codeCompletionModel = undefined
|
||||
} else {
|
||||
codeCompletionModel = AI_DEFAULT_MODELS[aiProvider][0] ?? ''
|
||||
}
|
||||
}}
|
||||
checked={codeCompletionModel != undefined}
|
||||
options={{
|
||||
right: 'Code completion'
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if codeCompletionModel != undefined}
|
||||
<Label label="Code completion model">
|
||||
<ArgEnum
|
||||
enum_={AI_DEFAULT_MODELS[aiProvider]}
|
||||
bind:value={codeCompletionModel}
|
||||
disabled={false}
|
||||
autofocus={false}
|
||||
defaultValue={undefined}
|
||||
valid={true}
|
||||
create={true}
|
||||
required={false}
|
||||
/>
|
||||
</Label>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<Button
|
||||
disabled={(aiResourcePath && aiModels.length === 0) ||
|
||||
(codeCompletionModel != undefined && codeCompletionModel.length === 0)}
|
||||
size="sm"
|
||||
on:click={editCopilotConfig}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
{:else if tab == 'windmill_lfs'}
|
||||
<div class="flex flex-col gap-4 my-8">
|
||||
|
||||
Reference in New Issue
Block a user