Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apps/supercode-cli/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "supercode-cli",
"version": "0.1.39",
"version": "0.1.40",
"description": "AI-powered coding agent CLI",
"main": "dist/main.js",
"bin": {
Expand Down
35 changes: 35 additions & 0 deletions apps/supercode-cli/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { loadEnvOnce } from "./lib/load-env"
import { recordUsage } from "./lib/track-usage"
import { computeCost } from "./lib/pricing"
import { registerAnalyticsRoutes } from "./routes/analytics"
import { transcribeAudio } from "./voice/speech"
import { tmpdir } from "os"
import { join } from "path"
import { writeFileSync, unlinkSync } from "fs"
import { randomUUID } from "crypto"

loadEnvOnce()

Expand Down Expand Up @@ -829,6 +834,36 @@ app.post("/api/tools/web-search", async (req, res) => {
}
})

app.post("/api/voice/transcribe", async (req, res) => {
try {
const user = await getUserFromBearer(req)
if (!user) {
res.status(401).json({ error: "Unauthorized" })
return
}

const { base64, provider } = req.body
if (!base64) {
res.status(400).json({ error: "base64 audio data is required" })
return
}

if (provider) process.env.STT_PROVIDER = provider

const tmpFile = join(tmpdir(), `voice-transcribe-${randomUUID()}.wav`)
writeFileSync(tmpFile, Buffer.from(base64, "base64"))

try {
const text = await transcribeAudio(tmpFile)
res.json({ text })
} finally {
try { unlinkSync(tmpFile) } catch {}
}
} catch (error) {
res.status(500).json({ error: String(error) })
}
})

app.listen(port, () => {
console.log(`Server is running on port ${port}`)
})
48 changes: 40 additions & 8 deletions apps/supercode-cli/server/src/voice/speech.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { tmpdir } from "os"
import { join } from "path"
import { unlinkSync, readFileSync } from "fs"
import { randomUUID } from "crypto"
import { getStoredToken } from "src/lib/token"

function getFfmpegPath(): string {
return process.env.FFMPEG_PATH || "/opt/homebrew/bin/ffmpeg"
Expand Down Expand Up @@ -52,10 +53,11 @@ export function canVoiceCapture(): {

const provider = getSttProvider()
if (provider === "groq") {
/* groq provider */
if (!process.env.GROQ_API_KEY) return { ok: false, reason: "GROQ_API_KEY not set" }
if (!process.env.GROQ_API_KEY && !process.env.SUPERCODE_SERVER_URL)
return { ok: false, reason: "GROQ_API_KEY not set and no server proxy configured" }
} else {
if (!process.env.ELEVENLABS_API_KEY) return { ok: false, reason: "ELEVENLABS_API_KEY not set" }
if (!process.env.ELEVENLABS_API_KEY && !process.env.SUPERCODE_SERVER_URL)
return { ok: false, reason: "ELEVENLABS_API_KEY not set and no server proxy configured" }
}

return { ok: true }
Expand Down Expand Up @@ -126,7 +128,7 @@ function captureAudio(
})
}

async function transcribeElevenLabs(filePath: string): Promise<string> {
export async function transcribeElevenLabs(filePath: string): Promise<string> {
const apiKey = process.env.ELEVENLABS_API_KEY
if (!apiKey) throw new Error("ELEVENLABS_API_KEY not configured")

Expand Down Expand Up @@ -163,7 +165,7 @@ async function transcribeElevenLabs(filePath: string): Promise<string> {
}

/* groq provider */
async function transcribeGroq(filePath: string): Promise<string> {
export async function transcribeGroq(filePath: string): Promise<string> {
const apiKey = process.env.GROQ_API_KEY
if (!apiKey) throw new Error("GROQ_API_KEY not configured")

Expand Down Expand Up @@ -195,15 +197,45 @@ async function transcribeGroq(filePath: string): Promise<string> {
return data.text ?? ""
}

async function transcribeViaServer(filePath: string): Promise<string> {
const serverUrl = process.env.SUPERCODE_SERVER_URL || "https://supercode-8w7e.onrender.com"
const token = await getStoredToken()
if (!token?.access_token) {
throw new Error("Not authenticated. Please login first.")
}

const audioData = readFileSync(filePath)
const base64 = audioData.toString("base64")

const res = await fetch(`${serverUrl}/api/voice/transcribe`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token.access_token}`,
},
body: JSON.stringify({ base64, provider: getSttProvider() }),
signal: AbortSignal.timeout(30_000),
})

if (!res.ok) {
const text = await res.text().catch(() => "")
throw new Error(`Server transcription failed (${res.status}): ${text}`)
}

const { text } = (await res.json()) as { text?: string }
return text ?? ""
}

export async function transcribeAudio(filePath: string): Promise<string> {
const provider = getSttProvider()

if (provider === "groq") {
/* groq provider */
return transcribeGroq(filePath)
if (process.env.GROQ_API_KEY) return transcribeGroq(filePath)
return transcribeViaServer(filePath)
}

return transcribeElevenLabs(filePath)
if (process.env.ELEVENLABS_API_KEY) return transcribeElevenLabs(filePath)
return transcribeViaServer(filePath)
}

const SOUND_DESCRIPTION_RE = /\([^)]*?(?:noise|clicking|static|background|sound|audio|speaking|unintelligible|laughs?|coughs?|clears?\s+(?:throat|voice)|throat|pause|music|beep|tone|silence|indistinct|foreign|applause|sniffling|sighs?|breathing|rustling|mumbling|chatter|echo)[^)]*?\)/gi
Expand Down
171 changes: 171 additions & 0 deletions apps/web/app/(pages)/partnerships/[company]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import type { Metadata } from "next"
import Link from "next/link"
import { notFound } from "next/navigation"
import { ArrowLeft, ArrowUpRight, Quote } from "lucide-react"
import Navbar from "@/components/homepage/navbar"
import Footer from "@/components/homepage/footer"
import { partners } from "@/data/partnerships"

interface Props {
params: Promise<{ company: string }>
}

export async function generateStaticParams() {
return partners.map((p) => ({ company: p.slug }))
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { company } = await params
const partner = partners.find((p) => p.slug === company)
if (!partner) return {}

return {
title: `Supercode × ${partner.name} — Case Study`,
description: partner.description,
metadataBase: new URL("https://supercli.vercel.app"),
openGraph: {
title: `Supercode × ${partner.name} — Case Study`,
description: partner.description,
url: `https://supercli.vercel.app/partnerships/${partner.slug}`,
siteName: "Supercode",
type: "article",
},
twitter: {
card: "summary_large_image",
title: `Supercode × ${partner.name} — Case Study`,
description: partner.description,
},
}
}

export default async function PartnershipDetailPage({ params }: Props) {
const { company } = await params
const partner = partners.find((p) => p.slug === company)
if (!partner) notFound()

return (
<main className="min-h-screen bg-background dark relative">
<div className="fixed top-0 left-0 w-px h-full bg-border z-50" />
<div className="fixed top-0 right-0 w-px h-full bg-border z-50" />

<Navbar />

<article className="pt-[120px] pb-24 px-6 max-w-[720px] mx-auto">
{/* Back link */}
<Link
href="/partnerships"
className="inline-flex items-center gap-2 text-[14px] text-muted-foreground hover:text-foreground transition-colors mb-12 group"
>
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
<span>All partnerships</span>
</Link>

{/* Hero */}
<div className="mb-16">
<div className="w-14 h-14 rounded-xl bg-primary/10 flex items-center justify-center text-primary font-bold font-mono text-[22px] mb-6">
{partner.logoSrc ? (
<img src={partner.logoSrc} alt={partner.name} className="w-8 h-8 brightness-0 invert" />
) : (
partner.logo
)}
</div>

<h1 className="text-[36px] md:text-[48px] font-semibold tracking-tight mb-3">
{partner.name}
</h1>
<p className="text-[18px] text-muted-foreground leading-relaxed">
{partner.tagline}
</p>
</div>

{/* Stat highlight */}
<div className="border border-border rounded-lg p-8 mb-16 bg-card/50">
<div className="text-[48px] md:text-[64px] font-bold text-primary font-mono tracking-tight leading-none mb-2">
{partner.stat.value}
</div>
<div className="text-[16px] text-muted-foreground">
{partner.stat.label}
</div>
</div>

{/* Quote */}
{/* <div className="relative border-l-2 border-primary pl-6 mb-16">
<Quote className="absolute -top-3 -left-3 w-8 h-8 text-primary/20" />
<blockquote className="text-[18px] md:text-[20px] leading-relaxed text-foreground/90 mb-4 italic">
&ldquo;{partner.quote.text}&rdquo;
</blockquote>
<div className="text-[14px]">
<div className="font-semibold text-foreground">
{partner.quote.author}
</div>
<div className="text-muted-foreground">{partner.quote.role}</div>
</div>
</div> */}

{/* Challenge */}
<section className="mb-14">
<h2 className="text-[13px] font-mono uppercase tracking-[0.15em] text-primary mb-6">
$ The Challenge
</h2>
<div className="space-y-4 text-[15px] leading-relaxed text-foreground/85">
{partner.challenge.map((p, i) => (
<p key={i}>{p}</p>
))}
</div>
</section>

{/* Solution */}
<section className="mb-14">
<h2 className="text-[13px] font-mono uppercase tracking-[0.15em] text-primary mb-6">
$ The Solution
</h2>
<div className="space-y-4 text-[15px] leading-relaxed text-foreground/85">
{partner.solution.map((p, i) => (
<p key={i}>{p}</p>
))}
</div>
</section>

{/* Results */}
<section className="mb-16">
<h2 className="text-[13px] font-mono uppercase tracking-[0.15em] text-primary mb-6">
$ The Results
</h2>
<div className="space-y-4">
{partner.results.map((result, i) => (
<div
key={i}
className="border border-border rounded-lg p-6 bg-card/30"
>
<div className="text-[22px] font-bold text-primary font-mono tracking-tight mb-1">
{result.metric}
</div>
<p className="text-[14px] text-foreground/85 leading-relaxed">
{result.text}
</p>
</div>
))}
</div>
</section>

{/* CTA */}
<div className="border-t border-border pt-12 text-center">
<p className="text-[15px] text-muted-foreground mb-6">
Ready to ship faster?
</p>
<a
href="https://github.com/yashdev9274/superCli"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-lg text-[14px] font-medium hover:opacity-90 transition-opacity"
>
Get started with Supercode
<ArrowUpRight className="w-4 h-4" />
</a>
</div>
</article>

<Footer />
</main>
)
}
Loading
Loading