Skip to content

Commit 0562be8

Browse files
committed
GitHub API integration and extend DFDA and Tavily services
### Summary: 1. **Add GitHub integration**: Implement `getRepoContents` with `Octokit` for fetching repository contents. 2. **Enhance DFDA services**: Add multiple search functions and improve user authentication and variable fetching logic. 3. **Integrate Tavily client**: Include search functionalities for topics and images using Tavily's API. 4. **Schema and workflow management**: Introduce Zod schemas for various API endpoints and a workflow for inquiry and follow-up handling. Took 55 seconds
1 parent 141e040 commit 0562be8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+5705
-42
lines changed

lib/actions/chat.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
'use server'
2+
3+
import { revalidatePath } from 'next/cache'
4+
import { redirect } from 'next/navigation'
5+
import { type Chat } from '@/lib/types'
6+
import { getRedisClient, RedisWrapper } from '@/lib/redis/config'
7+
8+
async function getRedis(): Promise<RedisWrapper> {
9+
return await getRedisClient()
10+
}
11+
12+
export async function getChats(userId?: string | null) {
13+
if (!userId) {
14+
return []
15+
}
16+
17+
try {
18+
const redis = await getRedis()
19+
const chats = await redis.zrange(`user:chat:${userId}`, 0, -1, {
20+
rev: true
21+
})
22+
23+
if (chats.length === 0) {
24+
return []
25+
}
26+
27+
const results = await Promise.all(
28+
chats.map(async chatKey => {
29+
const chat = await redis.hgetall(chatKey)
30+
return chat
31+
})
32+
)
33+
34+
return results
35+
.filter((result): result is Record<string, any> => {
36+
if (result === null || Object.keys(result).length === 0) {
37+
return false
38+
}
39+
return true
40+
})
41+
.map(chat => {
42+
const plainChat = { ...chat }
43+
if (typeof plainChat.messages === 'string') {
44+
try {
45+
plainChat.messages = JSON.parse(plainChat.messages)
46+
} catch (error) {
47+
plainChat.messages = []
48+
}
49+
}
50+
if (plainChat.createdAt && !(plainChat.createdAt instanceof Date)) {
51+
plainChat.createdAt = new Date(plainChat.createdAt)
52+
}
53+
return plainChat as Chat
54+
})
55+
} catch (error) {
56+
return []
57+
}
58+
}
59+
60+
export async function getChat(id: string, userId: string = 'anonymous') {
61+
const redis = await getRedis()
62+
const chat = await redis.hgetall<Chat>(`chat:${id}`)
63+
64+
if (!chat) {
65+
return null
66+
}
67+
68+
// Parse the messages if they're stored as a string
69+
if (typeof chat.messages === 'string') {
70+
try {
71+
chat.messages = JSON.parse(chat.messages)
72+
} catch (error) {
73+
chat.messages = []
74+
}
75+
}
76+
77+
// Ensure messages is always an array
78+
if (!Array.isArray(chat.messages)) {
79+
chat.messages = []
80+
}
81+
82+
return chat
83+
}
84+
85+
export async function clearChats(
86+
userId: string = 'anonymous'
87+
): Promise<{ error?: string }> {
88+
const redis = await getRedis()
89+
const chats = await redis.zrange(`user:chat:${userId}`, 0, -1)
90+
if (!chats.length) {
91+
return { error: 'No chats to clear' }
92+
}
93+
const pipeline = redis.pipeline()
94+
95+
for (const chat of chats) {
96+
pipeline.del(chat)
97+
pipeline.zrem(`user:chat:${userId}`, chat)
98+
}
99+
100+
await pipeline.exec()
101+
102+
revalidatePath('/')
103+
redirect('/')
104+
}
105+
106+
export async function saveChat(chat: Chat, userId: string = 'anonymous') {
107+
try {
108+
const redis = await getRedis()
109+
const pipeline = redis.pipeline()
110+
111+
const chatToSave = {
112+
...chat,
113+
messages: JSON.stringify(chat.messages)
114+
}
115+
116+
pipeline.hmset(`chat:${chat.id}`, chatToSave)
117+
pipeline.zadd(`user:chat:${userId}`, Date.now(), `chat:${chat.id}`)
118+
119+
const results = await pipeline.exec()
120+
121+
return results
122+
} catch (error) {
123+
throw error
124+
}
125+
}
126+
127+
export async function getSharedChat(id: string) {
128+
const redis = await getRedis()
129+
const chat = await redis.hgetall<Chat>(`chat:${id}`)
130+
131+
if (!chat || !chat.sharePath) {
132+
return null
133+
}
134+
135+
return chat
136+
}
137+
138+
export async function shareChat(id: string, userId: string = 'anonymous') {
139+
const redis = await getRedis()
140+
const chat = await redis.hgetall<Chat>(`chat:${id}`)
141+
142+
if (!chat || chat.userId !== userId) {
143+
return null
144+
}
145+
146+
const payload = {
147+
...chat,
148+
sharePath: `/search/share/${id}`
149+
}
150+
151+
await redis.hmset(`chat:${id}`, payload)
152+
153+
return payload
154+
}

lib/actions/workflow.tsx

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
'use server'
2+
3+
import React from 'react'
4+
import { Spinner } from '@/components/ui/spinner'
5+
import { Section } from '@/app/search/components/section'
6+
import { FollowupPanel } from '@/app/search/components/followup-panel'
7+
import {
8+
querySuggestor,
9+
inquire,
10+
taskManager,
11+
researcherWithOllama,
12+
researcher
13+
} from '@/lib/agents'
14+
import { createStreamableValue, createStreamableUI } from 'ai/rsc'
15+
import { CoreMessage, generateId } from 'ai'
16+
17+
export async function workflow(
18+
uiState: {
19+
uiStream: ReturnType<typeof createStreamableUI>
20+
isCollapsed: ReturnType<typeof createStreamableValue>
21+
isGenerating: ReturnType<typeof createStreamableValue>
22+
},
23+
aiState: any,
24+
messages: CoreMessage[],
25+
skip: boolean
26+
) {
27+
const { uiStream, isCollapsed, isGenerating } = uiState
28+
const id = generateId()
29+
30+
// Display spinner
31+
uiStream.append(<Spinner />)
32+
33+
let action = { object: { next: 'proceed' } }
34+
// If the user does not skip the task, run the task manager
35+
if (!skip) action = (await taskManager(messages)) ?? action
36+
37+
if (action.object.next === 'inquire') {
38+
// Generate inquiry
39+
const inquiry = await inquire(uiStream, messages)
40+
uiStream.done()
41+
aiState.done({
42+
...aiState.get(),
43+
messages: [
44+
...aiState.get().messages,
45+
{
46+
id: generateId(),
47+
role: 'assistant',
48+
content: `inquiry: ${inquiry?.question}`,
49+
type: 'inquiry'
50+
}
51+
]
52+
})
53+
54+
isCollapsed.done(false)
55+
isGenerating.done(false)
56+
return
57+
}
58+
59+
// Set the collapsed state to true
60+
isCollapsed.done(true)
61+
62+
const useOllama = process.env.OLLAMA_MODEL && process.env.OLLAMA_BASE_URL
63+
// Select the appropriate researcher function based on the environment variables
64+
const { text, toolResults } = useOllama
65+
? await researcherWithOllama(uiStream, messages)
66+
: await researcher(uiStream, messages)
67+
68+
aiState.update({
69+
...aiState.get(),
70+
messages: [
71+
...aiState.get().messages,
72+
...toolResults.map((toolResult: any) => ({
73+
id,
74+
role: 'tool',
75+
content: JSON.stringify(toolResult.result),
76+
name: toolResult.toolName,
77+
type: 'tool'
78+
})),
79+
{
80+
id,
81+
role: 'assistant',
82+
content: text,
83+
type: 'answer'
84+
}
85+
]
86+
})
87+
88+
const messagesWithAnswer: CoreMessage[] = [
89+
...messages,
90+
{
91+
role: 'assistant',
92+
content: text
93+
}
94+
]
95+
96+
// Generate related queries
97+
const relatedQueries = await querySuggestor(uiStream, messagesWithAnswer)
98+
// Add follow-up panel
99+
uiStream.append(
100+
<Section title="Follow-up">
101+
<FollowupPanel />
102+
</Section>
103+
)
104+
105+
uiStream.done()
106+
isGenerating.done(false)
107+
108+
aiState.done({
109+
...aiState.get(),
110+
messages: [
111+
...aiState.get().messages,
112+
{
113+
id,
114+
role: 'assistant',
115+
content: JSON.stringify(relatedQueries),
116+
type: 'related'
117+
},
118+
{
119+
id,
120+
role: 'assistant',
121+
content: 'followup',
122+
type: 'followup'
123+
}
124+
]
125+
})
126+
}

0 commit comments

Comments
 (0)