langflow/src/frontend/src/utils/utils.ts
2023-08-08 20:46:18 -03:00

412 lines
11 KiB
TypeScript

import clsx, { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { ADJECTIVES, DESCRIPTIONS, NOUNS } from "../flow_constants";
import { IVarHighlightType } from "../types/components";
import { FlowType, NodeType } from "../types/flow";
import { TabsState } from "../types/tabs";
import { buildTweaks } from "./reactflowUtils";
export function classNames(...classes: Array<string>) {
return classes.filter(Boolean).join(" ");
}
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function toNormalCase(str: string) {
let result = str
.split("_")
.map((word, index) => {
if (index === 0) {
return word[0].toUpperCase() + word.slice(1).toLowerCase();
}
return word.toLowerCase();
})
.join(" ");
return result
.split("-")
.map((word, index) => {
if (index === 0) {
return word[0].toUpperCase() + word.slice(1).toLowerCase();
}
return word.toLowerCase();
})
.join(" ");
}
export function normalCaseToSnakeCase(str: string) {
return str
.split(" ")
.map((word, index) => {
if (index === 0) {
return word[0].toUpperCase() + word.slice(1).toLowerCase();
}
return word.toLowerCase();
})
.join("_");
}
export function toTitleCase(str: string) {
let result = str
.split("_")
.map((word, index) => {
if (index === 0) {
return checkUpperWords(
word[0].toUpperCase() + word.slice(1).toLowerCase()
);
}
return checkUpperWords(word.toLowerCase());
})
.join(" ");
return result
.split("-")
.map((word, index) => {
if (index === 0) {
return checkUpperWords(
word[0].toUpperCase() + word.slice(1).toLowerCase()
);
}
return checkUpperWords(word.toLowerCase());
})
.join(" ");
}
export const upperCaseWords: string[] = ["llm", "uri"];
export function checkUpperWords(str: string) {
const words = str.split(" ").map((word) => {
return upperCaseWords.includes(word.toLowerCase())
? word.toUpperCase()
: word[0].toUpperCase() + word.slice(1).toLowerCase();
});
return words.join(" ");
}
export const isWrappedWithClass = (event: any, className: string | undefined) =>
event.target.closest(`.${className}`);
export function groupByFamily(data, baseClasses, left, flow?: NodeType[]) {
const baseClassesSet = new Set(baseClasses.split("\n"));
let arrOfPossibleInputs = [];
let arrOfPossibleOutputs = [];
let checkedNodes = new Map();
const excludeTypes = new Set([
"str",
"bool",
"float",
"code",
"prompt",
"file",
"int",
]);
const checkBaseClass = (template: any) =>
template.type &&
template.show &&
((!excludeTypes.has(template.type) && baseClassesSet.has(template.type)) ||
(template.input_types &&
template.input_types.some((inputType) =>
baseClassesSet.has(inputType)
)));
if (flow) {
for (const node of flow) {
const nodeData = node.data;
const foundNode = checkedNodes.get(nodeData.type);
checkedNodes.set(nodeData.type, {
hasBaseClassInTemplate:
foundNode?.hasBaseClassInTemplate ||
Object.values(nodeData.node.template).some(checkBaseClass),
hasBaseClassInBaseClasses:
foundNode?.hasBaseClassInBaseClasses ||
nodeData.node.base_classes.some((baseClass) =>
baseClassesSet.has(baseClass)
),
});
}
}
for (const [d, nodes] of Object.entries(data)) {
let tempInputs = [],
tempOutputs = [];
for (const [n, node] of Object.entries(nodes)) {
let foundNode = checkedNodes.get(n);
if (!foundNode) {
foundNode = {
hasBaseClassInTemplate: Object.values(node.template).some(
checkBaseClass
),
hasBaseClassInBaseClasses: node.base_classes.some((baseClass) =>
baseClassesSet.has(baseClass)
),
};
checkedNodes.set(n, foundNode);
}
if (foundNode.hasBaseClassInTemplate) tempInputs.push(n);
if (foundNode.hasBaseClassInBaseClasses) tempOutputs.push(n);
}
const totalNodes = Object.keys(nodes).length;
if (tempInputs.length)
arrOfPossibleInputs.push({
category: d,
nodes: tempInputs,
full: tempInputs.length === totalNodes,
});
if (tempOutputs.length)
arrOfPossibleOutputs.push({
category: d,
nodes: tempOutputs,
full: tempOutputs.length === totalNodes,
});
}
return left
? arrOfPossibleOutputs.map((output) => ({
family: output.category,
type: output.full ? "" : output.nodes.join(", "),
}))
: arrOfPossibleInputs.map((input) => ({
family: input.category,
type: input.full ? "" : input.nodes.join(", "),
}));
}
export function buildInputs(tabsState, id) {
return tabsState &&
tabsState[id] &&
tabsState[id].formKeysData &&
tabsState[id].formKeysData.input_keys &&
Object.keys(tabsState[id].formKeysData.input_keys).length > 0
? JSON.stringify(tabsState[id].formKeysData.input_keys)
: '{"input": "message"}';
}
export function getRandomElement<T>(array: T[]): T {
return array[Math.floor(Math.random() * array.length)];
}
export function getRandomDescription(): string {
return getRandomElement(DESCRIPTIONS);
}
export function getRandomName(
retry: number = 0,
noSpace: boolean = false,
maxRetries: number = 3
): string {
const left: string[] = ADJECTIVES;
const right: string[] = NOUNS;
const lv = getRandomElement(left);
const rv = getRandomElement(right);
// Condition to avoid "boring wozniak"
if (lv === "boring" && rv === "wozniak") {
if (retry < maxRetries) {
return getRandomName(retry + 1, noSpace, maxRetries);
} else {
console.warn("Max retries reached, returning as is");
}
}
// Append a suffix if retrying and noSpace is true
if (retry > 0 && noSpace) {
const retrySuffix = Math.floor(Math.random() * 10);
return `${lv}_${rv}${retrySuffix}`;
}
// Construct the final name
let final_name = noSpace ? `${lv}_${rv}` : `${lv} ${rv}`;
// Return title case final name
return toTitleCase(final_name);
}
export function getRandomKeyByssmm(): string {
const now = new Date();
const seconds = String(now.getSeconds()).padStart(2, "0");
const milliseconds = String(now.getMilliseconds()).padStart(3, "0");
return seconds + milliseconds + Math.abs(Math.floor(Math.random() * 10001));
}
export function varHighlightHTML({ name }: IVarHighlightType): string {
const html = `<span class="font-semibold chat-message-highlight">{${name}}</span>`;
return html;
}
export function buildTweakObject(tweak) {
tweak.forEach((el) => {
Object.keys(el).forEach((key) => {
for (let kp in el[key]) {
try {
el[key][kp] = JSON.parse(el[key][kp]);
} catch {}
}
});
});
const tweakString = JSON.stringify(tweak.at(-1), null, 2);
return tweakString;
}
/**
* Function to get Chat Input Field
* @param {FlowType} flow - The current flow.
* @param {TabsState} tabsState - The current tabs state.
* @returns {string} - The chat input field
*/
export function getChatInputField(flow: FlowType, tabsState?: TabsState) {
let chat_input_field = "text";
if (
tabsState[flow.id] &&
tabsState[flow.id].formKeysData &&
tabsState[flow.id].formKeysData.input_keys
) {
chat_input_field = Object.keys(
tabsState[flow.id].formKeysData.input_keys
)[0];
}
return chat_input_field;
}
/**
* Function to get the python code for the API
* @param {string} flowId - The id of the flow
* @returns {string} - The python code
*/
export function getPythonApiCode(
flow: FlowType,
tweak?: any[],
tabsState?: TabsState
): string {
const flowId = flow.id;
// create a dictionary of node ids and the values is an empty dictionary
// flow.data.nodes.forEach((node) => {
// node.data.id
// }
const tweaks = buildTweaks(flow);
const inputs = buildInputs(tabsState, flow.id);
return `import requests
from typing import Optional
BASE_API_URL = "${window.location.protocol}//${
window.location.host
}/api/v1/process"
FLOW_ID = "${flowId}"
# You can tweak the flow by adding a tweaks dictionary
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
TWEAKS = ${
tweak && tweak.length > 0
? buildTweakObject(tweak)
: JSON.stringify(tweaks, null, 2)
}
def run_flow(inputs: dict, flow_id: str, tweaks: Optional[dict] = None) -> dict:
"""
Run a flow with a given message and optional tweaks.
:param message: The message to send to the flow
:param flow_id: The ID of the flow to run
:param tweaks: Optional tweaks to customize the flow
:return: The JSON response from the flow
"""
api_url = f"{BASE_API_URL}/{flow_id}"
payload = {"inputs": inputs}
if tweaks:
payload["tweaks"] = tweaks
response = requests.post(api_url, json=payload)
return response.json()
# Setup any tweaks you want to apply to the flow
inputs = ${inputs}
print(run_flow(inputs, flow_id=FLOW_ID, tweaks=TWEAKS))`;
}
/**
* Function to get the curl code for the API
* @param {string} flowId - The id of the flow
* @returns {string} - The curl code
*/
export function getCurlCode(
flow: FlowType,
tweak?: any[],
tabsState?: TabsState
): string {
const flowId = flow.id;
const tweaks = buildTweaks(flow);
const inputs = buildInputs(tabsState, flow.id);
return `curl -X POST \\
${window.location.protocol}//${
window.location.host
}/api/v1/process/${flowId} \\
-H 'Content-Type: application/json' \\
-d '{"inputs": ${inputs}, "tweaks": ${
tweak && tweak.length > 0
? buildTweakObject(tweak)
: JSON.stringify(tweaks, null, 2)
}}'`;
}
/**
* Function to get the python code for the API
* @param {string} flow - The current flow
* @returns {string} - The python code
*/
export function getPythonCode(
flow: FlowType,
tweak?: any[],
tabsState?: TabsState
): string {
const flowName = flow.name;
const tweaks = buildTweaks(flow);
const inputs = buildInputs(tabsState, flow.id);
return `from langflow import load_flow_from_json
TWEAKS = ${
tweak && tweak.length > 0
? buildTweakObject(tweak)
: JSON.stringify(tweaks, null, 2)
}
flow = load_flow_from_json("${flowName}.json", tweaks=TWEAKS)
# Now you can use it like any chain
inputs = ${inputs}
flow(inputs)`;
}
/**
* Function to get the widget code for the API
* @param {string} flow - The current flow.
* @returns {string} - The widget code
*/
export function getWidgetCode(flow: FlowType, tabsState?: TabsState): string {
const flowId = flow.id;
const flowName = flow.name;
const inputs = buildInputs(tabsState, flow.id);
let chat_input_field = getChatInputField(flow, tabsState);
return `<script src="https://cdn.jsdelivr.net/gh/logspace-ai/langflow-embedded-chat@main/dist/build/static/js/bundle.min.js"></script>
<!-- chat_inputs: Stringified JSON with all the input keys and its values. The value of the key that is defined
as chat_input_field will be overwritten by the chat message.
chat_input_field: Input key that you want the chat to send the user message with. -->
<langflow-chat
window_title="${flowName}"
flow_id="${flowId}"
${
tabsState[flow.id] && tabsState[flow.id].formKeysData
? `chat_inputs='${inputs}'
chat_input_field="${chat_input_field}"
`
: ""
}host_url="http://localhost:7860"
></langflow-chat>`;
}