-
Notifications
You must be signed in to change notification settings - Fork 107
Staging #86
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
Staging #86
Changes from all commits
03d620e
4c7df10
c5c307b
c477238
306595e
30cb552
27f08fc
6fee867
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| .landing-container{ | ||
| max-width:1200px; | ||
| margin:3rem auto; | ||
| display:grid; | ||
| grid-template-columns:1fr 500px; | ||
| gap:2.5rem; | ||
| align-items:center; | ||
| padding:0 1rem; | ||
| } | ||
|
|
||
| .hero-left{padding:1rem 0} | ||
| .hero-title{ | ||
| font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; | ||
| font-weight:800; | ||
| font-size:4rem; | ||
| line-height:1.02; | ||
| margin:0 0 1rem 0; | ||
| color:var(--text-primary); | ||
| } | ||
| .hero-sub{ | ||
| color:var(--text-secondary); | ||
| font-size:1.05rem; | ||
| max-width:44rem; | ||
| margin-bottom:1.5rem; | ||
| } | ||
| .hero-ctas{display:flex;gap:1rem;align-items:center} | ||
| .btn-pill{display:inline-block;padding:0.9rem 1.25rem;border-radius:999px;background:var(--falkor-primary);color:#fff;text-decoration:none;box-shadow:0 8px 20px rgba(91,107,192,0.14);} | ||
| .btn-ghost{background:transparent;color:var(--text-primary);text-decoration:none;padding:0.9rem 1.25rem} | ||
|
|
||
| .demo-card{background:var(--falkor-secondary);border-radius:12px;box-shadow:0 16px 30px rgba(11,18,32,0.06);padding:1rem;border:1px solid var(--border-color)} | ||
|
|
||
| /* Use a neutral themed surface for the inner area so it adapts to light/dark */ | ||
| .demo-inner{background:var(--bg-tertiary);border-radius:8px;padding:1rem;border:1px solid var(--falkor-border-tertiary)} | ||
| .demo-label{font-size:0.9rem;color:var(--text-secondary);margin-bottom:0.5rem} | ||
|
|
||
| /* Use the theme's secondary surface for the white/black boxes so text contrast is correct in both themes */ | ||
| .demo-question{background:var(--falkor-secondary);border-radius:6px;padding:0.75rem 1rem;border:1px solid var(--falkor-border-tertiary);color:var(--text-primary);height:120px;white-space:pre-wrap;font-family:monospace;font-size:0.95rem;overflow:auto;line-height:1.3} | ||
| .demo-sql{background:var(--falkor-secondary);border-radius:6px;padding:0.75rem 1rem;border:1px solid var(--falkor-border-tertiary);color:var(--text-primary);margin-top:0.8rem;font-size:0.9rem;overflow:auto;height:200px;line-height:1.25} | ||
| .demo-sql.typing{position:relative} | ||
| .demo-sql.typing::after{ | ||
| content: ''; | ||
| display:inline-block; | ||
| width:10px; | ||
| height:1.1em; | ||
| background:var(--falkor-primary); | ||
| margin-left:6px; | ||
| vertical-align:bottom; | ||
| animation: blink-caret 1s steps(1) infinite; | ||
| } | ||
|
|
||
| @keyframes blink-caret{ | ||
| 0%, 50% { opacity: 1 } | ||
| 51%, 100% { opacity: 0 } | ||
| } | ||
| .demo-cta{margin-top:1rem;text-align:center} | ||
| .demo-cta .btn-full{display:inline-block;width:100%;padding:0.75rem;border-radius:8px;background:#e7f1ff;color:var(--falkor-primary);text-decoration:none} | ||
|
|
||
| @media (max-width:900px){ | ||
| .landing-container{grid-template-columns:1fr;gap:1.25rem} | ||
| .hero-title{font-size:2.4rem} | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| {% extends "base.j2" %} | ||
|
|
||
| {% block title %}QueryWeaver — Natural language to SQL{% endblock %} | ||
|
|
||
| {% block extra_css %} | ||
| <link rel="stylesheet" href="{{ url_for('static', filename='css/landing.css') }}"> | ||
| {% endblock %} | ||
|
|
||
| {% block content %} | ||
| <div class="landing-container"> | ||
|
|
||
| <!-- Left hero --> | ||
| <section class="hero-left"> | ||
| <h1 class="hero-title">Transform Plain<br/>English into Powerful<br/>SQL.</h1> | ||
| <p class="hero-sub">The intelligent AI solution that connects your business questions to data across multiple databases. Ask a question in plain English and get runnable SQL and visual results.</p> | ||
|
|
||
| <div class="hero-ctas"> | ||
| <a href="{{ url_for('auth.chat') }}" class="btn-pill">Sign Up for Free</a> | ||
| <a href="{{ url_for('auth.login_google') }}" class="btn-ghost">Sign In</a> | ||
| </div> | ||
| </section> | ||
|
|
||
| <!-- Right demo card --> | ||
| <aside class="demo-card"> | ||
| <div class="demo-inner"> | ||
| <div class="demo-label">Your Business Question:</div> | ||
| <div class="demo-question">Which customers from Germany who bought 'Chai' also have an open support ticket in Salesforce?</div> | ||
|
|
||
| <div class="demo-label">Generated SQL:</div> | ||
| <pre class="demo-sql"> | ||
| SELECT c.companyName | ||
| FROM customers c | ||
| JOIN orders o ON c.customerID = o.customerID | ||
| JOIN order_details od ON o.orderID = od.orderID | ||
| JOIN products p ON od.productID = p.productID | ||
| JOIN salesforce_tickets st ON c.customerID = st.customerID | ||
| WHERE c.country = 'Germany' | ||
| AND p.productName = 'Chai' | ||
| AND st.status = 'Open'; | ||
| </pre> | ||
|
|
||
| <div class="demo-cta"> | ||
| <button id="demo-next" class="btn-full" type="button">Try another question</button> | ||
| </div> | ||
| </div> | ||
| <script> | ||
| (function(){ | ||
| const examples = [ | ||
| { | ||
| q: "Which customers from Germany who bought 'Chai' also have an open support ticket in Salesforce?", | ||
| sql: `SELECT c.companyName\nFROM customers c\nJOIN orders o ON c.customerID = o.customerID\nJOIN order_details od ON o.orderID = od.orderID\nJOIN products p ON od.productID = p.productID\nJOIN salesforce_tickets st ON c.customerID = st.customerID\nWHERE c.country = 'Germany'\n AND p.productName = 'Chai'\n AND st.status = 'Open';` | ||
| }, | ||
| { | ||
| q: "Show me the top 5 customers by order count in London.", | ||
| sql: `SELECT c.companyName, COUNT(o.orderID) AS order_count\nFROM customers c\nJOIN orders o ON c.customerID = o.customerID\nWHERE c.city = 'London'\nGROUP BY c.companyName\nORDER BY order_count DESC\nLIMIT 5;` | ||
| }, | ||
| { | ||
| q: "What is the quarterly revenue trend for the 'Beverages' category over the last two years?", | ||
| sql: `SELECT strftime('%Y-Q%q', o.orderDate) AS quarter,\n SUM(od.unitPrice * od.quantity) AS total_revenue\nFROM orders o\nJOIN order_details od ON o.orderID = od.orderID\nJOIN products p ON od.productID = p.productID\nJOIN categories cat ON p.categoryID = cat.categoryID\nWHERE cat.categoryName = 'Beverages'\n AND o.orderDate >= date('now', '-2 years')\nGROUP BY quarter\nORDER BY quarter;` | ||
| } | ||
| ]; | ||
|
|
||
| let idx = 0; | ||
| const qEl = document.querySelector('.demo-question'); | ||
| const sEl = document.querySelector('.demo-sql'); | ||
| const btn = document.getElementById('demo-next'); | ||
|
|
||
| let typingTimer = null; | ||
| const typingSpeed = 15; // ms per character | ||
|
|
||
| function renderFull(i){ | ||
| const ex = examples[i % examples.length]; | ||
| if(qEl) qEl.textContent = ex.q; | ||
| if(sEl) { | ||
| sEl.classList.remove('typing'); | ||
| sEl.textContent = ex.sql; | ||
| } | ||
| } | ||
|
|
||
| function typeSql(text){ | ||
| if(!sEl) return; | ||
| // stop any previous typing | ||
| if(typingTimer) { | ||
| clearInterval(typingTimer); | ||
| typingTimer = null; | ||
| } | ||
| sEl.classList.add('typing'); | ||
| sEl.textContent = ''; | ||
| let pos = 0; | ||
| typingTimer = setInterval(() => { | ||
| pos += 1; | ||
| sEl.textContent = text.slice(0, pos); | ||
| if(pos >= text.length){ | ||
| clearInterval(typingTimer); | ||
| typingTimer = null; | ||
| sEl.classList.remove('typing'); | ||
| } | ||
| }, typingSpeed); | ||
| } | ||
|
|
||
| if(btn){ | ||
| btn.addEventListener('click', function(){ | ||
| idx = (idx + 1) % examples.length; | ||
| const ex = examples[idx]; | ||
| if(qEl) qEl.textContent = ex.q; | ||
| // animate SQL typing | ||
| typeSql(ex.sql); | ||
| }); | ||
| } | ||
|
|
||
| // initial render: show full first example without typing | ||
| renderFull(0); | ||
| })(); | ||
| </script> | ||
| </div> | ||
| </aside> | ||
|
|
||
| </div> | ||
| {% endblock %} | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,24 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "rewrites": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { "source": "/(.*)", "destination": "/api/index.py" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "source": "/(.*)", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "destination": "/api/index.py" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "functions": { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "api/index.py": { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "maxDuration": 300 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "builds": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "src": "app/package.json", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "use": "@vercel/static-build", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "config": { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "distDir": "public" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "src": "api/index.py", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "use": "@vercel/python", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "config": { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "maxDuration": 300 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+17
to
+21
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion maxDuration inside a builder’s config is ignored; it must be set via the functions property (builders and functions can’t be combined).
Recommend migrating away from "builds" and using:
Here’s a concrete replacement for the builds block that preserves behavior and enables maxDuration: - "builds": [
- {
- "src": "app/package.json",
- "use": "@vercel/static-build",
- "config": {
- "distDir": "public"
- }
- },
- {
- "src": "api/index.py",
- "use": "@vercel/python",
- "config": {
- "maxDuration": 300
- }
- }
- ]
+ "buildCommand": "npm --prefix app run build",
+ "outputDirectory": "app/public",
+ "functions": {
+ "api/index.py": {
+ "maxDuration": 300
+ }
+ }Notes:
📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Ensure landing.css is actually served by Flask’s static setup (path likely mismatched).
Template links to static/css/landing.css, but the stylesheet lives at app/public/css/landing.css in this PR. Unless the Flask app’s static_folder points to app/public (or your build copies to app/static/css), this will 404 and the page will be unstyled.
Options:
Quick verification:
If moving the file, run:
🏁 Script executed:
Length of output: 182
🏁 Script executed:
Length of output: 178
To pinpoint why the file move failed, let’s verify both the exact filesystem path and whether Git is tracking
landing.css. Please run:🏁 Script executed:
Length of output: 1234
Fix static asset path for landing.css
Your template at
app/templates/landing.j2usesbut Flask’s default
static_folderisapp/static, and no override topublicwas found. As a result, the file underapp/public/css/landing.csswill 404.We verified:
app/static/directory existsapp/public/css/landing.cssis present and tracked by Gitstatic_folder='public'setting in your Flask app ( confirmed this)Required fix (preferred): Move the stylesheet into your static directory so it’s served correctly.
• Create the CSS folder and move the file:
• Update any other assets in
app/public/css/if they need to be served viaurl_for('static', …).Alternative: Change your Flask app initialization (e.g. in
app/__init__.pyorwsgi.py) to:Locations requiring attention:
Flask(...)is called)Move landing.css into Flask’s static folder
Your Jinja template at
app/templates/landing.j2uses:but the stylesheet currently lives at
app/public/css/landing.css, and there’s no evidence ofstatic_folder='public'in your Flask setup. This will cause a 404.Required actions:
app/public/css/are moved or served appropriately.url_for('static', filename='css/…')links to files inapp/static/.Alternatively, if you intend to keep assets under
app/public, update your Flask app initialization:Files needing changes:
Flask(...)is instantiated)🤖 Prompt for AI Agents