OpenChat: The Flutter Chat Client That Treats Research Like a Pipeline

A local-first, multi-provider AI client that streams across providers, survives browser friction, and crawls the web with fallback logic built for real research workflows.

8 min read • View on GitHub • More from mhingston

A wide editorial illustration of a desk-sized research station with a chat window at the center and stacked trays behind it labeled by function. Requests travel through search, browse, extract, stream, and export stages like paper slips on rails, showing that the interface sits on top of a larger information pipeline.
OpenChat looks like a chat app, but the product is really a staged research system with fallback paths.
Key Takeaways

Why OpenChat Feels Like a Research Tool, Not a Wrapper

OpenChat starts with a familiar surface, a chat UI, but the repo is built around a different promise: keep the conversation useful when the task stops being conversational. The real product is the path from question to evidence, with provider switching, local history, and web retrieval stitched together into one workflow.

That matters because most AI clients optimize for the first answer. OpenChat optimizes for the second try, and the third if needed. It assumes providers fail, pages misbehave, and browsers get in the way, then builds around those failures instead of pretending they do not exist.

The Tiered Search Stack

The most distinctive part of the repo is its search ladder. OpenChat does not commit to one web source. It checks for Tavily first, then Brave, then DuckDuckGo scraping, which means the app can keep working even when one integration is missing or down.

OpenChat’s search pipeline is less a single API call than a sequence of ranked fallbacks, which is why it stays usable under messy real-world conditions.

Search layerWhat it doesWhy it matters
TavilyPrimary search source when availableFast path for structured web results
BraveSecond search optionKeeps the system working when the first source is unavailable
DuckDuckGo scrapingLast-resort search fallbackPreserves usefulness without depending on one commercial API
A close editorial illustration of a single query splitting into three ranked branches. One path dead-ends, one yields a search result, and one leads to a cleaned article page, while small labels show the fallback sequence from search to reader to crawl to recurse.
The search stack is designed as a ladder of recovery, not a single dependency chain.

From Search Result to Readable Page

OpenChat’s second-stage intelligence lives in WebPageBrowseService. It does not stop at a link list. It tries Jina Reader for cleaner extraction, falls back to Firecrawl when needed, and can keep walking if the first page looks like a homepage instead of the actual story.

That recursive step is the telling detail. A lot of research tools retrieve pages. Fewer of them ask whether the page is the answer or just a doorway to it. OpenChat does, and that changes the quality of the output more than a new model ever would.

Future<String> browse(String url) async {
  final readable = await jinaReader.read(url);
  if (readable.isNotEmpty) return readable;

  final firecrawl = await firecrawl.read(url);
  if (firecrawl.isNotEmpty) return firecrawl;

  if (looksLikeHomepage(firecrawl)) {
    final articleUrl = await followBestLink(firecrawl);
    return browse(articleUrl);
  }

  return firecrawl;
}

How OpenChat Talks to So Many Providers

The multi-provider story is not just a settings screen. The repo’s openai_compatible_client.dart layer manually adapts to different streaming formats, including OpenAI-style SSE, Anthropic behavior, and Ollama’s line-delimited output. That is what makes the app feel like a true client instead of a thin wrapper.

This matters because provider diversity usually breaks on the messy edge cases. OpenChat leans into those edges: it branches by provider type, handles stream parsing directly, and keeps the conversation moving even when the transport shape changes underneath it.

Provider behaviorTypical wrapperOpenChat
Streaming formatOften assumes one SDK shapeBranches for OpenAI, Anthropic, and Ollama patterns
Failure handlingStops at the first bad responseRetries and adapts at the client layer
PortabilityLocked to one vendor flowWorks as a multi-provider front end

Why the App Feels Local-First

OpenChat also behaves like software that expects to be kept. Conversation history lives locally through lightweight persistence, prompt templates are reusable, and export support makes chats portable. The goal is not just convenience. It is ownership.

That local-first posture is a product decision, not a marketing line. It says the client should preserve work, not just broker access to a service.

The Browser Problem, Solved Pragmatically

Flutter Web introduces a familiar trap: the browser does not let a client call every API endpoint directly. OpenChat answers with a CORS proxy in tool/, which is exactly the kind of practical escape hatch that makes a web app usable for developers instead of merely demoable.

It is a small detail with outsized value. Web readiness is often where experimental AI clients fall apart. Here it is treated as part of the product surface, not a footnote.

FeatureTypical LLM chat appOpenChat
Research depthOne search or noneTiered search plus recursive browsing
Provider supportSingle vendor or narrow API setOpenAI-compatible plus Anthropic and Ollama paths
Local ownershipOften ephemeralLocal history, export, reusable prompts
Web deploymentUsually fragile in browsersExplicit CORS workaround for web use

What This Architecture Suggests

OpenChat is a sign that AI clients are becoming orchestration layers. The competitive advantage is shifting away from the prettiest chat surface and toward the quality of the pipeline behind it: fallback logic, extraction, recursion, and portability.

That is why this repo is more interesting than a standard chatbot clone. It treats failure as part of the workflow, which is exactly what real research software has to do. The interface is only the visible edge of a system that keeps trying until it gets something worth reading.