Skip to content

Commit

Permalink
Support storage data in browser
Browse files Browse the repository at this point in the history
  • Loading branch information
0xinhua committed Jun 18, 2024
1 parent ea442f1 commit c84791f
Show file tree
Hide file tree
Showing 20 changed files with 541 additions and 279 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ GOOGLE_CLIENT_ID="xxxxxxxx"
GOOGLE_CLIENT_SECRET="*******"

GROQ_API_KEY="*******"

# Data storage mode: Set to "local" to save chat data in your browser
# Set to "cloud" to sync data to Supabase.
NEXT_PUBLIC_STORAGE_MODE="local"
116 changes: 12 additions & 104 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,119 +57,27 @@ pnpm dev

Your app template should now be running on [localhost:3000](http://localhost:3000/).

## Creating database instance on Supabase
## Data Storage

This [docs](https://supabase.com/docs/guides/getting-started) will assist you in creating and configuring your PostgreSQL database instance on Supabase, enabling your application to interact with it.
You can configure how your chat data is stored by setting the `NEXT_PUBLIC_STORAGE_MODE` environment variable in your `.env` file:

Remember to update your environment variables (`SUPABASE_CONNECTION_STRING`, `NEXT_PUBLIC_SUPABASE_URL`, `SUPABASE_PUBLIC_ANON_KEY`,) in the `.env` file with the appropriate credentials provided during the supabase database setup.
- **local**: This mode saves chat data directly in your browser's local storage.
- **cloud**: This mode syncs chat data to Supabase, a cloud-based PostgreSQL database.

Table definition and functions use for this project:

```sql
-- 创建 chat_dataset schema
CREATE SCHEMA chat_dataset;

GRANT USAGE ON SCHEMA chat_dataset TO anon, authenticated, service_role;
GRANT ALL ON ALL TABLES IN SCHEMA chat_dataset TO anon, authenticated, service_role;
GRANT ALL ON ALL ROUTINES IN SCHEMA chat_dataset TO anon, authenticated, service_role;
GRANT ALL ON ALL SEQUENCES IN SCHEMA chat_dataset TO anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA chat_dataset GRANT ALL ON TABLES TO anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA chat_dataset GRANT ALL ON ROUTINES TO anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA chat_dataset GRANT ALL ON SEQUENCES TO anon, authenticated, service_role;
Example:

```env
# Data storage mode: "local" for browser storage, "cloud" for Supabase storage
NEXT_PUBLIC_STORAGE_MODE="local"
```

```sql
CREATE TABLE IF NOT EXISTS
chat_dataset.chats (
id bigint generated always as identity,
chat_id text not null,
user_id uuid not null,
title text null,
path text null,
created_at bigint null default (
extract(
epoch
from
current_timestamp
) * (1000)::numeric
),
messages jsonb not null,
share_path text null,
updated_at bigint null default (
extract(
epoch
from
current_timestamp
) * (1000)::numeric
),
constraint chats_pkey primary key (id),
constraint chats_chat_id_key unique (chat_id),
constraint chats_user_id_fkey foreign key (user_id) references next_auth.users (id)
) tablespace pg_default;
```

```sql
CREATE OR REPLACE FUNCTION get_chat_data(p_user_id uuid, p_chat_id text)
RETURNS TABLE (
id bigint,
chat_id text,
user_id uuid,
title text,
path text,
created_at bigint,
messages jsonb,
share_path text,
updated_at bigint
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
c.id,
c.chat_id,
c.user_id,
c.title,
c.path,
c.created_at,
c.messages,
c.share_path,
c.updated_at
FROM chat_dataset.chats c
WHERE c.user_id = p_user_id AND c.chat_id = p_chat_id;
END;
$$;
To use Supabase for cloud storage, change the mode to `"cloud"`:

```env
NEXT_PUBLIC_STORAGE_MODE="cloud"
```

```sql
CREATE OR REPLACE FUNCTION upsert_chat(
p_chat_id text,
p_title text,
p_user_id uuid,
p_created_at bigint,
p_path text,
p_messages jsonb,
p_share_path text
)
RETURNS void
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO chat_dataset.chats (chat_id, title, user_id, created_at, path, messages, share_path)
VALUES (p_chat_id, p_title, p_user_id, p_created_at, p_path, p_messages, p_share_path)
ON CONFLICT (chat_id) DO UPDATE
SET
title = EXCLUDED.title,
user_id = EXCLUDED.user_id,
created_at = EXCLUDED.created_at,
path = EXCLUDED.path,
messages = EXCLUDED.messages,
share_path = EXCLUDED.share_path;
END;
$$;
```
If you choose the cloud mode, you need to configure the corresponding database table structure on Supabase. For detailed instructions, refer to this [documentation](./supabase/README.md).

## Thanks

Expand Down
22 changes: 16 additions & 6 deletions app/(chat)/chat/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,22 @@ export default function ChatPage({ params }: ChatPageProps) {

return <div>
<Suspense>
{loading ? <div className="text-center text-gray-400 flex justify-center gap-1 items-center">
Loading
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="animate-spin text-gray-400 size-4">
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
</svg>
</div> : <Chat id={params.id} initialMessages={chat?.messages} title={chat?.title} loading={chatLoading} />}
{
loading
? <div className="text-center text-gray-400 flex justify-center gap-1 items-center">
Loading
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="animate-spin text-gray-400 size-4">
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
</svg>
</div>
: <Chat
id={params.id}
initialMessages={chat?.messages || []}
title={chat?.title}
loading={chatLoading}
userId={session?.user.id}
/>
}
</Suspense>
</div>
}
10 changes: 3 additions & 7 deletions app/(chat)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,18 @@
import { nanoid } from '@/lib/utils'
import { Chat } from '@/components/chat'
import useUserSettingStore from '@/store/useSettingStore'
import { useEffect } from 'react'

export default function IndexPage() {

const {
systemPrompt,
fetchSystemPrompt
} = useUserSettingStore()

useEffect(() => {
fetchSystemPrompt()
}, [])

const id = nanoid()

return <Chat id={id} initialMessages={[{
return <Chat
id={id}
initialMessages={[{
id:'system-prompt',
role: 'system',
content: systemPrompt || "You are a helpful assistant.",
Expand Down
23 changes: 0 additions & 23 deletions app/actions.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
'use server'

import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

import { auth } from '@/auth'
import { type Chat } from '@/lib/types'
import { pgPool } from '@/lib/pg'

export async function getChat(chatId: string, userId: string) {
Expand All @@ -28,25 +24,6 @@ export async function getChat(chatId: string, userId: string) {
return rows[0]
}

export async function clearChats() {
const session = await auth()

if (!session?.user?.id) {
return {
error: 'Unauthorized'
}
}

const query = `
DELETE FROM chat_dataset.chats
WHERE user_id = $1;
`
await pgPool.query(query, [session?.user?.id])

revalidatePath('/')
return redirect('/')
}

export async function getSharedChat(id: string) {

const queryStr = `
Expand Down
7 changes: 5 additions & 2 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { GoogleGenerativeAIStream, Message } from 'ai'
import { auth } from '@/auth'
import { nanoid } from '@/lib/utils'
import { supabase } from '@/lib/supabase'
import { isLocalMode } from '@/lib/const'

// export const runtime = 'edge'

Expand Down Expand Up @@ -89,7 +90,7 @@ export async function POST(req: Request) {
})
}

console.log('model', model)
console.log('isLocalMode model id', isLocalMode, model, id,)

// use groqOpenAI llama provider
if (model.startsWith('llama3')) {
Expand Down Expand Up @@ -151,7 +152,9 @@ export async function POST(req: Request) {

const stream = OpenAIStream(res, {
async onCompletion(completion) {
handleCompletion(completion, messages, id, userId)
if (!isLocalMode) {
handleCompletion(completion, messages, id, userId)
}
}
})

Expand Down
35 changes: 34 additions & 1 deletion app/api/chats/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { auth } from '@/auth'
import { type Chat } from '@/lib/types'
import { NextResponse } from 'next/server'
import { pgPool } from '@/lib/pg'
import { supabase } from '@/lib/supabase'

export async function GET(req: Request) {
const userId = (await auth())?.user.id
Expand Down Expand Up @@ -42,6 +42,39 @@ export async function GET(req: Request) {
code: 0
})

} catch (error) {
console.log('error', error)
return NextResponse.json({
data: [],
code: 0
})
}
}

export async function DELETE(req: Request) {

const userId = (await auth())?.user.id

console.log('userId -> ', userId)

if (!userId) {
return new Response('Unauthorized', {
status: 401
})
}

try {

const { data, error } = await supabase.rpc('delete_user_chats', { p_user_id: userId });

console.log('delete rows data', error)

return NextResponse.json({
data: [],
code: 0,
message: 'success',
})

} catch (error) {
console.log('error', error)
return NextResponse.json({
Expand Down
11 changes: 1 addition & 10 deletions components/chat-history.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,7 @@ interface ChatHistoryProps {
export function ChatHistory({ userId, session }: ChatHistoryProps) {
const { isSidebarOpen, isLoading, toggleSidebar } = useSidebar()

const { chats, fetchHistory } = useChatStore(state => ({
chats: state.chats,
fetchHistory: state.fetchHistory
}))

useEffect(() => {
if (chats.length === 0) {
fetchHistory()
}
}, [fetchHistory, chats.length])
const { chats } = useChatStore()

return (
<div className="flex flex-col h-full">
Expand Down
Loading

0 comments on commit c84791f

Please sign in to comment.