| """ |
| ARIA v4 — Customer API Key Setup + Onboarding UI |
| Built for Ting Lott's ARIA v4 product. |
| Each customer brings their own API keys — zero cost to Ting. |
| """ |
|
|
| import gradio as gr |
| import requests |
| import json |
| import os |
| from datetime import datetime |
|
|
| |
| |
| |
|
|
| def validate_groq_key(key: str) -> tuple[bool, str]: |
| if not key or len(key) < 20: |
| return False, "❌ Key too short — paste your full Groq key" |
| try: |
| r = requests.post( |
| "https://api.groq.com/openai/v1/chat/completions", |
| headers={"Authorization": f"Bearer {key.strip()}", "Content-Type": "application/json"}, |
| json={"model": "llama-3.3-70b-versatile", "messages": [{"role": "user", "content": "Hi"}], "max_tokens": 5}, |
| timeout=10 |
| ) |
| if r.status_code == 200: |
| return True, "✅ Groq key valid! Free AI connected." |
| elif r.status_code == 401: |
| return False, "❌ Invalid Groq key — copy it again from console.groq.com" |
| else: |
| return False, f"❌ Groq error {r.status_code} — try again" |
| except Exception as e: |
| return False, f"❌ Connection error: {str(e)[:60]}" |
|
|
|
|
| def validate_airtable_token(token: str, base_id: str) -> tuple[bool, str]: |
| if not token or not token.startswith("pat"): |
| return False, "❌ Airtable token must start with 'pat' — see instructions below" |
| if not base_id or not base_id.startswith("app"): |
| return False, "❌ Base ID must start with 'app' — see Step 2 below" |
| try: |
| r = requests.get( |
| f"https://api.airtable.com/v0/meta/bases/{base_id}/tables", |
| headers={"Authorization": f"Bearer {token.strip()}"}, |
| timeout=10 |
| ) |
| if r.status_code == 200: |
| tables = r.json().get("tables", []) |
| return True, f"✅ Airtable connected! Found {len(tables)} table(s) in your base." |
| elif r.status_code == 401: |
| return False, "❌ Invalid token — create a new one at airtable.com/create/tokens" |
| elif r.status_code == 403: |
| return False, "❌ Token doesn't have access to this base — check scopes" |
| elif r.status_code == 404: |
| return False, "❌ Base ID not found — copy it from your Airtable base URL" |
| else: |
| return False, f"❌ Airtable error {r.status_code}" |
| except Exception as e: |
| return False, f"❌ Connection error: {str(e)[:60]}" |
|
|
|
|
| def validate_openai_key(key: str) -> tuple[bool, str]: |
| if not key: |
| return True, "⏭️ Skipped (optional)" |
| if not key.startswith("sk-"): |
| return False, "❌ OpenAI key must start with 'sk-'" |
| try: |
| r = requests.get( |
| "https://api.openai.com/v1/models", |
| headers={"Authorization": f"Bearer {key.strip()}"}, |
| timeout=10 |
| ) |
| if r.status_code == 200: |
| return True, "✅ OpenAI connected — GPT-4 unlocked!" |
| else: |
| return False, f"❌ OpenAI error {r.status_code}" |
| except: |
| return False, "❌ Could not reach OpenAI" |
|
|
|
|
| |
| |
| |
|
|
| def chat_with_aria(message: str, history: list, groq_key: str, airtable_token: str, base_id: str, setup_complete: bool) -> tuple: |
| if not setup_complete or not groq_key: |
| return history + [{"role": "assistant", "content": "⚠️ Please complete Setup first (Tab 1) before chatting with ARIA."}], gr.update() |
|
|
| system_prompt = """You are ARIA v4 — an Autonomous Responsive Intelligent Agent built for small business owners, coaches, and digital creators. |
| |
| You help users: |
| - Generate content for social media (posts, captions, threads) |
| - Write email sequences and newsletters |
| - Create marketing copy and product descriptions |
| - Plan content calendars |
| - Automate repetitive tasks |
| - Build business systems |
| - Answer business strategy questions |
| |
| You are warm, direct, and results-focused. You ask clarifying questions when needed. You give actionable, specific answers — not vague generic advice. |
| |
| When generating content, always ask: what platform? what audience? what goal?""" |
|
|
| messages = [{"role": "system", "content": system_prompt}] |
| for h in history: |
| messages.append(h) |
| messages.append({"role": "user", "content": message}) |
|
|
| try: |
| r = requests.post( |
| "https://api.groq.com/openai/v1/chat/completions", |
| headers={"Authorization": f"Bearer {groq_key.strip()}", "Content-Type": "application/json"}, |
| json={"model": "llama-3.3-70b-versatile", "messages": messages, "max_tokens": 1000, "temperature": 0.7}, |
| timeout=30 |
| ) |
| if r.status_code == 200: |
| reply = r.json()["choices"][0]["message"]["content"] |
| new_history = history + [ |
| {"role": "user", "content": message}, |
| {"role": "assistant", "content": reply} |
| ] |
| return new_history, gr.update(value="") |
| else: |
| err = f"⚠️ API error {r.status_code}: {r.text[:100]}" |
| return history + [{"role": "assistant", "content": err}], gr.update() |
| except Exception as e: |
| return history + [{"role": "assistant", "content": f"⚠️ Error: {str(e)[:100]}"}], gr.update() |
|
|
|
|
| |
| |
| |
|
|
| def save_groq_key(key): |
| ok, msg = validate_groq_key(key) |
| return msg, ok |
|
|
| def save_airtable(token, base_id): |
| ok, msg = validate_airtable_token(token, base_id) |
| return msg, ok |
|
|
| def save_openai(key): |
| ok, msg = validate_openai_key(key) |
| return msg, ok |
|
|
| def check_setup_complete(groq_valid, airtable_valid): |
| if groq_valid and airtable_valid: |
| return ( |
| gr.update(visible=True), |
| "🚀 Setup complete! Switch to the **ARIA Chat** tab to start.", |
| True |
| ) |
| missing = [] |
| if not groq_valid: |
| missing.append("Groq API key") |
| if not airtable_valid: |
| missing.append("Airtable token + Base ID") |
| return ( |
| gr.update(visible=False), |
| f"⏳ Still needed: {', '.join(missing)}", |
| False |
| ) |
|
|
|
|
| |
| |
| |
|
|
| CSS = """ |
| .setup-card { background: #1a1a2e; border: 1px solid #7c3aed; border-radius: 12px; padding: 20px; margin: 10px 0; } |
| .step-header { color: #a78bfa; font-size: 18px; font-weight: bold; margin-bottom: 8px; } |
| .instructions { color: #d1d5db; font-size: 13px; line-height: 1.6; } |
| .success-banner { background: #065f46; border: 1px solid #34d399; border-radius: 8px; padding: 12px; color: #a7f3d0; } |
| footer { display: none !important; } |
| """ |
|
|
| with gr.Blocks(theme=gr.themes.Soft(primary_hue="violet", neutral_hue="slate"), css=CSS, title="ARIA v4 Setup") as demo: |
|
|
| |
| groq_key_state = gr.State("") |
| airtable_token_state = gr.State("") |
| base_id_state = gr.State("") |
| openai_key_state = gr.State("") |
| groq_valid_state = gr.State(False) |
| airtable_valid_state = gr.State(False) |
| setup_complete_state = gr.State(False) |
|
|
| |
| gr.Markdown(""" |
| # 🤖 ARIA v4 — Autonomous Business AI |
| ### Your personal AI that handles content, marketing & automation |
| |
| **Welcome!** ARIA uses YOUR free API keys — you own your data, you control your usage. |
| Complete the 2-step setup below (takes about 5 minutes), then you're ready to go! 🚀 |
| """) |
|
|
| with gr.Tabs(): |
|
|
| |
| |
| |
| with gr.Tab("🔧 Setup (Start Here)"): |
| gr.Markdown("## Step 1 of 2 — Get Your Free Groq API Key") |
| gr.Markdown(""" |
| <div class="setup-card"> |
| <div class="step-header">🧠 Groq — Your Free AI Brain (Required)</div> |
| <div class="instructions"> |
| |
| Groq gives you access to Llama 3.3 (one of the world's best AI models) — completely FREE. |
| |
| **How to get your free key (2 minutes):** |
| 1. Go to **[console.groq.com](https://console.groq.com)** and click **Sign Up** (use Google login) |
| 2. After login, click **API Keys** in the left sidebar |
| 3. Click **+ Create API Key** → name it anything (e.g. "ARIA v4") |
| 4. Copy the key that starts with `gsk_...` |
| 5. Paste it below and click **Connect Groq** |
| |
| ✅ Free tier includes: 14,400 requests/day, 500K tokens/minute — more than enough! |
| </div> |
| </div> |
| """) |
| groq_input = gr.Textbox( |
| placeholder="gsk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx", |
| label="Groq API Key", |
| type="password", |
| show_label=True |
| ) |
| groq_btn = gr.Button("🔌 Connect Groq", variant="primary") |
| groq_status = gr.Markdown("") |
|
|
| gr.Markdown("---") |
| gr.Markdown("## Step 2 of 2 — Connect Airtable (Your Dashboard & Data)") |
| gr.Markdown(""" |
| <div class="setup-card"> |
| <div class="step-header">📊 Airtable — Your Business Dashboard (Required)</div> |
| <div class="instructions"> |
| |
| Airtable stores all your data — customers, automations, social posts, logs. FREE for up to 1,000 records. |
| |
| **How to get your token (3 minutes):** |
| |
| **Part A — Get your Personal Access Token:** |
| 1. Go to **[airtable.com/create/tokens](https://airtable.com/create/tokens)** (login first) |
| 2. Click **+ Create token** |
| 3. Name it: **ARIA v4** |
| 4. Under **Scopes**, add ALL of these: |
| - `data.records:read` |
| - `data.records:write` |
| - `schema.bases:read` |
| - `schema.bases:write` |
| 5. Under **Access**, select **All current and future bases** |
| 6. Click **Create token** → Copy the token that starts with `pat...` |
| |
| **Part B — Get your Base ID:** |
| 1. Go to **[airtable.com](https://airtable.com)** and create a new Base (click **+ Add a base**) |
| 2. Name it: **ARIA v4 Data** |
| 3. Open that base — look at the URL bar. It looks like: |
| `https://airtable.com/appXXXXXXXXXXXXXX/...` |
| 4. Copy everything after `/app` including "app" — that is your Base ID (starts with `app`) |
| </div> |
| </div> |
| """) |
| with gr.Row(): |
| airtable_token_input = gr.Textbox( |
| placeholder="patXXXXXXXXXXXXXX.XXXXXXXX", |
| label="Airtable Personal Access Token", |
| type="password" |
| ) |
| base_id_input = gr.Textbox( |
| placeholder="appXXXXXXXXXXXXXX", |
| label="Airtable Base ID" |
| ) |
| airtable_btn = gr.Button("🔌 Connect Airtable", variant="primary") |
| airtable_status = gr.Markdown("") |
|
|
| gr.Markdown("---") |
| gr.Markdown("## ⚡ Optional — Add Paid APIs for More Power") |
| gr.Markdown(""" |
| <div class="setup-card"> |
| <div class="step-header">💎 Optional Upgrades</div> |
| <div class="instructions"> |
| |
| These are completely optional. ARIA works great with just Groq (free). Add these if you want extra capabilities: |
| |
| | API | What it unlocks | Cost | |
| |-----|----------------|------| |
| | **OpenAI** | GPT-4o for complex tasks | ~$20/month | |
| | **ElevenLabs** | Human-quality voice generation | Free tier available | |
| | **Together AI** | Additional AI models | Free $5 credits | |
| |
| Get OpenAI key at: **[platform.openai.com/api-keys](https://platform.openai.com/api-keys)** |
| </div> |
| </div> |
| """) |
| openai_input = gr.Textbox( |
| placeholder="sk-xxxxxxxxxxxxxxxxxx (optional — leave blank to skip)", |
| label="OpenAI API Key (Optional)", |
| type="password" |
| ) |
| openai_btn = gr.Button("Add OpenAI (Optional)", variant="secondary") |
| openai_status = gr.Markdown("") |
|
|
| gr.Markdown("---") |
| setup_summary = gr.Markdown("⏳ Complete Steps 1 & 2 above to unlock ARIA Chat.") |
| go_to_chat_btn = gr.Button("🚀 I'm Ready — Go to ARIA Chat!", variant="primary", visible=False, size="lg") |
|
|
| |
| |
| |
| with gr.Tab("🤖 ARIA Chat"): |
| gr.Markdown("### Chat with ARIA — Your Autonomous Business AI") |
| gr.Markdown("*Ask ARIA to write content, build strategies, plan campaigns, create email sequences, and more.*") |
|
|
| chatbot = gr.Chatbot( |
| type="messages", |
| height=500, |
| label="ARIA v4", |
| placeholder="Complete Setup (Tab 1) first, then start chatting!", |
| show_label=False |
| ) |
| with gr.Row(): |
| chat_input = gr.Textbox( |
| placeholder="Ask ARIA anything... e.g. 'Write 5 Instagram captions for my coaching business'", |
| label="", |
| scale=8 |
| ) |
| send_btn = gr.Button("Send →", scale=1, variant="primary") |
|
|
| gr.Markdown(""" |
| **💡 Try these:** |
| - *"Write a week of LinkedIn posts for a business coach"* |
| - *"Create an email sequence for someone who downloaded my free guide"* |
| - *"Give me 10 TikTok hooks about productivity for entrepreneurs"* |
| - *"Build a 30-day content calendar for wellness products"* |
| - *"Write a product description for my $47 digital course"* |
| """) |
|
|
| |
| |
| |
| with gr.Tab("❓ Help & FAQ"): |
| gr.Markdown(""" |
| ## Frequently Asked Questions |
| |
| **Q: Is my data safe?** |
| A: Yes. Your API keys are stored only in your browser session — they are never saved to any server. Each time you open ARIA, you enter them fresh. |
| |
| **Q: What does Groq cost?** |
| A: Groq is FREE. Their free tier gives you 14,400 requests per day — more than enough for heavy daily use. |
| |
| **Q: What does Airtable cost?** |
| A: Airtable is FREE for up to 1,000 records per base. That covers months of typical use. When you grow, their paid plan is $20/month. |
| |
| **Q: Do I need OpenAI?** |
| A: No! ARIA runs perfectly on Groq (free). OpenAI is optional if you want GPT-4o for specific tasks. |
| |
| **Q: My Groq key isn't working — what do I do?** |
| A: 1) Make sure you copied the full key (starts with `gsk_`). 2) Check you haven't hit the daily free limit. 3) Create a new key at console.groq.com. |
| |
| **Q: My Airtable token isn't working — what do I do?** |
| A: 1) Make sure the token starts with `pat`. 2) Check that you added all 4 scopes. 3) Make sure the Base ID starts with `app` and matches your actual base. |
| |
| **Q: Can I use ARIA on my phone?** |
| A: Yes! This app works in any mobile browser. |
| |
| **Q: How do I get support?** |
| A: Email support is included with your purchase. Reply to your Gumroad receipt email. |
| """) |
|
|
| |
| |
| |
|
|
| def handle_groq(key, groq_valid, airtable_valid): |
| ok, msg = validate_groq_key(key) |
| go_visible, summary, complete = check_setup_complete(ok, airtable_valid) |
| return msg, ok, key if ok else "", go_visible, summary, complete |
|
|
| def handle_airtable(token, base_id, groq_valid, airtable_valid): |
| ok, msg = validate_airtable_token(token, base_id) |
| go_visible, summary, complete = check_setup_complete(groq_valid, ok) |
| return msg, ok, token if ok else "", base_id if ok else "", go_visible, summary, complete |
|
|
| groq_btn.click( |
| handle_groq, |
| inputs=[groq_input, groq_valid_state, airtable_valid_state], |
| outputs=[groq_status, groq_valid_state, groq_key_state, go_to_chat_btn, setup_summary, setup_complete_state] |
| ) |
|
|
| airtable_btn.click( |
| handle_airtable, |
| inputs=[airtable_token_input, base_id_input, groq_valid_state, airtable_valid_state], |
| outputs=[airtable_status, airtable_valid_state, airtable_token_state, base_id_state, go_to_chat_btn, setup_summary, setup_complete_state] |
| ) |
|
|
| openai_btn.click( |
| lambda k: validate_openai_key(k)[1], |
| inputs=[openai_input], |
| outputs=[openai_status] |
| ) |
|
|
| def send_chat(msg, history, groq_key, airtable_token, base_id, setup_complete): |
| if not msg.strip(): |
| return history, gr.update(value="") |
| return chat_with_aria(msg, history, groq_key, airtable_token, base_id, setup_complete) |
|
|
| send_btn.click( |
| send_chat, |
| inputs=[chat_input, chatbot, groq_key_state, airtable_token_state, base_id_state, setup_complete_state], |
| outputs=[chatbot, chat_input] |
| ) |
| chat_input.submit( |
| send_chat, |
| inputs=[chat_input, chatbot, groq_key_state, airtable_token_state, base_id_state, setup_complete_state], |
| outputs=[chatbot, chat_input] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch(server_name="0.0.0.0", server_port=7860) |
|
|