diff --git a/packages/api/package.json b/packages/api/package.json index 1c45f926e..111fc6502 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -23,6 +23,7 @@ "@google-cloud/storage": "^7.0.1", "@google-cloud/tasks": "^4.0.0", "@graphql-tools/utils": "^9.1.1", + "@langchain/openai": "^0.0.14", "@omnivore/content-handler": "1.0.0", "@omnivore/liqe": "1.0.0", "@omnivore/readability": "1.0.0", @@ -77,6 +78,7 @@ "ioredis": "^5.3.2", "jsonwebtoken": "^8.5.1", "jwks-rsa": "^2.0.3", + "langchain": "^0.1.21", "linkedom": "^0.14.9", "lodash": "^4.17.21", "luxon": "^3.2.1", diff --git a/packages/api/src/entity/AISummary.ts b/packages/api/src/entity/AISummary.ts new file mode 100644 index 000000000..d43cbbb7a --- /dev/null +++ b/packages/api/src/entity/AISummary.ts @@ -0,0 +1,36 @@ +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, +} from 'typeorm' +import { User } from './user' +import { LibraryItem } from './library_item' + +@Entity({ name: 'ai_summaries' }) +export class AISummary { + @PrimaryGeneratedColumn('uuid') + id!: string + + @ManyToOne(() => User) + @JoinColumn({ name: 'user_id' }) + user!: User + + @ManyToOne(() => LibraryItem) + @JoinColumn({ name: 'library_item_id' }) + libraryItem!: LibraryItem + + @Column('text') + summary?: string + + @Column('text') + title?: string + + @Column('text') + slug?: string + + @CreateDateColumn({ default: () => 'CURRENT_TIMESTAMP' }) + createdAt!: Date +} diff --git a/packages/api/src/jobs/ai-summarize.ts b/packages/api/src/jobs/ai-summarize.ts new file mode 100644 index 000000000..64cef703c --- /dev/null +++ b/packages/api/src/jobs/ai-summarize.ts @@ -0,0 +1,79 @@ +import { logger } from '../utils/logger' +import { loadSummarizationChain } from 'langchain/chains' +import { ChatOpenAI } from '@langchain/openai' +import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter' +import { authTrx } from '../repository' +import { libraryItemRepository } from '../repository/library_item' +import { htmlToMarkdown } from '../utils/parser' +import { AISummary } from '../entity/AISummary' +import { LibraryItemState } from '../entity/library_item' + +export interface AISummarizeJobData { + userId: string + promptId?: string + libraryItemId: string +} + +export const AI_SUMMARIZE_JOB_NAME = 'ai-summary-job' + +export const aiSummarize = async (jobData: AISummarizeJobData) => { + try { + const libraryItem = await authTrx( + async (tx) => + tx + .withRepository(libraryItemRepository) + .findById(jobData.libraryItemId), + undefined, + jobData.userId + ) + if (!libraryItem || libraryItem.state !== LibraryItemState.Succeeded) { + logger.info( + `Not ready to summarize library item job state: ${ + libraryItem?.state ?? 'null' + }` + ) + return + } + + const llm = new ChatOpenAI({ + configuration: { + apiKey: process.env.OPENAI_API_KEY, + }, + }) + const textSplitter = new RecursiveCharacterTextSplitter({ + chunkSize: 2000, + }) + + const document = htmlToMarkdown(libraryItem.readableContent) + const docs = await textSplitter.createDocuments([document]) + const chain = loadSummarizationChain(llm, { + type: 'map_reduce', // you can choose from map_reduce, stuff or refine + verbose: true, // to view the steps in the console + }) + const response = await chain.call({ + input_documents: docs, + }) + + if (typeof response.text !== 'string') { + logger.error(`AI summary did not return text`) + return + } + + const summary = response.text + const _ = await authTrx( + async (t) => { + return t.getRepository(AISummary).save({ + user: { id: jobData.userId }, + libraryItem: { id: jobData.libraryItemId }, + title: libraryItem.title, + slug: libraryItem.slug, + summary: summary, + }) + }, + undefined, + jobData.userId + ) + } catch (err) { + console.log('error creating summary: ', err) + } +} diff --git a/packages/api/src/pubsub.ts b/packages/api/src/pubsub.ts index 5ca9d2693..7bf25d921 100644 --- a/packages/api/src/pubsub.ts +++ b/packages/api/src/pubsub.ts @@ -5,12 +5,18 @@ import { env } from './env' import { ReportType } from './generated/graphql' import { Merge } from './util' import { + enqueueAISummarizeJob, enqueueExportItem, enqueueTriggerRuleJob, enqueueWebhookJob, } from './utils/createTask' import { deepDelete } from './utils/helpers' import { buildLogger } from './utils/logger' +import { + FeatureName, + findFeatureByName, + getFeatureName, +} from './services/features' const logger = buildLogger('pubsub') @@ -82,6 +88,13 @@ export const createPubSubClient = (): PubsubClient => { data, }) + if (await findFeatureByName(FeatureName.AISummaries, userId)) { + await enqueueAISummarizeJob({ + userId, + libraryItemId, + }) + } + return publish( 'entityCreated', Buffer.from(JSON.stringify({ type, userId, ...cleanData })) diff --git a/packages/api/src/queue-processor.ts b/packages/api/src/queue-processor.ts index cbd06787b..6cd13b8a0 100644 --- a/packages/api/src/queue-processor.ts +++ b/packages/api/src/queue-processor.ts @@ -43,6 +43,7 @@ import { redisDataSource } from './redis_data_source' import { CACHED_READING_POSITION_PREFIX } from './services/cached_reading_position' import { getJobPriority } from './utils/createTask' import { logger } from './utils/logger' +import { AI_SUMMARIZE_JOB_NAME, aiSummarize } from './jobs/ai-summarize' export const QUEUE_NAME = 'omnivore-backend-queue' export const JOB_VERSION = 'v001' @@ -113,6 +114,8 @@ export const createWorker = (connection: ConnectionOptions) => return callWebhook(job.data) case EXPORT_ITEM_JOB_NAME: return exportItem(job.data) + case AI_SUMMARIZE_JOB_NAME: + return aiSummarize(job.data) case EXPORT_ALL_ITEMS_JOB_NAME: return exportAllItems(job.data) } diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index b2084b3b7..1f646501a 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -141,6 +141,8 @@ import { markEmailAsItemResolver, recentEmailsResolver } from './recent_emails' import { recentSearchesResolver } from './recent_searches' import { WithDataSourcesContext } from './types' import { updateEmailResolver } from './user' +import { getAISummary } from '../services/ai-summaries' +import { findUserFeatures, getFeatureName } from '../services/features' /* eslint-disable @typescript-eslint/naming-convention */ type ResultResolveType = { @@ -346,6 +348,16 @@ export const functionResolvers = { } return undefined }, + async features( + user: User, + __: Record, + ctx: WithDataSourcesContext + ) { + if (!ctx.claims?.uid) { + return undefined + } + return findUserFeatures(ctx.claims.uid) + }, }, Article: { async url(article: Article, _: unknown, ctx: WithDataSourcesContext) { @@ -485,6 +497,15 @@ export const functionResolvers = { return [] }, + async aiSummary(item: SearchItem, _: unknown, ctx: WithDataSourcesContext) { + return ( + await getAISummary({ + userId: ctx.uid, + libraryItemId: item.id, + idx: 'latest', + }) + )?.summary + }, async highlights( item: { id: string diff --git a/packages/api/src/routers/ai_summary_router.ts b/packages/api/src/routers/ai_summary_router.ts new file mode 100644 index 000000000..faca835b5 --- /dev/null +++ b/packages/api/src/routers/ai_summary_router.ts @@ -0,0 +1,55 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { htmlToSpeechFile } from '@omnivore/text-to-speech-handler' +import cors from 'cors' +import express from 'express' +import { userRepository } from '../repository/user' +import { getClaimsByToken } from '../utils/auth' +import { corsConfig } from '../utils/corsConfig' +import { getAISummary } from '../services/ai-summaries' + +export function aiSummariesRouter() { + const router = express.Router() + + // Get an indexed summary for an individual library item + router.get( + '/library-item/:libraryItemId/:idx', + cors(corsConfig), + async (req, res) => { + const token = req?.cookies?.auth || req?.headers?.authorization + const claims = await getClaimsByToken(token) + if (!claims) { + return res.status(401).send('UNAUTHORIZED') + } + + const { uid } = claims + const user = await userRepository.findById(uid) + if (!user) { + return res.status(400).send('Bad Request') + } + + const libraryItemId = req.params.libraryItemId + console.log('params: ', req.params) + if (!libraryItemId) { + return res.status(400).send('Bad request - no library item id provided') + } + + const idx = req.params.idx + if (!idx) { + return res.status(400).send('Bad request - no idx provided') + } + + const result = await getAISummary({ + userId: user.id, + idx: req.params.idx, + libraryItemId: req.params.libraryItemId, + }) + + return res.send({ + summary: result?.summary, + }) + } + ) + + return router +} diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index f3175f731..86b1f46e2 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -88,6 +88,7 @@ const schema = gql` email: String source: String intercomHash: String + features: [String] } type Profile { @@ -1647,6 +1648,7 @@ const schema = gql` previewContentType: String links: JSON folder: String! + aiSummary: String } type SearchItemEdge { diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 176d88172..135a9c4f9 100755 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -48,6 +48,7 @@ import { } from './utils/auth' import { corsConfig } from './utils/corsConfig' import { buildLogger, buildLoggerTransport } from './utils/logger' +import { aiSummariesRouter } from './routers/ai_summary_router' const PORT = process.env.PORT || 4000 @@ -121,6 +122,7 @@ export const createApp = (): { app.use('/api/page', pageRouter()) app.use('/api/user', userRouter()) app.use('/api/article', articleRouter()) + app.use('/api/ai-summary', aiSummariesRouter()) app.use('/api/text-to-speech', textToSpeechRouter()) app.use('/api/notification', notificationRouter()) app.use('/api/integration', integrationRouter()) diff --git a/packages/api/src/services/ai-summaries.ts b/packages/api/src/services/ai-summaries.ts new file mode 100644 index 000000000..3c94ca4cd --- /dev/null +++ b/packages/api/src/services/ai-summaries.ts @@ -0,0 +1,34 @@ +import { AISummary } from '../entity/AISummary' +import { authTrx } from '../repository' + +export const getAISummary = async (data: { + userId: string + idx: string + libraryItemId: string +}): Promise => { + const aiSummary = await authTrx( + async (t) => { + const repo = t.getRepository(AISummary) + if (data.idx == 'latest') { + return repo.findOne({ + where: { + user: { id: data.userId }, + libraryItem: { id: data.libraryItemId }, + }, + order: { createdAt: 'DESC' }, + }) + } else { + return repo.findOne({ + where: { + id: data.idx, + user: { id: data.userId }, + libraryItem: { id: data.libraryItemId }, + }, + }) + } + }, + undefined, + data.userId + ) + return aiSummary ?? undefined +} diff --git a/packages/api/src/services/features.ts b/packages/api/src/services/features.ts index e0a9b337a..699c0f66e 100644 --- a/packages/api/src/services/features.ts +++ b/packages/api/src/services/features.ts @@ -7,6 +7,7 @@ import { getRepository } from '../repository' import { logger } from '../utils/logger' export enum FeatureName { + AISummaries = 'ai-summaries', UltraRealisticVoice = 'ultra-realistic-voice', } @@ -21,7 +22,6 @@ export const optInFeature = async ( if (name === FeatureName.UltraRealisticVoice) { return optInUltraRealisticVoice(uid) } - return undefined } @@ -100,6 +100,16 @@ export const signFeatureToken = ( ) } +export const findUserFeatures = async (userId: string): Promise => { + return ( + await getRepository(Feature).find({ + where: { + user: { id: userId }, + }, + }) + ).map((feature) => feature.name) +} + export const findFeatureByName = async ( name: FeatureName, userId: string diff --git a/packages/api/src/utils/createTask.ts b/packages/api/src/utils/createTask.ts index 95777a240..e63cd6c7f 100644 --- a/packages/api/src/utils/createTask.ts +++ b/packages/api/src/utils/createTask.ts @@ -44,6 +44,7 @@ import { CreateTaskError } from './errors' import { stringToHash } from './helpers' import { logger } from './logger' import View = google.cloud.tasks.v2.Task.View +import { AISummarizeJobData, AI_SUMMARIZE_JOB_NAME } from '../jobs/ai-summarize' // Instantiates a client. const client = new CloudTasksClient() @@ -66,6 +67,7 @@ export const getJobPriority = (jobName: string): number => { case TRIGGER_RULE_JOB_NAME: case CALL_WEBHOOK_JOB_NAME: case EXPORT_ITEM_JOB_NAME: + case AI_SUMMARIZE_JOB_NAME: return 5 case BULK_ACTION_JOB_NAME: case `${REFRESH_FEED_JOB_NAME}_high`: @@ -694,6 +696,18 @@ export const enqueueWebhookJob = async (data: CallWebhookJobData) => { }) } +export const enqueueAISummarizeJob = async (data: AISummarizeJobData) => { + const queue = await getBackendQueue() + if (!queue) { + return undefined + } + + return queue.add(AI_SUMMARIZE_JOB_NAME, data, { + priority: getJobPriority(AI_SUMMARIZE_JOB_NAME), + attempts: 3, + }) +} + export const bulkEnqueueUpdateLabels = async (data: UpdateLabelsData[]) => { const queue = await getBackendQueue() if (!queue) { diff --git a/packages/db/migrations/0166.do.add_ai_summary_tables.sql b/packages/db/migrations/0166.do.add_ai_summary_tables.sql new file mode 100755 index 000000000..2ef165f23 --- /dev/null +++ b/packages/db/migrations/0166.do.add_ai_summary_tables.sql @@ -0,0 +1,31 @@ +-- Type: DO +-- Name: add_ai_summary_tables +-- Description: Add tables for generating AI summaries + +BEGIN; + +CREATE TABLE omnivore.ai_prompts ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(), + user_id uuid NOT NULL REFERENCES omnivore.user ON DELETE CASCADE, + prompt TEXT NOT NULL, + created_at timestamptz NOT NULL default current_timestamp +); + +CREATE TABLE omnivore.ai_summaries ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(), + user_id uuid NOT NULL REFERENCES omnivore.user ON DELETE CASCADE, +-- prompt_id uuid NOT NULL REFERENCES omnivore.ai_prompts ON DELETE CASCADE, + library_item_id uuid NOT NULL REFERENCES omnivore.library_item ON DELETE CASCADE, + summary TEXT NOT NULL, + title TEXT NOT NULL, + slug TEXT NOT NULL, + created_at timestamptz NOT NULL default current_timestamp +); + +CREATE POLICY create_summary on omnivore.ai_summaries + FOR INSERT TO omnivore_user + WITH CHECK (true); + +GRANT SELECT, INSERT ON omnivore.ai_summaries TO omnivore_user; + +COMMIT; diff --git a/packages/db/migrations/0166.undo.add_ai_summary_tables.sql b/packages/db/migrations/0166.undo.add_ai_summary_tables.sql new file mode 100755 index 000000000..10f495a76 --- /dev/null +++ b/packages/db/migrations/0166.undo.add_ai_summary_tables.sql @@ -0,0 +1,12 @@ +-- Type: UNDO +-- Name: add_ai_summary_tables +-- Description: Add tables for generating AI summaries + +BEGIN; + +DROP TABLE omnivore.ai_summaries; + +DROP TABLE omnivore.ai_prompts; + + +COMMIT; diff --git a/packages/web/components/elements/Button.tsx b/packages/web/components/elements/Button.tsx index 54e399801..cb88f527d 100644 --- a/packages/web/components/elements/Button.tsx +++ b/packages/web/components/elements/Button.tsx @@ -33,6 +33,26 @@ export const Button = styled('button', { border: '0px solid $ctaBlue', }, }, + tldr: { + gap: '10px', + display: 'flex', + alignItems: 'center', + borderRadius: '100px', + px: '10px', + py: '10px', + + fontFamily: '$inter', + fontSize: '12px', + fontWeight: '500', + cursor: 'pointer', + color: '#EDEDED', + border: '1px solid $thLibraryMultiselectHover', + bg: 'transparent', + '&:hover': { + opacity: '0.6', + border: '1px solid $ctaBlue', + }, + }, ctaDarkYellow: { border: '1px solid transparent', diff --git a/packages/web/components/elements/FormElements.tsx b/packages/web/components/elements/FormElements.tsx index 1d22715dd..d2e3c7691 100644 --- a/packages/web/components/elements/FormElements.tsx +++ b/packages/web/components/elements/FormElements.tsx @@ -36,6 +36,7 @@ export const FormInput = styled('input', { textIndent: '8px', marginBottom: '2px', height: '38px', + pl: '10px', color: '$grayTextContrast', '&:focus': { outline: 'none', diff --git a/packages/web/components/elements/icons/AIPromotIcon.tsx b/packages/web/components/elements/icons/AIPromotIcon.tsx new file mode 100644 index 000000000..7f2fe48a1 --- /dev/null +++ b/packages/web/components/elements/icons/AIPromotIcon.tsx @@ -0,0 +1,53 @@ +/* eslint-disable functional/no-class */ +/* eslint-disable functional/no-this-expression */ +import { IconProps } from './IconProps' + +import React from 'react' + +export class AIPromptIcon extends React.Component { + render() { + return ( + + + + + + + + + ) + } +} diff --git a/packages/web/components/elements/icons/BrowserIcon.tsx b/packages/web/components/elements/icons/BrowserIcon.tsx new file mode 100644 index 000000000..33db6004f --- /dev/null +++ b/packages/web/components/elements/icons/BrowserIcon.tsx @@ -0,0 +1,61 @@ +/* eslint-disable functional/no-class */ +/* eslint-disable functional/no-this-expression */ +import { IconProps } from './IconProps' + +import React from 'react' + +export class BrowserIcon extends React.Component { + render() { + const size = (this.props.size || 26).toString() + const color = (this.props.color || '#2A2A2A').toString() + + return ( + + + + + + + + + + + ) + } +} diff --git a/packages/web/components/elements/icons/HeaderCheckboxIcon.tsx b/packages/web/components/elements/icons/HeaderCheckboxIcon.tsx index 265b7bfd7..c7cf3397c 100644 --- a/packages/web/components/elements/icons/HeaderCheckboxIcon.tsx +++ b/packages/web/components/elements/icons/HeaderCheckboxIcon.tsx @@ -1,7 +1,6 @@ /* eslint-disable functional/no-class */ /* eslint-disable functional/no-this-expression */ import { IconProps } from './IconProps' -import { SpanBox } from '../LayoutPrimitives' import React from 'react' import { MultiSelectMode } from '../../templates/homeFeed/LibraryHeader' @@ -16,147 +15,112 @@ export const HeaderCheckboxIcon = ( switch (props.multiSelectMode) { case 'search': case 'visible': - return + return case 'none': case 'off': - return + return case 'some': - return + return } } export class HeaderCheckboxUncheckedIcon extends React.Component { render() { + const size = (this.props.size || 26).toString() return ( - - - + - - - - - + + ) } } export class HeaderCheckboxCheckedIcon extends React.Component { render() { + const size = (this.props.size || 26).toString() return ( - - - + - - + + ) } } export class HeaderCheckboxHalfCheckedIcon extends React.Component { render() { + const size = (this.props.size || 26).toString() return ( - - - + - - - - - + + + ) } } diff --git a/packages/web/components/elements/icons/HeaderToggleTLDRIcon.tsx b/packages/web/components/elements/icons/HeaderToggleTLDRIcon.tsx new file mode 100644 index 000000000..0f4e1fbcb --- /dev/null +++ b/packages/web/components/elements/icons/HeaderToggleTLDRIcon.tsx @@ -0,0 +1,111 @@ +/* eslint-disable functional/no-class */ +/* eslint-disable functional/no-this-expression */ +import { SpanBox } from '../LayoutPrimitives' +import { IconProps } from './IconProps' + +import React from 'react' + +export class HeaderToggleTLDRIcon extends React.Component { + render() { + return ( + + + + + + + + {/* + + + + + + + + + */} + + ) + } +} diff --git a/packages/web/components/patterns/HighlightHoverActions.tsx b/packages/web/components/patterns/HighlightHoverActions.tsx index d027c879b..4903fa116 100644 --- a/packages/web/components/patterns/HighlightHoverActions.tsx +++ b/packages/web/components/patterns/HighlightHoverActions.tsx @@ -28,7 +28,7 @@ export const HighlightHoverActions = (props: HighlightHoverActionsProps) => { - {!isTouchScreenDevice() && ( + {!isTouchScreenDevice() && props.multiSelectMode == 'off' && ( { position: 'absolute', top: 0, left: 0, - m: '10px', + m: '12px', lineHeight: '1', }} > diff --git a/packages/web/components/patterns/LibraryCards/LibraryHoverActions.tsx b/packages/web/components/patterns/LibraryCards/LibraryHoverActions.tsx index eb1724bba..3d357749f 100644 --- a/packages/web/components/patterns/LibraryCards/LibraryHoverActions.tsx +++ b/packages/web/components/patterns/LibraryCards/LibraryHoverActions.tsx @@ -12,6 +12,7 @@ import { NotebookIcon } from '../../elements/icons/NotebookIcon' import { TrashIcon } from '../../elements/icons/TrashIcon' import { LabelIcon } from '../../elements/icons/LabelIcon' import { UnarchiveIcon } from '../../elements/icons/UnarchiveIcon' +import { BrowserIcon } from '../../elements/icons/BrowserIcon' type LibraryHoverActionsProps = { viewer: UserBasicData @@ -31,7 +32,7 @@ export const LibraryHoverActions = (props: LibraryHoverActionsProps) => { overflow: 'clip', height: '33px', - width: '184px', + width: '200px', bg: '$thBackground', display: 'flex', @@ -120,7 +121,10 @@ export const LibraryHoverActions = (props: LibraryHoverActionsProps) => { event.stopPropagation() }} > - + - {!isTouchScreenDevice() && ( + {!isTouchScreenDevice() && props.multiSelectMode == 'off' && ( diff --git a/packages/web/components/templates/article/AISummary.tsx b/packages/web/components/templates/article/AISummary.tsx new file mode 100644 index 000000000..ac89fdeab --- /dev/null +++ b/packages/web/components/templates/article/AISummary.tsx @@ -0,0 +1,62 @@ +import { useGetAISummary } from '../../../lib/networking/queries/useGetAISummary' +import { SpanBox, VStack } from '../../elements/LayoutPrimitives' +import { AIPromptIcon } from '../../elements/icons/AIPromotIcon' + +type AISummaryProps = { + idx: string + libraryItemId: string + + fontFamily: string + fontSize: number + lineHeight: number + readerFontColor: string +} + +export const AISummary = (props: AISummaryProps): JSX.Element => { + const aisummary = useGetAISummary({ + idx: props.idx, + libraryItemId: props.libraryItemId, + }) + + if (!aisummary.summary) { + return <> + } + + return ( + + + AI Summary + + {aisummary.summary} + + + + + ) +} diff --git a/packages/web/components/templates/article/ArticleContainer.tsx b/packages/web/components/templates/article/ArticleContainer.tsx index 91e5cdb1d..31299f316 100644 --- a/packages/web/components/templates/article/ArticleContainer.tsx +++ b/packages/web/components/templates/article/ArticleContainer.tsx @@ -19,6 +19,8 @@ import { Label } from '../../../lib/networking/fragments/labelFragment' import { Recommendation } from '../../../lib/networking/queries/useGetLibraryItemsQuery' import { Avatar } from '../../elements/Avatar' import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery' +import { AISummary } from './AISummary' +import { userHasFeature } from '../../../lib/featureFlag' type ArticleContainerProps = { viewer: UserBasicData @@ -444,6 +446,16 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element { recommendationsWithNotes={recommendationsWithNotes} /> )} + {userHasFeature(props.viewer, 'ai-summaries') && ( + + )}
{ if (!query.startsWith('#')) return @@ -140,11 +141,17 @@ export function HomeFeedContainer(): JSX.Element { }, [queryValue]) useEffect(() => { + console.log('ueryInputs.searchQuery', queryInputs.searchQuery) if ( queryInputs.searchQuery && queryInputs.searchQuery?.indexOf('mode:highlights') > -1 ) { setMode('highlights') + } else if ( + queryInputs.searchQuery && + queryInputs.searchQuery?.indexOf('mode:tldr') > -1 + ) { + setMode('tldr') } else { setMode('reads') } @@ -218,7 +225,6 @@ export function HomeFeedContainer(): JSX.Element { const updatedArticle = { ...item } updatedArticle.node = { ...item.node } updatedArticle.isLoading = false - console.log(`Updating Metadata of ${item.node.slug}.`) performActionOnItem('update-item', updatedArticle) return } @@ -975,17 +981,20 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { {props.mode != 'highlights' && ( { props.applySearchQuery(searchQuery) }} + mode={props.mode} + setMode={props.setMode} showFilterMenu={showFilterMenu} setShowFilterMenu={setShowFilterMenu} multiSelectMode={props.multiSelectMode} @@ -1048,6 +1057,10 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { /> )} + {showItems && props.mode == 'tldr' && ( + + )} + {props.showAddLinkModal && ( void @@ -34,6 +32,9 @@ type LibraryHeaderProps = { showFilterMenu: boolean setShowFilterMenu: (show: boolean) => void + mode: LibraryMode + setMode: (set: LibraryMode) => void + numItemsSelected: number multiSelectMode: MultiSelectMode setMultiSelectMode: (mode: MultiSelectMode) => void @@ -81,18 +82,21 @@ export function LibraryHeader(props: LibraryHeaderProps): JSX.Element { return ( <> {props.multiSelectMode !== 'off' ? ( <> - ) : ( @@ -131,33 +134,7 @@ function LargeHeaderLayout(props: LibraryHeaderProps): JSX.Element { ) } -const CheckBoxButton = (props: LibraryHeaderProps): JSX.Element => { - return ( - - ) -} - const HeaderControls = (props: LibraryHeaderProps): JSX.Element => { - const [showSearchBar, setShowSearchBar] = useState(false) return ( <> { > - - {showSearchBar ? ( - - ) : ( + + + + {userHasFeature(props.viewer, 'ai-summaries') && ( + + )} + - )} - - + ) } @@ -257,31 +243,15 @@ export function MenuHeaderButton(props: MenuHeaderButtonProps): JSX.Element { ) } -export type SearchBoxProps = { - searchTerm: string | undefined - applySearchQuery: (searchQuery: string) => void - setShowSearchBar: (show: boolean) => void - - compact?: boolean - onClose?: () => void -} - -export function SearchBox(props: SearchBoxProps): JSX.Element { +export function SearchBox(props: LibraryHeaderProps): JSX.Element { const inputRef = useRef(null) const [focused, setFocused] = useState(false) const [searchTerm, setSearchTerm] = useState(props.searchTerm ?? '') - const [isAddAction, setIsAddAction] = useState(false) - const IS_URL_REGEX = - /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/ useEffect(() => { setSearchTerm(props.searchTerm ?? '') }, [props.searchTerm]) - useEffect(() => { - setIsAddAction(IS_URL_REGEX.test(searchTerm)) - }, [searchTerm, props.searchTerm]) - useKeyboardShortcuts( searchBarCommands((action) => { if (action === 'focusSearchBar' && inputRef.current) { @@ -301,10 +271,7 @@ export function SearchBox(props: SearchBoxProps): JSX.Element { width: '100%', maxWidth: '521px', bg: '$thLibrarySearchbox', - borderRadius: '100px', - border: focused - ? '2px solid $searchActiveOutline' - : '2px solid transparent', + borderRadius: '6px', boxShadow: focused ? 'none' : '0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);', @@ -317,251 +284,126 @@ export function SearchBox(props: SearchBoxProps): JSX.Element { > { - inputRef.current?.focus() - e.preventDefault() + display: 'flex', + bg: props.multiSelectMode !== 'off' ? '$ctaBlue' : 'transparent', + borderTopLeftRadius: '6px', + borderBottomLeftRadius: '6px', + '--checkbox-color': 'var(--colors-thLibraryMultiselectCheckbox)', + '&:hover': { + bg: '$thLibraryMultiselectHover', + '--checkbox-color': + 'var(--colors-thLibraryMultiselectCheckboxHover)', + }, }} > - {(() => { - if (isAddAction) { - return ( - - ) - } - - return ( - - ) - })()} + -
{ - event.preventDefault() - props.applySearchQuery(searchTerm || '') - inputRef.current?.blur() - if (props.onClose) { - props.onClose() - } - }} - style={{ width: '100%' }} - > - { - event.target.select() - setFocused(true) - }} - onBlur={() => { - setFocused(false) - }} - onChange={(event) => { - setSearchTerm(event.target.value) - }} - onKeyDown={(event) => { - const key = event.key.toLowerCase() - if (key == 'escape') { - event.currentTarget.blur() - } - }} - /> - - {/* */} - { - if (searchTerm && searchTerm.length && searchTerm != 'in:inbox') { - event.preventDefault() + { + event.target.select() + setFocused(true) + }} + onBlur={() => { + setFocused(false) + }} + onChange={(event) => { + setSearchTerm(event.target.value) + }} + onKeyDown={(event) => { + const key = event.key.toLowerCase() + if (key == 'escape') { + event.currentTarget.blur() + } + }} + /> + + + { setSearchTerm('in:inbox') props.applySearchQuery('') - } else { - props.setShowSearchBar(false) - } - }} - tabIndex={-1} - > - - + ) } -type ControlButtonBoxProps = { - layout: LayoutType - updateLayout: (layout: LayoutType) => void - setShowInlineSearch?: (show: boolean) => void - - numItemsSelected: number - multiSelectMode: MultiSelectMode - setMultiSelectMode: (mode: MultiSelectMode) => void - - performMultiSelectAction: (action: BulkAction, labelIds?: string[]) => void +type CancelSearchButtonProps = { + onClick: () => void } -function MultiSelectControls(props: ControlButtonBoxProps): JSX.Element { - const [showConfirmDelete, setShowConfirmDelete] = useState(false) - const [showLabelsModal, setShowLabelsModal] = useState(false) - const compact = false - +const CancelSearchButton = (props: CancelSearchButtonProps): JSX.Element => { + const [color, setColor] = useState( + theme.colors.thTextContrast2.toString() + ) return ( - { + setColor('white') + event.preventDefault() + }} + onMouseLeave={(event) => { + setColor(theme.colors.thTextContrast2.toString()) + event.preventDefault() + }} + onClick={(event) => { + event.preventDefault() + props.onClick() }} > - { - e.preventDefault() - }} - > - - {props.numItemsSelected} items selected - - - - - {showConfirmDelete && ( - { - props.performMultiSelectAction(BulkAction.DELETE) - }} - onOpenChange={(open: boolean) => { - setShowConfirmDelete(false) - }} - /> - )} - {showLabelsModal && ( - { - const labelIds = labels.map((l) => l.id) - props.performMultiSelectAction(BulkAction.ADD_LABELS, labelIds) - }} - onOpenChange={(open: boolean) => { - setShowLabelsModal(false) - }} - /> - )} - - - + + ) } diff --git a/packages/web/components/templates/homeFeed/MultiSelectControls.tsx b/packages/web/components/templates/homeFeed/MultiSelectControls.tsx new file mode 100644 index 000000000..4361bef88 --- /dev/null +++ b/packages/web/components/templates/homeFeed/MultiSelectControls.tsx @@ -0,0 +1,301 @@ +import { useState } from 'react' +import { theme } from '../../tokens/stitches.config' +import { Box, HStack, SpanBox } from '../../elements/LayoutPrimitives' +import { Button } from '../../elements/Button' +import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation' +import { ArchiveIcon } from '../../elements/icons/ArchiveIcon' +import { LabelIcon } from '../../elements/icons/LabelIcon' +import { TrashIcon } from '../../elements/icons/TrashIcon' +import { ConfirmationModal } from '../../patterns/ConfirmationModal' +import { AddBulkLabelsModal } from '../article/AddBulkLabelsModal' +import { X } from 'phosphor-react' +import { LibraryHeaderProps } from './LibraryHeader' +import { HeaderCheckboxIcon } from '../../elements/icons/HeaderCheckboxIcon' +import { Label } from '../../../lib/networking/fragments/labelFragment' + +export const MultiSelectControls = (props: LibraryHeaderProps): JSX.Element => { + const [showConfirmDelete, setShowConfirmDelete] = useState(false) + const [showLabelsModal, setShowLabelsModal] = useState(false) + // Don't change on immediate hover, the button has to be blurred at least once + const [hoveredOut, setHoveredOut] = useState(false) + const [hoverColor, setHoverColor] = useState( + theme.colors.thTextContrast2.toString() + ) + const compact = false + + return ( + { + setHoveredOut(true) + event.preventDefault() + }} + > + { + e.preventDefault() + }} + > + + + + + + {props.numItemsSelected} items selected + + + + + {showConfirmDelete && ( + { + props.performMultiSelectAction(BulkAction.DELETE) + }} + onOpenChange={(open: boolean) => { + setShowConfirmDelete(false) + }} + /> + )} + {showLabelsModal && ( + { + const labelIds = labels.map((l) => l.id) + props.performMultiSelectAction(BulkAction.ADD_LABELS, labelIds) + }} + onOpenChange={(open: boolean) => { + setShowLabelsModal(false) + }} + /> + )} + + + + + ) +} + +export const CheckBoxButton = (props: LibraryHeaderProps): JSX.Element => { + return ( + + ) +} + +export const ArchiveButton = (props: LibraryHeaderProps): JSX.Element => { + const [color, setColor] = useState( + theme.colors.thTextContrast2.toString() + ) + return ( + + ) +} + +type AddLabelsButtonProps = { + setShowLabelsModal: (set: boolean) => void +} + +export const AddLabelsButton = (props: AddLabelsButtonProps): JSX.Element => { + const [color, setColor] = useState( + theme.colors.thTextContrast2.toString() + ) + return ( + + ) +} + +type RemoveItemsButtonProps = { + setShowConfirmDelete: (set: boolean) => void +} + +export const RemoveItemsButton = ( + props: RemoveItemsButtonProps +): JSX.Element => { + const [color, setColor] = useState( + theme.colors.thTextContrast2.toString() + ) + return ( + + ) +} + +export const CancelButton = (props: LibraryHeaderProps): JSX.Element => { + const [color, setColor] = useState( + theme.colors.thTextContrast2.toString() + ) + return ( + + ) +} diff --git a/packages/web/components/templates/homeFeed/TLDRLayout.tsx b/packages/web/components/templates/homeFeed/TLDRLayout.tsx new file mode 100644 index 000000000..53eafdc00 --- /dev/null +++ b/packages/web/components/templates/homeFeed/TLDRLayout.tsx @@ -0,0 +1,296 @@ +import { LayoutType } from './HomeFeedContainer' +import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery' +import { LibraryItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery' +import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' +import { Toaster } from 'react-hot-toast' +import TopBarProgress from 'react-topbar-progress-indicator' +import { StyledText } from '../../elements/StyledText' +import { Button } from '../../elements/Button' +import { ArchiveIcon } from '../../elements/icons/ArchiveIcon' +import { TrashIcon } from '../../elements/icons/TrashIcon' +import { BrowserIcon } from '../../elements/icons/BrowserIcon' +import { styled } from '@stitches/react' +import { siteName } from '../../patterns/LibraryCards/LibraryCardStyles' +import { theme } from '../../tokens/stitches.config' +import { DotsThree } from 'phosphor-react' +import { useState } from 'react' + +type TLDRLayoutProps = { + layout: LayoutType + viewer?: UserBasicData + + items: LibraryItem[] + isValidating: boolean + + hasMore: boolean + totalItems: number + + loadMore: () => void +} + +const SiteIcon = styled('img', { + width: '22px', + height: '22px', + borderRadius: '100px', +}) + +export function TLDRLayout(props: TLDRLayoutProps): JSX.Element { + return ( + <> + + + + {props.isValidating && props.items.length == 0 && } + + {props.items.map((item) => { + const source = siteName( + item.node.originalArticleUrl, + item.node.url, + item.node.siteName + ) + return ( + + + + + + {source && ( + + {item.node.siteName} + + )} + {source && item.node.author && ( + + • + + )} + {item.node.author && ( + + {item.node.author} + + )} + + + + + + + + {item.node.title} + + + {item.node.aiSummary} + + + + + + + + + + ) + })} + + + {props.hasMore ? ( + + ) : ( + + )} + + + + ) +} + +const ArchiveButton = (): JSX.Element => { + const [foreground, setForegroundColor] = useState( + theme.colors.thTextContrast2.toString() + ) + return ( + + ) +} + +const RemoveButton = (): JSX.Element => { + const [foreground, setForegroundColor] = useState( + theme.colors.thTextContrast2.toString() + ) + return ( + + ) +} + +const OpenOriginalButton = (): JSX.Element => { + const [foreground, setForegroundColor] = useState( + theme.colors.thTextContrast2.toString() + ) + return ( + + ) +} diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index 693030058..85baec7a1 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -27,10 +27,7 @@ import { HighlightsIcon } from '../../elements/icons/HighlightsIcon' import { CoverImage } from '../../elements/CoverImage' import { Shortcut } from '../../../pages/settings/shortcuts' import { OutlinedLabelChip } from '../../elements/OutlinedLabelChip' -import { NewsletterFlairIcon } from '../../elements/icons/NewsletterFlairIcon' -import { FeedFlairIcon } from '../../elements/icons/FeedFlairIcon' import { NewsletterIcon } from '../../elements/icons/NewsletterIcon' -import { DropdownMenu } from '@radix-ui/react-dropdown-menu' import { Dropdown, DropdownOption } from '../../elements/DropdownElements' import { useRouter } from 'next/router' diff --git a/packages/web/components/templates/navMenu/SettingsDropdown.tsx b/packages/web/components/templates/navMenu/SettingsDropdown.tsx index 11e9dbf1a..a54da2f9e 100644 --- a/packages/web/components/templates/navMenu/SettingsDropdown.tsx +++ b/packages/web/components/templates/navMenu/SettingsDropdown.tsx @@ -1,4 +1,3 @@ -import { DropdownMenu } from '@radix-ui/react-dropdown-menu' import { SETTINGS_SECTION_1, SETTINGS_SECTION_2 } from './SettingsMenu' import { Dropdown, diff --git a/packages/web/components/templates/navMenu/SettingsMenu.tsx b/packages/web/components/templates/navMenu/SettingsMenu.tsx index e0bd60c5e..3a06506ce 100644 --- a/packages/web/components/templates/navMenu/SettingsMenu.tsx +++ b/packages/web/components/templates/navMenu/SettingsMenu.tsx @@ -16,7 +16,7 @@ export const SETTINGS_SECTION_1 = [ { name: 'Feeds', destination: '/settings/feeds' }, { name: 'Subscriptions', destination: '/settings/subscriptions' }, { name: 'Labels', destination: '/settings/labels' }, - { name: 'Shortcuts', destination: '/settings/shortcuts' }, + // { name: 'Shortcuts', destination: '/settings/shortcuts' }, { name: 'Saved Searches', destination: '/settings/saved-searches' }, { name: 'Pinned Searches', destination: '/settings/pinned-searches' }, ] diff --git a/packages/web/components/tokens/stitches.config.ts b/packages/web/components/tokens/stitches.config.ts index 8369b93cc..d863bf16d 100644 --- a/packages/web/components/tokens/stitches.config.ts +++ b/packages/web/components/tokens/stitches.config.ts @@ -185,10 +185,16 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } = thLibrarySelectionColor: '#FFEA9F', thLibraryNavigationMenuFooter: '#EFEADE', thLibraryMenuFooterHover: '#FFFFFF', + thLibraryMultiselectHover: '#D9D9D9', + thLibraryMultiselectCheckbox: '#3D3D3D', + thLibraryMultiselectCheckboxHover: '#3D3D3D', + + thTLDRText: '#434343', + thFormInput: '#EBEBEB', thHomeIcon: '#2A2A2A', - thLabelChipForeground: '#2A2A2A', // : '#2A2A2A' + thLabelChipForeground: '#2A2A2A', thLabelChipBackground: '#F5F5F5', thLabelChipSelectedBorder: 'black', thLabelChipUnselectedBorder: '#F5F5F5', @@ -218,6 +224,9 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } = thHighContrast: '#3D3D3D', thHighlightBar: '#D9D9D9', + thLibraryAISummaryBorder: '#6A6968', + thLibraryAISummaryBackground: '#343434', + thFallbackImageForeground: '#2A2A2A', thFallbackImageBackground: '#EDEDED', @@ -316,6 +325,12 @@ const darkThemeSpec = { thLibrarySelectionColor: '#6A6968', thLibraryNavigationMenuFooter: '#3D3D3D', thLibraryMenuFooterHover: '#6A6968', + thLibraryMultiselectHover: '#6A6968', + thLibraryMultiselectCheckbox: 'white', + thLibraryMultiselectCheckboxHover: 'white', + + thTLDRText: '#D9D9D9', + searchActiveOutline: '#866D15', thFormInput: '#3D3D3D', thHomeIcon: '#FFFFFF', @@ -352,6 +367,9 @@ const darkThemeSpec = { thHighlightBar: '#6A6968', + thLibraryAISummaryBorder: '#6A6968', + thLibraryAISummaryBackground: '#343434', + thFallbackImageForeground: '#FEFFFF', thFallbackImageBackground: '#3C3C3C', diff --git a/packages/web/lib/featureFlag.ts b/packages/web/lib/featureFlag.ts index ab7258a5c..5eb198e11 100644 --- a/packages/web/lib/featureFlag.ts +++ b/packages/web/lib/featureFlag.ts @@ -1,7 +1,17 @@ -import { UserBasicData } from "./networking/queries/useGetViewerQuery" +import { UserBasicData } from './networking/queries/useGetViewerQuery' const VIP_USERS = ['jacksonh', 'satindar', 'hongbo', 'nat'] export const isVipUser = (user: UserBasicData): boolean => { return VIP_USERS.includes(user.profile.username) } + +export const userHasFeature = ( + user: UserBasicData | undefined, + feature: string +): boolean => { + if (!user) { + return false + } + return user.features.includes(feature) +} diff --git a/packages/web/lib/hooks/useSetHighlightLabels.tsx b/packages/web/lib/hooks/useSetHighlightLabels.tsx index 2e1567d5e..71d199437 100644 --- a/packages/web/lib/hooks/useSetHighlightLabels.tsx +++ b/packages/web/lib/hooks/useSetHighlightLabels.tsx @@ -3,7 +3,6 @@ import { Label } from '../networking/fragments/labelFragment' import { showErrorToast } from '../toastHelpers' import { setLabelsForHighlight } from '../networking/mutations/setLabelsForHighlight' import { LabelsDispatcher } from './useSetPageLabels' -import { Highlight } from '../networking/fragments/highlightFragment' export const useSetHighlightLabels = ( highlightId?: string diff --git a/packages/web/lib/networking/networkHelpers.ts b/packages/web/lib/networking/networkHelpers.ts index e5ffa3325..4e0c02e98 100644 --- a/packages/web/lib/networking/networkHelpers.ts +++ b/packages/web/lib/networking/networkHelpers.ts @@ -60,6 +60,17 @@ export function gqlFetcher( return graphQLClient.request(query, variables, requestHeaders()) } +export function apiFetcher(path: string): Promise { + const url = new URL(path, fetchEndpoint) + return fetch(url.toString(), { + headers: requestHeaders(), + credentials: 'include', + mode: 'cors', + }).then((result) => { + return result.json() + }) +} + export function makePublicGqlFetcher( variables?: unknown ): (query: string) => Promise { diff --git a/packages/web/lib/networking/queries/useGetAISummary.tsx b/packages/web/lib/networking/queries/useGetAISummary.tsx new file mode 100644 index 000000000..32c337150 --- /dev/null +++ b/packages/web/lib/networking/queries/useGetAISummary.tsx @@ -0,0 +1,41 @@ +import useSWR, { Fetcher } from 'swr' +import { apiFetcher } from '../networkHelpers' + +export interface AISummary { + id: string + summary: string +} + +export interface AISummaryResponse { + error: any + isValidating: boolean + summary: string | undefined +} + +export function useGetAISummary(params: { + idx: string + libraryItemId: string +}): AISummaryResponse { + const { idx, libraryItemId } = params + const { data, error, isValidating } = useSWR( + `/api/ai-summary/library-item/${libraryItemId}/${idx}`, + apiFetcher + ) + + try { + const result = data as AISummary + console.log('ai summary result: ', result) + return { + error, + isValidating, + summary: result.summary, + } + } catch (error) { + console.log('error', error) + return { + error, + isValidating: false, + summary: undefined, + } + } +} diff --git a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx index 9f9257fcf..5cdb59843 100644 --- a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx +++ b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx @@ -95,10 +95,12 @@ export type LibraryItemNode = { state: State pageType: PageType siteName?: string + siteIcon?: string subscription?: string readAt?: string savedAt?: string wordsCount?: number + aiSummary?: string recommendations?: Recommendation[] highlights?: Highlight[] } @@ -172,6 +174,7 @@ export function useGetLibraryItemsQuery({ ownedByViewer originalArticleUrl uploadFileId + aiSummary labels { id name @@ -183,6 +186,7 @@ export function useGetLibraryItemsQuery({ annotation state siteName + siteIcon subscription readAt savedAt diff --git a/packages/web/lib/networking/queries/useGetViewerQuery.tsx b/packages/web/lib/networking/queries/useGetViewerQuery.tsx index ffd2303c3..88beccd8b 100644 --- a/packages/web/lib/networking/queries/useGetViewerQuery.tsx +++ b/packages/web/lib/networking/queries/useGetViewerQuery.tsx @@ -20,6 +20,7 @@ export type UserBasicData = { email: string source: string intercomHash: string + features: string[] } export type UserProfile = { @@ -45,6 +46,7 @@ export function useGetViewerQuery(): ViewerQueryResponse { email source intercomHash + features } } ` diff --git a/packages/web/pages/home.tsx b/packages/web/pages/home.tsx index 612d28d97..1935d846b 100644 --- a/packages/web/pages/home.tsx +++ b/packages/web/pages/home.tsx @@ -16,9 +16,14 @@ function LoadedContent(): JSX.Element { pageTestId="home-page-tag" > diff --git a/packages/web/pages/settings/account.tsx b/packages/web/pages/settings/account.tsx index 3d57d36e0..041cbdad9 100644 --- a/packages/web/pages/settings/account.tsx +++ b/packages/web/pages/settings/account.tsx @@ -406,6 +406,49 @@ export default function Account(): JSX.Element { {/* */}
+ + Beta features + {!isValidating && ( + <> + {viewerData?.me?.features.map((feature) => { + return ( + + + {feature} + + ) + })} + + To learn more about beta features available,{' '} + + join the Omnivore Discord + + + + )} + {/* */} + + User, (user) => user.articles, { eager: true }) + user!: User + + @Column('text') + name!: string + + @Column('timestamp', { nullable: true }) + grantedAt?: Date | null + + @Column('timestamp', { nullable: true }) + expiresAt?: Date | null + + @Column({ type: 'timestamp', name: 'created_at' }) + createdAt!: Date + + @Column({ type: 'timestamp', name: 'updated_at' }) + updatedAt!: Date +} diff --git a/pkg/admin/src/index.ts b/pkg/admin/src/index.ts index 20875919c..73740109c 100644 --- a/pkg/admin/src/index.ts +++ b/pkg/admin/src/index.ts @@ -15,6 +15,7 @@ import { LibraryItem, Recommendation, GroupMembership, + Feature, } from './db' import { compare, hashSync } from 'bcryptjs' const readYamlFile = require('read-yaml-file') @@ -50,6 +51,7 @@ const ADMIN_USER_EMAIL = }, { resource: Recommendation, options: { parent: { name: 'Users' } } }, { resource: GroupMembership, options: { parent: { name: 'Users' } } }, + { resource: Feature, options: { parent: { name: 'Users' } } }, ], }) diff --git a/yarn.lock b/yarn.lock index b442a9303..61adde55a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,6 +50,21 @@ lodash "^4.17.21" resize-observer-polyfill "^1.5.1" +"@anthropic-ai/sdk@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.9.1.tgz#b2d2b7bf05c90dce502c9a2e869066870f69ba88" + integrity sha512-wa1meQ2WSfoY8Uor3EdrJq0jTiZJoKoSii2ZVWRY1oN4Tlr5s59pADg9T79FTbPe1/se5c3pBeZgJL63wmuoBA== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + digest-fetch "^1.3.0" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + web-streams-polyfill "^3.2.1" + "@apollo/protobufjs@1.2.6": version "1.2.6" resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.6.tgz#d601e65211e06ae1432bf5993a1a0105f2862f27" @@ -3601,6 +3616,57 @@ dependencies: lodash "^4.17.21" +"@langchain/community@~0.0.33": + version "0.0.33" + resolved "https://registry.yarnpkg.com/@langchain/community/-/community-0.0.33.tgz#5568fe36b1e2f8947d49414d47e14a27da5b65c9" + integrity sha512-m7KfOB2t/6ZQkx29FcqHeOe+jxQZDJdRqpMsCAxRebCaIUiAwNJgRqqukQOcDsG574jhEyEMYuYDfImfXSY7aw== + dependencies: + "@langchain/core" "~0.1.36" + "@langchain/openai" "~0.0.14" + flat "^5.0.2" + langsmith "~0.1.1" + uuid "^9.0.0" + zod "^3.22.3" + +"@langchain/core@~0.1.13", "@langchain/core@~0.1.36", "@langchain/core@~0.1.39": + version "0.1.39" + resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.1.39.tgz#c9b993f857d935afe1b66d1cc001805f1b7db8a0" + integrity sha512-bhsMOSLPkWbVZuE3uPd9dgiOeqFwexU7IGfjWht+mWrAW9spuBtAOKOcrmxK3v5TYaKpoDbIiH641FobyD947g== + dependencies: + ansi-styles "^5.0.0" + camelcase "6" + decamelize "1.2.0" + js-tiktoken "^1.0.8" + langsmith "~0.1.7" + ml-distance "^4.0.0" + p-queue "^6.6.2" + p-retry "4" + uuid "^9.0.0" + zod "^3.22.4" + zod-to-json-schema "^3.22.3" + +"@langchain/openai@^0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@langchain/openai/-/openai-0.0.14.tgz#27a6ba83f6b754391868b22f3b90cd440038acf0" + integrity sha512-co6nRylPrLGY/C3JYxhHt6cxLq07P086O7K3QaZH7SFFErIN9wSzJonpvhZR07DEUq6eK6wKgh2ORxA/NcjSRQ== + dependencies: + "@langchain/core" "~0.1.13" + js-tiktoken "^1.0.7" + openai "^4.26.0" + zod "^3.22.4" + zod-to-json-schema "^3.22.3" + +"@langchain/openai@~0.0.14": + version "0.0.15" + resolved "https://registry.yarnpkg.com/@langchain/openai/-/openai-0.0.15.tgz#541271bb36066422957ac7f822ca2a45281630b1" + integrity sha512-ILecml9YopmQxfpaquYEG+KfEz7svJqpcla671J1tVngqtPwRqg9PLUOa2eDrPsunScxKUeLd8HjAGLd/eaefQ== + dependencies: + "@langchain/core" "~0.1.39" + js-tiktoken "^1.0.7" + openai "^4.26.0" + zod "^3.22.4" + zod-to-json-schema "^3.22.3" + "@lerna/child-process@7.4.1": version "7.4.1" resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-7.4.1.tgz#efacbbe79794ef977feb86873d853bb8708707be" @@ -6012,6 +6078,11 @@ resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.3.tgz#1185726610acc37317ddab11c3c7f9066966bd20" integrity sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg== +"@sqltools/formatter@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" + integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw== + "@stitches/react@^1.2.5": version "1.2.8" resolved "https://registry.yarnpkg.com/@stitches/react/-/react-1.2.8.tgz#954f8008be8d9c65c4e58efa0937f32388ce3a38" @@ -7812,6 +7883,14 @@ "@types/node" "*" form-data "^3.0.0" +"@types/node-fetch@^2.6.4": + version "2.6.11" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24" + integrity sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g== + dependencies: + "@types/node" "*" + form-data "^4.0.0" + "@types/node-fetch@^2.6.6": version "2.6.7" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.7.tgz#a1abe2ce24228b58ad97f99480fdcf9bbc6ab16d" @@ -7840,6 +7919,20 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.68.tgz#3155f64a961b3d8d10246c80657f9a7292e3421a" integrity sha512-sG3hPIQwJLoewrN7cr0dwEy+yF5nD4D/4FxtQpFciRD/xwUzgD+G05uxZHv5mhfXo4F9Jkp13jjn0CC2q325sg== +"@types/node@^18.11.18": + version "18.19.19" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.19.tgz#fe8bdbf85683a461ad685ead52a57ab2ee2315e4" + integrity sha512-qqV6hSy9zACEhQUy5CEGeuXAZN0fNjqLWRIvOXOwdFYhFoKBiY08VKR5kgchr90+TitLVhpUEb54hk4bYaArUw== + dependencies: + undici-types "~5.26.4" + +"@types/node@^20.11.0": + version "20.11.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.21.tgz#ad67e65652f7be15686e2df87a38076a81c5e9c5" + integrity sha512-/ySDLGscFPNasfqStUuWWPfL78jompfIoVzLJPVVAHBh6rpG68+pI2Gk+fNLeI8/f1yPYL4s46EleVIc20F1Ow== + dependencies: + undici-types "~5.26.4" + "@types/nodemailer@^6.4.4": version "6.4.4" resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.4.tgz#c265f7e7a51df587597b3a49a023acaf0c741f4b" @@ -8006,6 +8099,11 @@ "@types/tough-cookie" "*" form-data "^2.5.0" +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + "@types/retry@^0.12.0": version "0.12.1" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" @@ -8231,6 +8329,11 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.0.tgz#53ef263e5239728b56096b0a869595135b7952d2" integrity sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q== +"@types/uuid@^9.0.1": + version "9.0.8" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" + integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== + "@types/voca@^1.4.0": version "1.4.2" resolved "https://registry.yarnpkg.com/@types/voca/-/voca-1.4.2.tgz#4d2ceef3582c2a1f6a1574f71f0897400d910583" @@ -9357,6 +9460,11 @@ app-root-path@^3.0.0: resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.0.0.tgz#210b6f43873227e18a4b810a032283311555d5ad" integrity sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw== +app-root-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86" + integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA== + apparatus@^0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/apparatus/-/apparatus-0.0.10.tgz#81ea756772ada77863db54ceee8202c109bdca3e" @@ -10255,7 +10363,12 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1: +base-64@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb" + integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA== + +base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -10381,6 +10494,11 @@ binary-extensions@^2.2.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +binary-search@^1.3.5: + version "1.3.6" + resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.6.tgz#e32426016a0c5092f0f3598836a1c7da3560565c" + integrity sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA== + binary@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" @@ -11011,6 +11129,11 @@ camelcase-keys@^7.0.0: quick-lru "^5.1.1" type-fest "^1.2.1" +camelcase@6, camelcase@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -11021,11 +11144,6 @@ camelcase@^6.0.0, camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -camelcase@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001251, caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001317: version "1.0.30001527" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001527.tgz" @@ -11288,6 +11406,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== + check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" @@ -11830,6 +11953,11 @@ command-score@^0.1.2: resolved "https://registry.yarnpkg.com/command-score/-/command-score-0.1.2.tgz#b986ad7e8c0beba17552a56636c44ae38363d381" integrity sha512-VtDvQpIJBvBatnONUsPzXYFVKQQAhuf3XTNOAsdBxCNO/QCtUUd8LSgjn0GVarBkCad6aJCZfXgrjYbl/KRr7w== +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + commander@^2.19.0, commander@^2.20.0, commander@^2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -12481,6 +12609,11 @@ cross-undici-fetch@^0.1.19: undici "^4.9.3" web-streams-polyfill "^3.2.0" +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -12790,7 +12923,7 @@ dateformat@^3.0.0, dateformat@^3.0.3: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dayjs@1.x, dayjs@^1.10.4, dayjs@^1.11.7: +dayjs@1.x, dayjs@^1.10.4, dayjs@^1.11.7, dayjs@^1.11.9: version "1.11.10" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== @@ -12841,7 +12974,7 @@ decamelize-keys@^1.1.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.1.0, decamelize@^1.2.0: +decamelize@1.2.0, decamelize@^1.1.0, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -13260,6 +13393,14 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +digest-fetch@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/digest-fetch/-/digest-fetch-1.3.0.tgz#898e69264d00012a23cf26e8a3e40320143fc661" + integrity sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA== + dependencies: + base-64 "^0.1.0" + md5 "^2.3.0" + dir-glob@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" @@ -13513,6 +13654,11 @@ dotenv@^16.0.1: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== +dotenv@^16.0.3: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + dotenv@^8.0.0, dotenv@^8.2.0: version "8.6.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" @@ -14783,6 +14929,11 @@ exponential-backoff@^3.1.1: resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== +expr-eval@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expr-eval/-/expr-eval-2.0.2.tgz#fa6f044a7b0c93fde830954eb9c5b0f7fbc7e201" + integrity sha512-4EMSHGOPSwAfBiibw3ndnP0AvjDWLsMvGOvWEZ2F96IGk0bIVdjQisOHxReSkE13mHcfbuCiXw+G4y0zv6N8Eg== + express-http-context2@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/express-http-context2/-/express-http-context2-1.0.0.tgz#58cd9fb0d233739e0dcd7aabb766d1dc74522d77" @@ -15491,6 +15642,11 @@ fork-ts-checker-webpack-plugin@^6.0.4: semver "^7.3.2" tapable "^1.0.0" +form-data-encoder@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" + integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== + form-data-encoder@^1.7.1: version "1.7.1" resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.1.tgz#ac80660e4f87ee0d3d3c3638b7da8278ddb8ec96" @@ -15545,6 +15701,14 @@ formdata-node@^4.3.1: node-domexception "1.0.0" web-streams-polyfill "4.0.0-beta.1" +formdata-node@^4.3.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" + integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.3" + formidable@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.0.1.tgz#4310bc7965d185536f9565184dee74fbb75557ff" @@ -16159,7 +16323,7 @@ glob@7.2.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glo once "^1.3.0" path-is-absolute "^1.0.0" -glob@^10.2.2: +glob@^10.2.2, glob@^10.3.10: version "10.3.10" resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== @@ -17759,6 +17923,11 @@ is-alphanumerical@^1.0.0: is-alphabetical "^1.0.0" is-decimal "^1.0.0" +is-any-array@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-any-array/-/is-any-array-2.0.1.tgz#9233242a9c098220290aa2ec28f82ca7fa79899e" + integrity sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ== + is-arguments@^1.0.4, is-arguments@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" @@ -17822,7 +17991,7 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@^1.0.2, is-buffer@^1.1.5: +is-buffer@^1.0.2, is-buffer@^1.1.5, is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -19150,6 +19319,13 @@ js-string-escape@^1.0.1: resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8= +js-tiktoken@^1.0.7, js-tiktoken@^1.0.8: + version "1.0.10" + resolved "https://registry.yarnpkg.com/js-tiktoken/-/js-tiktoken-1.0.10.tgz#2b343ec169399dcee8f9ef9807dbd4fafd3b30dc" + integrity sha512-ZoSxbGjvGyMT13x6ACo9ebhDha/0FHdKA+OsQcMOWcm1Zs7r90Rhk5lhERLzji+3rA7EKpXCgwXcM5fF3DMpdA== + dependencies: + base64-js "^1.5.1" + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -19401,6 +19577,11 @@ jsonparse@^1.2.0, jsonparse@^1.3.1: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= +jsonpointer@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" + integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== + jsonwebtoken@^8.5.1: version "8.5.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" @@ -19648,6 +19829,46 @@ kuler@^2.0.0: resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== +langchain@^0.1.21: + version "0.1.23" + resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.1.23.tgz#51eb49548c5514aefa2fca68509f424ead58ae04" + integrity sha512-IzvF0c+mi+6kqrC0akWOQnJ0ynwkuh4qhg5bpgr56mlQItaJimO87ujktgxrOb1xMVU7fF9Y52SNc2Kjg7ihJw== + dependencies: + "@anthropic-ai/sdk" "^0.9.1" + "@langchain/community" "~0.0.33" + "@langchain/core" "~0.1.36" + "@langchain/openai" "~0.0.14" + binary-extensions "^2.2.0" + expr-eval "^2.0.2" + js-tiktoken "^1.0.7" + js-yaml "^4.1.0" + jsonpointer "^5.0.1" + langchainhub "~0.0.8" + langsmith "~0.1.7" + ml-distance "^4.0.0" + openapi-types "^12.1.3" + p-retry "4" + uuid "^9.0.0" + yaml "^2.2.1" + zod "^3.22.4" + zod-to-json-schema "^3.22.3" + +langchainhub@~0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/langchainhub/-/langchainhub-0.0.8.tgz#fd4b96dc795e22e36c1a20bad31b61b0c33d3110" + integrity sha512-Woyb8YDHgqqTOZvWIbm2CaFDGfZ4NTSyXV687AG4vXEfoNo7cGQp7nhl7wL3ehenKWmNEmcxCLgOZzW8jE6lOQ== + +langsmith@~0.1.1, langsmith@~0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.1.8.tgz#a98c7c3d0ecbf6c44be4b30f3be66070c099b963" + integrity sha512-GMEPhUPmkOPUih2ho07kSMhHYpCDkavc6Zg0XgBjhLsYqYaobOxFFNyOc806jOvH2yw2tmiKLuiAdlQAVbDnHg== + dependencies: + "@types/uuid" "^9.0.1" + commander "^10.0.1" + p-queue "^6.6.2" + p-retry "4" + uuid "^9.0.0" + language-subtag-registry@~0.3.2: version "0.3.21" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" @@ -20837,6 +21058,15 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +md5@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + mdast-squeeze-paragraphs@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz#7c4c114679c3bee27ef10b58e2e015be79f1ef97" @@ -21829,11 +22059,52 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mkdirp@^2.1.3: + version "2.1.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" + integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== + mkdirp@~0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" integrity sha512-8OCq0De/h9ZxseqzCH8Kw/Filf5pF/vMI6+BH7Lu0jXz2pqYCjTAQRolSxRIi+Ax+oCCjlxoJMP0YQ4XlrQNHg== +ml-array-mean@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/ml-array-mean/-/ml-array-mean-1.1.6.tgz#d951a700dc8e3a17b3e0a583c2c64abd0c619c56" + integrity sha512-MIdf7Zc8HznwIisyiJGRH9tRigg3Yf4FldW8DxKxpCCv/g5CafTw0RRu51nojVEOXuCQC7DRVVu5c7XXO/5joQ== + dependencies: + ml-array-sum "^1.1.6" + +ml-array-sum@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/ml-array-sum/-/ml-array-sum-1.1.6.tgz#d1d89c20793cd29c37b09d40e85681aa4515a955" + integrity sha512-29mAh2GwH7ZmiRnup4UyibQZB9+ZLyMShvt4cH4eTK+cL2oEMIZFnSyB3SS8MlsTh6q/w/yh48KmqLxmovN4Dw== + dependencies: + is-any-array "^2.0.0" + +ml-distance-euclidean@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ml-distance-euclidean/-/ml-distance-euclidean-2.0.0.tgz#3a668d236649d1b8fec96380b9435c6f42c9a817" + integrity sha512-yC9/2o8QF0A3m/0IXqCTXCzz2pNEzvmcE/9HFKOZGnTjatvBbsn4lWYJkxENkA4Ug2fnYl7PXQxnPi21sgMy/Q== + +ml-distance@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/ml-distance/-/ml-distance-4.0.1.tgz#4741d17a1735888c5388823762271dfe604bd019" + integrity sha512-feZ5ziXs01zhyFUUUeZV5hwc0f5JW0Sh0ckU1koZe/wdVkJdGxcP06KNQuF0WBTj8FttQUzcvQcpcrOp/XrlEw== + dependencies: + ml-array-mean "^1.1.6" + ml-distance-euclidean "^2.0.0" + ml-tree-similarity "^1.0.0" + +ml-tree-similarity@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ml-tree-similarity/-/ml-tree-similarity-1.0.0.tgz#24705a107e32829e24d945e87219e892159c53f0" + integrity sha512-XJUyYqjSuUQkNQHMscr6tcjldsOoAekxADTplt40QKfwW6nd++1wHWV9AArl0Zvw/TIHgNaZZNvr8QGvE8wLRg== + dependencies: + binary-search "^1.3.5" + num-sort "^2.0.0" + mocha-unfunk-reporter@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/mocha-unfunk-reporter/-/mocha-unfunk-reporter-0.4.0.tgz#59eda97aec6ae6e26d7af4173490a65b7b082d20" @@ -22947,6 +23218,11 @@ nullthrows@^1.1.1: resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== +num-sort@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/num-sort/-/num-sort-2.1.0.tgz#1cbb37aed071329fdf41151258bc011898577a9b" + integrity sha512-1MQz1Ed8z2yckoBeSfkQHHO9K1yDRxxtotKSJ9yvcTUUxSvfvzEq5GwBrjjHEpMlq/k5gvXdmJ1SbYxWtpNoVg== + num2fraction@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" @@ -23365,6 +23641,26 @@ open@^8.4.0: is-docker "^2.1.1" is-wsl "^2.2.0" +openai@^4.26.0: + version "4.28.0" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.28.0.tgz#ded00e3d98c25758b5406c9675ec27a957e00930" + integrity sha512-JM8fhcpmpGN0vrUwGquYIzdcEQHtFuom6sRCbbCM6CfzZXNuRk33G7KfeRAIfnaCxSpzrP5iHtwJzIm6biUZ2Q== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + digest-fetch "^1.3.0" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + web-streams-polyfill "^3.2.1" + +openapi-types@^12.1.3: + version "12.1.3" + resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3" + integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== + opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -23603,7 +23899,7 @@ p-pipe@3.1.0: resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== -p-queue@6.6.2: +p-queue@6.6.2, p-queue@^6.6.2: version "6.6.2" resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== @@ -23621,6 +23917,14 @@ p-reduce@^3.0.0: resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-3.0.0.tgz#f11773794792974bd1f7a14c72934248abff4160" integrity sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q== +p-retry@4: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + p-retry@^4.5.0: version "4.6.1" resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" @@ -26047,6 +26351,14 @@ read-pkg@^7.1.0: parse-json "^5.2.0" type-fest "^2.0.0" +read-yaml-file@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/read-yaml-file/-/read-yaml-file-2.1.0.tgz#c5866712db9ef5343b4d02c2413bada53c41c4a9" + integrity sha512-UkRNRIwnhG+y7hpqnycCL/xbTk7+ia9VuVTC0S+zVbwd65DI9eUpRMfsWIGrCWxTU/mi+JW8cHQCrv+zfCbEPQ== + dependencies: + js-yaml "^4.0.0" + strip-bom "^4.0.0" + read@1, read@^1.0.7, read@~1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" @@ -26189,6 +26501,11 @@ reflect-metadata@^0.1.13: resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== +reflect-metadata@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.1.tgz#8d5513c0f5ef2b4b9c3865287f3c0940c1f67f74" + integrity sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw== + reflect.getprototypeof@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz#aaccbf41aca3821b87bb71d9dcbc7ad0ba50a3f3" @@ -29214,7 +29531,7 @@ tslib@^1.0.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0: +tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -29430,6 +29747,27 @@ typeorm-naming-strategies@^4.1.0: resolved "https://registry.yarnpkg.com/typeorm-naming-strategies/-/typeorm-naming-strategies-4.1.0.tgz#1ec6eb296c8d7b69bb06764d5b9083ff80e814a9" integrity sha512-vPekJXzZOTZrdDvTl1YoM+w+sUIfQHG4kZTpbFYoTsufyv9NIBRe4Q+PdzhEAFA2std3D9LZHEb1EjE9zhRpiQ== +typeorm@^0.3.19: + version "0.3.20" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.20.tgz#4b61d737c6fed4e9f63006f88d58a5e54816b7ab" + integrity sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q== + dependencies: + "@sqltools/formatter" "^1.2.5" + app-root-path "^3.1.0" + buffer "^6.0.3" + chalk "^4.1.2" + cli-highlight "^2.1.11" + dayjs "^1.11.9" + debug "^4.3.4" + dotenv "^16.0.3" + glob "^10.3.10" + mkdirp "^2.1.3" + reflect-metadata "^0.2.1" + sha.js "^2.4.11" + tslib "^2.5.0" + uuid "^9.0.0" + yargs "^17.6.2" + typeorm@^0.3.4: version "0.3.7" resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.7.tgz#5776ed5058f0acb75d64723b39ff458d21de64c1" @@ -29468,6 +29806,11 @@ typescript@^4.4.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + ua-parser-js@^0.7.30: version "0.7.33" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532" @@ -29546,6 +29889,11 @@ underscore@^1.13.4, underscore@^1.9.1: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.4.tgz#7886b46bbdf07f768e0052f1828e1dcab40c0dee" integrity sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + undici@^4.9.3: version "4.14.1" resolved "https://registry.yarnpkg.com/undici/-/undici-4.14.1.tgz#7633b143a8a10d6d63335e00511d071e8d52a1d9" @@ -30309,11 +30657,21 @@ web-streams-polyfill@4.0.0-beta.1: resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.1.tgz#3b19b9817374b7cee06d374ba7eeb3aeb80e8c95" integrity sha512-3ux37gEX670UUphBF9AMCq8XM6iQ8Ac6A+DSRRjDoRBm1ufCkaCDdNVbaqq60PsEkdNlLKrGtv/YBP4EJXqNtQ== +web-streams-polyfill@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" + integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== + web-streams-polyfill@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965" integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA== +web-streams-polyfill@^3.2.1: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -31061,6 +31419,11 @@ yaml@^1.7.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.2.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.0.tgz#2376db1083d157f4b3a452995803dbcf43b08140" + integrity sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ== + yargs-parser@20.2.4: version "20.2.4" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" @@ -31207,6 +31570,16 @@ yup@^0.31.0: property-expr "^2.0.4" toposort "^2.0.2" +zod-to-json-schema@^3.22.3: + version "3.22.4" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.22.4.tgz#f8cc691f6043e9084375e85fb1f76ebafe253d70" + integrity sha512-2Ed5dJ+n/O3cU383xSY28cuVi0BCQhF8nYqWU5paEpl7fVdqdAmiLdqLyfblbNdfOFwFfi/mqU4O1pwc60iBhQ== + +zod@^3.22.3, zod@^3.22.4: + version "3.22.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" + integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== + zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"