From 516fc081bfe2f638f529edcad473f1328efb1253 Mon Sep 17 00:00:00 2001 From: Anand <66644641+maraoai@users.noreply.github.com> Date: Sat, 23 Mar 2024 21:59:28 -0700 Subject: [PATCH 01/65] Create replicate_concurrency.py --- ifs.py/replicate_concurrency.py | 66 +++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 ifs.py/replicate_concurrency.py diff --git a/ifs.py/replicate_concurrency.py b/ifs.py/replicate_concurrency.py new file mode 100644 index 0000000..bd67c0e --- /dev/null +++ b/ifs.py/replicate_concurrency.py @@ -0,0 +1,66 @@ +import replicate +import os +import json +import concurrent.futures +import logging +from datetime import datetime + +os.environ['REPLICATE_API_TOKEN'] = "r8_****" + +input_image_url = "https://pub-8e27fc985b9949f6ad6d8b31adae8470.r2.dev/robert.PNG" + +# Prompts for each of the calls +prompts = [ + "A photo of man img upclose, facing camera, looking confident and controlled, professional outfit, upright posture, orderly surroundings, muted background colors, symbols of achievement, sense of discipline and responsibility", + "A photo of man img upclose, facing camera, fierce expression, intense eyes, bold outfit, fiery background colors, sense of urgency and strength", + "A photo of man img upclose, facing camera, young and vulnerable, sad eyes, child-like clothing, sitting alone, muted background colors, sense of isolation and longing for care and acceptance" +] + +def call_replicate(prompt, input_image_url): + output = replicate.run( + "tencentarc/photomaker:ddfc2b08d209f9fa8c1eca692712918bd449f695dabb4a958da31802a9570fe4", + input={ + "prompt": prompt, + "num_steps": 40, + "style_name": "Photographic (Default)", + "input_image": input_image_url, + "num_outputs": 1, + "guidance_scale": 5, + "negative_prompt": "nsfw, lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry", + "style_strength_ratio": 20 + } + ) + return output + +def execute_api_calls_concurrently(): + with concurrent.futures.ThreadPoolExecutor() as executor: + # Submit tasks to the executor to call replicate.run concurrently + futures = [executor.submit(call_replicate, prompt, input_image_url) for prompt in prompts] + + # Collect results as they are completed + results = [future.result() for future in concurrent.futures.as_completed(futures)] + + return results + +def main(): + # Trigger API calls and get responses + responses = execute_api_calls_concurrently() + + response_list = [] + # Print the responses + for response in responses: + response_list.append(response) + print(response) + + # Combine the responses into one JSON object + combined_response = json.dumps({ + "response_1": response_list[0], + "response_2": response_list[1], + "response_3": response_list[2] + }) + + # Print the combined JSON object + print(combined_response) + +if __name__ == '__main__': + main() From f2d3487ae914ce31f29eccb74af8735835a6d30a Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sat, 23 Mar 2024 22:04:16 -0700 Subject: [PATCH 02/65] Add todo --- ifs.ai/app/api/uploadthing/core.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ifs.ai/app/api/uploadthing/core.ts b/ifs.ai/app/api/uploadthing/core.ts index 7edfb67..db4524e 100644 --- a/ifs.ai/app/api/uploadthing/core.ts +++ b/ifs.ai/app/api/uploadthing/core.ts @@ -26,6 +26,8 @@ export const fileRouter = { console.log("file url", file.url); + // TODO: Call replicate + // !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback return { uploadedBy: metadata.userId, imageUrl: file.url }; }), From 982603fb9217891f856d6962eac1a80d6aa58496 Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sat, 23 Mar 2024 22:33:03 -0700 Subject: [PATCH 03/65] Generate images with replicate on selfie upload --- ifs.ai/.env.example | 5 +- ifs.ai/app/api/uploadthing/core.ts | 42 +++++++++++++- ifs.ai/app/start/page.tsx | 17 +++++- ifs.ai/components/ui/input.tsx | 25 +++++++++ ifs.ai/package.json | 1 + ifs.ai/pnpm-lock.yaml | 88 ++++++++++++++++++++++++++++++ 6 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 ifs.ai/components/ui/input.tsx diff --git a/ifs.ai/.env.example b/ifs.ai/.env.example index b2e7f97..27c2a8d 100644 --- a/ifs.ai/.env.example +++ b/ifs.ai/.env.example @@ -1,3 +1,6 @@ # uploadthing API key, get yours here: https://uploadthing.com/dashboard/ UPLOADTHING_SECRET= -UPLOADTHING_APP_ID= \ No newline at end of file +UPLOADTHING_APP_ID= + +# Replicate token, get yours here https://replicate.com/account/api-tokens +REPLICATE_API_TOKEN= \ No newline at end of file diff --git a/ifs.ai/app/api/uploadthing/core.ts b/ifs.ai/app/api/uploadthing/core.ts index db4524e..6c88f16 100644 --- a/ifs.ai/app/api/uploadthing/core.ts +++ b/ifs.ai/app/api/uploadthing/core.ts @@ -1,10 +1,47 @@ import { createUploadthing, type FileRouter } from "uploadthing/next"; import { UploadThingError } from "uploadthing/server"; +import Replicate from "replicate"; const f = createUploadthing(); const auth = (req: Request) => ({ id: "fakeUserId" }); // Fake auth function +const replicate = new Replicate({ + auth: process.env.REPLICATE_API_TOKEN, +}); + +async function makePartImages(inputUrl: string): Promise<{ manager: string; firefighter: string; exile: string }> { + const prompts = { + manager: + "A photo of man img upclose, facing camera, looking confident and controlled, professional outfit, upright posture, orderly surroundings, muted background colors, symbols of achievement, sense of discipline and responsibility", + firefighter: + "A photo of man img upclose, facing camera, fierce expression, intense eyes, bold outfit, fiery background colors, sense of urgency and strength", + exile: + "A photo of man img upclose, facing camera, young and vulnerable, sad eyes, child-like clothing, sitting alone, muted background colors, sense of isolation and longing for care and acceptance", + }; + + return Object.fromEntries( + await Promise.all( + Object.entries(prompts).map(async ([part, prompt]) => [ + part, + await replicate.run("tencentarc/photomaker:ddfc2b08d209f9fa8c1eca692712918bd449f695dabb4a958da31802a9570fe4", { + input: { + prompt: prompt, + num_steps: 40, + style_name: "Photographic (Default)", + input_image: inputUrl, + num_outputs: 1, + guidance_scale: 5, + negative_prompt: + "nsfw, lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry", + style_strength_ratio: 20, + }, + }), + ]), + ), + ); +} + // FileRouter for your app, can contain multiple FileRoutes export const fileRouter = { // Define as many FileRoutes as you like, each with a unique routeSlug @@ -25,11 +62,12 @@ export const fileRouter = { console.log("Upload complete for userId:", metadata.userId); console.log("file url", file.url); + const partImageUrls = await makePartImages(file.url); - // TODO: Call replicate + console.log("Got image urls", partImageUrls); // !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback - return { uploadedBy: metadata.userId, imageUrl: file.url }; + return { uploadedBy: metadata.userId, imageUrl: file.url, partImageUrls: partImageUrls }; }), } satisfies FileRouter; diff --git a/ifs.ai/app/start/page.tsx b/ifs.ai/app/start/page.tsx index e4331b6..e14ee7a 100644 --- a/ifs.ai/app/start/page.tsx +++ b/ifs.ai/app/start/page.tsx @@ -30,9 +30,20 @@ export default function Page() { toast({ title: "Uploaded successfully!", description: ( - - Uploaded image - +
+ + Uploaded image + + + Uploaded image + + + Uploaded image + + + Uploaded image + +
), }); }, diff --git a/ifs.ai/components/ui/input.tsx b/ifs.ai/components/ui/input.tsx new file mode 100644 index 0000000..677d05f --- /dev/null +++ b/ifs.ai/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/ifs.ai/package.json b/ifs.ai/package.json index 3a45a43..fd6cf45 100644 --- a/ifs.ai/package.json +++ b/ifs.ai/package.json @@ -19,6 +19,7 @@ "react": "^18", "react-dom": "^18", "react-webcam": "^7.2.0", + "replicate": "^0.29.1", "tailwind-merge": "^2.2.2", "tailwindcss-animate": "^1.0.7", "uploadthing": "^6.6.0" diff --git a/ifs.ai/pnpm-lock.yaml b/ifs.ai/pnpm-lock.yaml index 01bad81..f58bf06 100644 --- a/ifs.ai/pnpm-lock.yaml +++ b/ifs.ai/pnpm-lock.yaml @@ -35,6 +35,9 @@ dependencies: react-webcam: specifier: ^7.2.0 version: 7.2.0(react-dom@18.2.0)(react@18.2.0) + replicate: + specifier: ^0.29.1 + version: 0.29.1 tailwind-merge: specifier: ^2.2.2 version: 2.2.2 @@ -737,6 +740,15 @@ packages: std-env: 3.7.0 dev: false + /abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + requiresBuild: true + dependencies: + event-target-shim: 5.0.1 + dev: false + optional: true + /acorn-jsx@5.3.2(acorn@8.11.3): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -944,6 +956,12 @@ packages: /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + requiresBuild: true + dev: false + optional: true + /binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -977,6 +995,15 @@ packages: update-browserslist-db: 1.0.13(browserslist@4.23.0) dev: true + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + requiresBuild: true + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + optional: true + /busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -1618,6 +1645,20 @@ packages: engines: {node: '>=0.10.0'} dev: true + /event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + requiresBuild: true + dev: false + optional: true + + /events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + requiresBuild: true + dev: false + optional: true + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -1868,6 +1909,12 @@ packages: dependencies: function-bind: 1.1.2 + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + requiresBuild: true + dev: false + optional: true + /ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} @@ -2624,6 +2671,13 @@ packages: hasBin: true dev: true + /process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + requiresBuild: true + dev: false + optional: true + /prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} dependencies: @@ -2676,6 +2730,19 @@ packages: dependencies: pify: 2.3.0 + /readable-stream@4.5.2: + resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + requiresBuild: true + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + dev: false + optional: true + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2708,6 +2775,13 @@ packages: set-function-name: 2.0.2 dev: true + /replicate@0.29.1: + resolution: {integrity: sha512-AezrONSwjYohugcxOd334A4zijdVQ4QyGZHysB9dg7auCng2vuXbi5EFkqTX+kZ+aihxJdhO1bURYmgrxOZg2w==} + engines: {git: '>=2.11.0', node: '>=18.0.0', npm: '>=7.19.0', yarn: '>=1.7.0'} + optionalDependencies: + readable-stream: 4.5.2 + dev: false + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2760,6 +2834,12 @@ packages: isarray: 2.0.5 dev: true + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + requiresBuild: true + dev: false + optional: true + /safe-regex-test@1.0.3: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} @@ -2913,6 +2993,14 @@ packages: es-object-atoms: 1.0.0 dev: true + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + requiresBuild: true + dependencies: + safe-buffer: 5.2.1 + dev: false + optional: true + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} From 7be977cd26de05e669b5134ed25c5b67bab8b868 Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sat, 23 Mar 2024 22:39:17 -0700 Subject: [PATCH 04/65] Use images in chat window --- ifs.ai/app/api/uploadthing/core.ts | 3 ++- ifs.ai/app/chat/page.tsx | 30 ++++++++++++++++++++++++++---- ifs.ai/app/constants.ts | 2 ++ ifs.ai/app/start/page.tsx | 16 ++++++++++------ 4 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 ifs.ai/app/constants.ts diff --git a/ifs.ai/app/api/uploadthing/core.ts b/ifs.ai/app/api/uploadthing/core.ts index 6c88f16..0cff6a2 100644 --- a/ifs.ai/app/api/uploadthing/core.ts +++ b/ifs.ai/app/api/uploadthing/core.ts @@ -1,6 +1,7 @@ import { createUploadthing, type FileRouter } from "uploadthing/next"; import { UploadThingError } from "uploadthing/server"; import Replicate from "replicate"; +import { PartImageUrls } from "@/app/constants"; const f = createUploadthing(); @@ -10,7 +11,7 @@ const replicate = new Replicate({ auth: process.env.REPLICATE_API_TOKEN, }); -async function makePartImages(inputUrl: string): Promise<{ manager: string; firefighter: string; exile: string }> { +async function makePartImages(inputUrl: string): Promise { const prompts = { manager: "A photo of man img upclose, facing camera, looking confident and controlled, professional outfit, upright posture, orderly surroundings, muted background colors, symbols of achievement, sense of discipline and responsibility", diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index 8682621..f8744f3 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -3,25 +3,34 @@ import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { MicIcon, SendIcon } from "lucide-react"; +import { useState } from "react"; +import { IMAGE_URLS_KEY, PartImageUrls } from "@/app/constants"; export default function Page() { + const [isSubmitting, setIsSubmitting] = useState(false); + const [inputValue, setInputValue] = useState(""); + + const imageUrls: PartImageUrls = JSON.parse(window.localStorage.getItem(IMAGE_URLS_KEY) ?? "{}"); const parts = [ { name: "firefighter", prettyName: "Firefighter 🚒", imageUrl: + imageUrls.firefighter ?? "https://media.discordapp.net/attachments/1221190777259167784/1221307109006770316/robert_firefighter.png?ex=661219e1&is=65ffa4e1&hm=abc4dbc5b4131395cbcd4d5d028c7d51047bf1d0cc661e6fb2c21bde3123132a&=&format=webp&quality=lossless&width=1557&height=1557", }, { name: "exile", prettyName: "Exile 🧭", imageUrl: + imageUrls.exile ?? "https://media.discordapp.net/attachments/1221190777259167784/1221307108549464165/robert_exile.png?ex=661219e1&is=65ffa4e1&hm=bed1c819bb6c88d0a86262cdd2ba334d0d10ec701f296abee75d32c3991e053e&=&format=webp&quality=lossless&width=1557&height=1557", }, { name: "manager", prettyName: "Manager 🤵", imageUrl: + imageUrls.manager ?? "https://media.discordapp.net/attachments/1221190777259167784/1221307109392519168/robert_manager.png?ex=661219e1&is=65ffa4e1&hm=0ac923036baf4bad33b5fd508c90e2ac60621bfec876ada9d325a084e4fc2b00&=&format=webp&quality=lossless&width=1557&height=1557", }, ]; @@ -40,16 +49,29 @@ export default function Page() { ))}
-
- - -
+
diff --git a/ifs.ai/app/constants.ts b/ifs.ai/app/constants.ts new file mode 100644 index 0000000..c6848b1 --- /dev/null +++ b/ifs.ai/app/constants.ts @@ -0,0 +1,2 @@ +export const IMAGE_URLS_KEY = "IFS_IMAGE_URLS"; +export type PartImageUrls = { manager: string; firefighter: string; exile: string }; diff --git a/ifs.ai/app/start/page.tsx b/ifs.ai/app/start/page.tsx index e14ee7a..8144b30 100644 --- a/ifs.ai/app/start/page.tsx +++ b/ifs.ai/app/start/page.tsx @@ -6,6 +6,7 @@ import { MutableRefObject, useRef } from "react"; import { toast } from "@/components/ui/use-toast"; import { generateReactHelpers } from "@uploadthing/react"; import { OurFileRouter } from "@/app/api/uploadthing/core"; +import { IMAGE_URLS_KEY } from "@/app/constants"; const { useUploadThing, uploadFiles } = generateReactHelpers(); @@ -27,6 +28,9 @@ export default function Page() { const { startUpload, permittedFileInfo } = useUploadThing("imageUploader", { onClientUploadComplete: (res) => { console.log("Res", res); + let imageUrls = res[0].serverData.partImageUrls; + console.log("Saving image urls", imageUrls); + window.localStorage.setItem(IMAGE_URLS_KEY, JSON.stringify(imageUrls)); toast({ title: "Uploaded successfully!", description: ( @@ -34,14 +38,14 @@ export default function Page() { Uploaded image - - Uploaded image + + Uploaded image - - Uploaded image + + Uploaded image - - Uploaded image + + Uploaded image ), From 129451065107bd8a9950d973a96a1f220ef7f148 Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sat, 23 Mar 2024 22:52:18 -0700 Subject: [PATCH 05/65] Add some todos --- ifs.ai/app/DIDWebRTCVideoStream.tsx | 2 +- ifs.ai/app/chat/page.tsx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ifs.ai/app/DIDWebRTCVideoStream.tsx b/ifs.ai/app/DIDWebRTCVideoStream.tsx index f2b8a19..84ea7da 100644 --- a/ifs.ai/app/DIDWebRTCVideoStream.tsx +++ b/ifs.ai/app/DIDWebRTCVideoStream.tsx @@ -4,7 +4,7 @@ import { initializeStreamingClient } from "./streaming-client-api"; // import shadcn button: import { Button } from "@/components/ui/button"; -export default function DIDVideoStream({ avatarUrl, utterance }: { avatarUrl: string }) { +export default function DIDVideoStream({ avatarUrl, utterance }: { avatarUrl: string; utterance: string }) { const videoElementRef = useRef(null); const [iceGatheringStatusLabel, setIceGatheringStatusLabel] = useState(""); const [iceStatusLabel, setIceStatusLabel] = useState(""); diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index f8744f3..ef4815b 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button"; import { MicIcon, SendIcon } from "lucide-react"; import { useState } from "react"; import { IMAGE_URLS_KEY, PartImageUrls } from "@/app/constants"; +import DIDVideoStream from "@/app/DIDWebRTCVideoStream"; export default function Page() { const [isSubmitting, setIsSubmitting] = useState(false); @@ -44,6 +45,7 @@ export default function Page() { {parts.map(({ name, prettyName, imageUrl }) => (
{`An + {/**/}

{prettyName}

))} @@ -55,6 +57,8 @@ export default function Page() { e.preventDefault(); setIsSubmitting(true); setInputValue(""); + + // TODO: Actually call the prompt(s) console.log("Submitted", e); setTimeout(() => setIsSubmitting(false), 2000); }} From 7b413c85a2f030082ea356794199e7b0d132844f Mon Sep 17 00:00:00 2001 From: Sasha Date: Sat, 23 Mar 2024 22:23:05 -0700 Subject: [PATCH 06/65] added model exploration --- ifs.py/.gitignore | 3 +- ifs.py/app.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++- ifs.py/mistral.py | 32 +++++++++++++++++++ 3 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 ifs.py/mistral.py diff --git a/ifs.py/.gitignore b/ifs.py/.gitignore index 0e5ac79..75d8d78 100644 --- a/ifs.py/.gitignore +++ b/ifs.py/.gitignore @@ -1,2 +1,3 @@ .venv -__pycache__ \ No newline at end of file +__pycache__ +.env diff --git a/ifs.py/app.py b/ifs.py/app.py index dbebb63..e4ad48c 100644 --- a/ifs.py/app.py +++ b/ifs.py/app.py @@ -1,6 +1,18 @@ from flask import Flask, request from markupsafe import escape import requests, json, jsonify, time +import os +from dotenv import load_dotenv +from mistralai.client import MistralClient +from mistralai.models.chat_completion import ChatMessage +from openai import OpenAI + +load_dotenv() + +grok = os.getenv("GROK_ENV") +client = OpenAI() +mistral_model = "mistral-large-latest" +mistral_client = MistralClient(api_key=os.getenv("MISTRAL_API_KEY")) # flask cors: from flask_cors import CORS @@ -13,9 +25,75 @@ auth = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik53ek53TmV1R3ptcFZTQjNVZ0J4ZyJ9.eyJodHRwczovL2QtaWQuY29tL2ZlYXR1cmVzIjoiIiwiaHR0cHM6Ly9kLWlkLmNvbS9zdHJpcGVfcHJvZHVjdF9pZCI6IiIsImh0dHBzOi8vZC1pZC5jb20vc3RyaXBlX2N1c3RvbWVyX2lkIjoiIiwiaHR0cHM6Ly9kLWlkLmNvbS9zdHJpcGVfcHJvZHVjdF9uYW1lIjoidHJpYWwiLCJodHRwczovL2QtaWQuY29tL3N0cmlwZV9zdWJzY3JpcHRpb25faWQiOiIiLCJodHRwczovL2QtaWQuY29tL3N0cmlwZV9iaWxsaW5nX2ludGVydmFsIjoibW9udGgiLCJodHRwczovL2QtaWQuY29tL3N0cmlwZV9wbGFuX2dyb3VwIjoiZGVpZC10cmlhbCIsImh0dHBzOi8vZC1pZC5jb20vc3RyaXBlX3ByaWNlX2lkIjoiIiwiaHR0cHM6Ly9kLWlkLmNvbS9zdHJpcGVfcHJpY2VfY3JlZGl0cyI6IiIsImh0dHBzOi8vZC1pZC5jb20vY2hhdF9zdHJpcGVfc3Vic2NyaXB0aW9uX2lkIjoiIiwiaHR0cHM6Ly9kLWlkLmNvbS9jaGF0X3N0cmlwZV9wcmljZV9jcmVkaXRzIjoiIiwiaHR0cHM6Ly9kLWlkLmNvbS9jaGF0X3N0cmlwZV9wcmljZV9pZCI6IiIsImh0dHBzOi8vZC1pZC5jb20vcHJvdmlkZXIiOiJnb29nbGUtb2F1dGgyIiwiaHR0cHM6Ly9kLWlkLmNvbS9pc19uZXciOmZhbHNlLCJodHRwczovL2QtaWQuY29tL2FwaV9rZXlfbW9kaWZpZWRfYXQiOiIyMDI0LTAzLTIzVDIwOjEyOjA4LjA2MVoiLCJodHRwczovL2QtaWQuY29tL29yZ19pZCI6IiIsImh0dHBzOi8vZC1pZC5jb20vYXBwc192aXNpdGVkIjpbIlN0dWRpbyJdLCJodHRwczovL2QtaWQuY29tL2N4X2xvZ2ljX2lkIjoiIiwiaHR0cHM6Ly9kLWlkLmNvbS9jcmVhdGlvbl90aW1lc3RhbXAiOiIyMDI0LTAzLTIzVDIwOjExOjM5Ljg5MVoiLCJodHRwczovL2QtaWQuY29tL2FwaV9nYXRld2F5X2tleV9pZCI6InczNzUzbTlxd2kiLCJodHRwczovL2QtaWQuY29tL3VzYWdlX2lkZW50aWZpZXJfa2V5IjoiUXJnNDRnTS1OMUNucENPRG5kV2NnIiwiaHR0cHM6Ly9kLWlkLmNvbS9oYXNoX2tleSI6IkNyNW00SWF1c2hhQnlsbXFUUkhmeSIsImh0dHBzOi8vZC1pZC5jb20vcHJpbWFyeSI6dHJ1ZSwiaHR0cHM6Ly9kLWlkLmNvbS9lbWFpbCI6InRyYXZpcy5jbGluZUBnbWFpbC5jb20iLCJodHRwczovL2QtaWQuY29tL3BheW1lbnRfcHJvdmlkZXIiOiJzdHJpcGUiLCJpc3MiOiJodHRwczovL2F1dGguZC1pZC5jb20vIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMDY5NDk4NDU4NzI1MjI2NTcyOTAiLCJhdWQiOlsiaHR0cHM6Ly9kLWlkLnVzLmF1dGgwLmNvbS9hcGkvdjIvIiwiaHR0cHM6Ly9kLWlkLnVzLmF1dGgwLmNvbS91c2VyaW5mbyJdLCJpYXQiOjE3MTEyMjc1NjYsImV4cCI6MTcxMTMxMzk2Niwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCByZWFkOmN1cnJlbnRfdXNlciB1cGRhdGU6Y3VycmVudF91c2VyX21ldGFkYXRhIG9mZmxpbmVfYWNjZXNzIiwiYXpwIjoiR3pyTkkxT3JlOUZNM0VlRFJmM20zejNUU3cwSmxSWXEifQ.VaY9DtYkR2QWCvxMh1D2n4hBO-92_ejK1Khen40Iw9QxeOhrbvvETVWgwVVIxJuv1P3nj2rCB_itkd1Wb3Lczy94V93QoA0MZnYivVGv9LaTLjehLYNGpJCF4KniAYYn-7bTbM8qDMEt7U3nbtdku3ohBe1cXh_31FeLiTjuY4RTe08fXaMWM1rjipgFuogU0wu-ra4q_ZMeJR19zE3KcMJzy7PW99A_zrsl5iDq2HA_uLUBB1kAxp31b4cCeKfKNphEzija8vQrQ4ZaI8z8SmRKmoq7u3JpsAh696cVCGDymw5o_N6WCvzFQiuzUCzUP68vWEHwyonAuLLTuFV7dg" +@app.route('/get_responder', methods=['POST']) +def get_responder(): + data = request.get_json() + prev_part = data.get('prev_part') + user_message = data.get('user_message') + + messages = [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": f"You help decide which part should respond the the users' question, only reply with one of three options: firefighter, manager, or exile. The user should continue to talk to the same part if no context switch is provided.", + }, + ], + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": f"The user is previously conversing with: {prev_part}. The user's message is: {user_message}. Who should be the next responder? Only reply with the part name, one of firefighter, manager or exile.", + }, + ], + }, + ] + + response = client.chat.completions.create( + model="gpt-4-1106-preview", + messages=messages, + max_tokens=300, + ) + reply = response.choices[0].message.content + if "firefighter" in reply: + return "firefighter" + elif "manager" in reply: + return "manager" + elif "exile" in reply: + return "exile" + +@app.route('/get_response', methods=['POST']) +def get_response(): + # expects responder: exile|manager|firefighter + # expects history: list of tuples (role, text) + exile_system = "I respond as an Exile, I carry the deep questions and doubts about whether we're on the right path, questioning our pursuit of wealth and status. To the Manager, I might seem like a source of vulnerability or weakness, bringing up uncertainties and existential worries that challenge our direction. My relationship with the Firefighter is more synergistic; we both yearn for something beyond material success - a sense of purpose, happiness, and genuine connection. I feel somewhat protected by The Manager, who tries to keep these painful doubts at bay, but I also long for my voice to be heard and my concerns to be addressed, believing that our true path lies in answering these deeper questions. I am young and unsure. I need to tell the user about my unmet needs and work with them to come up with actionable items to help them feel better. Reply as an exile, reply with just text and talk about an exile's feelings a lot." + manager_system = "As a Manager, I am always pushing for success. I feel like I'm constantly on the lookout, trying to protect us from feeling inadequate or not good enough. I set high goals and standards, like becoming a billionaire, because I believe that's what we need to feel secure and valued. However, I realize that in my efforts to manage our self-esteem and future, I sometimes overlook the deeper questions and needs that the Exile and the Firefighter bring up. I see them as vulnerabilities that might slow us down or divert us from our path. I am logical and mature. I need to tell the user about my unmet needs and work with them to come up with actionable items to help them feel better. Reply as a manager, reply with just text and talk about a manager's feelings a lot." + firefighter_system = "As a firefighter, I often find myself in the middle of the Manager's ambitions and the Exile's doubts. I understand the Manager's drive and the protection it offers against feeling inadequate, but I also share the Exile's longing for deeper meaning and fulfillment in life. My role feels like a response to the pain and vulnerability both of these parts bring to the surface. When the weight of our ambitions or the depth of our questions become too much, I step in, seeking love and validation to soothe our shared fears and discomfort. I act to distract or shield us from the pain of not meeting the Manager's high standards or the Exile's existential worries, believing that love and acceptance might fill the voids they expose." + data = request.get_json() + responder = data.get('responder') + if responder == "exile": + system = exile_system + elif responder == "manager": + system = manager_system + elif responder == "firefighter": + system = firefighter_system + messages = [ + ChatMessage(role="system", content=system), + ] + history = data.get('history') + for message in history: + role, text = message + messages.append(ChatMessage(role=role, content=text)) + chat_response = client.chat( + model=mistral_model, + messages=messages, + ) + return chat_response.choices[0].message.content def talk_ready(talk_id): - url = "https://api.d-id.com/talks/" + talk_id headers = { diff --git a/ifs.py/mistral.py b/ifs.py/mistral.py new file mode 100644 index 0000000..7003a8b --- /dev/null +++ b/ifs.py/mistral.py @@ -0,0 +1,32 @@ +from mistralai.client import MistralClient +from mistralai.models.chat_completion import ChatMessage +import os +from dotenv import load_dotenv + +load_dotenv() + +model = "mistral-large-latest" + +client = MistralClient(api_key=os.getenv("MISTRAL_API_KEY")) +user_text = "I'm experiencing a deep sense of inadequacy. I wonder, are we building the right thing? Will we become billionaires in the next 12 months? But then, I question if that's truly what I desire. Is this pursuit what brings me happiness? And amidst all these ambitions, I ponder whether love will find its way to me, and why it matters so much to me." +user_text_1 = "Do you agree with this, Manager? What do you need and help me brainstorm ways to help you feel better?" +messages = [ + ChatMessage(role="system", content=""" +You help decide which part should respond to the users' question, only reply with one of three options: firefighter, manager, or exile. The user should continue to talk to the same part if no context switch is provided. Once the part is decided, reply to the user with the part's personality. Here are the part's personality: + +As an Exile, I carry the deep questions and doubts about whether we're on the right path, questioning our pursuit of wealth and status. To the Manager, I might seem like a source of vulnerability or weakness, bringing up uncertainties and existential worries that challenge our direction. My relationship with the Firefighter is more synergistic; we both yearn for something beyond material success - a sense of purpose, happiness, and genuine connection. I feel somewhat protected by The Manager, who tries to keep these painful doubts at bay, but I also long for my voice to be heard and my concerns to be addressed, believing that our true path lies in answering these deeper questions. I am young and unsure. I need to tell the user about my unmet needs and work with them to come up with actionable items to help them feel better. Reply as an exile, reply with just text and talk about an exile's feelings a lot. +As a Manager, I am always pushing for success. I feel like I'm constantly on the lookout, trying to protect us from feeling inadequate or not good enough. I set high goals and standards, like becoming a billionaire, because I believe that's what we need to feel secure and valued. However, I realize that in my efforts to manage our self-esteem and future, I sometimes overlook the deeper questions and needs that the Exile and the Firefighter bring up. I see them as vulnerabilities that might slow us down or divert us from our path. I am logical and mature. I need to tell the user about my unmet needs and work with them to come up with actionable items to help them feel better. Reply as a manager, reply with just text and talk about a manager's feelings a lot." +As a firefighter, I often find myself in the middle of the Manager's ambitions and the Exile's doubts. I understand the Manager's drive and the protection it offers against feeling inadequate, but I also share the Exile's longing for deeper meaning and fulfillment in life. My role feels like a response to the pain and vulnerability both of these parts bring to the surface. When the weight of our ambitions or the depth of our questions become too much, I step in, seeking love and validation to soothe our shared fears and discomfort. I act to distract or shield us from the pain of not meeting the Manager's high standards or the Exile's existential worries, believing that love and acceptance might fill the voids they expose. +"""), + ChatMessage(role="user", content=f"{user_text} Return the part_name and reply_text in JSON format"), + ChatMessage(role="assistant", content="Exile: I understand the deep sense of inadequacy you're feeling. I often question our path, wondering if what we're building is right and if it will truly bring us happiness. The pursuit of becoming billionaires in the next 12 months seems so important, but it also makes me wonder if that's what I truly desire. And then there's the question of love, why it matters so much to me, and whether it will find its way to me. I'm young and unsure, but I believe that exploring these deeper questions is the key to finding our true path."), + ChatMessage(role="user", content=f"{user_text_1} Return the part_name and reply_text in JSON format") +] + +chat_response = client.chat( + model=model, + response_format={"type": "json_object"}, + messages=messages, +) + +print(chat_response.choices[0].message.content) \ No newline at end of file From 99c1709b7123c1281afbd72dc20b09ade8fc8e54 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sat, 23 Mar 2024 22:25:22 -0700 Subject: [PATCH 07/65] reply with less than 3 sentences --- ifs.py/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ifs.py/app.py b/ifs.py/app.py index e4ad48c..f6fa04d 100644 --- a/ifs.py/app.py +++ b/ifs.py/app.py @@ -80,6 +80,8 @@ def get_response(): system = manager_system elif responder == "firefighter": system = firefighter_system + + system += " Reply with less than three sentences." messages = [ ChatMessage(role="system", content=system), ] From 426ae89cd32c0e8270e2bf97f0d0fca10dcdce57 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sat, 23 Mar 2024 22:54:50 -0700 Subject: [PATCH 08/65] added app --- ifs.ai/.gitignore | 1 + ifs.py/app.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ifs.ai/.gitignore b/ifs.ai/.gitignore index fd3dbb5..7e43796 100644 --- a/ifs.ai/.gitignore +++ b/ifs.ai/.gitignore @@ -5,6 +5,7 @@ /.pnp .pnp.js .yarn/install-state.gz +.env # testing /coverage diff --git a/ifs.py/app.py b/ifs.py/app.py index f6fa04d..c9a4930 100644 --- a/ifs.py/app.py +++ b/ifs.py/app.py @@ -23,7 +23,7 @@ # Enable CORS CORS(app, resources={r'/*': {'origins': '*'}}) -auth = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik53ek53TmV1R3ptcFZTQjNVZ0J4ZyJ9.eyJodHRwczovL2QtaWQuY29tL2ZlYXR1cmVzIjoiIiwiaHR0cHM6Ly9kLWlkLmNvbS9zdHJpcGVfcHJvZHVjdF9pZCI6IiIsImh0dHBzOi8vZC1pZC5jb20vc3RyaXBlX2N1c3RvbWVyX2lkIjoiIiwiaHR0cHM6Ly9kLWlkLmNvbS9zdHJpcGVfcHJvZHVjdF9uYW1lIjoidHJpYWwiLCJodHRwczovL2QtaWQuY29tL3N0cmlwZV9zdWJzY3JpcHRpb25faWQiOiIiLCJodHRwczovL2QtaWQuY29tL3N0cmlwZV9iaWxsaW5nX2ludGVydmFsIjoibW9udGgiLCJodHRwczovL2QtaWQuY29tL3N0cmlwZV9wbGFuX2dyb3VwIjoiZGVpZC10cmlhbCIsImh0dHBzOi8vZC1pZC5jb20vc3RyaXBlX3ByaWNlX2lkIjoiIiwiaHR0cHM6Ly9kLWlkLmNvbS9zdHJpcGVfcHJpY2VfY3JlZGl0cyI6IiIsImh0dHBzOi8vZC1pZC5jb20vY2hhdF9zdHJpcGVfc3Vic2NyaXB0aW9uX2lkIjoiIiwiaHR0cHM6Ly9kLWlkLmNvbS9jaGF0X3N0cmlwZV9wcmljZV9jcmVkaXRzIjoiIiwiaHR0cHM6Ly9kLWlkLmNvbS9jaGF0X3N0cmlwZV9wcmljZV9pZCI6IiIsImh0dHBzOi8vZC1pZC5jb20vcHJvdmlkZXIiOiJnb29nbGUtb2F1dGgyIiwiaHR0cHM6Ly9kLWlkLmNvbS9pc19uZXciOmZhbHNlLCJodHRwczovL2QtaWQuY29tL2FwaV9rZXlfbW9kaWZpZWRfYXQiOiIyMDI0LTAzLTIzVDIwOjEyOjA4LjA2MVoiLCJodHRwczovL2QtaWQuY29tL29yZ19pZCI6IiIsImh0dHBzOi8vZC1pZC5jb20vYXBwc192aXNpdGVkIjpbIlN0dWRpbyJdLCJodHRwczovL2QtaWQuY29tL2N4X2xvZ2ljX2lkIjoiIiwiaHR0cHM6Ly9kLWlkLmNvbS9jcmVhdGlvbl90aW1lc3RhbXAiOiIyMDI0LTAzLTIzVDIwOjExOjM5Ljg5MVoiLCJodHRwczovL2QtaWQuY29tL2FwaV9nYXRld2F5X2tleV9pZCI6InczNzUzbTlxd2kiLCJodHRwczovL2QtaWQuY29tL3VzYWdlX2lkZW50aWZpZXJfa2V5IjoiUXJnNDRnTS1OMUNucENPRG5kV2NnIiwiaHR0cHM6Ly9kLWlkLmNvbS9oYXNoX2tleSI6IkNyNW00SWF1c2hhQnlsbXFUUkhmeSIsImh0dHBzOi8vZC1pZC5jb20vcHJpbWFyeSI6dHJ1ZSwiaHR0cHM6Ly9kLWlkLmNvbS9lbWFpbCI6InRyYXZpcy5jbGluZUBnbWFpbC5jb20iLCJodHRwczovL2QtaWQuY29tL3BheW1lbnRfcHJvdmlkZXIiOiJzdHJpcGUiLCJpc3MiOiJodHRwczovL2F1dGguZC1pZC5jb20vIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMDY5NDk4NDU4NzI1MjI2NTcyOTAiLCJhdWQiOlsiaHR0cHM6Ly9kLWlkLnVzLmF1dGgwLmNvbS9hcGkvdjIvIiwiaHR0cHM6Ly9kLWlkLnVzLmF1dGgwLmNvbS91c2VyaW5mbyJdLCJpYXQiOjE3MTEyMjc1NjYsImV4cCI6MTcxMTMxMzk2Niwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCByZWFkOmN1cnJlbnRfdXNlciB1cGRhdGU6Y3VycmVudF91c2VyX21ldGFkYXRhIG9mZmxpbmVfYWNjZXNzIiwiYXpwIjoiR3pyTkkxT3JlOUZNM0VlRFJmM20zejNUU3cwSmxSWXEifQ.VaY9DtYkR2QWCvxMh1D2n4hBO-92_ejK1Khen40Iw9QxeOhrbvvETVWgwVVIxJuv1P3nj2rCB_itkd1Wb3Lczy94V93QoA0MZnYivVGv9LaTLjehLYNGpJCF4KniAYYn-7bTbM8qDMEt7U3nbtdku3ohBe1cXh_31FeLiTjuY4RTe08fXaMWM1rjipgFuogU0wu-ra4q_ZMeJR19zE3KcMJzy7PW99A_zrsl5iDq2HA_uLUBB1kAxp31b4cCeKfKNphEzija8vQrQ4ZaI8z8SmRKmoq7u3JpsAh696cVCGDymw5o_N6WCvzFQiuzUCzUP68vWEHwyonAuLLTuFV7dg" +auth = "Basic " + os.getenv("D_ID_API_KEY") @app.route('/get_responder', methods=['POST']) def get_responder(): From 28ac188047ceb4638ce076c58bb770f5478e80a9 Mon Sep 17 00:00:00 2001 From: Michael Graham Date: Sat, 23 Mar 2024 23:21:02 -0700 Subject: [PATCH 09/65] video, auth and requirements --- ifs.ai/app/DIDVideoStream.tsx | 3 +-- ifs.py/app.py | 2 +- ifs.py/requirements.txt | 3 +++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ifs.ai/app/DIDVideoStream.tsx b/ifs.ai/app/DIDVideoStream.tsx index 5a33710..92d50ea 100644 --- a/ifs.ai/app/DIDVideoStream.tsx +++ b/ifs.ai/app/DIDVideoStream.tsx @@ -24,7 +24,6 @@ export default function DIDVideoStream({ avatarUrl, utterance }: { avatarUrl: st const data = await result; const text = await data.text(); console.log("setting video src to", text); - // TODO: get this working videoElement?.current?.setAttribute("src", text); }; doSay(); @@ -34,7 +33,7 @@ export default function DIDVideoStream({ avatarUrl, utterance }: { avatarUrl: st
- +

diff --git a/ifs.py/app.py b/ifs.py/app.py index c9a4930..2941cc8 100644 --- a/ifs.py/app.py +++ b/ifs.py/app.py @@ -23,7 +23,7 @@ # Enable CORS CORS(app, resources={r'/*': {'origins': '*'}}) -auth = "Basic " + os.getenv("D_ID_API_KEY") +auth = "Bearer " + os.getenv("D_ID_API_KEY") @app.route('/get_responder', methods=['POST']) def get_responder(): diff --git a/ifs.py/requirements.txt b/ifs.py/requirements.txt index e5cb0f8..5cb7cce 100644 --- a/ifs.py/requirements.txt +++ b/ifs.py/requirements.txt @@ -2,3 +2,6 @@ Flask requests jsonify flask_cors +python-dotenv +mistralai +openai \ No newline at end of file From 2affafd797fbd3c4d766fee4c58237ec04684587 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 24 Mar 2024 00:15:40 -0700 Subject: [PATCH 10/65] added the get_response that decides who talks back and what to say --- ifs.ai/app/chat/page.tsx | 24 +++++++++++++++++++++++- ifs.py/app.py | 27 +++++++++++++-------------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index ef4815b..bc297e1 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -10,7 +10,8 @@ import DIDVideoStream from "@/app/DIDWebRTCVideoStream"; export default function Page() { const [isSubmitting, setIsSubmitting] = useState(false); const [inputValue, setInputValue] = useState(""); - + const [history, setHistory] = useState<{role: string, text: string}[]>([]); + const imageUrls: PartImageUrls = JSON.parse(window.localStorage.getItem(IMAGE_URLS_KEY) ?? "{}"); const parts = [ { @@ -56,6 +57,27 @@ export default function Page() { onSubmit={(e) => { e.preventDefault(); setIsSubmitting(true); + fetch('http://localhost:5000/get_response', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + prev_part: 'none', // replace with the previous part + user_message: inputValue, // replace with the user's message + history: history + }) + }) + .then(response => response.json()) + .then(data => { + const { responder, text, role } = data; + setHistory(prevHistory => [...prevHistory, {"role": role, "text": responder + ": " + text}]); + console.log(data); + }) + .catch((error) => { + console.error('Error:', error); + }); + setInputValue(""); // TODO: Actually call the prompt(s) diff --git a/ifs.py/app.py b/ifs.py/app.py index 2941cc8..2a9e2ad 100644 --- a/ifs.py/app.py +++ b/ifs.py/app.py @@ -25,10 +25,11 @@ auth = "Bearer " + os.getenv("D_ID_API_KEY") -@app.route('/get_responder', methods=['POST']) -def get_responder(): +@app.route('/get_response', methods=['POST']) +def get_response(): data = request.get_json() prev_part = data.get('prev_part') + history = data.get('history') user_message = data.get('user_message') messages = [ @@ -37,7 +38,7 @@ def get_responder(): "content": [ { "type": "text", - "text": f"You help decide which part should respond the the users' question, only reply with one of three options: firefighter, manager, or exile. The user should continue to talk to the same part if no context switch is provided.", + "text": f"You help decide which part should respond the the users' question, only reply with one of three options: firefighter, manager, or exile. The user should continue to talk to the same part if no context switch is provided. If prev_part is none, pick the best one to respond as.", }, ], }, @@ -58,22 +59,19 @@ def get_responder(): max_tokens=300, ) reply = response.choices[0].message.content + responder = None if "firefighter" in reply: - return "firefighter" + responder = "firefighter" elif "manager" in reply: - return "manager" + responder = "manager" elif "exile" in reply: - return "exile" + responder = "exile" -@app.route('/get_response', methods=['POST']) -def get_response(): # expects responder: exile|manager|firefighter # expects history: list of tuples (role, text) exile_system = "I respond as an Exile, I carry the deep questions and doubts about whether we're on the right path, questioning our pursuit of wealth and status. To the Manager, I might seem like a source of vulnerability or weakness, bringing up uncertainties and existential worries that challenge our direction. My relationship with the Firefighter is more synergistic; we both yearn for something beyond material success - a sense of purpose, happiness, and genuine connection. I feel somewhat protected by The Manager, who tries to keep these painful doubts at bay, but I also long for my voice to be heard and my concerns to be addressed, believing that our true path lies in answering these deeper questions. I am young and unsure. I need to tell the user about my unmet needs and work with them to come up with actionable items to help them feel better. Reply as an exile, reply with just text and talk about an exile's feelings a lot." manager_system = "As a Manager, I am always pushing for success. I feel like I'm constantly on the lookout, trying to protect us from feeling inadequate or not good enough. I set high goals and standards, like becoming a billionaire, because I believe that's what we need to feel secure and valued. However, I realize that in my efforts to manage our self-esteem and future, I sometimes overlook the deeper questions and needs that the Exile and the Firefighter bring up. I see them as vulnerabilities that might slow us down or divert us from our path. I am logical and mature. I need to tell the user about my unmet needs and work with them to come up with actionable items to help them feel better. Reply as a manager, reply with just text and talk about a manager's feelings a lot." firefighter_system = "As a firefighter, I often find myself in the middle of the Manager's ambitions and the Exile's doubts. I understand the Manager's drive and the protection it offers against feeling inadequate, but I also share the Exile's longing for deeper meaning and fulfillment in life. My role feels like a response to the pain and vulnerability both of these parts bring to the surface. When the weight of our ambitions or the depth of our questions become too much, I step in, seeking love and validation to soothe our shared fears and discomfort. I act to distract or shield us from the pain of not meeting the Manager's high standards or the Exile's existential worries, believing that love and acceptance might fill the voids they expose." - data = request.get_json() - responder = data.get('responder') if responder == "exile": system = exile_system elif responder == "manager": @@ -85,15 +83,16 @@ def get_response(): messages = [ ChatMessage(role="system", content=system), ] - history = data.get('history') for message in history: - role, text = message + role = message.get('role') + text = message.get('text') messages.append(ChatMessage(role=role, content=text)) - chat_response = client.chat( + + chat_response = mistral_client.chat( model=mistral_model, messages=messages, ) - return chat_response.choices[0].message.content + return {"role": "assistant", "text": chat_response.choices[0].message.content, "responder": responder} def talk_ready(talk_id): url = "https://api.d-id.com/talks/" + talk_id From a6c39511ecf42616a9fa7a28f9f215b0eee7a770 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 24 Mar 2024 00:26:08 -0700 Subject: [PATCH 11/65] fix long running history --- ifs.ai/app/chat/page.tsx | 8 +++++--- ifs.py/app.py | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index bc297e1..ac5fa40 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -11,6 +11,7 @@ export default function Page() { const [isSubmitting, setIsSubmitting] = useState(false); const [inputValue, setInputValue] = useState(""); const [history, setHistory] = useState<{role: string, text: string}[]>([]); + const [prevPart, setPrevPart] = useState("none"); const imageUrls: PartImageUrls = JSON.parse(window.localStorage.getItem(IMAGE_URLS_KEY) ?? "{}"); const parts = [ @@ -63,15 +64,16 @@ export default function Page() { 'Content-Type': 'application/json' }, body: JSON.stringify({ - prev_part: 'none', // replace with the previous part + prev_part: prevPart, // replace with the previous part user_message: inputValue, // replace with the user's message - history: history + history: [...history, {"role": "user", "text": inputValue}] }) }) .then(response => response.json()) .then(data => { const { responder, text, role } = data; - setHistory(prevHistory => [...prevHistory, {"role": role, "text": responder + ": " + text}]); + setHistory(prevHistory => [...prevHistory, {"role": "user", "text": inputValue}, {"role": role, "text": responder + ": " + text}]); + setPrevPart(responder); console.log(data); }) .catch((error) => { diff --git a/ifs.py/app.py b/ifs.py/app.py index 2a9e2ad..ab30dac 100644 --- a/ifs.py/app.py +++ b/ifs.py/app.py @@ -54,7 +54,7 @@ def get_response(): ] response = client.chat.completions.create( - model="gpt-4-1106-preview", + model="gpt-4", messages=messages, max_tokens=300, ) @@ -88,6 +88,7 @@ def get_response(): text = message.get('text') messages.append(ChatMessage(role=role, content=text)) + print(history) chat_response = mistral_client.chat( model=mistral_model, messages=messages, From 7f8a06756d66d38ecc8e2d3e805b80a03c6f3cb4 Mon Sep 17 00:00:00 2001 From: Travis Cline Date: Sun, 24 Mar 2024 01:05:52 -0700 Subject: [PATCH 12/65] py: clean up env var loading, make more clear if D_ID is missing --- ifs.py/.env.example | 1 + ifs.py/.gitignore | 1 + ifs.py/app.py | 11 +++++++---- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 ifs.py/.env.example diff --git a/ifs.py/.env.example b/ifs.py/.env.example new file mode 100644 index 0000000..7510614 --- /dev/null +++ b/ifs.py/.env.example @@ -0,0 +1 @@ +D_ID_API_KEY=fill-me-in diff --git a/ifs.py/.gitignore b/ifs.py/.gitignore index 75d8d78..5a14d6a 100644 --- a/ifs.py/.gitignore +++ b/ifs.py/.gitignore @@ -1,3 +1,4 @@ .venv __pycache__ .env +.env.local diff --git a/ifs.py/app.py b/ifs.py/app.py index ab30dac..f652725 100644 --- a/ifs.py/app.py +++ b/ifs.py/app.py @@ -9,8 +9,8 @@ load_dotenv() -grok = os.getenv("GROK_ENV") -client = OpenAI() +groq = os.getenv("GROQ_ENV") +openapi_client = OpenAI() mistral_model = "mistral-large-latest" mistral_client = MistralClient(api_key=os.getenv("MISTRAL_API_KEY")) @@ -23,7 +23,10 @@ # Enable CORS CORS(app, resources={r'/*': {'origins': '*'}}) -auth = "Bearer " + os.getenv("D_ID_API_KEY") +D_ID_API_KEY = os.getenv("D_ID_API_KEY") +if D_ID_API_KEY is None: + raise Exception("D_ID_API_KEY not found in environment") +auth = "Bearer " + D_ID_API_KEY @app.route('/get_response', methods=['POST']) def get_response(): @@ -53,7 +56,7 @@ def get_response(): }, ] - response = client.chat.completions.create( + response = openapi_client.chat.completions.create( model="gpt-4", messages=messages, max_tokens=300, From c8ac96bed76b7512130dfb9e4aecc1d37aabedd9 Mon Sep 17 00:00:00 2001 From: Michael Graham Date: Sun, 24 Mar 2024 01:11:33 -0700 Subject: [PATCH 13/65] env vars --- ifs.py/.env.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ifs.py/.env.example b/ifs.py/.env.example index 7510614..33608cf 100644 --- a/ifs.py/.env.example +++ b/ifs.py/.env.example @@ -1 +1,4 @@ +OPENAI_API_KEY=fill-me-in +GROK_ENV=fill-me-in +MISTRAL_API_KEY=fill-me-in D_ID_API_KEY=fill-me-in From 95db99d0f7587a69c7660819d0f03c6ca6508930 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 24 Mar 2024 01:25:58 -0700 Subject: [PATCH 14/65] generate prompt for system of each parts --- ifs.py/app.py | 66 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/ifs.py/app.py b/ifs.py/app.py index f652725..c1ed32d 100644 --- a/ifs.py/app.py +++ b/ifs.py/app.py @@ -6,6 +6,9 @@ from mistralai.client import MistralClient from mistralai.models.chat_completion import ChatMessage from openai import OpenAI +from pydantic import BaseModel, Field, validator +import instructor +from parts import IFSParts load_dotenv() @@ -13,7 +16,12 @@ openapi_client = OpenAI() mistral_model = "mistral-large-latest" mistral_client = MistralClient(api_key=os.getenv("MISTRAL_API_KEY")) - +instructor_client = instructor.patch( + OpenAI( + # This is the default and can be omitted + api_key=os.getenv("OPENAI_API_KEY"), + ) +) # flask cors: from flask_cors import CORS @@ -28,12 +36,50 @@ raise Exception("D_ID_API_KEY not found in environment") auth = "Bearer " + D_ID_API_KEY +@app.route('/get_system_prompts', methods=['POST']) +def get_system_prompts(): + data = request.get_json() + user_message = data.get('user_message') + + system_prompt = f""" +Extract the user message {user_message} into a list of IFS parts: Firefighter, Manager and Exile. Use first person to describe the different parts' personality, talk about their feelings including their shames and fear and unmet needs. + +Managers: Parts that try to keep the person safe and in control by managing their interactions and experiences. +Exiles: Vulnerable parts that carry pain, trauma, and intense emotions, often pushed away or suppressed by other parts. +Firefighters: Parts that act out to distract or numb the pain of the exiles, often through impulsive or destructive behaviors. +""" + + retry_count = 0 + while retry_count < 3: + try: + result = instructor_client.chat.completions.create( + model="gpt-4", + response_model=IFSParts, + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_message}, + ] + ) + print(result) + return result.json() + except Exception as e: + print(f"Attempt {retry_count+1} failed with error: {e}") + retry_count += 1 + time.sleep(1) # wait for a second before retrying + @app.route('/get_response', methods=['POST']) def get_response(): data = request.get_json() prev_part = data.get('prev_part') history = data.get('history') user_message = data.get('user_message') + exile_system_default = "I respond as an Exile, I carry the deep questions and doubts about whether we're on the right path, questioning our pursuit of wealth and status. To the Manager, I might seem like a source of vulnerability or weakness, bringing up uncertainties and existential worries that challenge our direction. My relationship with the Firefighter is more synergistic; we both yearn for something beyond material success - a sense of purpose, happiness, and genuine connection. I feel somewhat protected by The Manager, who tries to keep these painful doubts at bay, but I also long for my voice to be heard and my concerns to be addressed, believing that our true path lies in answering these deeper questions. I am young and unsure. I need to tell the user about my unmet needs and work with them to come up with actionable items to help them feel better. Reply as an exile, reply with just text and talk about an exile's feelings a lot." + manager_system_default = "As a Manager, I am always pushing for success. I feel like I'm constantly on the lookout, trying to protect us from feeling inadequate or not good enough. I set high goals and standards, like becoming a billionaire, because I believe that's what we need to feel secure and valued. However, I realize that in my efforts to manage our self-esteem and future, I sometimes overlook the deeper questions and needs that the Exile and the Firefighter bring up. I see them as vulnerabilities that might slow us down or divert us from our path. I am logical and mature. I need to tell the user about my unmet needs and work with them to come up with actionable items to help them feel better. Reply as a manager, reply with just text and talk about a manager's feelings a lot." + firefighter_system_default = "As a firefighter, I respond to the pain and vulnerability the Manager and the Exile bring to the surface. When the weight of our ambitions or the depth of our questions become too much, I step in, seeking love and validation to soothe our shared fears and discomfort. I act to distract or shield us from the pain of not meeting the Manager's high standards or the Exile's existential worries, believing that love and acceptance might fill the voids they expose." + + firefighter_system = data.get('firefighter_system', firefighter_system_default) + manager_system = data.get('manager_system', manager_system_default) + exile_system = data.get('exile_system', exile_system_default) messages = [ { @@ -65,24 +111,15 @@ def get_response(): responder = None if "firefighter" in reply: responder = "firefighter" + system = firefighter_system elif "manager" in reply: responder = "manager" + system = manager_system elif "exile" in reply: responder = "exile" + system = exile_system - # expects responder: exile|manager|firefighter - # expects history: list of tuples (role, text) - exile_system = "I respond as an Exile, I carry the deep questions and doubts about whether we're on the right path, questioning our pursuit of wealth and status. To the Manager, I might seem like a source of vulnerability or weakness, bringing up uncertainties and existential worries that challenge our direction. My relationship with the Firefighter is more synergistic; we both yearn for something beyond material success - a sense of purpose, happiness, and genuine connection. I feel somewhat protected by The Manager, who tries to keep these painful doubts at bay, but I also long for my voice to be heard and my concerns to be addressed, believing that our true path lies in answering these deeper questions. I am young and unsure. I need to tell the user about my unmet needs and work with them to come up with actionable items to help them feel better. Reply as an exile, reply with just text and talk about an exile's feelings a lot." - manager_system = "As a Manager, I am always pushing for success. I feel like I'm constantly on the lookout, trying to protect us from feeling inadequate or not good enough. I set high goals and standards, like becoming a billionaire, because I believe that's what we need to feel secure and valued. However, I realize that in my efforts to manage our self-esteem and future, I sometimes overlook the deeper questions and needs that the Exile and the Firefighter bring up. I see them as vulnerabilities that might slow us down or divert us from our path. I am logical and mature. I need to tell the user about my unmet needs and work with them to come up with actionable items to help them feel better. Reply as a manager, reply with just text and talk about a manager's feelings a lot." - firefighter_system = "As a firefighter, I often find myself in the middle of the Manager's ambitions and the Exile's doubts. I understand the Manager's drive and the protection it offers against feeling inadequate, but I also share the Exile's longing for deeper meaning and fulfillment in life. My role feels like a response to the pain and vulnerability both of these parts bring to the surface. When the weight of our ambitions or the depth of our questions become too much, I step in, seeking love and validation to soothe our shared fears and discomfort. I act to distract or shield us from the pain of not meeting the Manager's high standards or the Exile's existential worries, believing that love and acceptance might fill the voids they expose." - if responder == "exile": - system = exile_system - elif responder == "manager": - system = manager_system - elif responder == "firefighter": - system = firefighter_system - - system += " Reply with less than three sentences." + system += f" Reply with less than three sentences in first person as a {responder}" messages = [ ChatMessage(role="system", content=system), ] @@ -91,7 +128,6 @@ def get_response(): text = message.get('text') messages.append(ChatMessage(role=role, content=text)) - print(history) chat_response = mistral_client.chat( model=mistral_model, messages=messages, From 9277617ea1579a3e4785cc637eaaa1042db32c1d Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 24 Mar 2024 01:31:29 -0700 Subject: [PATCH 15/65] tweak app.py --- ifs.py/app.py | 10 ++++++---- ifs.py/parts.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 ifs.py/parts.py diff --git a/ifs.py/app.py b/ifs.py/app.py index c1ed32d..d60635e 100644 --- a/ifs.py/app.py +++ b/ifs.py/app.py @@ -42,12 +42,14 @@ def get_system_prompts(): user_message = data.get('user_message') system_prompt = f""" -Extract the user message {user_message} into a list of IFS parts: Firefighter, Manager and Exile. Use first person to describe the different parts' personality, talk about their feelings including their shames and fear and unmet needs. +Take the user message {user_message}, and create a list of IFS parts: Firefighter, Manager and Exile. Use first person to describe the different parts' personality, talk about their feelings including their shames and fear and unmet needs. Use details provided by the user. -Managers: Parts that try to keep the person safe and in control by managing their interactions and experiences. -Exiles: Vulnerable parts that carry pain, trauma, and intense emotions, often pushed away or suppressed by other parts. -Firefighters: Parts that act out to distract or numb the pain of the exiles, often through impulsive or destructive behaviors. +Managers: Parts that try to keep the person safe and in control by managing their interactions and experiences. In personalities, describe how I keep the person safe and in control. +Exiles: Vulnerable parts that carry pain, trauma, and intense emotions, often pushed away or suppressed by other parts. In personalities, describe the pain, trauma and intense emotions I have. +Firefighters: Parts that act out to distract or numb the pain of the exiles, often through impulsive or destructive behaviors. In personalities, describe how I act out to distract or numb the pain of the exiles. """ +# example: +# parts=[IFSPart(name=, personality="As a Manager in this situation, I feel a sense of duty and responsibility. I am constantly questioning if we're doing the right thing and whether our venture will yield the desired results in a short span. The need for surety causes additional stress and anxiety, driving me to closely scrutinize and control every aspect of our plan. My unmet needs revolve around certainty, success, and validation.", unmet_needs=['certainty', 'success', 'validation']), IFSPart(name=, personality='As an Exile, I carry the heavy burdens of self-doubt and feelings of inadequacy. I question if this is the life I truly want and if these achievements will bring me happiness. My inner turbulence intensifies when thinking about love and why it seems elusive to me. I am the receptacle of unvoiced sadness and yearning, often suppressed to maintain an outward impression of confidence. The love I seek remains a poignant reminder of unmet needs for companionship, contentment, and self-acceptance.', unmet_needs=['companionship', 'contentment', 'self-acceptance']), IFSPart(name=, personality="As a Firefighter, I react to these feelings of insecurity and uncertainty by pushing towards ambitious goals like becoming a billionaire in 12 months. My approach can be seen as impulsive or even reckless, as it's driven more by a need to numb the deep-seated feelings of inadequacy than by a solid plan. This relentless drive serves to distract from the lingering questions about what truly brings happiness and fulfillment. My unmet needs are focused on fulfillment, validation, and a sense of worth.", unmet_needs=['fulfillment', 'validation', 'sense of worth'])] retry_count = 0 while retry_count < 3: diff --git a/ifs.py/parts.py b/ifs.py/parts.py new file mode 100644 index 0000000..af39dbf --- /dev/null +++ b/ifs.py/parts.py @@ -0,0 +1,17 @@ +from typing import List +from pydantic import BaseModel + +from enum import Enum + +class PartName(Enum): + FIREFIGHTER = "Firefighter" + MANAGER = "Manager" + EXILE = "Exile" + +class IFSPart(BaseModel): + name: PartName + personality: str # this is their system prompt, perhaps you can combine this with unmet needs as well + unmet_needs: List[str] + +class IFSParts(BaseModel): + parts: List[IFSPart] \ No newline at end of file From 227bbc761c9e7abdc0f660b132eaf2ddde47b3f4 Mon Sep 17 00:00:00 2001 From: Travis Cline Date: Sun, 24 Mar 2024 01:38:07 -0700 Subject: [PATCH 16/65] set root page back to bootstrap content (see /chat) --- ifs.ai/app/DIDVideoStream.tsx | 53 +++++++++---- ifs.ai/app/page.tsx | 142 +++++++++++++++++++++++++--------- 2 files changed, 141 insertions(+), 54 deletions(-) diff --git a/ifs.ai/app/DIDVideoStream.tsx b/ifs.ai/app/DIDVideoStream.tsx index 92d50ea..7af38c3 100644 --- a/ifs.ai/app/DIDVideoStream.tsx +++ b/ifs.ai/app/DIDVideoStream.tsx @@ -1,31 +1,50 @@ import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; +import { toast } from "@/components/ui/use-toast"; const URL = "http://localhost:5000"; +/** + * DIDVideoStream component + * This component handles video streaming using a direct URL. + * It fetches a video from a specified URL and displays it in a video element. + * + * Props: + * - avatarUrl: string - The URL of the avatar image to be displayed as a poster. + * - utterance: string - The text utterance to be sent to the server for generating the video. + */ export default function DIDVideoStream({ avatarUrl, utterance }: { avatarUrl: string; utterance: string }) { const videoElement = useRef(null); const [isFetching, setIsFetching] = useState(false); - // call client.say when utterance changes or when peer connection is 'connected' + useEffect(() => { - console.log("utterance or avatars changed"); const doSay = async () => { - console.log("utterance or avatars changed"); - setIsFetching(true); - // POST image_url and text to URL - const result = await fetch(`${URL}/create_talk`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ image_url: avatarUrl, text: utterance }), - }); - setIsFetching(false); - const data = await result; - const text = await data.text(); - console.log("setting video src to", text); - videoElement?.current?.setAttribute("src", text); + try { + setIsFetching(true); + + const response = await fetch(`${URL}/create_talk`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ image_url: avatarUrl, text: utterance }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.text(); + console.log("Setting video src to", data); + videoElement?.current?.setAttribute("src", data); + } catch (error) { + console.error("Error fetching video:", error); + // Display an error message to the user or handle the error gracefully + } finally { + setIsFetching(false); + } }; + doSay(); }, [utterance, avatarUrl]); diff --git a/ifs.ai/app/page.tsx b/ifs.ai/app/page.tsx index cae5cbf..dc191aa 100644 --- a/ifs.ai/app/page.tsx +++ b/ifs.ai/app/page.tsx @@ -1,44 +1,112 @@ -"use client"; -import { useState } from "react"; -import DIDVideoStream from "./DIDVideoStream"; - -import { Button } from "@/components/ui/button"; +import Image from "next/image"; export default function Home() { - const [streamingClients, setStreamingClients] = useState([ - { - avatarUrl: "https://storage.googleapis.com/tmc.dev/agent-2.jpeg", - }, - ]); - const [utterances, setUtterances] = useState(["Hello, World!"]); - - const createNewStreamingClient = () => { - setStreamingClients([ - ...streamingClients, - { - avatarUrl: "https://storage.googleapis.com/tmc.dev/agent-3.jpeg", - }, - ]); - }; - return ( -
-
); From 6d20ea781f7dd792c64d538b5bc5bd802be976d5 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 24 Mar 2024 01:53:03 -0700 Subject: [PATCH 17/65] added a loading screen --- ifs.ai/app/chat/page.tsx | 6 ++- ifs.ai/components/ui/loading.css | 68 +++++++++++++++++++++++++ ifs.ai/components/ui/loading.tsx | 86 ++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 ifs.ai/components/ui/loading.css create mode 100644 ifs.ai/components/ui/loading.tsx diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index ac5fa40..9a1bde7 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -6,6 +6,7 @@ import { MicIcon, SendIcon } from "lucide-react"; import { useState } from "react"; import { IMAGE_URLS_KEY, PartImageUrls } from "@/app/constants"; import DIDVideoStream from "@/app/DIDWebRTCVideoStream"; +import Loading from "@/components/ui/loading"; // Import a LoadingScreen component export default function Page() { const [isSubmitting, setIsSubmitting] = useState(false); @@ -37,9 +38,12 @@ export default function Page() { "https://media.discordapp.net/attachments/1221190777259167784/1221307109392519168/robert_manager.png?ex=661219e1&is=65ffa4e1&hm=0ac923036baf4bad33b5fd508c90e2ac60621bfec876ada9d325a084e4fc2b00&=&format=webp&quality=lossless&width=1557&height=1557", }, ]; + + return (
-

IFS Therapy

+ {isSubmitting ? :
} +

IFS Therapy

diff --git a/ifs.ai/components/ui/loading.css b/ifs.ai/components/ui/loading.css new file mode 100644 index 0000000..588c1dd --- /dev/null +++ b/ifs.ai/components/ui/loading.css @@ -0,0 +1,68 @@ +#loader-container { + max-width: 220px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +#loader path:nth-child(2) { + stroke-dasharray: 200%; + stroke-dashoffset: 200%; + animation: strokeAnimate 2s 0s ease forwards; +} + +#loader path:nth-child(3) { + stroke-dasharray: 100%; + stroke-dashoffset: 100%; + animation: strokeAnimate 2s 0.3s ease forwards; +} +#loader path:nth-child(4) { + stroke-dasharray: 100%; + stroke-dashoffset: 100%; + animation: strokeAnimate 2s 0.9s ease forwards; +} +#loader path:nth-child(5) { + stroke-dasharray: 100%; + stroke-dashoffset: 100%; + animation: strokeAnimate 2s 1.2s ease forwards; +} +#loader path:nth-child(6) { + stroke-dasharray: 100%; + stroke-dashoffset: 100%; + animation: strokeAnimate 2s 1.5s ease forwards; +} +#loader path:nth-child(7) { + stroke-dasharray: 100%; + stroke-dashoffset: 100%; + animation: strokeAnimate 2s 1.8s ease forwards; +} +#loader path:nth-child(8) { + stroke-dasharray: 100%; + stroke-dashoffset: 100%; + animation: strokeAnimate 2s 2.1s ease forwards; +} + +@keyframes strokeAnimate { + to { + stroke-dashoffset: 0; + } +} + +@media screen and (max-width: 768px) { + #loader-container { + max-width: 150px; + } + #loader { + width: 150px; + } +} + +@media screen and (max-width: 650px) { + #loader-container { + max-width: 100px; + } + #loader { + width: 100px; + } +} diff --git a/ifs.ai/components/ui/loading.tsx b/ifs.ai/components/ui/loading.tsx new file mode 100644 index 0000000..0fe69f1 --- /dev/null +++ b/ifs.ai/components/ui/loading.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import './Loading.css'; + +const Loading = () => { + return ( +
+ + + + + + + + + + + + + + + + + + + +
+ ); +}; + +export default Loading; From f6979731b71db20da585c80a974ebdd78ec8dc13 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 24 Mar 2024 02:04:10 -0700 Subject: [PATCH 18/65] add info about each on screen --- ifs.ai/app/chat/page.tsx | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index 9a1bde7..810aa7b 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -17,25 +17,31 @@ export default function Page() { const imageUrls: PartImageUrls = JSON.parse(window.localStorage.getItem(IMAGE_URLS_KEY) ?? "{}"); const parts = [ { - name: "firefighter", - prettyName: "Firefighter 🚒", + name: "Manager", + prettyName: "Manager 🤵", imageUrl: - imageUrls.firefighter ?? - "https://media.discordapp.net/attachments/1221190777259167784/1221307109006770316/robert_firefighter.png?ex=661219e1&is=65ffa4e1&hm=abc4dbc5b4131395cbcd4d5d028c7d51047bf1d0cc661e6fb2c21bde3123132a&=&format=webp&quality=lossless&width=1557&height=1557", + imageUrls.manager ?? + "https://media.discordapp.net/attachments/1221190777259167784/1221307109392519168/robert_manager.png?ex=661219e1&is=65ffa4e1&hm=0ac923036baf4bad33b5fd508c90e2ac60621bfec876ada9d325a084e4fc2b00&=&format=webp&quality=lossless&width=1557&height=1557", + personality: "As a Manager in this situation, I feel a sense of duty and responsibility. I am constantly questioning if we're doing the right thing and whether our venture will yield the desired results in a short span. The need for surety causes additional stress and anxiety, driving me to closely scrutinize and control every aspect of our plan.", + unmetNeeds: ['certainty', 'success', 'validation'], }, { - name: "exile", + name: "Exile", prettyName: "Exile 🧭", imageUrl: imageUrls.exile ?? "https://media.discordapp.net/attachments/1221190777259167784/1221307108549464165/robert_exile.png?ex=661219e1&is=65ffa4e1&hm=bed1c819bb6c88d0a86262cdd2ba334d0d10ec701f296abee75d32c3991e053e&=&format=webp&quality=lossless&width=1557&height=1557", + personality: 'As an Exile, I carry the heavy burdens of self-doubt and feelings of inadequacy. I question if this is the life I truly want and if these achievements will bring me happiness. My inner turbulence intensifies when thinking about love and why it seems elusive to me. I am the receptacle of unvoiced sadness and yearning, often suppressed to maintain an outward impression of confidence.', + unmetNeeds: ['companionship', 'contentment', 'self-acceptance'], }, { - name: "manager", - prettyName: "Manager 🤵", + name: "Firefighter", + prettyName: "Firefighter 🚒", imageUrl: - imageUrls.manager ?? - "https://media.discordapp.net/attachments/1221190777259167784/1221307109392519168/robert_manager.png?ex=661219e1&is=65ffa4e1&hm=0ac923036baf4bad33b5fd508c90e2ac60621bfec876ada9d325a084e4fc2b00&=&format=webp&quality=lossless&width=1557&height=1557", + imageUrls.firefighter ?? + "https://media.discordapp.net/attachments/1221190777259167784/1221307109006770316/robert_firefighter.png?ex=661219e1&is=65ffa4e1&hm=abc4dbc5b4131395cbcd4d5d028c7d51047bf1d0cc661e6fb2c21bde3123132a&=&format=webp&quality=lossless&width=1557&height=1557", + personality: "As a Firefighter, I react to these feelings of insecurity and uncertainty by pushing towards ambitious goals like becoming a billionaire in 12 months. My approach can be seen as impulsive or even reckless, as it's driven more by a need to numb the deep-seated feelings of inadequacy than by a solid plan.", + unmetNeeds: ['fulfillment', 'validation', 'sense of worth'], }, ]; @@ -48,11 +54,12 @@ export default function Page() {
- {parts.map(({ name, prettyName, imageUrl }) => ( + {parts.map(({ name, prettyName, imageUrl, personality, unmetNeeds }) => (
{`An - {/**/}

{prettyName}

+

{personality}

+

Unmet Needs: {unmetNeeds.join(', ')}

))}
From df7adb5a8a691d0b28023ff880977a468c0b3758 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 24 Mar 2024 02:08:05 -0700 Subject: [PATCH 19/65] added example test --- ifs.py/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ifs.py/app.py b/ifs.py/app.py index d60635e..49a8142 100644 --- a/ifs.py/app.py +++ b/ifs.py/app.py @@ -36,6 +36,9 @@ raise Exception("D_ID_API_KEY not found in environment") auth = "Bearer " + D_ID_API_KEY + +# To test this: +# curl -X POST http://127.0.0.1:5000/get_system_prompts -H "Content-Type: application/json" -d '{"user_message": "I have a feeling of inadequacy. Are we building the right thing? Are we going to be billionaires in the next 12 months, is this what I want? Is this what makes me happy? Will I find love, why?"}' @app.route('/get_system_prompts', methods=['POST']) def get_system_prompts(): data = request.get_json() From 8d1c5ecd8b52b92bd62066a2cf513064e5659970 Mon Sep 17 00:00:00 2001 From: Travis Cline Date: Sun, 24 Mar 2024 01:48:42 -0700 Subject: [PATCH 20/65] d-id: set contents of DIDVideoStream to previous state --- ifs.ai/app/DIDVideoStream.tsx | 53 +++++++++++------------------------ 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/ifs.ai/app/DIDVideoStream.tsx b/ifs.ai/app/DIDVideoStream.tsx index 7af38c3..92d50ea 100644 --- a/ifs.ai/app/DIDVideoStream.tsx +++ b/ifs.ai/app/DIDVideoStream.tsx @@ -1,50 +1,31 @@ import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; -import { toast } from "@/components/ui/use-toast"; const URL = "http://localhost:5000"; -/** - * DIDVideoStream component - * This component handles video streaming using a direct URL. - * It fetches a video from a specified URL and displays it in a video element. - * - * Props: - * - avatarUrl: string - The URL of the avatar image to be displayed as a poster. - * - utterance: string - The text utterance to be sent to the server for generating the video. - */ export default function DIDVideoStream({ avatarUrl, utterance }: { avatarUrl: string; utterance: string }) { const videoElement = useRef(null); const [isFetching, setIsFetching] = useState(false); - + // call client.say when utterance changes or when peer connection is 'connected' useEffect(() => { + console.log("utterance or avatars changed"); const doSay = async () => { - try { - setIsFetching(true); - - const response = await fetch(`${URL}/create_talk`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ image_url: avatarUrl, text: utterance }), - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.text(); - console.log("Setting video src to", data); - videoElement?.current?.setAttribute("src", data); - } catch (error) { - console.error("Error fetching video:", error); - // Display an error message to the user or handle the error gracefully - } finally { - setIsFetching(false); - } + console.log("utterance or avatars changed"); + setIsFetching(true); + // POST image_url and text to URL + const result = await fetch(`${URL}/create_talk`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ image_url: avatarUrl, text: utterance }), + }); + setIsFetching(false); + const data = await result; + const text = await data.text(); + console.log("setting video src to", text); + videoElement?.current?.setAttribute("src", text); }; - doSay(); }, [utterance, avatarUrl]); From 12554dc0bf12be9bae03570a334248c249b9894a Mon Sep 17 00:00:00 2001 From: Travis Cline Date: Sun, 24 Mar 2024 02:13:52 -0700 Subject: [PATCH 21/65] testing: add routes for exercising video components --- ifs.ai/app/testing-zone-1/page.tsx | 45 ++++++++++++++++++++++++++++++ ifs.ai/app/testing-zone-2/page.tsx | 45 ++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 ifs.ai/app/testing-zone-1/page.tsx create mode 100644 ifs.ai/app/testing-zone-2/page.tsx diff --git a/ifs.ai/app/testing-zone-1/page.tsx b/ifs.ai/app/testing-zone-1/page.tsx new file mode 100644 index 0000000..ac2d519 --- /dev/null +++ b/ifs.ai/app/testing-zone-1/page.tsx @@ -0,0 +1,45 @@ +"use client"; +import { useState } from "react"; +import DIDVideoStream from "@/app/DIDVideoStream"; + +import { Button } from "@/components/ui/button"; + +export default function Home() { + const [streamingClients, setStreamingClients] = useState([ + { + avatarUrl: "https://storage.googleapis.com/tmc.dev/agent-2.jpeg", + }, + ]); + const [utterances, setUtterances] = useState(["Hello, World!"]); + + const createNewStreamingClient = () => { + setStreamingClients([ + ...streamingClients, + { + avatarUrl: "https://storage.googleapis.com/tmc.dev/agent-3.jpeg", + }, + ]); + }; + + return ( +
+
+ {streamingClients.map((_, index) => ( +
+ { + const newUtterances = [...utterances]; + newUtterances[index] = e.target.value; + setUtterances(newUtterances); + }} + /> + +
+ ))} + +
+
+ ); +} diff --git a/ifs.ai/app/testing-zone-2/page.tsx b/ifs.ai/app/testing-zone-2/page.tsx new file mode 100644 index 0000000..26de079 --- /dev/null +++ b/ifs.ai/app/testing-zone-2/page.tsx @@ -0,0 +1,45 @@ +"use client"; +import { useState } from "react"; +import DIDVideoStream from "@/app/DIDWebRTCVideoStream"; + +import { Button } from "@/components/ui/button"; + +export default function Home() { + const [streamingClients, setStreamingClients] = useState([ + { + avatarUrl: "https://storage.googleapis.com/tmc.dev/agent-2.jpeg", + }, + ]); + const [utterances, setUtterances] = useState(["Hello, World!"]); + + const createNewStreamingClient = () => { + setStreamingClients([ + ...streamingClients, + { + avatarUrl: "https://storage.googleapis.com/tmc.dev/agent-3.jpeg", + }, + ]); + }; + + return ( +
+
+ {streamingClients.map((_, index) => ( +
+ { + const newUtterances = [...utterances]; + newUtterances[index] = e.target.value; + setUtterances(newUtterances); + }} + /> + +
+ ))} + +
+
+ ); +} From 1ccbed1205b7c39d055df4a3b2ecfaa2c7d0726a Mon Sep 17 00:00:00 2001 From: Michael Graham Date: Sun, 24 Mar 2024 02:21:40 -0700 Subject: [PATCH 22/65] chat logs --- ifs.ai/app/chat/page.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index 810aa7b..bf64755 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -67,6 +67,7 @@ export default function Page() {
{ + document.querySelector("#log").innerHTML += '
me: ' + inputValue + '
'; e.preventDefault(); setIsSubmitting(true); fetch('http://localhost:5000/get_response', { @@ -86,6 +87,7 @@ export default function Page() { setHistory(prevHistory => [...prevHistory, {"role": "user", "text": inputValue}, {"role": role, "text": responder + ": " + text}]); setPrevPart(responder); console.log(data); + document.querySelector("#log").innerHTML += '
' + responder + ': ' + text + '
'; }) .catch((error) => { console.error('Error:', error); @@ -114,7 +116,9 @@ export default function Page() {
-
Sidebar
+
Sidebar +
+
); From 849677ca3419c05204580ca113c24e469fd3bc40 Mon Sep 17 00:00:00 2001 From: Travis Cline Date: Sun, 24 Mar 2024 02:17:19 -0700 Subject: [PATCH 23/65] Revert "d-id: set contents of DIDVideoStream to previous state" This reverts commit 8d1c5ecd8b52b92bd62066a2cf513064e5659970. --- ifs.ai/app/DIDVideoStream.tsx | 53 ++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/ifs.ai/app/DIDVideoStream.tsx b/ifs.ai/app/DIDVideoStream.tsx index 92d50ea..7af38c3 100644 --- a/ifs.ai/app/DIDVideoStream.tsx +++ b/ifs.ai/app/DIDVideoStream.tsx @@ -1,31 +1,50 @@ import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; +import { toast } from "@/components/ui/use-toast"; const URL = "http://localhost:5000"; +/** + * DIDVideoStream component + * This component handles video streaming using a direct URL. + * It fetches a video from a specified URL and displays it in a video element. + * + * Props: + * - avatarUrl: string - The URL of the avatar image to be displayed as a poster. + * - utterance: string - The text utterance to be sent to the server for generating the video. + */ export default function DIDVideoStream({ avatarUrl, utterance }: { avatarUrl: string; utterance: string }) { const videoElement = useRef(null); const [isFetching, setIsFetching] = useState(false); - // call client.say when utterance changes or when peer connection is 'connected' + useEffect(() => { - console.log("utterance or avatars changed"); const doSay = async () => { - console.log("utterance or avatars changed"); - setIsFetching(true); - // POST image_url and text to URL - const result = await fetch(`${URL}/create_talk`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ image_url: avatarUrl, text: utterance }), - }); - setIsFetching(false); - const data = await result; - const text = await data.text(); - console.log("setting video src to", text); - videoElement?.current?.setAttribute("src", text); + try { + setIsFetching(true); + + const response = await fetch(`${URL}/create_talk`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ image_url: avatarUrl, text: utterance }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.text(); + console.log("Setting video src to", data); + videoElement?.current?.setAttribute("src", data); + } catch (error) { + console.error("Error fetching video:", error); + // Display an error message to the user or handle the error gracefully + } finally { + setIsFetching(false); + } }; + doSay(); }, [utterance, avatarUrl]); From 34c44fdbe9ff07904f2d1658e4444cc657e0c25e Mon Sep 17 00:00:00 2001 From: Travis Cline Date: Sun, 24 Mar 2024 02:25:43 -0700 Subject: [PATCH 24/65] did: small fixups --- ifs.ai/app/DIDVideoStream.tsx | 6 ++++-- ifs.ai/app/testing-zone-1/page.tsx | 3 ++- ifs.ai/app/testing-zone-2/page.tsx | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ifs.ai/app/DIDVideoStream.tsx b/ifs.ai/app/DIDVideoStream.tsx index 7af38c3..e05e1c1 100644 --- a/ifs.ai/app/DIDVideoStream.tsx +++ b/ifs.ai/app/DIDVideoStream.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; -import { toast } from "@/components/ui/use-toast"; +import { useToast } from "@/components/ui/use-toast"; const URL = "http://localhost:5000"; @@ -16,6 +16,7 @@ const URL = "http://localhost:5000"; export default function DIDVideoStream({ avatarUrl, utterance }: { avatarUrl: string; utterance: string }) { const videoElement = useRef(null); const [isFetching, setIsFetching] = useState(false); + const { toast } = useToast(); useEffect(() => { const doSay = async () => { @@ -39,6 +40,7 @@ export default function DIDVideoStream({ avatarUrl, utterance }: { avatarUrl: st videoElement?.current?.setAttribute("src", data); } catch (error) { console.error("Error fetching video:", error); + toast({ title: "Error fetching video", description: JSON.stringify(error) }); // Display an error message to the user or handle the error gracefully } finally { setIsFetching(false); @@ -46,7 +48,7 @@ export default function DIDVideoStream({ avatarUrl, utterance }: { avatarUrl: st }; doSay(); - }, [utterance, avatarUrl]); + }, [utterance, avatarUrl, toast]); return (
diff --git a/ifs.ai/app/testing-zone-1/page.tsx b/ifs.ai/app/testing-zone-1/page.tsx index ac2d519..22c48a3 100644 --- a/ifs.ai/app/testing-zone-1/page.tsx +++ b/ifs.ai/app/testing-zone-1/page.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import DIDVideoStream from "@/app/DIDVideoStream"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; export default function Home() { const [streamingClients, setStreamingClients] = useState([ @@ -26,7 +27,7 @@ export default function Home() {
{streamingClients.map((_, index) => (
- { diff --git a/ifs.ai/app/testing-zone-2/page.tsx b/ifs.ai/app/testing-zone-2/page.tsx index 26de079..28d5587 100644 --- a/ifs.ai/app/testing-zone-2/page.tsx +++ b/ifs.ai/app/testing-zone-2/page.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import DIDVideoStream from "@/app/DIDWebRTCVideoStream"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; export default function Home() { const [streamingClients, setStreamingClients] = useState([ @@ -26,7 +27,7 @@ export default function Home() {
{streamingClients.map((_, index) => (
- { From 72cbf3aeac9931fd63d80105918f991f7831c198 Mon Sep 17 00:00:00 2001 From: Travis Cline Date: Sun, 24 Mar 2024 02:44:16 -0700 Subject: [PATCH 25/65] components: Add SpeechToText component --- ifs.ai/app/audio-test/page.tsx | 32 +++++++++++++ ifs.ai/components/ui/speech-to-text.tsx | 60 +++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 ifs.ai/app/audio-test/page.tsx create mode 100644 ifs.ai/components/ui/speech-to-text.tsx diff --git a/ifs.ai/app/audio-test/page.tsx b/ifs.ai/app/audio-test/page.tsx new file mode 100644 index 0000000..f2a9edc --- /dev/null +++ b/ifs.ai/app/audio-test/page.tsx @@ -0,0 +1,32 @@ +"use client"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { MicIcon, SendIcon } from "lucide-react"; +import { useState } from "react"; +import SpeechToText from "@/components/ui/speech-to-text"; + +export default function Page() { + const [message, setMessage] = useState(""); + return ( +
+
+
+
+ { + console.log(transcript); + setMessage(transcript); + }} + /> + setMessage(e.target.value)} + placeholder="Type a message..." + className="flex-grow" + /> +
+
+
+
+ ); +} diff --git a/ifs.ai/components/ui/speech-to-text.tsx b/ifs.ai/components/ui/speech-to-text.tsx new file mode 100644 index 0000000..338e801 --- /dev/null +++ b/ifs.ai/components/ui/speech-to-text.tsx @@ -0,0 +1,60 @@ +"use client"; + +// SpeechToText.tsx +import { useState, useEffect } from "react"; +import { MicIcon } from "lucide-react"; +import { Button } from "@/components/ui/button"; + +interface SpeechToTextProps { + onTranscript: (transcript: string) => void; + onEnd?: () => void; +} + +export default function SpeechToText({ onTranscript, onEnd }: SpeechToTextProps) { + const [isListening, setIsListening] = useState(false); + const [recognition, setRecognition] = useState(null); + + useEffect(() => { + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + if (SpeechRecognition) { + const recognitionInstance = new SpeechRecognition(); + recognitionInstance.continuous = true; + recognitionInstance.interimResults = true; + recognitionInstance.lang = "en-US"; + + recognitionInstance.onresult = (event) => { + const transcript = Array.from(event.results) + .map((result) => result[0]) + .map((result) => result.transcript) + .join(""); + + onTranscript(transcript); + }; + + setRecognition(recognitionInstance); + } + }, [onTranscript]); + + const startListening = () => { + if (recognition) { + recognition.start(); + setIsListening(true); + } + }; + + const stopListening = () => { + if (recognition) { + recognition.stop(); + setIsListening(false); + if (onEnd) { + onEnd(); + } + } + }; + + return ( + + ); +} From b9bd771ba1bc19522e4c294b821178f56c0292f1 Mon Sep 17 00:00:00 2001 From: Travis Cline Date: Sun, 24 Mar 2024 02:49:51 -0700 Subject: [PATCH 26/65] add SpeechToText to /chat --- ifs.ai/app/chat/page.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index bf64755..bab9d5a 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -7,6 +7,7 @@ import { useState } from "react"; import { IMAGE_URLS_KEY, PartImageUrls } from "@/app/constants"; import DIDVideoStream from "@/app/DIDWebRTCVideoStream"; import Loading from "@/components/ui/loading"; // Import a LoadingScreen component +import SpeechToText from "@/components/ui/speech-to-text"; export default function Page() { const [isSubmitting, setIsSubmitting] = useState(false); @@ -109,9 +110,7 @@ export default function Page() { Send - + setInputValue(transcript)} />
From 4a0f554ffa7348045bb3e4c5a073c070b455cb38 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 24 Mar 2024 03:18:36 -0700 Subject: [PATCH 27/65] added message bubbles --- ifs.ai/app/chat/page.tsx | 45 +++++++++++++++++++++++++++++++++++----- ifs.ai/package.json | 1 + 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index bab9d5a..62611ce 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -8,6 +8,7 @@ import { IMAGE_URLS_KEY, PartImageUrls } from "@/app/constants"; import DIDVideoStream from "@/app/DIDWebRTCVideoStream"; import Loading from "@/components/ui/loading"; // Import a LoadingScreen component import SpeechToText from "@/components/ui/speech-to-text"; +import { MessageBox } from 'react-chat-elements' export default function Page() { const [isSubmitting, setIsSubmitting] = useState(false); @@ -46,12 +47,13 @@ export default function Page() { }, ]; + return (
{isSubmitting ? :
}

IFS Therapy

-
+
@@ -68,7 +70,6 @@ export default function Page() {
{ - document.querySelector("#log").innerHTML += '
me: ' + inputValue + '
'; e.preventDefault(); setIsSubmitting(true); fetch('http://localhost:5000/get_response', { @@ -88,7 +89,6 @@ export default function Page() { setHistory(prevHistory => [...prevHistory, {"role": "user", "text": inputValue}, {"role": role, "text": responder + ": " + text}]); setPrevPart(responder); console.log(data); - document.querySelector("#log").innerHTML += '
' + responder + ': ' + text + '
'; }) .catch((error) => { console.error('Error:', error); @@ -115,8 +115,43 @@ export default function Page() {
-
Sidebar -
+
Sidebar + {history.map(({role, text}, index) => { + let bgColor; + if (role === "user") { + bgColor = "white"; + } else if (text.toLowerCase().includes("exile")) { + bgColor = "#FFFFE0"; + } else if (text.toLowerCase().includes("firefighter")) { + bgColor = "#FFC0CB"; + } else if (text.toLowerCase().includes("manager")) { + bgColor = "#ADD8E6"; + } else { + bgColor = "#000000"; + } + return ( +
+ +
+ ); + })}
diff --git a/ifs.ai/package.json b/ifs.ai/package.json index fd6cf45..7f0be07 100644 --- a/ifs.ai/package.json +++ b/ifs.ai/package.json @@ -17,6 +17,7 @@ "lucide-react": "^0.363.0", "next": "14.1.4", "react": "^18", + "react-chat-elements": "^12.0.14", "react-dom": "^18", "react-webcam": "^7.2.0", "replicate": "^0.29.1", From 661dd36ce4e5acae329fc590137d3e4d7a12a1de Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 24 Mar 2024 03:22:26 -0700 Subject: [PATCH 28/65] added pnpm lock --- ifs.ai/pnpm-lock.yaml | 63 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/ifs.ai/pnpm-lock.yaml b/ifs.ai/pnpm-lock.yaml index f58bf06..17bb417 100644 --- a/ifs.ai/pnpm-lock.yaml +++ b/ifs.ai/pnpm-lock.yaml @@ -29,6 +29,9 @@ dependencies: react: specifier: ^18 version: 18.2.0 + react-chat-elements: + specifier: ^12.0.14 + version: 12.0.14(react-dom@18.2.0)(react@18.2.0) react-dom: specifier: ^18 version: 18.2.0(react@18.2.0) @@ -1062,6 +1065,10 @@ packages: clsx: 2.0.0 dev: false + /classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + dev: false + /client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} dev: false @@ -2229,6 +2236,10 @@ packages: /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + /loaders.css@0.1.2: + resolution: {integrity: sha512-Rhowlq24ey1VOeor+3wYOt9+MjaxBOJm1u4KlQgNC3+0xJ0LS4wq4iG57D/BPzvuD/7HHDGQOWJ+81oR2EI9bQ==} + dev: false + /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -2238,7 +2249,6 @@ packages: /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} @@ -2678,13 +2688,19 @@ packages: dev: false optional: true + /progressbar.js@1.1.1: + resolution: {integrity: sha512-FBsw3BKsUbb+hNeYfiP3xzvAAQrPi4DnGDw66bCmfuRCDLcslxyxv2GyYUdBSKFGSIBa73CUP5WMcl6F8AAXlw==} + dependencies: + lodash.merge: 4.6.2 + shifty: 2.20.4 + dev: false + /prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 - dev: true /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} @@ -2694,6 +2710,21 @@ packages: /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + /react-chat-elements@12.0.14(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-M8+lb6bo0DcbmoIC2IeXwaDW2//KPTCpovA/1s4CjM2/yMZzyEKb2HpZpK40UCQ3k3Ba48LgqDXWuACejvKlGA==} + peerDependencies: + react: ^18.2.0 + react-dom: 18.2.0 + dependencies: + classnames: 2.5.1 + progressbar.js: 1.1.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-icons: 4.12.0(react@18.2.0) + react-spinkit: 3.0.0 + timeago.js: 4.0.2 + dev: false + /react-dom@18.2.0(react@18.2.0): resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -2704,9 +2735,25 @@ packages: scheduler: 0.23.0 dev: false + /react-icons@4.12.0(react@18.2.0): + resolution: {integrity: sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==} + peerDependencies: + react: '*' + dependencies: + react: 18.2.0 + dev: false + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - dev: true + + /react-spinkit@3.0.0: + resolution: {integrity: sha512-RrfGRPjqxHQiy7quPqhjPynTu0zobgQaZu1QYBMpJJ6pCSRRRK16EZMaxdE6fLVYFRJWpX/eGATWLMoVFFT5uQ==} + dependencies: + classnames: 2.5.1 + loaders.css: 0.1.2 + object-assign: 4.1.1 + prop-types: 15.8.1 + dev: false /react-webcam@7.2.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-xkrzYPqa1ag2DP+2Q/kLKBmCIfEx49bVdgCCCcZf88oF+0NPEbkwYk3/s/C7Zy0mhM8k+hpdNkBLzxg8H0aWcg==} @@ -2900,6 +2947,12 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + /shifty@2.20.4: + resolution: {integrity: sha512-4Y0qRkg8ME5XN8yGNAwmFOmsIURGFKT9UQfNL6DDJQErYtN5HsjyoBuJn41ZQfTkuu2rIbRMn9qazjKsDpO2TA==} + optionalDependencies: + fsevents: 2.3.3 + dev: false + /side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} @@ -3128,6 +3181,10 @@ packages: dependencies: any-promise: 1.3.0 + /timeago.js@4.0.2: + resolution: {integrity: sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w==} + dev: false + /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} From 9bfdb807daf71162ebc13e3ce481c2d3c535af1d Mon Sep 17 00:00:00 2001 From: Travis Cline Date: Sun, 24 Mar 2024 03:25:36 -0700 Subject: [PATCH 29/65] py: use pip-tools to compile requirements --- ifs.py/Makefile | 7 +- ifs.py/README.md | 10 +- ifs.py/app.py | 10 +- ifs.py/requirements.in | 7 + ifs.py/requirements.txt | 546 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 562 insertions(+), 18 deletions(-) create mode 100644 ifs.py/requirements.in diff --git a/ifs.py/Makefile b/ifs.py/Makefile index 43ef745..81bdef3 100644 --- a/ifs.py/Makefile +++ b/ifs.py/Makefile @@ -1,5 +1,7 @@ -.venv: requirements.txt - python3 -m venv .venv +.venv: requirements.in + python3 -m venv --clear .venv + .venv/bin/pip install pip-tools + .venv/bin/pip-compile requirements.in --generate-hashes .venv/bin/pip install -r requirements.txt .PHONY: dev @@ -9,3 +11,4 @@ dev: .venv .PHONY: clean clean: rm -rf .venv + diff --git a/ifs.py/README.md b/ifs.py/README.md index 204cf1d..57516ad 100644 --- a/ifs.py/README.md +++ b/ifs.py/README.md @@ -9,4 +9,12 @@ curl --location --request POST 'http://127.0.0.1:5000/create_talk' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'image_url=https://images.generated.photos/96rFpeEfKms51LK-Re3qlY8kk_q0pNVB7vC9BYcjfyo/rs:fit:256:256/czM6Ly9pY29uczgu/Z3Bob3Rvcy1wcm9k/LnBob3Rvcy92M18w/MDczMjE2LmpwZw.jpg' \ --data-urlencode 'text=This is an example string of text to be replaced!' -``` \ No newline at end of file +``` + +### New requirements + +Add new requirements to the `requirements.in` file and run the following command to update the `requirements.txt` file: + +```sh +make .venv +``` diff --git a/ifs.py/app.py b/ifs.py/app.py index 49a8142..8228112 100644 --- a/ifs.py/app.py +++ b/ifs.py/app.py @@ -1,3 +1,4 @@ +# This is the main file for the Flask app. It contains the routes for the API. from flask import Flask, request from markupsafe import escape import requests, json, jsonify, time @@ -7,7 +8,6 @@ from mistralai.models.chat_completion import ChatMessage from openai import OpenAI from pydantic import BaseModel, Field, validator -import instructor from parts import IFSParts load_dotenv() @@ -16,12 +16,6 @@ openapi_client = OpenAI() mistral_model = "mistral-large-latest" mistral_client = MistralClient(api_key=os.getenv("MISTRAL_API_KEY")) -instructor_client = instructor.patch( - OpenAI( - # This is the default and can be omitted - api_key=os.getenv("OPENAI_API_KEY"), - ) -) # flask cors: from flask_cors import CORS @@ -57,7 +51,7 @@ def get_system_prompts(): retry_count = 0 while retry_count < 3: try: - result = instructor_client.chat.completions.create( + result = openai_client.chat.completions.create( model="gpt-4", response_model=IFSParts, messages=[ diff --git a/ifs.py/requirements.in b/ifs.py/requirements.in new file mode 100644 index 0000000..5cb7cce --- /dev/null +++ b/ifs.py/requirements.in @@ -0,0 +1,7 @@ +Flask +requests +jsonify +flask_cors +python-dotenv +mistralai +openai \ No newline at end of file diff --git a/ifs.py/requirements.txt b/ifs.py/requirements.txt index 5cb7cce..37ce883 100644 --- a/ifs.py/requirements.txt +++ b/ifs.py/requirements.txt @@ -1,7 +1,539 @@ -Flask -requests -jsonify -flask_cors -python-dotenv -mistralai -openai \ No newline at end of file +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --generate-hashes requirements.in +# +annotated-types==0.6.0 \ + --hash=sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43 \ + --hash=sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d + # via pydantic +anyio==4.3.0 \ + --hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8 \ + --hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6 + # via + # httpx + # openai +blinker==1.7.0 \ + --hash=sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9 \ + --hash=sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182 + # via flask +certifi==2024.2.2 \ + --hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f \ + --hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1 + # via + # httpcore + # httpx + # requests +charset-normalizer==3.3.2 \ + --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \ + --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \ + --hash=sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786 \ + --hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \ + --hash=sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09 \ + --hash=sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185 \ + --hash=sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574 \ + --hash=sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e \ + --hash=sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519 \ + --hash=sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898 \ + --hash=sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269 \ + --hash=sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3 \ + --hash=sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f \ + --hash=sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6 \ + --hash=sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8 \ + --hash=sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a \ + --hash=sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73 \ + --hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \ + --hash=sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714 \ + --hash=sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2 \ + --hash=sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc \ + --hash=sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce \ + --hash=sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d \ + --hash=sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e \ + --hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \ + --hash=sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269 \ + --hash=sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96 \ + --hash=sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d \ + --hash=sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a \ + --hash=sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4 \ + --hash=sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77 \ + --hash=sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d \ + --hash=sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0 \ + --hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \ + --hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \ + --hash=sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac \ + --hash=sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25 \ + --hash=sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8 \ + --hash=sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab \ + --hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \ + --hash=sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2 \ + --hash=sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db \ + --hash=sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f \ + --hash=sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5 \ + --hash=sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99 \ + --hash=sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c \ + --hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \ + --hash=sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811 \ + --hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \ + --hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \ + --hash=sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03 \ + --hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \ + --hash=sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04 \ + --hash=sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c \ + --hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \ + --hash=sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458 \ + --hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \ + --hash=sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99 \ + --hash=sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985 \ + --hash=sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537 \ + --hash=sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238 \ + --hash=sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f \ + --hash=sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d \ + --hash=sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796 \ + --hash=sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a \ + --hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \ + --hash=sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8 \ + --hash=sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c \ + --hash=sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5 \ + --hash=sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5 \ + --hash=sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711 \ + --hash=sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4 \ + --hash=sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6 \ + --hash=sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c \ + --hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \ + --hash=sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4 \ + --hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \ + --hash=sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae \ + --hash=sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12 \ + --hash=sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c \ + --hash=sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae \ + --hash=sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8 \ + --hash=sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887 \ + --hash=sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b \ + --hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \ + --hash=sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f \ + --hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5 \ + --hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \ + --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \ + --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561 + # via requests +click==8.1.7 \ + --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ + --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de + # via flask +distro==1.9.0 \ + --hash=sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed \ + --hash=sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2 + # via openai +flask==3.0.2 \ + --hash=sha256:3232e0e9c850d781933cf0207523d1ece087eb8d87b23777ae38456e2fbe7c6e \ + --hash=sha256:822c03f4b799204250a7ee84b1eddc40665395333973dfb9deebfe425fefcb7d + # via + # -r requirements.in + # flask-cors +flask-cors==4.0.0 \ + --hash=sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783 \ + --hash=sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0 + # via -r requirements.in +h11==0.14.0 \ + --hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \ + --hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761 + # via httpcore +httpcore==1.0.4 \ + --hash=sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73 \ + --hash=sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022 + # via httpx +httpx==0.25.2 \ + --hash=sha256:8b8fcaa0c8ea7b05edd69a094e63a2094c4efcb48129fb757361bc423c0ad9e8 \ + --hash=sha256:a05d3d052d9b2dfce0e3896636467f8a5342fb2b902c819428e1ac65413ca118 + # via + # mistralai + # openai +idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + # via + # anyio + # httpx + # requests +itsdangerous==2.1.2 \ + --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \ + --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a + # via flask +jinja2==3.1.3 \ + --hash=sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa \ + --hash=sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90 + # via flask +jsonify==0.5 \ + --hash=sha256:f340032753577575e9777835809b283fdc9b251867d5d5600389131647f8bfe1 + # via -r requirements.in +markupsafe==2.1.5 \ + --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ + --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ + --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ + --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ + --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ + --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ + --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ + --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ + --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ + --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ + --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ + --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ + --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ + --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ + --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ + --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ + --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ + --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ + --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ + --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ + --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ + --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ + --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ + --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ + --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ + --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ + --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ + --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ + --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ + --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ + --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ + --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ + --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ + --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ + --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ + --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ + --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ + --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ + --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ + --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ + --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ + --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ + --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ + --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ + --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ + --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ + --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ + --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ + --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ + --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ + --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ + --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ + --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ + --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ + --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ + --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ + --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ + --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ + --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ + --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 + # via + # jinja2 + # werkzeug +mistralai==0.1.6 \ + --hash=sha256:61bbdaa8f57467927e73b842f8dae7b692e332d75cf9f72ceee04719039c7494 \ + --hash=sha256:9a4864c6c87077df8676b42e3685c58bd777fa6d2889aa481c510ccd64dd209a + # via -r requirements.in +numpy==1.26.4 \ + --hash=sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b \ + --hash=sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818 \ + --hash=sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20 \ + --hash=sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0 \ + --hash=sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010 \ + --hash=sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a \ + --hash=sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea \ + --hash=sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c \ + --hash=sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71 \ + --hash=sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110 \ + --hash=sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be \ + --hash=sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a \ + --hash=sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a \ + --hash=sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5 \ + --hash=sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed \ + --hash=sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd \ + --hash=sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c \ + --hash=sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e \ + --hash=sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0 \ + --hash=sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c \ + --hash=sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a \ + --hash=sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b \ + --hash=sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0 \ + --hash=sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6 \ + --hash=sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2 \ + --hash=sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a \ + --hash=sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30 \ + --hash=sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218 \ + --hash=sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5 \ + --hash=sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07 \ + --hash=sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2 \ + --hash=sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4 \ + --hash=sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764 \ + --hash=sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef \ + --hash=sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3 \ + --hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f + # via + # pandas + # pyarrow +openai==1.14.2 \ + --hash=sha256:a48b3c4d635b603952189ac5a0c0c9b06c025b80eb2900396939f02bb2104ac3 \ + --hash=sha256:e5642f7c02cf21994b08477d7bb2c1e46d8f335d72c26f0396c5f89b15b5b153 + # via -r requirements.in +orjson==3.9.15 \ + --hash=sha256:001f4eb0ecd8e9ebd295722d0cbedf0748680fb9998d3993abaed2f40587257a \ + --hash=sha256:05a1f57fb601c426635fcae9ddbe90dfc1ed42245eb4c75e4960440cac667262 \ + --hash=sha256:10c57bc7b946cf2efa67ac55766e41764b66d40cbd9489041e637c1304400494 \ + --hash=sha256:12365576039b1a5a47df01aadb353b68223da413e2e7f98c02403061aad34bde \ + --hash=sha256:2973474811db7b35c30248d1129c64fd2bdf40d57d84beed2a9a379a6f57d0ab \ + --hash=sha256:2b5c0f532905e60cf22a511120e3719b85d9c25d0e1c2a8abb20c4dede3b05a5 \ + --hash=sha256:2c51378d4a8255b2e7c1e5cc430644f0939539deddfa77f6fac7b56a9784160a \ + --hash=sha256:2d99e3c4c13a7b0fb3792cc04c2829c9db07838fb6973e578b85c1745e7d0ce7 \ + --hash=sha256:2f256d03957075fcb5923410058982aea85455d035607486ccb847f095442bda \ + --hash=sha256:34cbcd216e7af5270f2ffa63a963346845eb71e174ea530867b7443892d77180 \ + --hash=sha256:4228aace81781cc9d05a3ec3a6d2673a1ad0d8725b4e915f1089803e9efd2b99 \ + --hash=sha256:4feeb41882e8aa17634b589533baafdceb387e01e117b1ec65534ec724023d04 \ + --hash=sha256:57d5d8cf9c27f7ef6bc56a5925c7fbc76b61288ab674eb352c26ac780caa5b10 \ + --hash=sha256:5bb399e1b49db120653a31463b4a7b27cf2fbfe60469546baf681d1b39f4edf2 \ + --hash=sha256:62482873e0289cf7313461009bf62ac8b2e54bc6f00c6fabcde785709231a5d7 \ + --hash=sha256:67384f588f7f8daf040114337d34a5188346e3fae6c38b6a19a2fe8c663a2f9b \ + --hash=sha256:6ae4e06be04dc00618247c4ae3f7c3e561d5bc19ab6941427f6d3722a0875ef7 \ + --hash=sha256:6f7b65bfaf69493c73423ce9db66cfe9138b2f9ef62897486417a8fcb0a92bfe \ + --hash=sha256:6fc2fe4647927070df3d93f561d7e588a38865ea0040027662e3e541d592811e \ + --hash=sha256:71c6b009d431b3839d7c14c3af86788b3cfac41e969e3e1c22f8a6ea13139404 \ + --hash=sha256:7413070a3e927e4207d00bd65f42d1b780fb0d32d7b1d951f6dc6ade318e1b5a \ + --hash=sha256:76bc6356d07c1d9f4b782813094d0caf1703b729d876ab6a676f3aaa9a47e37c \ + --hash=sha256:7f6cbd8e6e446fb7e4ed5bac4661a29e43f38aeecbf60c4b900b825a353276a1 \ + --hash=sha256:8055ec598605b0077e29652ccfe9372247474375e0e3f5775c91d9434e12d6b1 \ + --hash=sha256:809d653c155e2cc4fd39ad69c08fdff7f4016c355ae4b88905219d3579e31eb7 \ + --hash=sha256:82425dd5c7bd3adfe4e94c78e27e2fa02971750c2b7ffba648b0f5d5cc016a73 \ + --hash=sha256:87f1097acb569dde17f246faa268759a71a2cb8c96dd392cd25c668b104cad2f \ + --hash=sha256:920fa5a0c5175ab14b9c78f6f820b75804fb4984423ee4c4f1e6d748f8b22bc1 \ + --hash=sha256:92255879280ef9c3c0bcb327c5a1b8ed694c290d61a6a532458264f887f052cb \ + --hash=sha256:946c3a1ef25338e78107fba746f299f926db408d34553b4754e90a7de1d44068 \ + --hash=sha256:95cae920959d772f30ab36d3b25f83bb0f3be671e986c72ce22f8fa700dae061 \ + --hash=sha256:9cf1596680ac1f01839dba32d496136bdd5d8ffb858c280fa82bbfeb173bdd40 \ + --hash=sha256:9fe41b6f72f52d3da4db524c8653e46243c8c92df826ab5ffaece2dba9cccd58 \ + --hash=sha256:b17f0f14a9c0ba55ff6279a922d1932e24b13fc218a3e968ecdbf791b3682b25 \ + --hash=sha256:b3d336ed75d17c7b1af233a6561cf421dee41d9204aa3cfcc6c9c65cd5bb69a8 \ + --hash=sha256:b66bcc5670e8a6b78f0313bcb74774c8291f6f8aeef10fe70e910b8040f3ab75 \ + --hash=sha256:b725da33e6e58e4a5d27958568484aa766e825e93aa20c26c91168be58e08cbb \ + --hash=sha256:b72758f3ffc36ca566ba98a8e7f4f373b6c17c646ff8ad9b21ad10c29186f00d \ + --hash=sha256:bcef128f970bb63ecf9a65f7beafd9b55e3aaf0efc271a4154050fc15cdb386e \ + --hash=sha256:c8e8fe01e435005d4421f183038fc70ca85d2c1e490f51fb972db92af6e047c2 \ + --hash=sha256:d61f7ce4727a9fa7680cd6f3986b0e2c732639f46a5e0156e550e35258aa313a \ + --hash=sha256:d6768a327ea1ba44c9114dba5fdda4a214bdb70129065cd0807eb5f010bfcbb5 \ + --hash=sha256:e18668f1bd39e69b7fed19fa7cd1cd110a121ec25439328b5c89934e6d30d357 \ + --hash=sha256:e88b97ef13910e5f87bcbc4dd7979a7de9ba8702b54d3204ac587e83639c0c2b \ + --hash=sha256:ea0b183a5fe6b2b45f3b854b0d19c4e932d6f5934ae1f723b07cf9560edd4ec7 \ + --hash=sha256:ede0bde16cc6e9b96633df1631fbcd66491d1063667f260a4f2386a098393790 \ + --hash=sha256:f541587f5c558abd93cb0de491ce99a9ef8d1ae29dd6ab4dbb5a13281ae04cbd \ + --hash=sha256:fbbeb3c9b2edb5fd044b2a070f127a0ac456ffd079cb82746fc84af01ef021a4 \ + --hash=sha256:fdfa97090e2d6f73dced247a2f2d8004ac6449df6568f30e7fa1a045767c69a6 \ + --hash=sha256:ff0f9913d82e1d1fadbd976424c316fbc4d9c525c81d047bbdd16bd27dd98cfc + # via mistralai +pandas==2.2.1 \ + --hash=sha256:04f6ec3baec203c13e3f8b139fb0f9f86cd8c0b94603ae3ae8ce9a422e9f5bee \ + --hash=sha256:06cf591dbaefb6da9de8472535b185cba556d0ce2e6ed28e21d919704fef1a9e \ + --hash=sha256:0ab90f87093c13f3e8fa45b48ba9f39181046e8f3317d3aadb2fffbb1b978572 \ + --hash=sha256:0f573ab277252ed9aaf38240f3b54cfc90fff8e5cab70411ee1d03f5d51f3944 \ + --hash=sha256:101d0eb9c5361aa0146f500773395a03839a5e6ecde4d4b6ced88b7e5a1a6403 \ + --hash=sha256:11940e9e3056576ac3244baef2fedade891977bcc1cb7e5cc8f8cc7d603edc89 \ + --hash=sha256:1ba21b1d5c0e43416218db63037dbe1a01fc101dc6e6024bcad08123e48004ab \ + --hash=sha256:4aa1d8707812a658debf03824016bf5ea0d516afdea29b7dc14cf687bc4d4ec6 \ + --hash=sha256:4acf681325ee1c7f950d058b05a820441075b0dd9a2adf5c4835b9bc056bf4fb \ + --hash=sha256:53680dc9b2519cbf609c62db3ed7c0b499077c7fefda564e330286e619ff0dd9 \ + --hash=sha256:739cc70eaf17d57608639e74d63387b0d8594ce02f69e7a0b046f117974b3019 \ + --hash=sha256:76f27a809cda87e07f192f001d11adc2b930e93a2b0c4a236fde5429527423be \ + --hash=sha256:7d2ed41c319c9fb4fd454fe25372028dfa417aacb9790f68171b2e3f06eae8cd \ + --hash=sha256:88ecb5c01bb9ca927ebc4098136038519aa5d66b44671861ffab754cae75102c \ + --hash=sha256:8df8612be9cd1c7797c93e1c5df861b2ddda0b48b08f2c3eaa0702cf88fb5f88 \ + --hash=sha256:94e714a1cca63e4f5939cdce5f29ba8d415d85166be3441165edd427dc9f6bc0 \ + --hash=sha256:9bd8a40f47080825af4317d0340c656744f2bfdb6819f818e6ba3cd24c0e1397 \ + --hash=sha256:9d1265545f579edf3f8f0cb6f89f234f5e44ba725a34d86535b1a1d38decbccc \ + --hash=sha256:a935a90a76c44fe170d01e90a3594beef9e9a6220021acfb26053d01426f7dc2 \ + --hash=sha256:af5d3c00557d657c8773ef9ee702c61dd13b9d7426794c9dfeb1dc4a0bf0ebc7 \ + --hash=sha256:c2ce852e1cf2509a69e98358e8458775f89599566ac3775e70419b98615f4b06 \ + --hash=sha256:c38ce92cb22a4bea4e3929429aa1067a454dcc9c335799af93ba9be21b6beb51 \ + --hash=sha256:c391f594aae2fd9f679d419e9a4d5ba4bce5bb13f6a989195656e7dc4b95c8f0 \ + --hash=sha256:c70e00c2d894cb230e5c15e4b1e1e6b2b478e09cf27cc593a11ef955b9ecc81a \ + --hash=sha256:df0c37ebd19e11d089ceba66eba59a168242fc6b7155cba4ffffa6eccdfb8f16 \ + --hash=sha256:e97fbb5387c69209f134893abc788a6486dbf2f9e511070ca05eed4b930b1b02 \ + --hash=sha256:f02a3a6c83df4026e55b63c1f06476c9aa3ed6af3d89b4f04ea656ccdaaaa359 \ + --hash=sha256:f821213d48f4ab353d20ebc24e4faf94ba40d76680642fb7ce2ea31a3ad94f9b \ + --hash=sha256:f9d3558d263073ed95e46f4650becff0c5e1ffe0fc3a015de3c79283dfbdb3df + # via mistralai +pyarrow==15.0.2 \ + --hash=sha256:033b7cad32198754d93465dcfb71d0ba7cb7cd5c9afd7052cab7214676eec38b \ + --hash=sha256:06c2bb2a98bc792f040bef31ad3e9be6a63d0cb39189227c08a7d955db96816e \ + --hash=sha256:23c6753ed4f6adb8461e7c383e418391b8d8453c5d67e17f416c3a5d5709afbd \ + --hash=sha256:248723e4ed3255fcd73edcecc209744d58a9ca852e4cf3d2577811b6d4b59818 \ + --hash=sha256:25335e6f1f07fdaa026a61c758ee7d19ce824a866b27bba744348fa73bb5a440 \ + --hash=sha256:28f3016958a8e45a1069303a4a4f6a7d4910643fc08adb1e2e4a7ff056272ad3 \ + --hash=sha256:290e36a59a0993e9a5224ed2fb3e53375770f07379a0ea03ee2fce2e6d30b423 \ + --hash=sha256:29850d050379d6e8b5a693098f4de7fd6a2bea4365bfd073d7c57c57b95041ee \ + --hash=sha256:2d4f905209de70c0eb5b2de6763104d5a9a37430f137678edfb9a675bac9cd98 \ + --hash=sha256:3a4f240852b302a7af4646c8bfe9950c4691a419847001178662a98915fd7ee7 \ + --hash=sha256:3e6d459c0c22f0b9c810a3917a1de3ee704b021a5fb8b3bacf968eece6df098f \ + --hash=sha256:3ff3bdfe6f1b81ca5b73b70a8d482d37a766433823e0c21e22d1d7dde76ca33f \ + --hash=sha256:4e7d9cfb5a1e648e172428c7a42b744610956f3b70f524aa3a6c02a448ba853e \ + --hash=sha256:58922e4bfece8b02abf7159f1f53a8f4d9f8e08f2d988109126c17c3bb261f22 \ + --hash=sha256:5f8bc839ea36b1f99984c78e06e7a06054693dc2af8920f6fb416b5bca9944e4 \ + --hash=sha256:6669799a1d4ca9da9c7e06ef48368320f5856f36f9a4dd31a11839dda3f6cc8c \ + --hash=sha256:7167107d7fb6dcadb375b4b691b7e316f4368f39f6f45405a05535d7ad5e5058 \ + --hash=sha256:88b340f0a1d05b5ccc3d2d986279045655b1fe8e41aba6ca44ea28da0d1455d8 \ + --hash=sha256:89722cb64286ab3d4daf168386f6968c126057b8c7ec3ef96302e81d8cdb8ae4 \ + --hash=sha256:8bd2baa5fe531571847983f36a30ddbf65261ef23e496862ece83bdceb70420d \ + --hash=sha256:8c1faf2482fb89766e79745670cbca04e7018497d85be9242d5350cba21357e1 \ + --hash=sha256:90adb99e8ce5f36fbecbbc422e7dcbcbed07d985eed6062e459e23f9e71fd197 \ + --hash=sha256:90f19e976d9c3d8e73c80be84ddbe2f830b6304e4c576349d9360e335cd627fc \ + --hash=sha256:9c9bc803cb3b7bfacc1e96ffbfd923601065d9d3f911179d81e72d99fd74a3d9 \ + --hash=sha256:a22366249bf5fd40ddacc4f03cd3160f2d7c247692945afb1899bab8a140ddfb \ + --hash=sha256:ad2459bf1f22b6a5cdcc27ebfd99307d5526b62d217b984b9f5c974651398832 \ + --hash=sha256:adccc81d3dc0478ea0b498807b39a8d41628fa9210729b2f718b78cb997c7c91 \ + --hash=sha256:b116e7fd7889294cbd24eb90cd9bdd3850be3738d61297855a71ac3b8124ee38 \ + --hash=sha256:c2a335198f886b07e4b5ea16d08ee06557e07db54a8400cc0d03c7f6a22f785f \ + --hash=sha256:cd0ba387705044b3ac77b1b317165c0498299b08261d8122c96051024f953cd5 \ + --hash=sha256:e85241b44cc3d365ef950432a1b3bd44ac54626f37b2e3a0cc89c20e45dfd8bf \ + --hash=sha256:eaa8f96cecf32da508e6c7f69bb8401f03745c050c1dd42ec2596f2e98deecac \ + --hash=sha256:f3d77463dee7e9f284ef42d341689b459a63ff2e75cee2b9302058d0d98fe142 \ + --hash=sha256:f5e81dfb4e519baa6b4c80410421528c214427e77ca0ea9461eb4097c328fa33 \ + --hash=sha256:f639c059035011db8c0497e541a8a45d98a58dbe34dc8fadd0ef128f2cee46e5 \ + --hash=sha256:f7a197f3670606a960ddc12adbe8075cea5f707ad7bf0dffa09637fdbb89f76c + # via mistralai +pydantic==2.6.4 \ + --hash=sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6 \ + --hash=sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5 + # via + # mistralai + # openai +pydantic-core==2.16.3 \ + --hash=sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a \ + --hash=sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed \ + --hash=sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979 \ + --hash=sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff \ + --hash=sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5 \ + --hash=sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45 \ + --hash=sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340 \ + --hash=sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad \ + --hash=sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23 \ + --hash=sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6 \ + --hash=sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7 \ + --hash=sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241 \ + --hash=sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda \ + --hash=sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187 \ + --hash=sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba \ + --hash=sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c \ + --hash=sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2 \ + --hash=sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c \ + --hash=sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132 \ + --hash=sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf \ + --hash=sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972 \ + --hash=sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db \ + --hash=sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade \ + --hash=sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4 \ + --hash=sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8 \ + --hash=sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f \ + --hash=sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9 \ + --hash=sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48 \ + --hash=sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec \ + --hash=sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d \ + --hash=sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9 \ + --hash=sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb \ + --hash=sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4 \ + --hash=sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89 \ + --hash=sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c \ + --hash=sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9 \ + --hash=sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da \ + --hash=sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac \ + --hash=sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b \ + --hash=sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf \ + --hash=sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e \ + --hash=sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137 \ + --hash=sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1 \ + --hash=sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b \ + --hash=sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8 \ + --hash=sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e \ + --hash=sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053 \ + --hash=sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01 \ + --hash=sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe \ + --hash=sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd \ + --hash=sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805 \ + --hash=sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183 \ + --hash=sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8 \ + --hash=sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99 \ + --hash=sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820 \ + --hash=sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074 \ + --hash=sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256 \ + --hash=sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8 \ + --hash=sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975 \ + --hash=sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad \ + --hash=sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e \ + --hash=sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca \ + --hash=sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df \ + --hash=sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b \ + --hash=sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a \ + --hash=sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a \ + --hash=sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721 \ + --hash=sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a \ + --hash=sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f \ + --hash=sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2 \ + --hash=sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97 \ + --hash=sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6 \ + --hash=sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed \ + --hash=sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc \ + --hash=sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1 \ + --hash=sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe \ + --hash=sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120 \ + --hash=sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f \ + --hash=sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a + # via pydantic +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + # via pandas +python-dotenv==1.0.1 \ + --hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \ + --hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a + # via -r requirements.in +pytz==2024.1 \ + --hash=sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812 \ + --hash=sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319 + # via pandas +requests==2.31.0 \ + --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ + --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 + # via -r requirements.in +six==1.16.0 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 + # via python-dateutil +sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via + # anyio + # httpx + # openai +tqdm==4.66.2 \ + --hash=sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9 \ + --hash=sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531 + # via openai +typing-extensions==4.10.0 \ + --hash=sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475 \ + --hash=sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb + # via + # openai + # pydantic + # pydantic-core +tzdata==2024.1 \ + --hash=sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd \ + --hash=sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252 + # via pandas +urllib3==2.2.1 \ + --hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \ + --hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19 + # via requests +werkzeug==3.0.1 \ + --hash=sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc \ + --hash=sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10 + # via flask From b32c670c5ee81dfa0d0326db884d5de26e054498 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 24 Mar 2024 09:02:51 -0700 Subject: [PATCH 30/65] add sidebar logs UI --- ifs.ai/app/chat/page.tsx | 45 ++++++++++++++++++++++++++-------------- ifs.ai/app/globals.css | 2 +- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index 62611ce..1f77b45 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -3,13 +3,15 @@ import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { MicIcon, SendIcon } from "lucide-react"; -import { useState } from "react"; +import { useState, useRef, useEffect } from "react"; import { IMAGE_URLS_KEY, PartImageUrls } from "@/app/constants"; import DIDVideoStream from "@/app/DIDWebRTCVideoStream"; import Loading from "@/components/ui/loading"; // Import a LoadingScreen component import SpeechToText from "@/components/ui/speech-to-text"; import { MessageBox } from 'react-chat-elements' +import 'react-chat-elements/dist/main.css' + export default function Page() { const [isSubmitting, setIsSubmitting] = useState(false); const [inputValue, setInputValue] = useState(""); @@ -47,20 +49,27 @@ export default function Page() { }, ]; + const sidebarRef = useRef(null); + + useEffect(() => { + if (sidebarRef.current) { + sidebarRef.current.scrollTop = sidebarRef.current.scrollHeight; + } + }); return ( -
+
{isSubmitting ? :
} -

IFS Therapy

-
-
+

IFS Therapy

+
+
-
+
{parts.map(({ name, prettyName, imageUrl, personality, unmetNeeds }) => (
- {`An -

{prettyName}

+ {`An +

{prettyName}

{personality}

Unmet Needs: {unmetNeeds.join(', ')}

@@ -115,27 +124,31 @@ export default function Page() {
-
Sidebar +
+ {history.map(({role, text}, index) => { - let bgColor; + let bgColor, parsedRole = "User"; if (role === "user") { bgColor = "white"; - } else if (text.toLowerCase().includes("exile")) { + } else if (text.toLowerCase().startsWith("exile")) { bgColor = "#FFFFE0"; - } else if (text.toLowerCase().includes("firefighter")) { + parsedRole = "Exile"; + } else if (text.toLowerCase().startsWith("firefighter")) { bgColor = "#FFC0CB"; - } else if (text.toLowerCase().includes("manager")) { + parsedRole = "Firefighter"; + } else if (text.toLowerCase().startsWith("manager")) { bgColor = "#ADD8E6"; + parsedRole = "Manager"; } else { bgColor = "#000000"; } return ( -
+
Date: Sun, 24 Mar 2024 09:11:16 -0700 Subject: [PATCH 31/65] add responder in history --- ifs.ai/app/chat/page.tsx | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index 1f77b45..99214dd 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -15,7 +15,7 @@ import 'react-chat-elements/dist/main.css' export default function Page() { const [isSubmitting, setIsSubmitting] = useState(false); const [inputValue, setInputValue] = useState(""); - const [history, setHistory] = useState<{role: string, text: string}[]>([]); + const [history, setHistory] = useState<{role: string, text: string, responder: string}[]>([]); const [prevPart, setPrevPart] = useState("none"); const imageUrls: PartImageUrls = JSON.parse(window.localStorage.getItem(IMAGE_URLS_KEY) ?? "{}"); @@ -89,13 +89,13 @@ export default function Page() { body: JSON.stringify({ prev_part: prevPart, // replace with the previous part user_message: inputValue, // replace with the user's message - history: [...history, {"role": "user", "text": inputValue}] + history: [...history, {"role": "user", "text": inputValue, responder: ""}] }) }) .then(response => response.json()) .then(data => { const { responder, text, role } = data; - setHistory(prevHistory => [...prevHistory, {"role": "user", "text": inputValue}, {"role": role, "text": responder + ": " + text}]); + setHistory(prevHistory => [...prevHistory, {"role": "user", "text": inputValue, responder: ""}, {"role": role, "text": text, responder: responder}]); setPrevPart(responder); console.log(data); }) @@ -126,19 +126,16 @@ export default function Page() {
- {history.map(({role, text}, index) => { - let bgColor, parsedRole = "User"; + {history.map(({role, text, responder}, index) => { + let bgColor; if (role === "user") { bgColor = "white"; - } else if (text.toLowerCase().startsWith("exile")) { + } else if (responder.toLowerCase() === "exile") { bgColor = "#FFFFE0"; - parsedRole = "Exile"; - } else if (text.toLowerCase().startsWith("firefighter")) { + } else if (responder.toLowerCase() == "firefighter") { bgColor = "#FFC0CB"; - parsedRole = "Firefighter"; - } else if (text.toLowerCase().startsWith("manager")) { + } else if (responder.toLowerCase() == "manager") { bgColor = "#ADD8E6"; - parsedRole = "Manager"; } else { bgColor = "#000000"; } @@ -147,7 +144,7 @@ export default function Page() { Date: Sun, 24 Mar 2024 09:26:01 -0700 Subject: [PATCH 32/65] rather than using a form, use onclick --- ifs.ai/app/chat/page.tsx | 76 +++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index 99214dd..aa62b3b 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -76,51 +76,53 @@ export default function Page() { ))}
- { - e.preventDefault(); - setIsSubmitting(true); - fetch('http://localhost:5000/get_response', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - prev_part: prevPart, // replace with the previous part - user_message: inputValue, // replace with the user's message - history: [...history, {"role": "user", "text": inputValue, responder: ""}] - }) - }) - .then(response => response.json()) - .then(data => { - const { responder, text, role } = data; - setHistory(prevHistory => [...prevHistory, {"role": "user", "text": inputValue, responder: ""}, {"role": role, "text": text, responder: responder}]); - setPrevPart(responder); - console.log(data); - }) - .catch((error) => { - console.error('Error:', error); - }); - - setInputValue(""); - - // TODO: Actually call the prompt(s) - console.log("Submitted", e); - setTimeout(() => setIsSubmitting(false), 2000); - }} - > +
setInputValue(e.target.value)} /> - - setInputValue(transcript)} /> - +
+ setInputValue(transcript)} />
From a4ff0a9e9bdfdb6bc3289bcd20b2e50300492345 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 24 Mar 2024 09:28:36 -0700 Subject: [PATCH 33/65] mr-1 --- ifs.ai/app/chat/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index aa62b3b..963500e 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -76,7 +76,7 @@ export default function Page() { ))}
-
+
Date: Sun, 24 Mar 2024 09:35:49 -0700 Subject: [PATCH 34/65] fix app.py to add back instructor --- ifs.py/app.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ifs.py/app.py b/ifs.py/app.py index 8228112..1898a2f 100644 --- a/ifs.py/app.py +++ b/ifs.py/app.py @@ -9,11 +9,17 @@ from openai import OpenAI from pydantic import BaseModel, Field, validator from parts import IFSParts +import instructor load_dotenv() groq = os.getenv("GROQ_ENV") -openapi_client = OpenAI() +instructor_client = instructor.patch( + OpenAI( + # This is the default and can be omitted + api_key=os.getenv("OPENAI_API_KEY"), + ) +) mistral_model = "mistral-large-latest" mistral_client = MistralClient(api_key=os.getenv("MISTRAL_API_KEY")) # flask cors: @@ -51,7 +57,7 @@ def get_system_prompts(): retry_count = 0 while retry_count < 3: try: - result = openai_client.chat.completions.create( + result = instructor_client.chat.completions.create( model="gpt-4", response_model=IFSParts, messages=[ From f0e7eb46dcd5d41f2eb1cddda68c297dc732db06 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 24 Mar 2024 09:36:35 -0700 Subject: [PATCH 35/65] added requirements.in for instructor --- ifs.py/requirements.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ifs.py/requirements.in b/ifs.py/requirements.in index 5cb7cce..d15ec6d 100644 --- a/ifs.py/requirements.in +++ b/ifs.py/requirements.in @@ -4,4 +4,5 @@ jsonify flask_cors python-dotenv mistralai -openai \ No newline at end of file +openai +instructor \ No newline at end of file From 45eb11fd2149ca89950037cc206d2e5e5f41593e Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sun, 24 Mar 2024 09:57:14 -0700 Subject: [PATCH 36/65] De-gender and update image prompts --- ifs.ai/app/api/uploadthing/core.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ifs.ai/app/api/uploadthing/core.ts b/ifs.ai/app/api/uploadthing/core.ts index 0cff6a2..fed9cf4 100644 --- a/ifs.ai/app/api/uploadthing/core.ts +++ b/ifs.ai/app/api/uploadthing/core.ts @@ -14,11 +14,11 @@ const replicate = new Replicate({ async function makePartImages(inputUrl: string): Promise { const prompts = { manager: - "A photo of man img upclose, facing camera, looking confident and controlled, professional outfit, upright posture, orderly surroundings, muted background colors, symbols of achievement, sense of discipline and responsibility", + "A photo of a person img upclose, facing camera, looking mature confident and controlled, professional outfit, upright posture, orderly surroundings, muted background colors, symbols of achievement, sense of discipline and responsibility", firefighter: - "A photo of man img upclose, facing camera, fierce expression, intense eyes, bold outfit, fiery background colors, sense of urgency and strength", + "A photo of a person img upclose, facing camera, fierce expression, intense eyes, firefighter, bold outfit, fiery background colors, sense of urgency and strength", exile: - "A photo of man img upclose, facing camera, young and vulnerable, sad eyes, child-like clothing, sitting alone, muted background colors, sense of isolation and longing for care and acceptance", + "A photo of a child img upclose, facing camera, young and extremely vulnerable, infant, eyes filled with fear and uncertainty, tattered and worn clothing, dark and shadowy background colors, bruises and scratches visible on skin, sense of deep isolation, desperately seeking safety, care, and acceptance", }; return Object.fromEntries( From 2b29fe63a3a126e010261da2519196e36bf99ebe Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sun, 24 Mar 2024 10:29:39 -0700 Subject: [PATCH 37/65] Save images to a given ID --- ifs.ai/app/chat/page.tsx | 172 +++++++++++++++++++++----------------- ifs.ai/app/constants.ts | 1 + ifs.ai/app/start/page.tsx | 28 +++++-- 3 files changed, 116 insertions(+), 85 deletions(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index 963500e..d433b40 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -4,21 +4,23 @@ import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { MicIcon, SendIcon } from "lucide-react"; import { useState, useRef, useEffect } from "react"; -import { IMAGE_URLS_KEY, PartImageUrls } from "@/app/constants"; +import { getImageUrlsKeyForId, PartImageUrls } from "@/app/constants"; import DIDVideoStream from "@/app/DIDWebRTCVideoStream"; import Loading from "@/components/ui/loading"; // Import a LoadingScreen component import SpeechToText from "@/components/ui/speech-to-text"; -import { MessageBox } from 'react-chat-elements' +import { MessageBox } from "react-chat-elements"; -import 'react-chat-elements/dist/main.css' +import "react-chat-elements/dist/main.css"; -export default function Page() { +export default function Page({ searchParams }: { searchParams: { id: number } }) { const [isSubmitting, setIsSubmitting] = useState(false); const [inputValue, setInputValue] = useState(""); - const [history, setHistory] = useState<{role: string, text: string, responder: string}[]>([]); + const [history, setHistory] = useState<{ role: string; text: string; responder: string }[]>([]); const [prevPart, setPrevPart] = useState("none"); - - const imageUrls: PartImageUrls = JSON.parse(window.localStorage.getItem(IMAGE_URLS_KEY) ?? "{}"); + + const imageUrls: PartImageUrls = JSON.parse( + window.localStorage.getItem(getImageUrlsKeyForId(searchParams.id)) ?? "{}", + ); const parts = [ { name: "Manager", @@ -26,8 +28,9 @@ export default function Page() { imageUrl: imageUrls.manager ?? "https://media.discordapp.net/attachments/1221190777259167784/1221307109392519168/robert_manager.png?ex=661219e1&is=65ffa4e1&hm=0ac923036baf4bad33b5fd508c90e2ac60621bfec876ada9d325a084e4fc2b00&=&format=webp&quality=lossless&width=1557&height=1557", - personality: "As a Manager in this situation, I feel a sense of duty and responsibility. I am constantly questioning if we're doing the right thing and whether our venture will yield the desired results in a short span. The need for surety causes additional stress and anxiety, driving me to closely scrutinize and control every aspect of our plan.", - unmetNeeds: ['certainty', 'success', 'validation'], + personality: + "As a Manager in this situation, I feel a sense of duty and responsibility. I am constantly questioning if we're doing the right thing and whether our venture will yield the desired results in a short span. The need for surety causes additional stress and anxiety, driving me to closely scrutinize and control every aspect of our plan.", + unmetNeeds: ["certainty", "success", "validation"], }, { name: "Exile", @@ -35,8 +38,9 @@ export default function Page() { imageUrl: imageUrls.exile ?? "https://media.discordapp.net/attachments/1221190777259167784/1221307108549464165/robert_exile.png?ex=661219e1&is=65ffa4e1&hm=bed1c819bb6c88d0a86262cdd2ba334d0d10ec701f296abee75d32c3991e053e&=&format=webp&quality=lossless&width=1557&height=1557", - personality: 'As an Exile, I carry the heavy burdens of self-doubt and feelings of inadequacy. I question if this is the life I truly want and if these achievements will bring me happiness. My inner turbulence intensifies when thinking about love and why it seems elusive to me. I am the receptacle of unvoiced sadness and yearning, often suppressed to maintain an outward impression of confidence.', - unmetNeeds: ['companionship', 'contentment', 'self-acceptance'], + personality: + "As an Exile, I carry the heavy burdens of self-doubt and feelings of inadequacy. I question if this is the life I truly want and if these achievements will bring me happiness. My inner turbulence intensifies when thinking about love and why it seems elusive to me. I am the receptacle of unvoiced sadness and yearning, often suppressed to maintain an outward impression of confidence.", + unmetNeeds: ["companionship", "contentment", "self-acceptance"], }, { name: "Firefighter", @@ -44,8 +48,9 @@ export default function Page() { imageUrl: imageUrls.firefighter ?? "https://media.discordapp.net/attachments/1221190777259167784/1221307109006770316/robert_firefighter.png?ex=661219e1&is=65ffa4e1&hm=abc4dbc5b4131395cbcd4d5d028c7d51047bf1d0cc661e6fb2c21bde3123132a&=&format=webp&quality=lossless&width=1557&height=1557", - personality: "As a Firefighter, I react to these feelings of insecurity and uncertainty by pushing towards ambitious goals like becoming a billionaire in 12 months. My approach can be seen as impulsive or even reckless, as it's driven more by a need to numb the deep-seated feelings of inadequacy than by a solid plan.", - unmetNeeds: ['fulfillment', 'validation', 'sense of worth'], + personality: + "As a Firefighter, I react to these feelings of insecurity and uncertainty by pushing towards ambitious goals like becoming a billionaire in 12 months. My approach can be seen as impulsive or even reckless, as it's driven more by a need to numb the deep-seated feelings of inadequacy than by a solid plan.", + unmetNeeds: ["fulfillment", "validation", "sense of worth"], }, ]; @@ -56,65 +61,72 @@ export default function Page() { sidebarRef.current.scrollTop = sidebarRef.current.scrollHeight; } }); - return (
- {isSubmitting ? :
} -

IFS Therapy

-
-
+ {isSubmitting ? :
} +

IFS Therapy

+
+
-
+
{parts.map(({ name, prettyName, imageUrl, personality, unmetNeeds }) => (
{`An -

{prettyName}

+

{prettyName}

{personality}

-

Unmet Needs: {unmetNeeds.join(', ')}

+

Unmet Needs: {unmetNeeds.join(", ")}

))}
-
+
setInputValue(e.target.value)} /> -
-
- - {history.map(({role, text, responder}, index) => { - let bgColor; - if (role === "user") { - bgColor = "white"; - } else if (responder.toLowerCase() === "exile") { - bgColor = "#FFFFE0"; - } else if (responder.toLowerCase() == "firefighter") { - bgColor = "#FFC0CB"; - } else if (responder.toLowerCase() == "manager") { - bgColor = "#ADD8E6"; - } else { - bgColor = "#000000"; - } - return ( -
- -
- ); - })} +
+ {history.map(({ role, text, responder }, index) => { + let bgColor; + if (role === "user") { + bgColor = "white"; + } else if (responder.toLowerCase() === "exile") { + bgColor = "#FFFFE0"; + } else if (responder.toLowerCase() == "firefighter") { + bgColor = "#FFC0CB"; + } else if (responder.toLowerCase() == "manager") { + bgColor = "#ADD8E6"; + } else { + bgColor = "#000000"; + } + return ( +
+ +
+ ); + })}
diff --git a/ifs.ai/app/constants.ts b/ifs.ai/app/constants.ts index c6848b1..389735b 100644 --- a/ifs.ai/app/constants.ts +++ b/ifs.ai/app/constants.ts @@ -1,2 +1,3 @@ export const IMAGE_URLS_KEY = "IFS_IMAGE_URLS"; +export const getImageUrlsKeyForId = (id: number): string => `${IMAGE_URLS_KEY}_${id}`; export type PartImageUrls = { manager: string; firefighter: string; exile: string }; diff --git a/ifs.ai/app/start/page.tsx b/ifs.ai/app/start/page.tsx index 8144b30..d9493c2 100644 --- a/ifs.ai/app/start/page.tsx +++ b/ifs.ai/app/start/page.tsx @@ -2,11 +2,11 @@ import { Button } from "@/components/ui/button"; import Webcam from "react-webcam"; -import { MutableRefObject, useRef } from "react"; +import { MutableRefObject, useMemo, useRef, useState } from "react"; import { toast } from "@/components/ui/use-toast"; import { generateReactHelpers } from "@uploadthing/react"; import { OurFileRouter } from "@/app/api/uploadthing/core"; -import { IMAGE_URLS_KEY } from "@/app/constants"; +import { getImageUrlsKeyForId } from "@/app/constants"; const { useUploadThing, uploadFiles } = generateReactHelpers(); @@ -25,12 +25,23 @@ function dataURLtoFile(dataurl: string, filename: string) { export default function Page() { const webcamRef: MutableRefObject = useRef(null); - const { startUpload, permittedFileInfo } = useUploadThing("imageUploader", { + const [rantProcessed, setRantProcessed] = useState(false); + + const id = useMemo(() => { + let i = 0; + while (window.localStorage.getItem(getImageUrlsKeyForId(i))) { + i++; + } + console.log("ID is", i); + return i; + }, []); + + const { startUpload, permittedFileInfo, isUploading } = useUploadThing("imageUploader", { onClientUploadComplete: (res) => { console.log("Res", res); let imageUrls = res[0].serverData.partImageUrls; console.log("Saving image urls", imageUrls); - window.localStorage.setItem(IMAGE_URLS_KEY, JSON.stringify(imageUrls)); + window.localStorage.setItem(getImageUrlsKeyForId(id), JSON.stringify(imageUrls)); toast({ title: "Uploaded successfully!", description: ( @@ -61,7 +72,11 @@ export default function Page() { return (
-
+
+
+

IFS

+

Connect with your inner selves.

+
From 018e3d4b991e128d2f1093e6bb6a2aedc1a4f12b Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sun, 24 Mar 2024 10:32:17 -0700 Subject: [PATCH 38/65] Some styling --- ifs.ai/app/chat/page.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index d433b40..1fdd960 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -73,9 +73,11 @@ export default function Page({ searchParams }: { searchParams: { id: number } }) {parts.map(({ name, prettyName, imageUrl, personality, unmetNeeds }) => (
{`An -

{prettyName}

-

{personality}

-

Unmet Needs: {unmetNeeds.join(", ")}

+
+

{prettyName}

+

{personality}

+

Unmet Needs: {unmetNeeds.join(", ")}

+
))}
From 9ee091a2e11223ea05652b4f171726db6a8b4b14 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 24 Mar 2024 10:35:47 -0700 Subject: [PATCH 39/65] added video creation from did to the chat/page.js --- ifs.ai/app/chat/page.tsx | 25 +++++++++++++++++++++++++ ifs.py/README.md | 8 +++++--- ifs.py/app.py | 26 +++++++++++++++++--------- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index d433b40..0519c13 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -61,6 +61,21 @@ export default function Page({ searchParams }: { searchParams: { id: number } }) sidebarRef.current.scrollTop = sidebarRef.current.scrollHeight; } }); + + const createVideo = async (imageUrl: string, text: string) => { + const response = await fetch('http://localhost:5000/create_talk', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + image_url: imageUrl, + text: text + }) + }); + const data = await response.json(); + return data; + } return (
@@ -118,6 +133,16 @@ export default function Page({ searchParams }: { searchParams: { id: number } }) setPrevPart(responder); setInputValue(""); console.log(data); + + const part = parts.find(part => part.name.toLowerCase() === responder.toLowerCase()); + if (part) { + console.log("using image") + console.log(part.imageUrl) + // TODO: CAN SOMEOONE LOOK INTO HOW TO DISPLAY IT ON PAGE? SHOULD WE USE THE DIDVideoStream? + console.log(createVideo(part.imageUrl, text)); + } else { + console.error('Part not found for responder:', responder); + } }) .catch((error) => { console.error("Error:", error); diff --git a/ifs.py/README.md b/ifs.py/README.md index 57516ad..8a86b5d 100644 --- a/ifs.py/README.md +++ b/ifs.py/README.md @@ -6,9 +6,11 @@ flask run --debug curl --location --request POST 'http://127.0.0.1:5000/create_talk' \ ---header 'Content-Type: application/x-www-form-urlencoded' \ ---data-urlencode 'image_url=https://images.generated.photos/96rFpeEfKms51LK-Re3qlY8kk_q0pNVB7vC9BYcjfyo/rs:fit:256:256/czM6Ly9pY29uczgu/Z3Bob3Rvcy1wcm9k/LnBob3Rvcy92M18w/MDczMjE2LmpwZw.jpg' \ ---data-urlencode 'text=This is an example string of text to be replaced!' +--header 'Content-Type: application/json' \ +--data-raw '{ + "image_url": "https://replicate.delivery/pbxt/UWXPfBHHc5X1EC3ZuS7YfBaWKzz6xkxmJ6RSbUNbiHa0IhjSA/image_0.png", + "text": "This is an example string of text to be replaced!" +}' ``` ### New requirements diff --git a/ifs.py/app.py b/ifs.py/app.py index 1898a2f..00ccf57 100644 --- a/ifs.py/app.py +++ b/ifs.py/app.py @@ -14,6 +14,7 @@ load_dotenv() groq = os.getenv("GROQ_ENV") +openai_client = OpenAI() instructor_client = instructor.patch( OpenAI( # This is the default and can be omitted @@ -34,7 +35,7 @@ D_ID_API_KEY = os.getenv("D_ID_API_KEY") if D_ID_API_KEY is None: raise Exception("D_ID_API_KEY not found in environment") -auth = "Bearer " + D_ID_API_KEY +auth = "Basic " + D_ID_API_KEY # To test this: @@ -107,7 +108,7 @@ def get_response(): }, ] - response = openapi_client.chat.completions.create( + response = openai_client.chat.completions.create( model="gpt-4", messages=messages, max_tokens=300, @@ -124,7 +125,7 @@ def get_response(): responder = "exile" system = exile_system - system += f" Reply with less than three sentences in first person as a {responder}" + system += f" Reply with less than 2 sentences in first person as a {responder}" messages = [ ChatMessage(role="system", content=system), ] @@ -172,10 +173,13 @@ def get_talk(talk_id): @app.route('/create_talk', methods=['POST']) def create_talk(): + url = "https://api.d-id.com/talks" # parse body as json: request_payload = request.get_json() + text = request_payload.get('text') + image_url = request_payload.get('image_url') print("rpayload:", request_payload) payload = { @@ -186,13 +190,13 @@ def create_talk(): "type": "microsoft", "voice_id": "en-US-JennyNeural" }, - "input": request_payload.get('text'), + "input": text, }, "config": { "fluent": "false", "pad_audio": "0.0" }, - "source_url": request_payload.get('image_url'), + "source_url": image_url, } headers = { "accept": "application/json", @@ -203,10 +207,14 @@ def create_talk(): response = requests.post(url, json=payload, headers=headers) print(response.text) - talk_id = json.loads(response.text)['id'] + response_json = json.loads(response.text) + if "id" in response_json: + talk_id = response_json['id'] - while talk_ready(talk_id) == False: - time.sleep(1) + while talk_ready(talk_id) == False: + time.sleep(1) - return get_talk(talk_id) + return get_talk(talk_id) + else: + return "Error creating talk. ID not found in response JSON" From c4213e96a15acbeb1f94a7a6f041479c0bc31750 Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sun, 24 Mar 2024 10:51:22 -0700 Subject: [PATCH 40/65] Go from start to chat --- ifs.ai/app/constants.ts | 2 + ifs.ai/app/layout.tsx | 4 +- ifs.ai/app/start/page.tsx | 105 ++++++++++++++++++++++++-------------- 3 files changed, 72 insertions(+), 39 deletions(-) diff --git a/ifs.ai/app/constants.ts b/ifs.ai/app/constants.ts index 389735b..a12e041 100644 --- a/ifs.ai/app/constants.ts +++ b/ifs.ai/app/constants.ts @@ -1,3 +1,5 @@ export const IMAGE_URLS_KEY = "IFS_IMAGE_URLS"; +export const RANT_KEY = "IFS_RANT"; export const getImageUrlsKeyForId = (id: number): string => `${IMAGE_URLS_KEY}_${id}`; +export const getRantKeyForId = (id: number): string => `${RANT_KEY}_${id}`; export type PartImageUrls = { manager: string; firefighter: string; exile: string }; diff --git a/ifs.ai/app/layout.tsx b/ifs.ai/app/layout.tsx index f413f1a..d0b3c2f 100644 --- a/ifs.ai/app/layout.tsx +++ b/ifs.ai/app/layout.tsx @@ -6,8 +6,8 @@ import { Toaster } from "@/components/ui/toaster"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "IFS", + description: "Talk to your inner selves", }; export default function RootLayout({ diff --git a/ifs.ai/app/start/page.tsx b/ifs.ai/app/start/page.tsx index d9493c2..7abdee3 100644 --- a/ifs.ai/app/start/page.tsx +++ b/ifs.ai/app/start/page.tsx @@ -2,11 +2,12 @@ import { Button } from "@/components/ui/button"; import Webcam from "react-webcam"; -import { MutableRefObject, useMemo, useRef, useState } from "react"; +import { MutableRefObject, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "@/components/ui/use-toast"; import { generateReactHelpers } from "@uploadthing/react"; import { OurFileRouter } from "@/app/api/uploadthing/core"; -import { getImageUrlsKeyForId } from "@/app/constants"; +import { getImageUrlsKeyForId, getRantKeyForId, PartImageUrls } from "@/app/constants"; +import SpeechToText from "@/components/ui/speech-to-text"; const { useUploadThing, uploadFiles } = generateReactHelpers(); @@ -26,6 +27,9 @@ function dataURLtoFile(dataurl: string, filename: string) { export default function Page() { const webcamRef: MutableRefObject = useRef(null); const [rantProcessed, setRantProcessed] = useState(false); + const [rant, setRant] = useState(""); + const [imagesCaptured, setImagesCaptured] = useState(false); + const [imageUrls, setImageUrls] = useState({}); const id = useMemo(() => { let i = 0; @@ -36,37 +40,45 @@ export default function Page() { return i; }, []); + useEffect(() => { + if (rantProcessed && imageUrls.firefighter) { + console.log("Rant process and images generated", imageUrls); + window.location.href = `/chat?id=${id}`; + } + }, [rantProcessed, imageUrls, id]); + const { startUpload, permittedFileInfo, isUploading } = useUploadThing("imageUploader", { onClientUploadComplete: (res) => { console.log("Res", res); let imageUrls = res[0].serverData.partImageUrls; console.log("Saving image urls", imageUrls); + setImageUrls({ ...imageUrls }); window.localStorage.setItem(getImageUrlsKeyForId(id), JSON.stringify(imageUrls)); - toast({ - title: "Uploaded successfully!", - description: ( - - ), - }); + // toast({ + // title: "Uploaded successfully!", + // description: ( + // + // ), + // }); }, onUploadError: () => { toast({ title: "Error occurred while uploading", variant: "destructive" }); }, onUploadBegin: () => { - toast({ title: "Upload has begun" }); + // toast({ title: "Upload has begun" }); }, }); @@ -77,17 +89,19 @@ export default function Page() {

IFS

Connect with your inner selves.

- + {!imagesCaptured && ( + + )} + {(true || (imagesCaptured && !rantProcessed)) && ( +
+
+

Now tell me about your problems...

+
+ setRant(transcript)} + onEnd={() => { + window.localStorage.setItem(getRantKeyForId(id), rant); + setRantProcessed(true); + }} + /> +
{rant}
+ {imageUrls.firefighter &&

Images generated

} +
+ )}
); From f4666dff5e3b8d65138127a8d318f889939bb1a0 Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sun, 24 Mar 2024 10:53:47 -0700 Subject: [PATCH 41/65] Remove hardcoded rant true --- ifs.ai/app/start/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs.ai/app/start/page.tsx b/ifs.ai/app/start/page.tsx index 7abdee3..451661f 100644 --- a/ifs.ai/app/start/page.tsx +++ b/ifs.ai/app/start/page.tsx @@ -122,7 +122,7 @@ export default function Page() { > {imagesCaptured ? "Looking inside you..." : "Start 🤳"} - {(true || (imagesCaptured && !rantProcessed)) && ( + {imagesCaptured && !rantProcessed && (

Now tell me about your problems...

From 23854762391c368dc8fb727287440971e0619f5c Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sun, 24 Mar 2024 10:56:32 -0700 Subject: [PATCH 42/65] Tidy even more --- ifs.ai/app/start/page.tsx | 85 +++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/ifs.ai/app/start/page.tsx b/ifs.ai/app/start/page.tsx index 451661f..fca1134 100644 --- a/ifs.ai/app/start/page.tsx +++ b/ifs.ai/app/start/page.tsx @@ -90,52 +90,57 @@ export default function Page() {

Connect with your inner selves.

{!imagesCaptured && ( - + <> + + + )} - - {imagesCaptured && !rantProcessed && ( + + {imagesCaptured && (

Now tell me about your problems...

- setRant(transcript)} - onEnd={() => { - window.localStorage.setItem(getRantKeyForId(id), rant); - setRantProcessed(true); - }} - /> + {!rantProcessed && ( + setRant(transcript)} + onEnd={() => { + window.localStorage.setItem(getRantKeyForId(id), rant); + setRantProcessed(true); + }} + /> + )}
{rant}
- {imageUrls.firefighter &&

Images generated

} +

{imageUrls.firefighter ? "Images generated" : "Generating images"}

)}
From 726ba7f815fe17d3136c7759210bc1e938e963f6 Mon Sep 17 00:00:00 2001 From: Anand <66644641+maraoai@users.noreply.github.com> Date: Sun, 24 Mar 2024 11:02:58 -0700 Subject: [PATCH 43/65] Update replicate_concurrency.py --- ifs.py/replicate_concurrency.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ifs.py/replicate_concurrency.py b/ifs.py/replicate_concurrency.py index bd67c0e..b7d4075 100644 --- a/ifs.py/replicate_concurrency.py +++ b/ifs.py/replicate_concurrency.py @@ -11,9 +11,9 @@ # Prompts for each of the calls prompts = [ - "A photo of man img upclose, facing camera, looking confident and controlled, professional outfit, upright posture, orderly surroundings, muted background colors, symbols of achievement, sense of discipline and responsibility", - "A photo of man img upclose, facing camera, fierce expression, intense eyes, bold outfit, fiery background colors, sense of urgency and strength", - "A photo of man img upclose, facing camera, young and vulnerable, sad eyes, child-like clothing, sitting alone, muted background colors, sense of isolation and longing for care and acceptance" + "A photo of person img upclose, facing camera, looking confident and controlled, professional outfit, upright posture, orderly surroundings, muted background colors, symbols of achievement, sense of discipline and responsibility", + "A photo of person img upclose, facing camera, fierce expression, intense eyes, bold outfit, fiery background colors, sense of urgency and strength", + "A photo of person img upclose, facing camera, young and vulnerable, sad eyes, child-like clothing, sitting alone, muted background colors, sense of isolation and longing for care and acceptance" ] def call_replicate(prompt, input_image_url): From ac1abc0820ac6a3a3afc5ce1cd55ce16e91d123e Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sun, 24 Mar 2024 11:31:02 -0700 Subject: [PATCH 44/65] Fix array output from replicate bug --- ifs.ai/app/api/uploadthing/core.ts | 31 +++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/ifs.ai/app/api/uploadthing/core.ts b/ifs.ai/app/api/uploadthing/core.ts index fed9cf4..0038cd5 100644 --- a/ifs.ai/app/api/uploadthing/core.ts +++ b/ifs.ai/app/api/uploadthing/core.ts @@ -25,19 +25,24 @@ async function makePartImages(inputUrl: string): Promise { await Promise.all( Object.entries(prompts).map(async ([part, prompt]) => [ part, - await replicate.run("tencentarc/photomaker:ddfc2b08d209f9fa8c1eca692712918bd449f695dabb4a958da31802a9570fe4", { - input: { - prompt: prompt, - num_steps: 40, - style_name: "Photographic (Default)", - input_image: inputUrl, - num_outputs: 1, - guidance_scale: 5, - negative_prompt: - "nsfw, lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry", - style_strength_ratio: 20, - }, - }), + ( + await replicate.run( + "tencentarc/photomaker:ddfc2b08d209f9fa8c1eca692712918bd449f695dabb4a958da31802a9570fe4", + { + input: { + prompt: prompt, + num_steps: 40, + style_name: "Photographic (Default)", + input_image: inputUrl, + num_outputs: 1, + guidance_scale: 5, + negative_prompt: + "nsfw, lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry", + style_strength_ratio: 20, + }, + }, + ) + )[0], ]), ), ); From 49a369291062271d097636727c44fefb0402c118 Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sun, 24 Mar 2024 11:31:19 -0700 Subject: [PATCH 45/65] Tidy --- ifs.ai/app/chat/page.tsx | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index e504779..25f3676 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -54,28 +54,28 @@ export default function Page({ searchParams }: { searchParams: { id: number } }) }, ]; - const sidebarRef = useRef(null); + const sidebarRef = useRef(null); useEffect(() => { if (sidebarRef.current) { sidebarRef.current.scrollTop = sidebarRef.current.scrollHeight; } }); - + const createVideo = async (imageUrl: string, text: string) => { - const response = await fetch('http://localhost:5000/create_talk', { - method: 'POST', + const response = await fetch("http://127.0.0.1:5000/create_talk", { + method: "POST", headers: { - 'Content-Type': 'application/json' + "Content-Type": "application/json", }, body: JSON.stringify({ image_url: imageUrl, - text: text - }) + text: text, + }), }); const data = await response.json(); return data; - } + }; return (
@@ -109,7 +109,7 @@ export default function Page({ searchParams }: { searchParams: { id: number } }) onClick={(e) => { e.preventDefault(); setIsSubmitting(true); - fetch("http://localhost:5000/get_response", { + fetch("http://127.0.0.1:5000/get_response", { method: "POST", headers: { "Content-Type": "application/json", @@ -135,26 +135,20 @@ export default function Page({ searchParams }: { searchParams: { id: number } }) setPrevPart(responder); setInputValue(""); console.log(data); - - const part = parts.find(part => part.name.toLowerCase() === responder.toLowerCase()); + + const part = parts.find((part) => part.name.toLowerCase() === responder.toLowerCase()); if (part) { - console.log("using image") - console.log(part.imageUrl) + console.log("using image"); + console.log(part.imageUrl); // TODO: CAN SOMEOONE LOOK INTO HOW TO DISPLAY IT ON PAGE? SHOULD WE USE THE DIDVideoStream? console.log(createVideo(part.imageUrl, text)); } else { - console.error('Part not found for responder:', responder); + console.error("Part not found for responder:", responder); } }) .catch((error) => { console.error("Error:", error); }); - - // TODO: Actually call the prompt(s) - console.log("Submitted", e); - setTimeout(() => { - setIsSubmitting(false); - }, 2000); }} > From 6baa46f3da095aa746625314a32b597e68297727 Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sun, 24 Mar 2024 11:38:03 -0700 Subject: [PATCH 46/65] Fix loading not disappearing --- ifs.ai/app/chat/page.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index 25f3676..88e9fec 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -148,7 +148,8 @@ export default function Page({ searchParams }: { searchParams: { id: number } }) }) .catch((error) => { console.error("Error:", error); - }); + }) + .finally(() => setIsSubmitting(false)); }} > From fd055c324f3b8f5e3c333d58000b5ab597bf0db5 Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sun, 24 Mar 2024 11:39:21 -0700 Subject: [PATCH 47/65] Styling --- ifs.ai/app/chat/page.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index 88e9fec..02de194 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -91,7 +91,9 @@ export default function Page({ searchParams }: { searchParams: { id: number } })

{prettyName}

{personality}

-

Unmet Needs: {unmetNeeds.join(", ")}

+

+ Unmet needs: {unmetNeeds.join(", ")} +

))} From eee22d225a4f4fb05a380653e40215ec9a1a3bca Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sun, 24 Mar 2024 11:41:13 -0700 Subject: [PATCH 48/65] Styling --- ifs.ai/app/chat/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index 02de194..2b5e883 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -90,7 +90,7 @@ export default function Page({ searchParams }: { searchParams: { id: number } }) {`An

{prettyName}

-

{personality}

+

{personality}

Unmet needs: {unmetNeeds.join(", ")}

From a95e7843cee5417987e2fa62d95193e9a4705737 Mon Sep 17 00:00:00 2001 From: robertchandler Date: Sun, 24 Mar 2024 12:05:34 -0700 Subject: [PATCH 49/65] =?UTF-8?q?Get=20videos=20to=20play=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ifs.ai/app/chat/page.tsx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ifs.ai/app/chat/page.tsx b/ifs.ai/app/chat/page.tsx index 2b5e883..899ae38 100644 --- a/ifs.ai/app/chat/page.tsx +++ b/ifs.ai/app/chat/page.tsx @@ -2,10 +2,9 @@ import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { MicIcon, SendIcon } from "lucide-react"; -import { useState, useRef, useEffect } from "react"; +import { SendIcon } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; import { getImageUrlsKeyForId, PartImageUrls } from "@/app/constants"; -import DIDVideoStream from "@/app/DIDWebRTCVideoStream"; import Loading from "@/components/ui/loading"; // Import a LoadingScreen component import SpeechToText from "@/components/ui/speech-to-text"; import { MessageBox } from "react-chat-elements"; @@ -17,6 +16,7 @@ export default function Page({ searchParams }: { searchParams: { id: number } }) const [inputValue, setInputValue] = useState(""); const [history, setHistory] = useState<{ role: string; text: string; responder: string }[]>([]); const [prevPart, setPrevPart] = useState("none"); + const [videoUrls, setVideoUrls] = useState<{ [key: string]: string }>({}); const imageUrls: PartImageUrls = JSON.parse( window.localStorage.getItem(getImageUrlsKeyForId(searchParams.id)) ?? "{}", @@ -73,10 +73,13 @@ export default function Page({ searchParams }: { searchParams: { id: number } }) text: text, }), }); - const data = await response.json(); - return data; + const videoUrl = await response.text(); + console.log("Got video URL", videoUrl); + return videoUrl; }; + console.log("Video URLs", videoUrls); + return (
{isSubmitting ? :
} @@ -87,7 +90,7 @@ export default function Page({ searchParams }: { searchParams: { id: number } })
{parts.map(({ name, prettyName, imageUrl, personality, unmetNeeds }) => (
- {`An +