From 7bd4a0e998384783d63a7bd3530c71abb6dabc5f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:25:51 +0000 Subject: [PATCH 1/2] feat: hide header on mobile and enable resolution search with correct styling Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com> --- .github/FUNDING.yml | 2 +- README.md | 165 ++++++++------------------ components/header-search-button.tsx | 9 +- components/header.tsx | 2 +- components/mobile-icons-bar.tsx | 22 ++-- components/purchase-credits-popup.tsx | 2 +- components/usage-view.tsx | 2 +- patch_header_search.js | 16 +++ patch_mobile_icons.js | 7 ++ patch_mobile_icons_2.js | 24 ++++ 10 files changed, 120 insertions(+), 131 deletions(-) create mode 100644 patch_header_search.js create mode 100644 patch_mobile_icons.js create mode 100644 patch_mobile_icons_2.js diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 1a7275ce..5b968d8e 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -12,4 +12,4 @@ lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cl polar: # Replace with a single Polar username buy_me_a_coffee: # Replace with a single Buy Me a Coffee username thanks_dev: # Replace with a single thanks.dev username -custom: https://www.paypal.com/ncp/payment/H7JHQ5LZPY63S +custom: https://paypal.me/queuenorth diff --git a/README.md b/README.md index 10039de7..144aca00 100644 --- a/README.md +++ b/README.md @@ -1,165 +1,96 @@
-# QCX -**Quality Computer Experience** -Language → Maps -A General Intelligence Interface, A planet computer core +# Quality Computer Experience -
-[![Watch the demo](https://img.youtube.com/vi/y8M6qaFeZOw/0.jpg)](https://youtu.be/y8M6qaFeZOw?si=eYMXxqh7vQ4I-Mhr) -
- -[**Try Live**](https://www.queue.cx)  •  -[**Pricing / Pre-sale**](https://www.paypal.com/ncp/payment/G82N9CZ6ZLLCQ)  •  -[**@tryqcx on X**](https://x.com/tryqcx)  •  -[**Documentation**](https://deepwiki.com/QueueLab/QCX) +[**Pricing**](https://buy.stripe.com/14A3cv7K72TR3go14Nasg02)  |  [**Land**](https://wwww.queue.cx)  |  [**X**](https://x.com/tryqcx) +QCX - Artificial General Intelligence. | Product Hunt
-## What is QCX? +Screen Shot 2024-06-18 at 4 27 51 PM -QCX is an experimental **AI-first geospatial companion** that lets you explore the planet (and beyond) through natural language + interactive maps. -- Chat with AI about any location, event, or spatial question -- Draw on the map → get measurements, analysis & AI insights -- Real-time geolocation, 3D view, time-zone aware reasoning -- Powered by multi-agent orchestration + generative UI -- Currently interpolating full chat ↔ map bidirectional integration -Built as a research prototype from **QueueLab** — exploring the frontier between natural language, spatial reasoning and artificial general intelligence. -
-## ✨ Features (Current & In-progress) +## Contributing -- Conversational geospatial queries with tool-using agents -- Interactive Mapbox map with drawing, measurements & GeoJSON -- Generative UI via Vercel AI SDK (streaming React components) -- Multi-model support (Grok, OpenAI, Google, Bedrock, …) -- Efficient task routing to minimize unnecessary LLM calls -- Persistent chat + map state (Redis + database) -- Mobile-responsive chat + map experience +Welcome! Please see the issues for items that need attention, and below for some tools to aid in development and debugging. We're working to interpolate chat functionality onto the map module. -
+Documentation. -## Tech Stack +https://deepwiki.com/QueueLab/QCX -| Category | Technology | -|------------------------|-----------------------------------------| -| Framework | [Next.js 15](https://nextjs.org/) (App Router + RSC) | -| Language | TypeScript 5.x + React 19 | -| Runtime | [Bun](https://bun.sh/) | -| AI SDK | [Vercel AI SDK](https://sdk.vercel.ai/) | -| Models | Grok, OpenAI, Google, Amazon Bedrock, … | -| Search / RAG | [Tavily](https://tavily.com/), [Exa](https://exa.ai/) | -| Database / Cache | [Upstash Redis](https://upstash.com/), PostgreSQL + Drizzle | -| UI Components | [shadcn/ui](https://ui.shadcn.com/), [Radix UI](https://www.radix-ui.com/) | -| Styling | [Tailwind CSS](https://tailwindcss.com/) + Framer Motion | -| Maps | [Mapbox GL JS](https://www.mapbox.com/) + Mapbox Draw | -| Alternative Maps | Google Maps (optional) | +### Running the app on your own machine -
-## Quick Start – Run Locally +## Stack -### 1. Prerequisites +- App framework: [Next.js](https://nextjs.org/) +- Text streaming / Generative UI: [Vercel AI SDK](https://sdk.vercel.ai/docs) +- Generative Model [Varies](https://openai.com/) +- Search API: [Tavily AI](https://tavily.com/) / [Exa AI](https://exa.ai/) +- Serverless Database: [Upstash](https://upstash.com/) +- Component library: [shadcn/ui](https://ui.shadcn.com/) +- Headless component primitives: [Radix UI](https://www.radix-ui.com/) +- Styling: [Tailwind CSS](https://tailwindcss.com/) +- Mapping : [Mapbox] +(https://www.mapbox.com/) -- [Bun](https://bun.sh/) ≥ 1.1 -- Node.js (only for compatibility checks — app runs on Bun) -```bash -# Install Bun (if not already installed) -curl -fsSL https://bun.sh/install | bash -``` -### 2. Clone & Install +### 2. Install dependencies -```bash -git clone https://github.com/QueueLab/QCX.git -cd QCX +``` +install bun package manager bun install +bun run build +bun run dev ``` -### 3. Environment Variables +### 3. Setting up Upstash Redis + +Follow the guide below to set up Upstash Redis. Create a database and obtain `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`. Refer to the [Upstash guide](https://upstash.com/blog/rag-chatbot-upstash#setting-up-upstash-redis) for instructions on how to proceed. -```bash +### 4. Fill out secrets + +``` cp .env.local.example .env.local ``` -Fill in `.env.local`: +Your .env.local file should look like this: -```env -# AI providers (at least one required) -XAI_API_KEY= # https://console.x.ai -OPENAI_API_KEY= # optional -GOOGLE_API_KEY= # optional (Gemini) -BEDROCK_ACCESS_KEY_ID= # optional -BEDROCK_SECRET_ACCESS_KEY= # optional +``` +# XAI API key retrieved here: https://platform.openai.com/api-keys +XAI_API_KEY= -# Search +# Tavily API Key retrieved here: https://app.tavily.com/home TAVILY_API_KEY= -# or -EXA_API_KEY= -# Upstash Redis (for rate limiting, caching, prompt storage) +# Upstash Redis URL and Token retrieved here: https://console.upstash.com/redis UPSTASH_REDIS_REST_URL= UPSTASH_REDIS_REST_TOKEN= - -# Mapbox (required for maps) -NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN= - -# Optional: database, analytics, etc. +#Mapbox access token +NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN ``` -> **Note**: Complex generative UI and tool calling works best with frontier models (Grok family, GPT-4o, Claude 3.5/4, Gemini 1.5/2, etc.). Smaller or local models may not format output correctly. -### 4. Run -```bash -bun run dev -# or for production build preview -bun run build && bun run start -``` - -Open http://localhost:3000 -
-## Contributing +_Note: This project focuses on Generative UI and requires complex output from LLMs. Currently, it's assumed that the official state of the art models will be used. Although it's possible to set up other models, if you use an Standard-compatible model, but we don't guarantee that it'll work._ -We welcome contributions — especially around: +### 5. Run app locally -- Better map ↔ chat integration -- New geospatial tools / agents -- UI/UX polish (mobile especially) -- Model output parsing robustness -- Performance optimizations - -1. See open [issues](https://github.com/QueueLab/QCX/issues) -2. Fork & create a branch -3. Submit a PR with clear description - -Read the in-depth architecture & component docs here: -→ https://deepwiki.com/QueueLab/QCX - -
- -## Verified Models (Stable Output Formatting) - -- Grok-3-mini -- (add more models as tested — PRs welcome!) - -Models with reasoning / heavy tool calling can sometimes break generative UI — test carefully. - -
- -
- -**QCX** — Language is the new UI for exploring worlds. +``` +bun run dev +``` -Made with curiosity by [QueueLab](https://github.com/QueueLab) +You can now visit http://localhost:3000. -
+## Verified models +List of non reasoning verified models +Grok-3-mini diff --git a/components/header-search-button.tsx b/components/header-search-button.tsx index 04896457..97e73ff1 100644 --- a/components/header-search-button.tsx +++ b/components/header-search-button.tsx @@ -164,9 +164,12 @@ export function HeaderSearchButton() { ) const mobileButton = ( - ) diff --git a/components/header.tsx b/components/header.tsx index fd80bc44..d354120a 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -44,7 +44,7 @@ export const Header = () => { return ( <> setIsPurchaseOpen(false)} /> -
+
Chat diff --git a/components/mobile-icons-bar.tsx b/components/mobile-icons-bar.tsx index d35d4bb2..379764bc 100644 --- a/components/mobile-icons-bar.tsx +++ b/components/mobile-icons-bar.tsx @@ -19,6 +19,8 @@ import { MapToggle } from './map-toggle' import { ModeToggle } from './mode-toggle' import { ProfileToggle } from './profile-toggle' import { useCalendarToggle } from './calendar-toggle-context' +import { useUsageToggle } from './usage-toggle-context' +import { useProfileToggle } from './profile-toggle-context' interface MobileIconsBarProps { onAttachmentClick: () => void; @@ -29,6 +31,16 @@ export const MobileIconsBar: React.FC = ({ onAttachmentClic const [, setMessages] = useUIState() const { clearChat } = useActions() const { toggleCalendar } = useCalendarToggle() + const { toggleUsage, isUsageOpen } = useUsageToggle() + const { activeView, closeProfileView } = useProfileToggle() + + const handleUsageToggle = () => { + // If we're about to open usage and profile is open, close profile first + if (!isUsageOpen && activeView) { + closeProfileView() + } + toggleUsage() + } const handleNewChat = async () => { setMessages([]) @@ -45,14 +57,10 @@ export const MobileIconsBar: React.FC = ({ onAttachmentClic - - - - diff --git a/components/purchase-credits-popup.tsx b/components/purchase-credits-popup.tsx index 18946b99..b846f81b 100644 --- a/components/purchase-credits-popup.tsx +++ b/components/purchase-credits-popup.tsx @@ -19,7 +19,7 @@ interface PurchaseCreditsPopupProps { export function PurchaseCreditsPopup({ isOpen, onClose }: PurchaseCreditsPopupProps) { const handlePurchase = () => { - window.open('https://www.paypal.com/ncp/payment/G82N9CZ6ZLLCQ', '_blank'); + window.open('https://buy.stripe.com/14A3cv7K72TR3go14Nasg02', '_blank'); onClose(); }; diff --git a/components/usage-view.tsx b/components/usage-view.tsx index 728641ba..c7d3aaa8 100644 --- a/components/usage-view.tsx +++ b/components/usage-view.tsx @@ -32,7 +32,7 @@ export function UsageView() {
Free -
diff --git a/patch_header_search.js b/patch_header_search.js new file mode 100644 index 00000000..6c43095f --- /dev/null +++ b/patch_header_search.js @@ -0,0 +1,16 @@ +const fs = require('fs'); +let content = fs.readFileSync('components/header-search-button.tsx', 'utf8'); + +const regex = /const mobileButton = \([\s\S]*?<\/Button>\n \)/; +const replacement = `const mobileButton = ( + + )`; + +content = content.replace(regex, replacement); +fs.writeFileSync('components/header-search-button.tsx', content); diff --git a/patch_mobile_icons.js b/patch_mobile_icons.js new file mode 100644 index 00000000..79416b04 --- /dev/null +++ b/patch_mobile_icons.js @@ -0,0 +1,7 @@ +const fs = require('fs'); +let content = fs.readFileSync('components/mobile-icons-bar.tsx', 'utf8'); + +const searchButtonRegex = /`; + +content = content.replace(linkRegex, replacement); + +// The `useProfileToggle` was actually already imported? +// Let's remove duplicate imports just in case. + +fs.writeFileSync('components/mobile-icons-bar.tsx', content); From 169a1405ed0f10516b3213c22e405daa8530522e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:00:51 +0000 Subject: [PATCH 2/2] fix: ensure resolution search portal initializes on mobile and reduce padding Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com> --- app/globals.css | 10 ++++---- components/header-search-button.tsx | 21 +++++++++++++++- patch_header_portal.js | 37 +++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 patch_header_portal.js diff --git a/app/globals.css b/app/globals.css index 22f95cba..90cf1a1c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -131,7 +131,7 @@ border-bottom: 1px solid hsl(var(--border)); display: flex; align-items: center; - padding: 0 10px; + padding: 0 5px; z-index: 20; overflow-x: auto; -webkit-overflow-scrolling: touch; @@ -144,8 +144,8 @@ .mobile-icons-bar-content { display: flex; - gap: 20px; - padding: 0 10px; + gap: 10px; + padding: 0 5px; min-width: max-content; /* justify-content: space-between; */ } @@ -189,7 +189,7 @@ .mobile-chat-input-area { height: auto; - padding: 10px; + padding: 5px; background-color: hsl(var(--background)); /* border-top: 1px solid hsl(var(--border)); */ /* Removed for cleaner separation */ border-bottom: 1px solid hsl(var(--border)); /* Added for separation from messages area below */ @@ -205,7 +205,7 @@ /* left: 0; */ /* Handled by parent flex */ /* right: 0; */ /* Handled by parent flex */ width: 100%; /* Ensure it takes full width of its container */ - padding: 10px; + padding: 5px; background-color: hsl(var(--background)); /* border-top: 1px solid hsl(var(--border)); */ /* Removed to avoid double border */ /* z-index: 30; */ /* No longer needed */ diff --git a/components/header-search-button.tsx b/components/header-search-button.tsx index 97e73ff1..7090a52c 100644 --- a/components/header-search-button.tsx +++ b/components/header-search-button.tsx @@ -33,7 +33,26 @@ export function HeaderSearchButton() { useEffect(() => { // Portals can only be used on the client-side after the DOM has mounted setDesktopPortal(document.getElementById('header-search-portal')) - setMobilePortal(document.getElementById('mobile-header-search-portal')) + + // Mobile portal might mount later, so check periodically + const checkMobilePortal = () => { + const el = document.getElementById('mobile-header-search-portal') + if (el) { + setMobilePortal(el) + return true + } + return false + } + + if (!checkMobilePortal()) { + const interval = setInterval(() => { + if (checkMobilePortal()) { + clearInterval(interval) + } + }, 500) + + return () => clearInterval(interval) + } }, []) const handleResolutionSearch = async () => { diff --git a/patch_header_portal.js b/patch_header_portal.js new file mode 100644 index 00000000..8bbe5526 --- /dev/null +++ b/patch_header_portal.js @@ -0,0 +1,37 @@ +const fs = require('fs'); +let content = fs.readFileSync('components/header-search-button.tsx', 'utf8'); + +const oldUseEffect = ` useEffect(() => { + // Portals can only be used on the client-side after the DOM has mounted + setDesktopPortal(document.getElementById('header-search-portal')) + setMobilePortal(document.getElementById('mobile-header-search-portal')) + }, [])`; + +const newUseEffect = ` useEffect(() => { + // Portals can only be used on the client-side after the DOM has mounted + setDesktopPortal(document.getElementById('header-search-portal')) + + // Mobile portal might mount later, so check periodically + const checkMobilePortal = () => { + const el = document.getElementById('mobile-header-search-portal') + if (el) { + setMobilePortal(el) + return true + } + return false + } + + if (!checkMobilePortal()) { + const interval = setInterval(() => { + if (checkMobilePortal()) { + clearInterval(interval) + } + }, 500) + + return () => clearInterval(interval) + } + }, [])`; + +content = content.replace(oldUseEffect, newUseEffect); + +fs.writeFileSync('components/header-search-button.tsx', content);