Skip to content

Commit

Permalink
Merge pull request #303 from casistack/local-redis-support
Browse files Browse the repository at this point in the history
Local redis support
  • Loading branch information
miurla committed Aug 12, 2024
2 parents b65f0b8 + 8c328a3 commit f68855d
Show file tree
Hide file tree
Showing 9 changed files with 423 additions and 29 deletions.
6 changes: 5 additions & 1 deletion .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ OPENAI_API_KEY=[YOUR_OPENAI_API_KEY]
# Tavily API Key retrieved here: https://app.tavily.com/home
TAVILY_API_KEY=[YOUR_TAVILY_API_KEY]

# Redis Configuration
USE_LOCAL_REDIS=true
LOCAL_REDIS_URL=redis://localhost:6379 # or redis://redis:6379 if you're using docker compose

# Upstash Redis URL and Token retrieved here: https://console.upstash.com/redis
UPSTASH_REDIS_REST_URL=[YOUR_UPSTASH_REDIS_REST_URL]
UPSTASH_REDIS_REST_TOKEN=[YOUR_UPSTASH_REDIS_REST_TOKEN]
Expand Down Expand Up @@ -47,4 +51,4 @@ UPSTASH_REDIS_REST_TOKEN=[YOUR_UPSTASH_REDIS_REST_TOKEN]

# enable the video search tool
# Serper API Key retrieved here: https://serper.dev/api-key
# SERPER_API_KEY=[YOUR_SERPER_API_KEY]
# SERPER_API_KEY=[YOUR_SERPER_API_KEY]
16 changes: 9 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@

FROM oven/bun:1.1.3-alpine

RUN apk add --no-cache nodejs npm git

RUN git clone --depth=1 https://github.com/miurla/morphic /app && \
rm -rf /app/.git && \
cd /app && \
bun i && \
bun next telemetry disable

WORKDIR /app

CMD ["bun", "dev"]
COPY package.json bun.lockb ./
RUN bun install

COPY . .

RUN bun next telemetry disable

CMD ["bun", "dev", "-H", "0.0.0.0"]
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ TAVILY_API_KEY=
# Upstash Redis URL and Token retrieved here: https://console.upstash.com/redis
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
## Redis Configuration
This application supports both Upstash Redis and local Redis. To use local Redis:
1. Set `USE_LOCAL_REDIS=true` in your `.env.local` file.
2. Optionally, set `LOCAL_REDIS_URL` if your local Redis is not running on the default `localhost:6379` or `redis://redis:6379` if you're using docker compose.
To use Upstash Redis:
1. Set `USE_LOCAL_REDIS=false` or leave it unset in your `.env.local` file.
2. Set `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` with your Upstash credentials.
```

_Note: This project focuses on Generative UI and requires complex output from LLMs. Currently, it's assumed that the official OpenAI models will be used. Although it's possible to set up other models, if you use an OpenAI-compatible model, but we don't guarantee that it'll work._
Expand Down Expand Up @@ -157,3 +169,4 @@ This will allow you to use Morphic as your default search engine in the browser.
- LLaMA3.1 70B
- LLaMA3 8b
- LLaMA3 70b

8 changes: 7 additions & 1 deletion app/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,13 @@ export const AI = createAI<AIState, UIState>({
export const getUIStateFromAIState = (aiState: Chat) => {
const chatId = aiState.chatId
const isSharePage = aiState.isSharePage
return aiState.messages

// Ensure messages is an array of plain objects
const messages = Array.isArray(aiState.messages)
? aiState.messages.map(msg => ({...msg}))
: [];

return messages
.map((message, index) => {
const { role, content, id, type, name } = message

Expand Down
Binary file modified bun.lockb
Binary file not shown.
13 changes: 13 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,16 @@ services:
env_file: .env.local # Load environment variables
ports:
- "3000:3000" # Maps port 3000 on the host to port 3000 in the container.
depends_on:
- redis

redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes

volumes:
redis_data:
93 changes: 73 additions & 20 deletions lib/actions/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,90 @@
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { type Chat } from '@/lib/types'
import { Redis } from '@upstash/redis'
import { getRedisClient, RedisWrapper } from '@/lib/redis/config'

const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL || '',
token: process.env.UPSTASH_REDIS_REST_TOKEN || ''
})
async function getRedis(): Promise<RedisWrapper> {
return await getRedisClient()
}

export async function getChats(userId?: string | null) {
if (!userId) {
return []
}

try {
const pipeline = redis.pipeline()
const chats: string[] = await redis.zrange(`user:chat:${userId}`, 0, -1, {
const redis = await getRedis()
const chats = await redis.zrange(`user:chat:${userId}`, 0, -1, {
rev: true
})

for (const chat of chats) {
pipeline.hgetall(chat)
if (chats.length === 0) {
return []
}

const results = await pipeline.exec()

return results as Chat[]
const results = await Promise.all(
chats.map(async chatKey => {
const chat = await redis.hgetall(chatKey)
return chat
})
)

return results
.filter((result): result is Record<string, any> => {
if (result === null || Object.keys(result).length === 0) {
return false
}
return true
})
.map(chat => {
const plainChat = { ...chat }
if (typeof plainChat.messages === 'string') {
try {
plainChat.messages = JSON.parse(plainChat.messages)
} catch (error) {
plainChat.messages = []
}
}
if (plainChat.createdAt && !(plainChat.createdAt instanceof Date)) {
plainChat.createdAt = new Date(plainChat.createdAt)
}
return plainChat as Chat
})
} catch (error) {
return []
}
}

export async function getChat(id: string, userId: string = 'anonymous') {
const redis = await getRedis()
const chat = await redis.hgetall<Chat>(`chat:${id}`)

if (!chat) {
return null
}

// Parse the messages if they're stored as a string
if (typeof chat.messages === 'string') {
try {
chat.messages = JSON.parse(chat.messages)
} catch (error) {
chat.messages = []
}
}

// Ensure messages is always an array
if (!Array.isArray(chat.messages)) {
chat.messages = []
}

return chat
}

export async function clearChats(
userId: string = 'anonymous'
): Promise<{ error?: string }> {
const chats: string[] = await redis.zrange(`user:chat:${userId}`, 0, -1)
const redis = await getRedis()
const chats = await redis.zrange(`user:chat:${userId}`, 0, -1)
if (!chats.length) {
return { error: 'No chats to clear' }
}
Expand All @@ -64,16 +104,28 @@ export async function clearChats(
}

export async function saveChat(chat: Chat, userId: string = 'anonymous') {
const pipeline = redis.pipeline()
pipeline.hmset(`chat:${chat.id}`, chat)
pipeline.zadd(`user:chat:${chat.userId}`, {
score: Date.now(),
member: `chat:${chat.id}`
})
await pipeline.exec()
try {
const redis = await getRedis()
const pipeline = redis.pipeline()

const chatToSave = {
...chat,
messages: JSON.stringify(chat.messages)
}

pipeline.hmset(`chat:${chat.id}`, chatToSave)
pipeline.zadd(`user:chat:${userId}`, Date.now(), `chat:${chat.id}`)

const results = await pipeline.exec()

return results
} catch (error) {
throw error
}
}

export async function getSharedChat(id: string) {
const redis = await getRedis()
const chat = await redis.hgetall<Chat>(`chat:${id}`)

if (!chat || !chat.sharePath) {
Expand All @@ -84,6 +136,7 @@ export async function getSharedChat(id: string) {
}

export async function shareChat(id: string, userId: string = 'anonymous') {
const redis = await getRedis()
const chat = await redis.hgetall<Chat>(`chat:${id}`)

if (!chat || chat.userId !== userId) {
Expand Down
Loading

0 comments on commit f68855d

Please sign in to comment.