diff --git a/apps/supercode-cli/server/package.json b/apps/supercode-cli/server/package.json index e634b71..3c48c5d 100644 --- a/apps/supercode-cli/server/package.json +++ b/apps/supercode-cli/server/package.json @@ -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": { diff --git a/apps/supercode-cli/server/src/index.ts b/apps/supercode-cli/server/src/index.ts index 63a37b8..6f9d010 100644 --- a/apps/supercode-cli/server/src/index.ts +++ b/apps/supercode-cli/server/src/index.ts @@ -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() @@ -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}`) }) diff --git a/apps/supercode-cli/server/src/voice/speech.ts b/apps/supercode-cli/server/src/voice/speech.ts index 83b5c12..e7d67ed 100644 --- a/apps/supercode-cli/server/src/voice/speech.ts +++ b/apps/supercode-cli/server/src/voice/speech.ts @@ -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" @@ -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 } @@ -126,7 +128,7 @@ function captureAudio( }) } -async function transcribeElevenLabs(filePath: string): Promise { +export async function transcribeElevenLabs(filePath: string): Promise { const apiKey = process.env.ELEVENLABS_API_KEY if (!apiKey) throw new Error("ELEVENLABS_API_KEY not configured") @@ -163,7 +165,7 @@ async function transcribeElevenLabs(filePath: string): Promise { } /* groq provider */ -async function transcribeGroq(filePath: string): Promise { +export async function transcribeGroq(filePath: string): Promise { const apiKey = process.env.GROQ_API_KEY if (!apiKey) throw new Error("GROQ_API_KEY not configured") @@ -195,15 +197,45 @@ async function transcribeGroq(filePath: string): Promise { return data.text ?? "" } +async function transcribeViaServer(filePath: string): Promise { + 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 { 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 diff --git a/apps/web/app/(pages)/partnerships/[company]/page.tsx b/apps/web/app/(pages)/partnerships/[company]/page.tsx new file mode 100644 index 0000000..dd143e2 --- /dev/null +++ b/apps/web/app/(pages)/partnerships/[company]/page.tsx @@ -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 { + 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 ( +
+
+
+ + + +
+ {/* Back link */} + + + All partnerships + + + {/* Hero */} +
+
+ {partner.logoSrc ? ( + {partner.name} + ) : ( + partner.logo + )} +
+ +

+ {partner.name} +

+

+ {partner.tagline} +

+
+ + {/* Stat highlight */} +
+
+ {partner.stat.value} +
+
+ {partner.stat.label} +
+
+ + {/* Quote */} + {/*
+ +
+ “{partner.quote.text}” +
+
+
+ {partner.quote.author} +
+
{partner.quote.role}
+
+
*/} + + {/* Challenge */} +
+

+ $ The Challenge +

+
+ {partner.challenge.map((p, i) => ( +

{p}

+ ))} +
+
+ + {/* Solution */} +
+

+ $ The Solution +

+
+ {partner.solution.map((p, i) => ( +

{p}

+ ))} +
+
+ + {/* Results */} +
+

+ $ The Results +

+
+ {partner.results.map((result, i) => ( +
+
+ {result.metric} +
+

+ {result.text} +

+
+ ))} +
+
+ + {/* CTA */} +
+

+ Ready to ship faster? +

+ + Get started with Supercode + + +
+
+ +
+
+ ) +} diff --git a/apps/web/app/(pages)/partnerships/page.tsx b/apps/web/app/(pages)/partnerships/page.tsx new file mode 100644 index 0000000..5c52008 --- /dev/null +++ b/apps/web/app/(pages)/partnerships/page.tsx @@ -0,0 +1,87 @@ +import type { Metadata } from "next" +import Link from "next/link" +import Navbar from "@/components/homepage/navbar" +import Footer from "@/components/homepage/footer" +import { partners } from "@/data/partnerships" +import { ArrowUpRight } from "lucide-react" + +export const metadata: Metadata = { + title: "Supercode — Partnerships & Case Studies", + description: + "Learn how teams use Supercode to ship faster, improve code quality, and automate their workflows.", + metadataBase: new URL("https://supercli.vercel.app"), + openGraph: { + title: "Supercode — Partnerships & Case Studies", + description: + "Learn how teams use Supercode to ship faster, improve code quality, and automate their workflows.", + url: "https://supercli.vercel.app/partnerships", + siteName: "Supercode", + type: "website", + }, + twitter: { + card: "summary_large_image", + title: "Supercode — Partnerships & Case Studies", + description: + "Learn how teams use Supercode to ship faster, improve code quality, and automate their workflows.", + }, +} + +export default function PartnershipsPage() { + return ( +
+
+
+ + + +
+

+ Partnerships +

+

+ Real teams, real results. See how engineering organizations use + Supercode to ship faster and build better software. +

+ +
+ {partners.map((partner) => ( + +
+
+ {partner.logoSrc ? ( + {partner.name} + ) : ( + partner.logo + )} +
+ +
+ +

+ {partner.name} +

+

+ {partner.description} +

+ +
+
+ {partner.stat.value} +
+
+ {partner.stat.label} +
+
+ + ))} +
+
+ +
+
+ ) +} diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 621c37e..a6fa0cd 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -176,6 +176,11 @@ body { letter-spacing: var(--tracking-normal); } +:root { + --ease-out: cubic-bezier(0.23, 1, 0.32, 1); + --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); +} + @keyframes cursor-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index ee5f660..2cb60aa 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,5 +1,7 @@ import Navbar from "@/components/homepage/navbar"; import HeroSection from "@/components/homepage/hero"; +import GetStartedSection from "@/components/homepage/get-started"; +import PartnershipsSection from "@/components/homepage/partnerships-section"; import Footer from "@/components/homepage/footer"; export default async function Home() { @@ -11,6 +13,10 @@ export default async function Home() { + + + +