Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Terra #423

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open

Terra #423

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
# You must first activate a Billing Account here: https://platform.openai.com/account/billing/overview
# Then get your OpenAI API Key here: https://platform.openai.com/account/api-keys
OPENAI_API_KEY=XXXXXXXX

# Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32`
AUTH_SECRET=XXXXXXXX
# Get your Tune Studio API Key here: https://tune.studio/account/api-keys
TUNE_STUDIO_API_KEY=XXXXXXXX

# Instructions to create kv database here: https://vercel.com/docs/storage/vercel-kv/quickstart and
KV_URL=XXXXXXXX
KV_REST_API_URL=XXXXXXXX
KV_REST_API_TOKEN=XXXXXXXX
KV_REST_API_READ_ONLY_TOKEN=XXXXXXXX
# Clerk
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=XXXXXXXX
CLERK_SECRET=XXXXXXXX
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ yarn-error.log*

# local env files
.env.local
.env
.env.development.local
.env.test.local
.env.production.local
Expand Down
65 changes: 0 additions & 65 deletions app/(chat)/chat/[id]/page.tsx

This file was deleted.

166 changes: 1 addition & 165 deletions app/actions.ts
Original file line number Diff line number Diff line change
@@ -1,171 +1,7 @@
'use server'

import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { kv } from '@vercel/kv'

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

export async function getChats(userId?: string | null) {
const session = await auth()

if (!userId) {
return []
}

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

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

for (const chat of chats) {
pipeline.hgetall(chat)
}

const results = await pipeline.exec()

return results as Chat[]
} catch (error) {
return []
}
}

export async function getChat(id: string, userId: string) {
const session = await auth()

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

const chat = await kv.hgetall<Chat>(`chat:${id}`)

if (!chat || (userId && chat.userId !== userId)) {
return null
}

return chat
}

export async function removeChat({ id, path }: { id: string; path: string }) {
const session = await auth()

if (!session) {
return {
error: 'Unauthorized'
}
}

// Convert uid to string for consistent comparison with session.user.id
const uid = String(await kv.hget(`chat:${id}`, 'userId'))

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

await kv.del(`chat:${id}`)
await kv.zrem(`user:chat:${session.user.id}`, `chat:${id}`)

revalidatePath('/')
return revalidatePath(path)
}

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

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

const chats: string[] = await kv.zrange(`user:chat:${session.user.id}`, 0, -1)
if (!chats.length) {
return redirect('/')
}
const pipeline = kv.pipeline()

for (const chat of chats) {
pipeline.del(chat)
pipeline.zrem(`user:chat:${session.user.id}`, chat)
}

await pipeline.exec()

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

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

if (!chat || !chat.sharePath) {
return null
}

return chat
}

export async function shareChat(id: string) {
const session = await auth()

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

const chat = await kv.hgetall<Chat>(`chat:${id}`)

if (!chat || chat.userId !== session.user.id) {
return {
error: 'Something went wrong'
}
}

const payload = {
...chat,
sharePath: `/share/${chat.id}`
}

await kv.hmset(`chat:${chat.id}`, payload)

return payload
}

export async function saveChat(chat: Chat) {
const session = await auth()

if (session && session.user) {
const pipeline = kv.pipeline()
pipeline.hmset(`chat:${chat.id}`, chat)
pipeline.zadd(`user:chat:${chat.userId}`, {
score: Date.now(),
member: `chat:${chat.id}`
})
await pipeline.exec()
} else {
return
}
}

export async function refreshHistory(path: string) {
redirect(path)
}

export async function getMissingKeys() {
const keysRequired = ['OPENAI_API_KEY']
const keysRequired = ['TUNE_STUDIO_API_KEY']
return keysRequired
.map(key => (process.env[key] ? '' : key))
.filter(key => key !== '')
Expand Down
47 changes: 47 additions & 0 deletions app/api/terra-widget/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { NextRequest, NextResponse } from "next/server";

const api_key = process.env.TERRA_API_KEY;
const reference_id = process.env.TERRA_DEV_ID;

export async function POST(request: NextRequest) {
try {
console.log('API Key:', api_key);
console.log('Reference ID:', reference_id);

if (!api_key || !reference_id) {
console.error('Missing API key or reference ID');
return NextResponse.json({ error: 'Missing API key or reference ID' }, { status: 400 });
}

const response = await fetch(
'https://api.tryterra.co/v2/auth/generateWidgetSession',
{
method: 'POST',
headers: {
Accept: 'application/json',
'dev-id': 'testingTerra',
'content-type': 'application/json',
'x-api-key': api_key,
},
body: JSON.stringify({
reference_id: reference_id,
providers:
'GARMIN,WITHINGS,FITBIT,GOOGLE,OURA,WAHOO,PELOTON,ZWIFT,TRAININGPEAKS,FREESTYLELIBRE,DEXCOM,COROS,HUAWEI,OMRON,RENPHO,POLAR,SUUNTO,EIGHT,APPLE,CONCEPT2,WHOOP,IFIT,TEMPO,CRONOMETER,FATSECRET,NUTRACHECK,UNDERARMOUR',
language: 'en',
auth_success_redirect_url: 'terraficapp://request',
auth_failure_redirect_url: 'terraficapp://login',
}),
},
);

console.log('Terra API response status:', response.status);

const data = await response.json();
console.log('Terra API response data:', data);

return NextResponse.json(data);
} catch (error) {
console.error('Error in Terra API route:', error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
21 changes: 21 additions & 0 deletions app/api/terra/generateWidgetSession/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NextRequest, NextResponse } from "next/server";
import Terra from "terra-api";

const terra = new Terra(process.env.TERRA_DEV_ID ?? "", process.env.TERRA_API_KEY ?? "", process.env.TERRA_WEBHOOK_SECRET ?? "");

export async function GET(request: NextRequest) {
try {
const resp = await terra.generateWidgetSession({
referenceID: "HelloMIT",
language: "en",
authSuccessRedirectUrl: "http://localhost:3000",
authFailureRedirectUrl: "http://localhost:3000"
});
return NextResponse.json({ url: resp.url }, { status: 200 });
} catch (error) {
console.error('Error generating widget session:', error);
return NextResponse.json({ error: 'Failed to generate widget session' }, { status: 500 });
}
}


3 changes: 0 additions & 3 deletions app/(chat)/layout.tsx → app/chat/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { SidebarDesktop } from '@/components/sidebar-desktop'

interface ChatLayoutProps {
children: React.ReactNode
}

export default async function ChatLayout({ children }: ChatLayoutProps) {
return (
<div className="relative flex h-[calc(100vh_-_theme(spacing.16))] overflow-hidden">
<SidebarDesktop />
{children}
</div>
)
Expand Down
5 changes: 1 addition & 4 deletions app/(chat)/page.tsx → app/chat/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { nanoid } from '@/lib/utils'
import { Chat } from '@/components/chat'
import { AI } from '@/lib/chat/actions'
import { auth } from '@/auth'
import { Session } from '@/lib/types'
import { getMissingKeys } from '@/app/actions'

export const metadata = {
Expand All @@ -11,12 +9,11 @@ export const metadata = {

export default async function IndexPage() {
const id = nanoid()
const session = (await auth()) as Session
const missingKeys = await getMissingKeys()

return (
<AI initialAIState={{ chatId: id, messages: [] }}>
<Chat id={id} session={session} missingKeys={missingKeys} />
<Chat id={id} missingKeys={missingKeys} />
</AI>
)
}
21 changes: 21 additions & 0 deletions app/doctor/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { redirect } from 'next/navigation'
import { auth } from '@clerk/nextjs/server'

export default async function DoctorLayout({
children
}: {
children: React.ReactNode
}) {
const session = await auth()

const slug = session.orgSlug

if (!slug || slug !== 'doctor') {
redirect('/')
}
return (
<div className="relative flex h-[calc(100vh-_theme(spacing.16))] overflow-hidden">
{children}
</div>
)
}
Loading