UNPKG

@langchain/community

Version:
1 lines 21.8 kB
{"version":3,"file":"azion_edgesql.cjs","names":["BaseRetriever","SystemMessage","HumanMessage","Document"],"sources":["../../src/retrievers/azion_edgesql.ts"],"sourcesContent":["import { QueryResult, useQuery } from \"azion/sql\";\nimport type { EmbeddingsInterface } from \"@langchain/core/embeddings\";\nimport { Document } from \"@langchain/core/documents\";\nimport { BaseRetriever, BaseRetrieverInput } from \"@langchain/core/retrievers\";\nimport { SystemMessage, HumanMessage } from \"@langchain/core/messages\";\nimport { BaseChatModel } from \"@langchain/core/language_models/chat_models\";\n\n/**\n * Represents a filter condition for querying the Azion database\n * @property operator - The comparison operator to use (e.g. =, !=, >, <, etc)\n * @property column - The database column to filter on\n * @property value - The value to compare against\n */\nexport type AzionFilter = { operator: Operator; column: Column; value: string };\n\n/**\n * Represents a database column name\n */\nexport type Column = string;\n\n/**\n * Valid SQL operators that can be used in filter conditions\n */\nexport type Operator =\n | \"=\"\n | \"!=\"\n | \">\"\n | \"<>\"\n | \"<\" // Basic comparison operators\n | \">=\"\n | \"<=\" // Range operators\n | \"LIKE\"\n | \"NOT LIKE\" // Pattern matching\n | \"IN\"\n | \"NOT IN\" // Set membership\n | \"IS NULL\"\n | \"IS NOT NULL\"; // NULL checks\n\n/**\n * Interface for the response returned when searching embeddings.\n */\ninterface SearchEmbeddingsResponse {\n id: number;\n content: string;\n metadata: {\n searchtype: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * Interface for the arguments required to initialize an Azion library.\n */\nexport interface AzionRetrieverArgs extends BaseRetrieverInput {\n /**\n * Search type to perform. Cosine similarity and hybrid (vector + FTS) are currently supported.\n */\n searchType?: \"hybrid\" | \"similarity\";\n\n /**\n * The number of documents retrieved with cosine similarity (vector) search. Minimum is 1.\n */\n similarityK?: number;\n\n /**\n * The number of documents retrieved with full text search. Minimum is 1.\n */\n ftsK?: number;\n\n /**\n * The name of the database to search for documents.\n */\n dbName?: string;\n\n /**\n * The prompt to the chatmodel to extract entities to perform Full text search on the database\n */\n promptEntityExtractor?: string;\n\n /**\n * The chatmodel to extract entities to perform Full text search on the database\n */\n entityExtractor?: BaseChatModel;\n\n /**\n * Max items to maintain per searchtype. Default is 3.\n */\n maxItemsSearch?: number;\n\n /**\n * The columns from the tables that metadata must contain\n */\n metadataItems?: string[];\n\n /**\n * Name of the table to perform vector similarity seach. Default is 'documents'\n */\n vectorTable?: string;\n\n /**\n * Name of the table to perform full text search. Default is 'document_fts'\n */\n ftsTable?: string;\n\n /**\n * Filters to apply to the search. Default is an empty array.\n */\n filters?: AzionFilter[];\n\n /** Whether the metadata is contained in a single column or multiple columns */\n expandedMetadata?: boolean;\n}\n\n/**\n * class for performing hybrid search operations on Azion's Edge SQL database.\n * It extends the 'BaseRetriever' class and implements methods for\n * similarity search and full-text search (FTS).\n */\n/**\n * Example usage:\n * ```ts\n * // Initialize embeddings and chat model\n * const embeddings = new OpenAIEmbeddings();\n * const chatModel = new ChatOpenAI({ model: \"gpt-4o-mini\" });\n *\n * // Create retriever with hybrid search\n * const retriever = new AzionRetriever(embeddings, chatModel, {\n * searchType: 'hybrid',\n * similarityK: 3,\n * ftsK: 2,\n * dbName: 'my_docs',\n * metadataItems: ['category', 'author'],\n * vectorTable: 'documents',\n * ftsTable: 'documents_fts',\n * filters: [\n * { operator: '=', column: 'status', value: 'published' }\n * ]\n * });\n *\n * // Retrieve relevant documents\n * const docs = await retriever.invoke(\n * \"What are coral reefs in Australia?\"\n * );\n *\n * // Create retriever with similarity search only\n * const simRetriever = new AzionRetriever(embeddings, chatModel, {\n * searchType: 'similarity',\n * similarityK: 5,\n * dbName: 'my_docs',\n * vectorTable: 'documents'\n * });\n *\n * // Customize entity extraction prompt\n * const customRetriever = new AzionRetriever(embeddings, chatModel, {\n * searchType: 'hybrid',\n * similarityK: 3,\n * ftsK: 2,\n * dbName: 'my_docs',\n * promptEntityExtractor: \"Extract key entities from: {{query}}\"\n * });\n * ```\n */\n\nexport class AzionRetriever extends BaseRetriever {\n static lc_name() {\n return \"azionRetriever\";\n }\n\n /** Namespace for the retriever in LangChain */\n lc_namespace = [\"langchain\", \"retrievers\", \"azion\"];\n\n /** Type of search to perform - either hybrid (combining vector + FTS) or similarity only */\n searchType?: \"hybrid\" | \"similarity\";\n\n /** Number of results to return from similarity search. Minimum is 1. */\n similarityK: number;\n\n /** Number of results to return from full text search. Minimum is 1. */\n ftsK: number;\n\n /** Interface for generating embeddings from text */\n embeddings: EmbeddingsInterface;\n\n /** Name of the database to search */\n dbName: string;\n\n /** Optional ChatModel used to extract entities from queries */\n entityExtractor?: BaseChatModel;\n\n /** Prompt template for entity extraction */\n promptEntityExtractor: string;\n\n /** Optional metadata columns to include in results */\n metadataItems?: string[];\n\n /** Name of table containing vector embeddings for similarity search */\n vectorTable: string;\n\n /** Name of table containing documents for full text search */\n ftsTable: string;\n\n /** Array of filters to apply to search results */\n filters: AzionFilter[];\n\n /** Whether the metadata is contained in a single column or multiple columns */\n expandedMetadata: boolean;\n\n constructor(embeddings: EmbeddingsInterface, args: AzionRetrieverArgs) {\n super(args);\n\n this.ftsTable = args.ftsTable || \"vectors_fts\";\n this.vectorTable = args.vectorTable || \"vectors\";\n this.similarityK = Math.max(1, args.similarityK || 1);\n this.ftsK = Math.max(1, args.ftsK || 1);\n this.dbName = args.dbName || \"vectorstore\";\n\n this.embeddings = embeddings;\n this.searchType = args.searchType || \"similarity\";\n\n this.entityExtractor = args.entityExtractor || undefined;\n this.metadataItems = args.metadataItems || undefined;\n this.promptEntityExtractor =\n args.promptEntityExtractor ||\n \"Provide them as a space-separated string in lowercase, translated to English.\";\n this.filters = args.filters || [];\n this.expandedMetadata = args.expandedMetadata || false;\n }\n\n /**\n * Generates a string of filters for the SQL query.\n * @param {AzionFilter[]} filters - The filters to apply to the search.\n * @returns {string} A string of filters for the SQL query.\n */\n protected generateFilters(filters: AzionFilter[]): string {\n if (!filters || filters?.length === 0) {\n return \"\";\n }\n\n return `${filters\n .map(({ operator, column, value }) => {\n const columnRef = this.expandedMetadata\n ? this.sanitizeItem(column)\n : `metadata->>'$.${this.sanitizeItem(column)}'`;\n if ([\"IN\", \"NOT IN\"].includes(operator.toUpperCase())) {\n return `${columnRef} ${operator} (${this.sanitizeItem(value)})`;\n }\n return `${columnRef} ${operator} '${this.sanitizeItem(value)}'`;\n })\n .join(\" AND \")} AND `;\n }\n\n /**\n * Generates SQL queries for full-text search and similarity search.\n * @param {number[]} embeddedQuery - The embedded query vector.\n * @param {string} queryEntities - The entities extracted from the query for full-text search.\n * @param {string} metadata - Additional metadata columns to be included in the results.\n * @returns An object containing the FTS query and similarity query strings.\n */\n protected generateSqlQueries(\n embeddedQuery: number[],\n queryEntities: string,\n metadata: string\n ): { ftsQuery: string; similarityQuery: string } {\n const filters = this.generateFilters(this.filters);\n\n let rowsNumber = this.similarityK;\n if (this.searchType === \"hybrid\") {\n rowsNumber += this.ftsK;\n }\n\n const ftsQuery = `\n SELECT id, content, ${metadata.replace(\"hybrid\", \"fts\")}\n FROM ${this.ftsTable} \n WHERE ${filters} ${this.ftsTable} MATCH '${queryEntities}'\n ORDER BY rank \n LIMIT ${rowsNumber}\n `;\n\n const similarityQuery = `\n SELECT id, content, ${metadata.replace(\"hybrid\", \"similarity\")}\n FROM ${this.vectorTable} \n WHERE ${filters} rowid IN vector_top_k('${\n this.vectorTable\n }_idx', vector('[${embeddedQuery}]'), ${rowsNumber})\n `;\n\n return { ftsQuery, similarityQuery };\n }\n\n /**\n * Generates the SQL statements for the similarity search and full-text search.\n * @param query The user query.\n * @returns An array of SQL statements.\n */\n protected async generateStatements(query: string): Promise<string[]> {\n const embeddedQuery = await this.embeddings.embedQuery(query);\n\n const metadata = this.generateMetadata();\n\n let queryEntities = \"\";\n if (this.searchType === \"hybrid\") {\n queryEntities = await this.extractEntities(query);\n }\n\n const { ftsQuery, similarityQuery } = this.generateSqlQueries(\n embeddedQuery,\n queryEntities,\n metadata\n );\n\n if (this.searchType === \"similarity\") {\n return [similarityQuery];\n }\n\n return [similarityQuery, ftsQuery];\n }\n\n /**\n * Generates the metadata string for the SQL query.\n * @returns {string} The metadata string.\n */\n protected generateMetadata(): string {\n if (!this.metadataItems) {\n return `json_object('searchtype', '${this.searchType}') as metadata`;\n }\n\n if (this.expandedMetadata) {\n return `json_object('searchtype','${this.searchType}',${this.metadataItems\n .map(\n (item) => `'${this.sanitizeItem(item)}', ${this.sanitizeItem(item)}`\n )\n .join(\", \")}) as metadata`;\n }\n\n return `json_patch(json_object(${this.metadataItems\n ?.map(\n (item) =>\n `'${this.sanitizeItem(item)}', metadata->>'$.${this.sanitizeItem(\n item\n )}'`\n )\n .join(\", \")}), '{\"searchtype\":\"${this.searchType}\"}') as metadata`;\n }\n\n /**\n * Performs a similarity search on the vector store and returns the top 'similarityK' similar documents.\n * @param query The query string.\n * @returns A promise that resolves with the similarity search results when the search is complete.\n */\n protected async similaritySearchWithScore(\n query: string\n ): Promise<[Document][]> {\n const statements = await this.generateStatements(query);\n\n const { data: response, error: errorQuery } = await useQuery(\n this.dbName,\n statements\n );\n\n if (!response) {\n console.error(\"RESPONSE ERROR: \", errorQuery);\n throw this.searchError(errorQuery);\n }\n const searches = this.mapRows(response.results);\n const result = this.mapSearches(searches);\n return result;\n }\n\n /**\n * Extracts entities from a user query using the entityExtractor model.\n * @param query The user query\n * @returns A promise that resolves with the extracted entities when the extraction is complete.\n */\n protected async extractEntities(query: string): Promise<string> {\n if (!this.entityExtractor) {\n return this.convert2FTSQuery(query);\n }\n\n const entityExtractionPrompt = new SystemMessage(\n this.promptEntityExtractor\n );\n\n const entityQuery = await this.entityExtractor.invoke([\n entityExtractionPrompt,\n new HumanMessage(query),\n ]);\n return this.convert2FTSQuery(entityQuery.content.toString());\n }\n\n /**\n * Converts a query to a FTS query.\n * @param query The user query\n * @returns The converted FTS query\n */\n protected convert2FTSQuery(query: string): string {\n return query\n .replace(/[^a-záàâãéèêíïóôõöúçñA-ZÁÀÂÃÉÈÊÍÏÓÔÕÖÚÇÑ0-9\\s]/g, \"\") // Remove special chars keeping accents\n .replace(/\\s+/g, \" \") // Remove multiple spaces\n .trim() // Remove leading/trailing spaces\n .split(\" \")\n .join(\" OR \");\n }\n\n /**\n * Performs a hybrid search on the vector store, using cosine similarity and FTS search, and\n * returns the top 'similarityK' + 'ftsK' similar documents.\n * @param query The user query\n * @returns A promise that resolves with the hybrid search results when the search is complete.\n */\n protected async hybridSearchAzion(query: string): Promise<[Document][]> {\n const statements = await this.generateStatements(query);\n\n const { data: response, error: errorQuery } = await useQuery(\n this.dbName,\n statements\n );\n\n if (!response) {\n console.error(\"RESPONSE ERROR: \", errorQuery);\n throw this.searchError(errorQuery);\n }\n\n const results = this.mapRows(response.results);\n\n const finalResults = this.removeDuplicates(results);\n\n return this.mapSearches(finalResults);\n }\n\n /**\n * Generates an error document based on the provided error information\n * @param error The error object containing details about the issue\n * @returns A promise that resolves to an array containing a single Document representing the error\n */\n protected searchError(\n error:\n | {\n message: string;\n operation: string;\n }\n | undefined\n ): Error {\n throw new Error(error?.message);\n }\n\n /**\n * Performs the selected search and returns the documents retrieved.\n * @param query The user query\n * @returns A promise that resolves with the completion of the search results.\n */\n async _getRelevantDocuments(query: string): Promise<Document[]> {\n let result: [Document][];\n\n if (this.searchType === \"similarity\") {\n result = await this.similaritySearchWithScore(query);\n } else {\n result = await this.hybridSearchAzion(query);\n }\n\n return result.map(([doc]) => doc);\n }\n\n /**\n * Removes duplicate results from the search results, prioritizing a mix of similarity and FTS results.\n * @param {SearchEmbeddingsResponse[]} results - The array of search results to process.\n * @returns {SearchEmbeddingsResponse[]} An array of unique search results, with a maximum of 3 similarity and 3 FTS results.\n */\n private removeDuplicates(\n results: SearchEmbeddingsResponse[]\n ): SearchEmbeddingsResponse[] {\n const uniqueResults: SearchEmbeddingsResponse[] = [];\n const seenIds = new Set<number>();\n\n let similarityCount = 0;\n let ftsCount = 0;\n const maxItems = this.ftsK + this.similarityK;\n\n for (const result of results) {\n if (!seenIds.has(result.id)) {\n if (\n result.metadata.searchtype === \"similarity\" &&\n similarityCount < this.similarityK\n ) {\n seenIds.add(result.id);\n uniqueResults.push(result);\n similarityCount += 1;\n } else if (\n result.metadata.searchtype === \"fts\" &&\n ftsCount < this.ftsK\n ) {\n seenIds.add(result.id);\n uniqueResults.push(result);\n ftsCount += 1;\n }\n }\n if (similarityCount + ftsCount === maxItems) break;\n }\n return uniqueResults;\n }\n\n /**\n * Converts query results to SearchEmbeddingsResponse objects.\n * @param {QueryResult[]} results - The raw query results from the database.\n * @returns {SearchEmbeddingsResponse[]} An array of SearchEmbeddingsResponse objects.\n */\n private mapRows(\n results: QueryResult[] | undefined\n ): SearchEmbeddingsResponse[] {\n if (!results) {\n return [];\n }\n\n return results.flatMap(\n (queryResult: QueryResult): SearchEmbeddingsResponse[] => {\n if (!queryResult.rows || !queryResult.columns) {\n return [];\n }\n\n return queryResult.rows.map(\n (row): SearchEmbeddingsResponse => ({\n id: Number(row[0]),\n content: String(row[1]),\n metadata: JSON.parse(String(row[2])),\n })\n );\n }\n );\n }\n\n /**\n * Maps search results to Document objects.\n * @param {SearchEmbeddingsResponse[]} searches An array of SearchEmbeddingsResponse objects.\n * @returns An array of tuples, each containing a single Document object.\n */\n protected mapSearches(searches: SearchEmbeddingsResponse[]): [Document][] {\n return searches.map((resp: SearchEmbeddingsResponse) => [\n new Document({\n metadata: resp.metadata,\n pageContent: resp.content,\n id: resp.id.toString(),\n }),\n ]);\n }\n\n /**\n * Sanitizes an item by removing non-alphanumeric characters.\n * @param {string} item The item to sanitize.\n * @returns {string} The sanitized item.\n */\n private sanitizeItem(item: string | undefined): string {\n if (item) {\n return item.replace(/[^a-zA-Z0-9\\s]/g, \"\");\n }\n return \"\";\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmKA,IAAa,iBAAb,cAAoCA,2BAAAA,cAAc;CAChD,OAAO,UAAU;AACf,SAAO;;;CAIT,eAAe;EAAC;EAAa;EAAc;EAAQ;;CAGnD;;CAGA;;CAGA;;CAGA;;CAGA;;CAGA;;CAGA;;CAGA;;CAGA;;CAGA;;CAGA;;CAGA;CAEA,YAAY,YAAiC,MAA0B;AACrE,QAAM,KAAK;AAEX,OAAK,WAAW,KAAK,YAAY;AACjC,OAAK,cAAc,KAAK,eAAe;AACvC,OAAK,cAAc,KAAK,IAAI,GAAG,KAAK,eAAe,EAAE;AACrD,OAAK,OAAO,KAAK,IAAI,GAAG,KAAK,QAAQ,EAAE;AACvC,OAAK,SAAS,KAAK,UAAU;AAE7B,OAAK,aAAa;AAClB,OAAK,aAAa,KAAK,cAAc;AAErC,OAAK,kBAAkB,KAAK,mBAAmB,KAAA;AAC/C,OAAK,gBAAgB,KAAK,iBAAiB,KAAA;AAC3C,OAAK,wBACH,KAAK,yBACL;AACF,OAAK,UAAU,KAAK,WAAW,EAAE;AACjC,OAAK,mBAAmB,KAAK,oBAAoB;;;;;;;CAQnD,gBAA0B,SAAgC;AACxD,MAAI,CAAC,WAAW,SAAS,WAAW,EAClC,QAAO;AAGT,SAAO,GAAG,QACP,KAAK,EAAE,UAAU,QAAQ,YAAY;GACpC,MAAM,YAAY,KAAK,mBACnB,KAAK,aAAa,OAAO,GACzB,iBAAiB,KAAK,aAAa,OAAO,CAAC;AAC/C,OAAI,CAAC,MAAM,SAAS,CAAC,SAAS,SAAS,aAAa,CAAC,CACnD,QAAO,GAAG,UAAU,GAAG,SAAS,IAAI,KAAK,aAAa,MAAM,CAAC;AAE/D,UAAO,GAAG,UAAU,GAAG,SAAS,IAAI,KAAK,aAAa,MAAM,CAAC;IAC7D,CACD,KAAK,QAAQ,CAAC;;;;;;;;;CAUnB,mBACE,eACA,eACA,UAC+C;EAC/C,MAAM,UAAU,KAAK,gBAAgB,KAAK,QAAQ;EAElD,IAAI,aAAa,KAAK;AACtB,MAAI,KAAK,eAAe,SACtB,eAAc,KAAK;AAmBrB,SAAO;GAAE,UAhBQ;4BACO,SAAS,QAAQ,UAAU,MAAM,CAAC;aACjD,KAAK,SAAS;cACb,QAAQ,GAAG,KAAK,SAAS,UAAU,cAAc;;cAEjD,WAAW;;GAWF,iBARK;4BACA,SAAS,QAAQ,UAAU,aAAa,CAAC;aACxD,KAAK,YAAY;cAChB,QAAQ,0BACd,KAAK,YACN,kBAAkB,cAAc,OAAO,WAAW;;GAGjB;;;;;;;CAQtC,MAAgB,mBAAmB,OAAkC;EACnE,MAAM,gBAAgB,MAAM,KAAK,WAAW,WAAW,MAAM;EAE7D,MAAM,WAAW,KAAK,kBAAkB;EAExC,IAAI,gBAAgB;AACpB,MAAI,KAAK,eAAe,SACtB,iBAAgB,MAAM,KAAK,gBAAgB,MAAM;EAGnD,MAAM,EAAE,UAAU,oBAAoB,KAAK,mBACzC,eACA,eACA,SACD;AAED,MAAI,KAAK,eAAe,aACtB,QAAO,CAAC,gBAAgB;AAG1B,SAAO,CAAC,iBAAiB,SAAS;;;;;;CAOpC,mBAAqC;AACnC,MAAI,CAAC,KAAK,cACR,QAAO,8BAA8B,KAAK,WAAW;AAGvD,MAAI,KAAK,iBACP,QAAO,6BAA6B,KAAK,WAAW,IAAI,KAAK,cAC1D,KACE,SAAS,IAAI,KAAK,aAAa,KAAK,CAAC,KAAK,KAAK,aAAa,KAAK,GACnE,CACA,KAAK,KAAK,CAAC;AAGhB,SAAO,0BAA0B,KAAK,eAClC,KACC,SACC,IAAI,KAAK,aAAa,KAAK,CAAC,mBAAmB,KAAK,aAClD,KACD,CAAC,GACL,CACA,KAAK,KAAK,CAAC,qBAAqB,KAAK,WAAW;;;;;;;CAQrD,MAAgB,0BACd,OACuB;EACvB,MAAM,aAAa,MAAM,KAAK,mBAAmB,MAAM;EAEvD,MAAM,EAAE,MAAM,UAAU,OAAO,eAAe,OAAA,GAAA,UAAA,UAC5C,KAAK,QACL,WACD;AAED,MAAI,CAAC,UAAU;AACb,WAAQ,MAAM,oBAAoB,WAAW;AAC7C,SAAM,KAAK,YAAY,WAAW;;EAEpC,MAAM,WAAW,KAAK,QAAQ,SAAS,QAAQ;AAE/C,SADe,KAAK,YAAY,SAAS;;;;;;;CAS3C,MAAgB,gBAAgB,OAAgC;AAC9D,MAAI,CAAC,KAAK,gBACR,QAAO,KAAK,iBAAiB,MAAM;EAGrC,MAAM,yBAAyB,IAAIC,yBAAAA,cACjC,KAAK,sBACN;EAED,MAAM,cAAc,MAAM,KAAK,gBAAgB,OAAO,CACpD,wBACA,IAAIC,yBAAAA,aAAa,MAAM,CACxB,CAAC;AACF,SAAO,KAAK,iBAAiB,YAAY,QAAQ,UAAU,CAAC;;;;;;;CAQ9D,iBAA2B,OAAuB;AAChD,SAAO,MACJ,QAAQ,mDAAmD,GAAG,CAC9D,QAAQ,QAAQ,IAAI,CACpB,MAAM,CACN,MAAM,IAAI,CACV,KAAK,OAAO;;;;;;;;CASjB,MAAgB,kBAAkB,OAAsC;EACtE,MAAM,aAAa,MAAM,KAAK,mBAAmB,MAAM;EAEvD,MAAM,EAAE,MAAM,UAAU,OAAO,eAAe,OAAA,GAAA,UAAA,UAC5C,KAAK,QACL,WACD;AAED,MAAI,CAAC,UAAU;AACb,WAAQ,MAAM,oBAAoB,WAAW;AAC7C,SAAM,KAAK,YAAY,WAAW;;EAGpC,MAAM,UAAU,KAAK,QAAQ,SAAS,QAAQ;EAE9C,MAAM,eAAe,KAAK,iBAAiB,QAAQ;AAEnD,SAAO,KAAK,YAAY,aAAa;;;;;;;CAQvC,YACE,OAMO;AACP,QAAM,IAAI,MAAM,OAAO,QAAQ;;;;;;;CAQjC,MAAM,sBAAsB,OAAoC;EAC9D,IAAI;AAEJ,MAAI,KAAK,eAAe,aACtB,UAAS,MAAM,KAAK,0BAA0B,MAAM;MAEpD,UAAS,MAAM,KAAK,kBAAkB,MAAM;AAG9C,SAAO,OAAO,KAAK,CAAC,SAAS,IAAI;;;;;;;CAQnC,iBACE,SAC4B;EAC5B,MAAM,gBAA4C,EAAE;EACpD,MAAM,0BAAU,IAAI,KAAa;EAEjC,IAAI,kBAAkB;EACtB,IAAI,WAAW;EACf,MAAM,WAAW,KAAK,OAAO,KAAK;AAElC,OAAK,MAAM,UAAU,SAAS;AAC5B,OAAI,CAAC,QAAQ,IAAI,OAAO,GAAG;QAEvB,OAAO,SAAS,eAAe,gBAC/B,kBAAkB,KAAK,aACvB;AACA,aAAQ,IAAI,OAAO,GAAG;AACtB,mBAAc,KAAK,OAAO;AAC1B,wBAAmB;eAEnB,OAAO,SAAS,eAAe,SAC/B,WAAW,KAAK,MAChB;AACA,aAAQ,IAAI,OAAO,GAAG;AACtB,mBAAc,KAAK,OAAO;AAC1B,iBAAY;;;AAGhB,OAAI,kBAAkB,aAAa,SAAU;;AAE/C,SAAO;;;;;;;CAQT,QACE,SAC4B;AAC5B,MAAI,CAAC,QACH,QAAO,EAAE;AAGX,SAAO,QAAQ,SACZ,gBAAyD;AACxD,OAAI,CAAC,YAAY,QAAQ,CAAC,YAAY,QACpC,QAAO,EAAE;AAGX,UAAO,YAAY,KAAK,KACrB,SAAmC;IAClC,IAAI,OAAO,IAAI,GAAG;IAClB,SAAS,OAAO,IAAI,GAAG;IACvB,UAAU,KAAK,MAAM,OAAO,IAAI,GAAG,CAAC;IACrC,EACF;IAEJ;;;;;;;CAQH,YAAsB,UAAoD;AACxE,SAAO,SAAS,KAAK,SAAmC,CACtD,IAAIC,0BAAAA,SAAS;GACX,UAAU,KAAK;GACf,aAAa,KAAK;GAClB,IAAI,KAAK,GAAG,UAAU;GACvB,CAAC,CACH,CAAC;;;;;;;CAQJ,aAAqB,MAAkC;AACrD,MAAI,KACF,QAAO,KAAK,QAAQ,mBAAmB,GAAG;AAE5C,SAAO"}