From e24ea306dfdf823e51eadc83cf1cba7f604a16ae Mon Sep 17 00:00:00 2001 From: "Jose F. Romaniello" Date: Tue, 15 Oct 2024 17:32:32 -0300 Subject: [PATCH] chore: reorganize components code into folders --- llm/actions/confirm-purchase.tsx | 133 +++++++++--------- llm/actions/continue-conversation.tsx | 19 ++- .../{ => events}/calendar-events.tsx | 4 +- .../{ => events}/events-skeleton.tsx | 0 llm/components/{ => events}/events.tsx | 5 +- llm/components/events/index.ts | 2 + llm/components/{ => forecasts}/documents.tsx | 8 +- llm/components/forecasts/index.ts | 1 + llm/components/serialization.tsx | 13 +- .../{ => stocks}/conditional-purchase.tsx | 4 +- llm/components/stocks/index.ts | 8 ++ llm/components/{ => stocks}/positions.tsx | 4 +- .../{ => stocks}/stock-purchase-status.tsx | 0 .../{ => stocks}/stock-purchase.tsx | 2 +- .../{ => stocks}/stock-skeleton.tsx | 2 +- llm/components/{ => stocks}/stock.tsx | 3 +- .../{ => stocks}/stocks-skeleton.tsx | 0 llm/components/{ => stocks}/stocks.tsx | 4 +- llm/tools/{schedule => events}/get-events.tsx | 7 +- .../{trading => forecasts}/get-forecasts.tsx | 42 +++--- llm/tools/newsletter/README.md | 1 - llm/tools/profile/set-employeer.tsx | 35 +++-- llm/tools/profile/set-profile-attributes.tsx | 29 ++-- .../trading/add-conditional-purchase.tsx | 21 ++- llm/tools/trading/list-stocks.tsx | 17 ++- llm/tools/trading/show-current-positions.tsx | 5 +- llm/tools/trading/show-stock-price.tsx | 23 ++- llm/tools/trading/show-stock-purchase-ui.tsx | 20 ++- tailwind.config.ts | 2 +- 29 files changed, 222 insertions(+), 192 deletions(-) rename llm/components/{ => events}/calendar-events.tsx (97%) rename llm/components/{ => events}/events-skeleton.tsx (100%) rename llm/components/{ => events}/events.tsx (92%) create mode 100644 llm/components/events/index.ts rename llm/components/{ => forecasts}/documents.tsx (92%) create mode 100644 llm/components/forecasts/index.ts rename llm/components/{ => stocks}/conditional-purchase.tsx (98%) create mode 100644 llm/components/stocks/index.ts rename llm/components/{ => stocks}/positions.tsx (94%) rename llm/components/{ => stocks}/stock-purchase-status.tsx (100%) rename llm/components/{ => stocks}/stock-purchase.tsx (99%) rename llm/components/{ => stocks}/stock-skeleton.tsx (95%) rename llm/components/{ => stocks}/stock.tsx (99%) rename llm/components/{ => stocks}/stocks-skeleton.tsx (100%) rename llm/components/{ => stocks}/stocks.tsx (95%) rename llm/tools/{schedule => events}/get-events.tsx (91%) rename llm/tools/{trading => forecasts}/get-forecasts.tsx (74%) delete mode 100644 llm/tools/newsletter/README.md diff --git a/llm/actions/confirm-purchase.tsx b/llm/actions/confirm-purchase.tsx index 508838c..d0b5530 100644 --- a/llm/actions/confirm-purchase.tsx +++ b/llm/actions/confirm-purchase.tsx @@ -6,26 +6,24 @@ import { RELATION } from "@/lib/constants"; import { transactions } from "@/lib/db"; import { runAsyncFnWithoutBlocking } from "@/lib/utils"; import * as serialization from "@/llm/components/serialization"; -import { StockPurchase } from "@/llm/components/stock-purchase"; -import { StockPurchaseStatus } from "@/llm/components/stock-purchase-status"; +import { StockPurchase } from "@/llm/components/stocks/stock-purchase"; +import { StockPurchaseStatus } from "@/llm/components/stocks/stock-purchase-status"; import { ServerMessage } from "@/llm/types"; import { getUser, withFGA } from "@/sdk/fga"; import { withCheckPermission } from "@/sdk/fga/next/with-check-permission"; type confirmPurchaseParams = { - symbol: string, - price: number, - quantity: number, + symbol: string; + price: number; + quantity: number; /** * The message ID that triggered the component that called this action. */ - messageID: string, + messageID: string; }; -const confirmPurchaseInternal = async ( - { symbol, price, quantity, messageID }: confirmPurchaseParams -) => { +const confirmPurchaseInternal = async ({ symbol, price, quantity, messageID }: confirmPurchaseParams) => { "use server"; const user = await getUser(); const history = getMutableAIState(); @@ -38,74 +36,77 @@ const confirmPurchaseInternal = async ( await new Promise((resolve) => setTimeout(resolve, 1000)); purchasing.update( - + ); await transactions.create(symbol, price, quantity, "buy", user.sub); const message = `You have successfully purchased ${quantity} $${symbol}.`; - purchasing.done( - - ); + purchasing.done(); - history.done((messages: ServerMessage[]) => messages.map(m => { - return m.id === messageID ? { - ...m, - content: `User has successfully purchased ${quantity} stocks of ${symbol} at the price of $ ${price}.`, - componentName: serialization.names.get(StockPurchase), - params: { - ...m.params, - result: { - message, - status: "success", - } - } - } : m; - })); + history.done((messages: ServerMessage[]) => + messages.map((m) => { + return m.id === messageID + ? { + ...m, + content: `User has successfully purchased ${quantity} stocks of ${symbol} at the price of $ ${price}.`, + componentName: serialization.names.get(StockPurchase), + params: { + ...m.params, + result: { + message, + status: "success", + }, + }, + } + : m; + }) + ); }); return { purchasingUI: purchasing.value, }; -} +}; -export const confirmPurchase = withCheckPermission({ - checker: async (params) => { - return withFGA({ - object: `asset:${params.symbol.toLowerCase()}`, - relation: RELATION.CAN_BUY_STOCKS, - context: { current_time: new Date().toISOString() }, - }); - }, - onUnauthorized: async (params) => { - const purchasing = createStreamableUI(null); - const history = getMutableAIState(); - const message = `You are not authorized to purchase ${params.quantity} stocks of ${params.symbol}.`; +export const confirmPurchase = withCheckPermission( + { + checker: async (params) => { + return withFGA({ + object: `asset:${params.symbol.toLowerCase()}`, + relation: RELATION.CAN_BUY_STOCKS, + context: { current_time: new Date().toISOString() }, + }); + }, + onUnauthorized: async (params) => { + const purchasing = createStreamableUI(null); + const history = getMutableAIState(); + const message = `You are not authorized to purchase ${params.quantity} stocks of ${params.symbol}.`; - purchasing.done( - - ); + purchasing.done(); - history.done((messages: ServerMessage[]) => messages.map(m => { - return m.id === params.messageID ? { - ...m, - content: message, - componentName: serialization.names.get(StockPurchase), - params: { - ...m.params, - result: { - status: "failure", - message, - } - } - } : m; - })); + history.done((messages: ServerMessage[]) => + messages.map((m) => { + return m.id === params.messageID + ? { + ...m, + content: message, + componentName: serialization.names.get(StockPurchase), + params: { + ...m.params, + result: { + status: "failure", + message, + }, + }, + } + : m; + }) + ); - return { - purchasingUI: purchasing.value, - }; - } -}, confirmPurchaseInternal); + return { + purchasingUI: purchasing.value, + }; + }, + }, + confirmPurchaseInternal +); diff --git a/llm/actions/continue-conversation.tsx b/llm/actions/continue-conversation.tsx index 48b6e7f..cbd9ae6 100644 --- a/llm/actions/continue-conversation.tsx +++ b/llm/actions/continue-conversation.tsx @@ -8,13 +8,13 @@ import { userUsage } from "@/lib/db"; import { composeTools } from "@/llm/ai-helpers"; import * as serialization from "@/llm/components/serialization"; import { getSystemPrompt } from "@/llm/system-prompt"; +import getEvents from "@/llm/tools/events/get-events"; +import getForecasts from "@/llm/tools/forecasts/get-forecasts"; import checkSubscription from "@/llm/tools/newsletter/check-subscription"; import setSubscription from "@/llm/tools/newsletter/set-subscription"; import setEmployeer from "@/llm/tools/profile/set-employeer"; import setProfileAttributes from "@/llm/tools/profile/set-profile-attributes"; -import getEvents from "@/llm/tools/schedule/get-events"; import addConditionalPurchase from "@/llm/tools/trading/add-conditional-purchase"; -import getForecasts from "@/llm/tools/trading/get-forecasts"; import listStocks from "@/llm/tools/trading/list-stocks"; import showCurrentPositions from "@/llm/tools/trading/show-current-positions"; import showStockPrice from "@/llm/tools/trading/show-stock-price"; @@ -37,15 +37,13 @@ type ContinueConversationParams = { hidden: boolean; }; -export async function continueConversation( - input: string | ContinueConversationParams -): Promise { +export async function continueConversation(input: string | ContinueConversationParams): Promise { "use server"; const user = await getUser(); const history = getMutableAIState(); let hidden = false; - if (typeof input === 'object') { + if (typeof input === "object") { hidden = input.hidden; input = input.message; } @@ -58,10 +56,7 @@ export async function continueConversation( }; } - history.update((messages: ServerMessage[]) => [ - ...messages, - { role: "user", content: input, hidden }, - ]); + history.update((messages: ServerMessage[]) => [...messages, { role: "user", content: input, hidden }]); const promptMessages = history.get(); @@ -101,7 +96,9 @@ export async function continueConversation( // TODO: implement a max token limit onFinish: async ({ usage }) => { const stats = await userUsage.track(user.sub, usage); - console.log(`User ${user.email} used ${usage.totalTokens} tokens in this conversation. Last hour: ${stats.lastHour}, last day: ${stats.lastDay}.`); + console.log( + `User ${user.email} used ${usage.totalTokens} tokens in this conversation. Last hour: ${stats.lastHour}, last day: ${stats.lastDay}.` + ); }, }); diff --git a/llm/components/calendar-events.tsx b/llm/components/events/calendar-events.tsx similarity index 97% rename from llm/components/calendar-events.tsx rename to llm/components/events/calendar-events.tsx index 4f2bdd7..97fdea2 100644 --- a/llm/components/calendar-events.tsx +++ b/llm/components/events/calendar-events.tsx @@ -8,8 +8,8 @@ import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; import { EnsureAPIAccess } from "@/sdk/components/ensure-api-access"; -import { Event, EventAvailability } from "../actions/calendar-events"; -import { NotAvailableReadOnly } from "./not-available-read-only"; +import { Event, EventAvailability } from "../../actions/calendar-events"; +import { NotAvailableReadOnly } from "../not-available-read-only"; export function CalendarEvents({ events, diff --git a/llm/components/events-skeleton.tsx b/llm/components/events/events-skeleton.tsx similarity index 100% rename from llm/components/events-skeleton.tsx rename to llm/components/events/events-skeleton.tsx diff --git a/llm/components/events.tsx b/llm/components/events/events.tsx similarity index 92% rename from llm/components/events.tsx rename to llm/components/events/events.tsx index 244d0c7..333673b 100644 --- a/llm/components/events.tsx +++ b/llm/components/events/events.tsx @@ -2,9 +2,10 @@ import { format, parseISO } from "date-fns"; -import { checkAvailabilityForEvents } from "../actions/calendar-events"; +import { checkAvailabilityForEvents } from "@/llm/actions/calendar-events"; + +import WarningWrapper from "../warning-wrapper"; import { CalendarEvents } from "./calendar-events"; -import WarningWrapper from "./warning-wrapper"; interface Event { date: string; diff --git a/llm/components/events/index.ts b/llm/components/events/index.ts new file mode 100644 index 0000000..ef37087 --- /dev/null +++ b/llm/components/events/index.ts @@ -0,0 +1,2 @@ +export { Events } from "./events"; +export { EventsSkeleton } from "./events-skeleton"; diff --git a/llm/components/documents.tsx b/llm/components/forecasts/documents.tsx similarity index 92% rename from llm/components/documents.tsx rename to llm/components/forecasts/documents.tsx index 5241854..64c76b7 100644 --- a/llm/components/documents.tsx +++ b/llm/components/forecasts/documents.tsx @@ -7,10 +7,10 @@ import { useEffect, useState } from "react"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { ClientMessage, Document } from "@/llm/types"; -import { FormattedText } from "./FormattedText"; -import { NotAvailableReadOnly } from "./not-available-read-only"; -import { PromptUserContainer } from "./prompt-user-container"; -import WarningWrapper from "./warning-wrapper"; +import { FormattedText } from "../FormattedText"; +import { NotAvailableReadOnly } from "../not-available-read-only"; +import { PromptUserContainer } from "../prompt-user-container"; +import WarningWrapper from "../warning-wrapper"; export const Documents = ({ documents, diff --git a/llm/components/forecasts/index.ts b/llm/components/forecasts/index.ts new file mode 100644 index 0000000..f7d0cef --- /dev/null +++ b/llm/components/forecasts/index.ts @@ -0,0 +1 @@ +export { Documents } from "./documents"; diff --git a/llm/components/serialization.tsx b/llm/components/serialization.tsx index d7bcbfa..78606e8 100644 --- a/llm/components/serialization.tsx +++ b/llm/components/serialization.tsx @@ -1,12 +1,8 @@ -import { ConditionalPurchase } from "./conditional-purchase"; -import { Documents } from "./documents"; import { Events } from "./events"; +import { Documents } from "./forecasts/documents"; import { FormattedText } from "./FormattedText"; -import { Positions } from "./positions"; import { SimpleMessage } from "./simple-message"; -import { Stock } from "./stock"; -import { StockPurchase } from "./stock-purchase"; -import { Stocks } from "./stocks"; +import { ConditionalPurchase, Positions, Stock, StockPurchase, Stocks } from "./stocks"; export const components = { Documents, @@ -24,8 +20,5 @@ type ComponentsNames = keyof typeof components; type ComponentClasses = (typeof components)[ComponentsNames]; export const names = new Map( - Object.entries(components).map(([name, component]) => [ - component, - name as ComponentsNames, - ]) + Object.entries(components).map(([name, component]) => [component, name as ComponentsNames]) ); diff --git a/llm/components/conditional-purchase.tsx b/llm/components/stocks/conditional-purchase.tsx similarity index 98% rename from llm/components/conditional-purchase.tsx rename to llm/components/stocks/conditional-purchase.tsx index 7b2f3cb..961056c 100644 --- a/llm/components/conditional-purchase.tsx +++ b/llm/components/stocks/conditional-purchase.tsx @@ -10,8 +10,8 @@ import { ConditionalPurchase as ConditionalPurchaseType } from "@/lib/db/conditi import { getConditionalPurchaseById } from "@/llm/actions/conditional-purchases"; import { isGuardianEnrolled } from "@/sdk/auth0/mgmt"; -import { NotAvailableReadOnly } from "./not-available-read-only"; -import WarningWrapper from "./warning-wrapper"; +import { NotAvailableReadOnly } from "../not-available-read-only"; +import WarningWrapper from "../warning-wrapper"; export function ConditionalPurchase({ id, diff --git a/llm/components/stocks/index.ts b/llm/components/stocks/index.ts new file mode 100644 index 0000000..3421b1b --- /dev/null +++ b/llm/components/stocks/index.ts @@ -0,0 +1,8 @@ +export { Stocks } from "./stocks"; +export { StocksSkeleton } from "./stocks-skeleton"; +export { Stock } from "./stock"; +export { StockSkeleton } from "./stock-skeleton"; +export { StockPurchase } from "./stock-purchase"; +export { StockPurchaseStatus } from "./stock-purchase-status"; +export { ConditionalPurchase } from "./conditional-purchase"; +export { Positions } from "./positions"; diff --git a/llm/components/positions.tsx b/llm/components/stocks/positions.tsx similarity index 94% rename from llm/components/positions.tsx rename to llm/components/stocks/positions.tsx index f12bb95..ecce2c6 100644 --- a/llm/components/positions.tsx +++ b/llm/components/stocks/positions.tsx @@ -4,9 +4,9 @@ import { useActions, useUIState } from "ai/rsc"; import { transactions } from "@/lib/db"; import { cn } from "@/lib/utils"; +import { ClientMessage } from "@/llm/types"; -import { ClientMessage } from "../types"; -import WarningWrapper from "./warning-wrapper"; +import WarningWrapper from "../warning-wrapper"; export function Positions({ positions, readOnly = false }: { positions: transactions.Position[]; readOnly?: boolean }) { const [, setMessages] = useUIState(); diff --git a/llm/components/stock-purchase-status.tsx b/llm/components/stocks/stock-purchase-status.tsx similarity index 100% rename from llm/components/stock-purchase-status.tsx rename to llm/components/stocks/stock-purchase-status.tsx diff --git a/llm/components/stock-purchase.tsx b/llm/components/stocks/stock-purchase.tsx similarity index 99% rename from llm/components/stock-purchase.tsx rename to llm/components/stocks/stock-purchase.tsx index 2d737cc..a79214e 100644 --- a/llm/components/stock-purchase.tsx +++ b/llm/components/stocks/stock-purchase.tsx @@ -6,8 +6,8 @@ import { useId, useState } from "react"; import { StockDownIcon, StockUpIcon } from "@/components/icons"; import { formatNumber } from "@/lib/utils"; +import WarningWrapper from "../warning-wrapper"; import { StockPurchaseStatus } from "./stock-purchase-status"; -import WarningWrapper from "./warning-wrapper"; type StockPurchaseUIParams = { /** diff --git a/llm/components/stock-skeleton.tsx b/llm/components/stocks/stock-skeleton.tsx similarity index 95% rename from llm/components/stock-skeleton.tsx rename to llm/components/stocks/stock-skeleton.tsx index ebc7516..9d73141 100644 --- a/llm/components/stock-skeleton.tsx +++ b/llm/components/stocks/stock-skeleton.tsx @@ -1,4 +1,4 @@ -import WarningWrapper from "./warning-wrapper"; +import WarningWrapper from "../warning-wrapper"; export const StockSkeleton = () => { return ( diff --git a/llm/components/stock.tsx b/llm/components/stocks/stock.tsx similarity index 99% rename from llm/components/stock.tsx rename to llm/components/stocks/stock.tsx index 499e398..ec81da6 100644 --- a/llm/components/stock.tsx +++ b/llm/components/stocks/stock.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react-hooks/exhaustive-deps */ "use client"; import { scaleLinear } from "d3-scale"; @@ -8,7 +7,7 @@ import { useResizeObserver } from "usehooks-ts"; import { StockDownIcon, StockUpIcon } from "@/components/icons"; -import WarningWrapper from "./warning-wrapper"; +import WarningWrapper from "../warning-wrapper"; export function Stock({ symbol, diff --git a/llm/components/stocks-skeleton.tsx b/llm/components/stocks/stocks-skeleton.tsx similarity index 100% rename from llm/components/stocks-skeleton.tsx rename to llm/components/stocks/stocks-skeleton.tsx diff --git a/llm/components/stocks.tsx b/llm/components/stocks/stocks.tsx similarity index 95% rename from llm/components/stocks.tsx rename to llm/components/stocks/stocks.tsx index 1427fdf..5aef4db 100644 --- a/llm/components/stocks.tsx +++ b/llm/components/stocks/stocks.tsx @@ -5,8 +5,8 @@ import { useActions, useUIState } from "ai/rsc"; import { StockDownIcon, StockUpIcon } from "@/components/icons"; import { cn } from "@/lib/utils"; -import { ClientMessage } from "../types"; -import WarningWrapper from "./warning-wrapper"; +import { ClientMessage } from "../../types"; +import WarningWrapper from "../warning-wrapper"; export function Stocks({ stocks, readOnly = false }: { stocks: any[]; readOnly?: boolean }) { const [, setMessages] = useUIState(); diff --git a/llm/tools/schedule/get-events.tsx b/llm/tools/events/get-events.tsx similarity index 91% rename from llm/tools/schedule/get-events.tsx rename to llm/tools/events/get-events.tsx index 8f834ca..e6c49f4 100644 --- a/llm/tools/schedule/get-events.tsx +++ b/llm/tools/events/get-events.tsx @@ -2,11 +2,14 @@ import { z } from "zod"; import { getCompanyInfo } from "@/lib/market/stocks"; import { defineTool } from "@/llm/ai-helpers"; -import { Events } from "@/llm/components/events"; -import { EventsSkeleton } from "@/llm/components/events-skeleton"; +import { Events, EventsSkeleton } from "@/llm/components/events"; import * as serialization from "@/llm/components/serialization"; import { getHistory } from "@/llm/utils"; +/** + * This tool is used to get upcoming events for a stock. + * The events are automatically generated by the LLM. + */ export default defineTool("get_events", () => { const history = getHistory(); diff --git a/llm/tools/trading/get-forecasts.tsx b/llm/tools/forecasts/get-forecasts.tsx similarity index 74% rename from llm/tools/trading/get-forecasts.tsx rename to llm/tools/forecasts/get-forecasts.tsx index 820b0da..c4c4491 100644 --- a/llm/tools/trading/get-forecasts.tsx +++ b/llm/tools/forecasts/get-forecasts.tsx @@ -7,11 +7,15 @@ import stocks from "@/lib/market/stocks.json"; import { getSymbolRetriever } from "@/llm/actions/langchain-helpers"; import { defineTool } from "@/llm/ai-helpers"; import { aiParams } from "@/llm/ai-params"; -import { Documents } from "@/llm/components/documents"; +import { Documents } from "@/llm/components/forecasts"; import * as serialization from "@/llm/components/serialization"; import { getHistory } from "@/llm/utils"; import { getUser } from "@/sdk/fga"; +/** + * This is an example tool that uses Retrieval Augmented Generation (RAG) to generate a response. + * As part of the retrievel process, the tool checks in OKTA FGA if the user has access to the matching document. + */ export default defineTool("get_forecasts", () => { const history = getHistory(); @@ -20,19 +24,12 @@ export default defineTool("get_forecasts", () => { If the user request a forecast analysis always run this. Do not use previously shared forecasts in the conversation.`, parameters: z.object({ - symbol: z - .string() - .describe( - "The name or symbol of the stock or currency. e.g. DOGE/AAPL/USD." - ), - originalMessage: z.string() - .describe('The original and complete message the user send.') + symbol: z.string().describe("The name or symbol of the stock or currency. e.g. DOGE/AAPL/USD."), + originalMessage: z.string().describe("The original and complete message the user send."), }), generate: async function* ({ symbol, originalMessage }) { const retriever = await getSymbolRetriever(symbol); - const documents = await retriever.invoke( - originalMessage - ); + const documents = await retriever.invoke(originalMessage); const stock = stocks.find((stock) => stock.symbol === symbol); if (!stock) { return `I'm sorry, I couldn't find any information for the stock ${symbol}.`; @@ -50,14 +47,13 @@ Company Summary: ${stock.long_business_summary} Summarize your responses in no more than 200 words, focusing on key points. -${documents.length > 0 ? "The following documents are available to the user:" : "Inform the user that no relevant information is available."} +${ + documents.length > 0 + ? "The following documents are available to the user:" + : "Inform the user that no relevant information is available." +} -${documents - .map( - (document) => - `\nTitle: ${document.metadata.title}\nContent: ${document.pageContent}\n\n` - ) - .join("")} +${documents.map((document) => `\nTitle: ${document.metadata.title}\nContent: ${document.pageContent}\n\n`).join("")} Refer to the documents as "the documents available to you." @@ -68,10 +64,10 @@ If the documents available to the user do not include analyst forecasts: - Suggest that the user subscribe to the Market0 newsletter to gain access to these reports in the database. `, prompt: originalMessage, - onFinish: async ({usage}) => { + onFinish: async ({ usage }) => { const user = await getUser(); await userUsage.track(user.sub, usage); - } + }, }); const baseParams = { @@ -79,10 +75,10 @@ If the documents available to the user do not include analyst forecasts: documents: documents.map(toPlainObject), }; - let currentText = ''; + let currentText = ""; for await (const textPart of textStream) { currentText += textPart; - yield + yield ; } //once finished: @@ -93,7 +89,7 @@ If the documents available to the user do not include analyst forecasts: text, }; - history.update({ + history.update({ role: "assistant", content: text, componentName: serialization.names.get(Documents)!, diff --git a/llm/tools/newsletter/README.md b/llm/tools/newsletter/README.md deleted file mode 100644 index fa54e8a..0000000 --- a/llm/tools/newsletter/README.md +++ /dev/null @@ -1 +0,0 @@ -This folder contains the tools used to subscribe to the newsletter and check the subscription status. diff --git a/llm/tools/profile/set-employeer.tsx b/llm/tools/profile/set-employeer.tsx index a33cc33..aa39de5 100644 --- a/llm/tools/profile/set-employeer.tsx +++ b/llm/tools/profile/set-employeer.tsx @@ -5,18 +5,17 @@ import { defineTool } from "@/llm/ai-helpers"; import { withTextGeneration } from "@/llm/with-text-generation"; import { fgaClient, getUser } from "@/sdk/fga"; +/** + * This tool allows the user to set or unset its current employeer. + */ export default defineTool("set_employeer", async () => { return { description: `Allows the user to set or unset its current employeer. Use this tool when the user purposedly set or casually mention its current employeer. `, parameters: z.object({ - employeer: z - .string() - .describe("The ticker of the company the user is currently working for"), - status: z - .boolean() - .describe("Whether the user is currently working for the company"), + employeer: z.string().describe("The ticker of the company the user is currently working for"), + status: z.boolean().describe("Whether the user is currently working for the company"), }), generate: withTextGeneration({}, async function* ({ employeer, status }: { employeer: string; status: boolean }) { yield ; @@ -24,24 +23,22 @@ export default defineTool("set_employeer", async () => { const user = await getUser(); try { - await fgaClient.write( - { - [ status ? 'writes' : 'deletes']: [ - { - user: `user:${user.sub}`, - relation: "employee", - object: "company:atko", - }, - ], - } - ); - } catch(err: any) { + await fgaClient.write({ + [status ? "writes" : "deletes"]: [ + { + user: `user:${user.sub}`, + relation: "employee", + object: "company:atko", + }, + ], + }); + } catch (err: any) { // it might fail because the tuple does not exist // or already exist. we don't care about that. console.warn(err?.message); } - return `Noted that you are ${status ? 'now' : 'no longer'} working for ${employeer}.`; + return `Noted that you are ${status ? "now" : "no longer"} working for ${employeer}.`; }), }; }); diff --git a/llm/tools/profile/set-profile-attributes.tsx b/llm/tools/profile/set-profile-attributes.tsx index 39899f3..aaa63da 100644 --- a/llm/tools/profile/set-profile-attributes.tsx +++ b/llm/tools/profile/set-profile-attributes.tsx @@ -6,31 +6,32 @@ import { withTextGeneration } from "@/llm/with-text-generation"; import { updateUser } from "@/sdk/auth0/mgmt"; import { getUser } from "@/sdk/fga"; +/** + * This tool allows the user to set their first name and/or last name. + * The changes are propagated to the Auth0's user profile. + */ export default defineTool("set_profile_attributes", async () => { return { description: `Allows the user to set their first name and/or last name. `, parameters: z.object({ - givenName: z - .string() - .optional() - .describe("The first name of the user"), - familyName: z - .string() - .optional() - .describe("The last name of the user"), + givenName: z.string().optional().describe("The first name of the user"), + familyName: z.string().optional().describe("The last name of the user"), }), - generate: withTextGeneration({}, async function* ({ givenName, familyName }: { givenName?: string; familyName?: string }) { - yield ; + generate: withTextGeneration( + {}, + async function* ({ givenName, familyName }: { givenName?: string; familyName?: string }) { + yield ; - const user = await getUser(); - await updateUser(user.sub, { givenName, familyName }); + const user = await getUser(); + await updateUser(user.sub, { givenName, familyName }); - return ` + return ` Your profile has been updated successfully. ${givenName ? `Your first name is now ${givenName}.` : ""} ${familyName ? `Your last name is now ${familyName}.` : ""} `; - }), + } + ), }; }); diff --git a/llm/tools/trading/add-conditional-purchase.tsx b/llm/tools/trading/add-conditional-purchase.tsx index 40e0cd2..039d0b6 100644 --- a/llm/tools/trading/add-conditional-purchase.tsx +++ b/llm/tools/trading/add-conditional-purchase.tsx @@ -5,8 +5,8 @@ import Loader from "@/components/loader"; import { RELATION } from "@/lib/constants"; import { conditionalPurchases } from "@/lib/db"; import { defineTool } from "@/llm/ai-helpers"; -import { ConditionalPurchase } from "@/llm/components/conditional-purchase"; import * as serialization from "@/llm/components/serialization"; +import { ConditionalPurchase } from "@/llm/components/stocks"; import { getHistory } from "@/llm/utils"; import { isGuardianEnrolled } from "@/sdk/auth0/mgmt"; import { getUser, withFGA } from "@/sdk/fga"; @@ -20,6 +20,25 @@ type ToolParams = { threshold: number; }; +/** + * This tool enables users to place conditional stock purchase orders based on a financial metric. + * + * **Inputs:** + * - **Stock Symbol**: The ticker symbol of the stock the user wants to purchase. + * - **Quantity**: The number of shares the user wants to buy. + * - **Financial Metric**: A metric (e.g., price-to-earnings ratio, stock price, etc.) specified by the user in natural language to define the purchase condition. + * + * **Functionality:** + * - The user's conditional purchase request is stored in the database, waiting for the specified financial metric to meet the condition. + * - When the condition is satisfied, the system triggers a purchase order, pending user confirmation via Multi-Factor Authentication (MFA). + * + * **Workflow:** + * 1. User specifies the stock symbol, the number of shares, and a financial metric to monitor. + * 2. The request is saved in the database for monitoring. + * 3. When the metric meets the condition, the system prompts the user with an MFA confirmation before completing the purchase. + * + * **Note:** The tool only executes the purchase after user confirmation through MFA to ensure security. + */ export default defineTool("add_conditional_purchase", () => { const history = getHistory(); diff --git a/llm/tools/trading/list-stocks.tsx b/llm/tools/trading/list-stocks.tsx index 0dec185..361e17f 100644 --- a/llm/tools/trading/list-stocks.tsx +++ b/llm/tools/trading/list-stocks.tsx @@ -3,20 +3,19 @@ import { z } from "zod"; import allStocks from "@/lib/market/stocks.json"; import { defineTool } from "@/llm/ai-helpers"; import * as serialization from "@/llm/components/serialization"; -import { Stocks } from "@/llm/components/stocks"; -import { StocksSkeleton } from "@/llm/components/stocks-skeleton"; +import { Stocks, StocksSkeleton } from "@/llm/components/stocks"; import { getHistory } from "@/llm/utils"; +/** + * This tool allow the user to retrieve a list of stocks. + */ export default defineTool("list_stocks", () => { const history = getHistory(); return { - description: - "List stocks, always list ATKO.", + description: "List stocks, always list ATKO.", parameters: z.object({ - tickers: z.array( - z.string().describe("The symbol or ticker of the stock") - ) + tickers: z.array(z.string().describe("The symbol or ticker of the stock")), }), generate: async function* ({ tickers }) { yield ; @@ -31,8 +30,8 @@ export default defineTool("list_stocks", () => { delta: stock?.delta, market: stock?.exchange, company: stock?.shortname, - currency: 'USD', - } + currency: "USD", + }; }); history.update({ diff --git a/llm/tools/trading/show-current-positions.tsx b/llm/tools/trading/show-current-positions.tsx index e303681..8128d7a 100644 --- a/llm/tools/trading/show-current-positions.tsx +++ b/llm/tools/trading/show-current-positions.tsx @@ -2,11 +2,14 @@ import { z } from "zod"; import { transactions } from "@/lib/db"; import { defineTool } from "@/llm/ai-helpers"; -import { Positions } from "@/llm/components/positions"; import * as serialization from "@/llm/components/serialization"; +import { Positions } from "@/llm/components/stocks"; import { getHistory } from "@/llm/utils"; import { getUser } from "@/sdk/fga"; +/** + * This tool allow the user to check their current stock positions. + */ export default defineTool("show_current_positions", () => { const history = getHistory(); return { diff --git a/llm/tools/trading/show-stock-price.tsx b/llm/tools/trading/show-stock-price.tsx index e6f0686..061d7c0 100644 --- a/llm/tools/trading/show-stock-price.tsx +++ b/llm/tools/trading/show-stock-price.tsx @@ -3,22 +3,19 @@ import { z } from "zod"; import { getStockPrices } from "@/lib/market/stocks"; import { defineTool } from "@/llm/ai-helpers"; import * as serialization from "@/llm/components/serialization"; -import { Stock } from "@/llm/components/stock"; -import { StockSkeleton } from "@/llm/components/stock-skeleton"; +import { Stock, StockSkeleton } from "@/llm/components/stocks"; import { getHistory } from "@/llm/utils"; +/** + * This tool allow the user to check the price of a stock. + */ export default defineTool("show_stock_price", () => { const history = getHistory(); return { - description: - "Check price of an stock. Prefer this when there is not clear intention of buying.", + description: "Check price of an stock. Prefer this when there is not clear intention of buying.", parameters: z.object({ - symbol: z - .string() - .describe( - "The name or symbol of the stock. e.g. DOGE/AAPL/USD." - ), + symbol: z.string().describe("The name or symbol of the stock. e.g. DOGE/AAPL/USD."), }), generate: async function* ({ symbol }) { yield ; @@ -37,7 +34,7 @@ export default defineTool("show_stock_price", () => { price, delta, company: doc.shortname, - currency: 'USD', + currency: "USD", market: doc.exchange, }; @@ -48,11 +45,7 @@ export default defineTool("show_stock_price", () => { params, }); - return ( - - ); + return ; }, }; }); diff --git a/llm/tools/trading/show-stock-purchase-ui.tsx b/llm/tools/trading/show-stock-purchase-ui.tsx index 4ef5075..7af4c8c 100644 --- a/llm/tools/trading/show-stock-purchase-ui.tsx +++ b/llm/tools/trading/show-stock-purchase-ui.tsx @@ -5,7 +5,7 @@ import { RELATION } from "@/lib/constants"; import { getStockPrices } from "@/lib/market/stocks"; import { defineTool } from "@/llm/ai-helpers"; import * as serialization from "@/llm/components/serialization"; -import { StockPurchase } from "@/llm/components/stock-purchase"; +import { StockPurchase } from "@/llm/components/stocks"; import { getHistory } from "@/llm/utils"; import { withTextGeneration } from "@/llm/with-text-generation"; import { withFGA } from "@/sdk/fga"; @@ -20,6 +20,24 @@ type ToolParams = { company: string; }; +/** + * This tool allows users to buy stocks while enforcing access control through Fine-Grained Authorization (FGA). + * + * **Purpose:** + * - This demo illustrates the use of FGA to determine whether a user is authorized to purchase a specific stock, demonstrating stock purchase restrictions based on user roles or attributes. + * + * **Authorization Logic:** + * - FGA is used to impose restrictions, such as preventing users from buying certain stocks if they are insiders or employees of financial institutions. + * + * **Examples of Restrictions:** + * - Users may be restricted from buying a stock if they are employed by a financial institution. + * - Users who are classified as insiders for a particular stock may be prohibited from purchasing shares of that stock. + * + * **Workflow:** + * 1. The user selects a stock they wish to buy. + * 2. FGA checks the user’s eligibility based on their attributes (e.g., employment, insider status). + * 3. If authorized, the user proceeds with the purchase. If not, the purchase is blocked, and an appropriate message is returned. + */ export default defineTool("show_stock_purchase_ui", () => { const history = getHistory(); diff --git a/tailwind.config.ts b/tailwind.config.ts index 3879a52..38ddaa5 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -7,7 +7,7 @@ const config = { "./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}", - "./llm/components/*.{ts,tsx}", + "./llm/components/**/*.{ts,tsx}", "./sdk/**/*.{ts,tsx}", ], prefix: "",