Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ on:
jobs:
sync:
name: POST /functions/sync-templates
# Forks provide their own catalog delivery path. Never post a fork commit
# to InsForge's production marketplace sync endpoint.
if: github.repository == 'InsForge/insforge-templates'
runs-on: ubuntu-latest
steps:
- name: Trigger sync
Expand Down
37 changes: 30 additions & 7 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,6 @@ jobs:
working-directory: scripts
run: npm test

# We deliberately do NOT run `npm install` on each template in CI:
# contributors install locally before opening a PR, package-lock.json
# makes the install reproducible, and the slow per-template install
# would dominate CI time as the registry grows. The static checks below
# (JSON parse, file existence, secret patterns, SQL parse, edge-function
# tsc) cover what install would catch except the rare "package was
# yanked from npm" case — we'll catch that via Dependabot/Snyk later.
edge-function-tsc:
name: tsc --noEmit on edge functions
runs-on: ubuntu-latest
Expand Down Expand Up @@ -92,3 +85,33 @@ jobs:
exit 1
fi
done

publishable-builds:
name: Native template build and artifact admission
runs-on: ubuntu-latest
needs: [validate-registry]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Enable pinned package managers
run: corepack enable
- name: Build every native template with its controlled profile
run: |
{ node -e "for (const item of require('./registry.json')) if (item.publishingCompatibility === 'native') console.log(item.slug + '\t' + item.buildProfile)"; printf 'react\tvite-npm-v1\ntodo\tnext-static-npm-v1\n'; } |
while IFS=$'\t' read -r slug profile; do
case "$profile" in
vite-npm-v1|next-static-npm-v1)
(cd "$slug" && npm ci --ignore-scripts && npm run build)
;;
vite-pnpm-v1|next-static-pnpm-v1)
(cd "$slug" && pnpm install --frozen-lockfile --ignore-scripts && pnpm run build)
;;
*)
echo "::error::$slug declares unknown controlled profile $profile"
exit 1
;;
esac
node scripts/verify-publishable-template.mjs "$slug"
done
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ Templates listed on https://insforge.dev/templates live in this repo. Adding one
"framework": "nextjs",
"features": ["auth", "ai"],
"tags": ["my-tag"],
"requiredCapabilities": ["ai.chat", "ai.streaming", "storage"],
"cover": "assets/covers/my-template.png",
"demo_url": "https://my-demo.us-east.insforge.app",
"author": "Your Name",
"added_at": "YYYY-MM-DD"
}
```

`requiredCapabilities` is optional. Declare only capabilities the template actually uses;
Insight Flow rejects creation before provisioning when the active runtime cannot satisfy them.

6. Open the PR. CI (`Validate Registry`) must be green. A maintainer will review and merge.

## CI checks
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ If you prefer to inspect a template manually, clone this repository, move into a

| Template | Framework | Best for | Includes |
| --- | --- | --- | --- |
| [`chatbot`](./chatbot) | Next.js App Router | Building an AI chat product on top of InsForge | Persisted chat history, file uploads, auth, storage, optional Vercel AI Gateway support |
| [`chatbot`](./chatbot) | Next.js App Router | Building an AI chat product on the Insight Flow runtime | Persisted chat history, streaming AI, file uploads, auth, and Platform-backed object storage |
| [`crm`](./crm) | Next.js App Router | Building an authenticated internal tool or CRM | Sales pipeline, lead management, client flows, RLS, seeded defaults |
| [`e-commerce`](./e-commerce) | Next.js App Router | Launching a storefront with user accounts and checkout | Seeded catalog, product pages, cart, checkout, account area, analytics |
| [`web-research-agent`](./web-research-agent) | React + Vite | Building evidence-led browser research workflows | Browser-backed investigations, cited synthesis, evidence ledger, auth, and RLS |
| [`website-change-monitor`](./website-change-monitor) | React + Vite | Tracking meaningful changes across important web pages | Scheduled checks, durable snapshots, before/after diffs, AI classification, auth, and RLS |

### Auth Provider Overlays

Expand Down Expand Up @@ -100,6 +102,8 @@ For full setup details, go directly to the template README you want to use:
- [`chatbot/README.md`](./chatbot/README.md)
- [`crm/README.md`](./crm/README.md)
- [`e-commerce/README.md`](./e-commerce/README.md)
- [`web-research-agent/README.md`](./web-research-agent/README.md)
- [`website-change-monitor/README.md`](./website-change-monitor/README.md)
- [`nextjs/README.md`](./nextjs/README.md)
- [`react/README.md`](./react/README.md)

Expand Down
1 change: 1 addition & 0 deletions admin-dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</head>
<body>
<div id="root"></div>
<script src="/.well-known/insforge-runtime-config.js"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
7 changes: 6 additions & 1 deletion admin-dashboard/src/lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ const schema = z.object({
VITE_INSFORGE_ANON_KEY: z.string().min(1),
})

const parsed = schema.safeParse(import.meta.env)
const runtime = (window as Window & { __INSFORGE_RUNTIME_CONFIG__?: { apiBaseURL?: string; anonKey?: string } }).__INSFORGE_RUNTIME_CONFIG__
const parsed = schema.safeParse({
...import.meta.env,
VITE_INSFORGE_URL: runtime?.apiBaseURL ?? import.meta.env.VITE_INSFORGE_URL,
VITE_INSFORGE_ANON_KEY: runtime?.anonKey ?? import.meta.env.VITE_INSFORGE_ANON_KEY,
})

if (!parsed.success) {
// Surface a clear message in the browser console so missing config is obvious.
Expand Down
Binary file added assets/covers/chatbot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 2 additions & 10 deletions chatbot/.env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
NEXT_PUBLIC_INSFORGE_URL=https://your-project.region.insforge.app
NEXT_PUBLIC_INSFORGE_ANON_KEY=your-anon-key
NEXT_PUBLIC_APP_URL=https://your-project.insforge.site

# AI provider: "insforge" (default) or "vercel" (Vercel AI Gateway)
# AI_PROVIDER=insforge

# --- Vercel AI Gateway (only when AI_PROVIDER=vercel) ---
# Gateway auth (optional on Vercel deployments — OIDC is used automatically)
# AI_GATEWAY_API_KEY=your-gateway-api-key

# Provider API keys — add one for each provider you want to use.
# OPENAI_API_KEY=sk-...
# Optional. Leave unset to use the model selected by the Platform runtime.
# INSFORGE_AI_MODEL=provider/model
23 changes: 23 additions & 0 deletions chatbot/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Platform-managed AI and Storage contract

This template declares `ai.chat`, `ai.streaming`, and `storage`. Those runtime integrations are
part of the template contract even when an application adapts the framework, interface, or product
flow.

## AI

- Send model requests through the current application's InsForge Model Gateway. Prefer
`client.ai.chat.completions.create(...)` from `@insforge/sdk`.
- Omit `model` by default so the server-owned `AI_DEFAULT_MODEL` selects the configured model.
Pass an explicit model only when the product requirements demand an allowed override.
- Never read a provider API key from application or Edge Function code, and never call OpenRouter,
DeepSeek, LiteLLM, OpenAI, Anthropic, or another provider endpoint directly.
- Exercise the product's real model path before delivery. A fallback-only result does not satisfy
acceptance; verify the real model response and the explicit failure fallback separately.

## Storage

- Use InsForge Storage through `@insforge/sdk`. Browser and application source may contain only the
public InsForge endpoint and Anon Key.
- Never read or ship COS/S3 access keys. Bucket routing and the application prefix are owned by the
application's InsForge runtime.
21 changes: 21 additions & 0 deletions chatbot/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 InsForge and Lexmount contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
72 changes: 16 additions & 56 deletions chatbot/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
<a href="https://insforge.dev">
<h1 align="center">InsForge Chatbot Starter</h1>
<h1 align="center">Lexmount AI Chat + Storage Starter</h1>
</a>

<p align="center">
A Next.js chatbot starter with InsForge auth, database, storage, and optional Vercel AI Gateway support.
A Next.js starter that uses the AI model and object storage already configured by Insight Flow.
</p>

<p align="center">
<a href="#features"><strong>Features</strong></a> ·
<a href="#demo"><strong>Demo</strong></a> ·
<a href="#quick-launch"><strong>Quick Launch</strong></a> ·
<a href="#run-locally"><strong>Run locally</strong></a> ·
<a href="#vercel-ai-gateway"><strong>Vercel AI Gateway</strong></a> ·
<a href="#deploy-to-vercel"><strong>Deploy to Vercel</strong></a> ·
<a href="#first-try"><strong>First Try</strong></a>
</p>
Expand All @@ -25,31 +23,23 @@
- [Next.js](https://nextjs.org) App Router
- Streaming chat UI with persisted history and file attachments
- [InsForge](https://insforge.dev) auth, database, storage, and AI
- Optional routing through [Vercel AI Gateway](https://vercel.com/docs/ai-gateway)
- Multi-provider model selection with `provider/model` IDs
- Platform-owned AI credentials and default model; no provider key in application code
- InsForge Storage backed by the Platform-owned S3-compatible provider (Tencent COS in Lexmount)
- Optional model override with `provider/model` IDs
- [shadcn/ui](https://ui.shadcn.com) components
- Styling with [Tailwind CSS](https://tailwindcss.com)

## Demo

Demo: [demochatbot.insforge.site](https://demochatbot.insforge.site)

The starter includes a simple first-try chat experience, persisted history, file uploads, authentication, and optional routing through the Vercel AI Gateway.

## Quick Launch

If you want the fastest path, use the InsForge CLI and follow the prompts:

```bash
npx @insforge/cli create
```

From there:
In Insight Flow, choose **AI Chat + Storage Starter** when creating an application. The
Platform checks `ai.chat`, `ai.streaming`, and `storage` before provisioning, installs the
database migration and storage bucket, and generates `.env.local` with only the public InsForge
endpoint and anon key.

1. Choose the chatbot template
2. Create or connect your InsForge project
3. Let the CLI set up the project files
4. Choose to deploy with [InsForge](https://insforge.dev) automatically from the guided flow
The model selector starts at **Platform default**. That option intentionally omits the model from
the request, allowing the server-owned `AI_DEFAULT_MODEL` to select DeepSeek, LiteLLM, or another
configured OpenAI-compatible model. Files are uploaded through InsForge Storage; the application
never receives COS credentials.

Use the local setup below if you want to inspect the repo, edit environment variables manually, or control the setup step by step.

Expand All @@ -58,7 +48,7 @@ Use the local setup below if you want to inspect the repo, edit environment vari
1. Clone the repository and move into the chatbot template:

```bash
git clone https://github.com/InsForge/insforge-templates.git
git clone https://github.com/lexmount/insforge-templates.git
cd insforge-templates/chatbot
```

Expand All @@ -85,7 +75,6 @@ Use the local setup below if you want to inspect the repo, edit environment vari
```env
NEXT_PUBLIC_INSFORGE_URL=https://your-project.region.insforge.app
NEXT_PUBLIC_INSFORGE_ANON_KEY=your-public-anon-key
NEXT_PUBLIC_APP_URL=http://localhost:3000
```

7. Apply the included schema and seed data to your InsForge project. You can either ask your agent using this prompt:
Expand All @@ -108,45 +97,16 @@ Use the local setup below if you want to inspect the repo, edit environment vari

9. Open [http://localhost:3000](http://localhost:3000)

## Vercel AI Gateway

To route AI requests through the [Vercel AI Gateway](https://vercel.com/docs/ai-gateway) instead of InsForge AI:

1. Enable the provider:

```env
AI_PROVIDER=vercel
AI_GATEWAY_API_KEY=your-gateway-api-key
```

On Vercel deployments `AI_GATEWAY_API_KEY` is optional — the gateway authenticates automatically via OIDC.

2. Add provider credentials for the models you want to use. To get started, `OPENAI_API_KEY` is enough for `openai/*` models:

```env
OPENAI_API_KEY=sk-...
```

If you want to use other providers from the model picker, add the matching provider key in your environment or configure it in Vercel AI Gateway settings. Selecting a model whose provider key is missing will return a clear error in the chat UI — there is no silent fallback to another provider.

Alternatively, if deploying on Vercel, you can configure provider keys in the Vercel dashboard under AI Gateway settings instead of using environment variables.

**Current limitations:**
- PDF file parsing (`fileParser`) is InsForge-only. PDFs are forwarded as base64 file parts, but results depend on the model. A toast warning is shown when this applies.
- InsForge is still required for auth, database, storage, and file uploads regardless of AI provider.

## Deploy to Vercel

After cloning the repo and running the starter locally, you can deploy it on Vercel:

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FInsForge%2Finsforge-templates%2Ftree%2Fmain%2Fchatbot&root-directory=chatbot&project-name=insforge-chatbot&repository-name=insforge-chatbot&env=NEXT_PUBLIC_INSFORGE_URL,NEXT_PUBLIC_INSFORGE_ANON_KEY&envDescription=Connect%20your%20InsForge%20project%20URL%20and%20anon%20key.&external-id=https%3A%2F%2Fgithub.com%2FInsForge%2Finsforge-templates%2Ftree%2Fmain%2Fchatbot&demo-title=InsForge%20Chatbot%20Starter&demo-description=A%20Next.js%20chatbot%20starter%20with%20InsForge%20auth%2C%20database%2C%20storage%2C%20and%20optional%20Vercel%20AI%20Gateway%20support.&demo-image=https%3A%2F%2Fraw.githubusercontent.com%2FInsForge%2Finsforge-templates%2Fmain%2Fchatbot%2Fpublic%2Fchatbot-readme-cover.png)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Flexmount%2Finsforge-templates%2Ftree%2Fmain%2Fchatbot&root-directory=chatbot&project-name=insforge-chatbot&repository-name=insforge-chatbot&env=NEXT_PUBLIC_INSFORGE_URL,NEXT_PUBLIC_INSFORGE_ANON_KEY&envDescription=Connect%20your%20InsForge%20project%20URL%20and%20anon%20key.)

1. Set `NEXT_PUBLIC_INSFORGE_URL`
2. Set `NEXT_PUBLIC_INSFORGE_ANON_KEY`
3. Deploy the project
4. In Vercel, open your project, go to `Settings` → `Environment Variables`, and set `NEXT_PUBLIC_APP_URL` to your deployed app URL
5. Redeploy the project
6. In the InsForge dashboard, open `Authentication` → `General` → `Allowed Redirect URLs`, then add your deployed callback URL (for example `https://your-project.vercel.app/auth/callback`)
4. In the InsForge dashboard, open `Authentication` → `General` → `Allowed Redirect URLs`, then add your deployed callback URL (for example `https://your-project.vercel.app/auth/callback`)

## First Try

Expand Down
18 changes: 0 additions & 18 deletions chatbot/components/chat-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,8 @@ import { ChatMarkdown } from '@/components/chat-markdown';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import {
DEFAULT_MODEL,
FILE_INPUT_ACCEPT,
MAX_FILE_SIZE,
MODEL_OPTIONS,
SUGGESTED_PROMPTS,
isAllowedAttachmentFile,
} from '@/lib/constants';
Expand Down Expand Up @@ -290,7 +288,6 @@ export function ChatShell({ initialViewer }: { initialViewer: AuthViewer }) {
const [activeChatId, setActiveChatId] = useState<string | null>(null);
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [input, setInput] = useState('');
const [selectedModel, setSelectedModel] = useState(DEFAULT_MODEL);
const [isBootstrapping, setIsBootstrapping] = useState(true);
const [isLoadingThread, setIsLoadingThread] = useState(false);
const [isSending, setIsSending] = useState(false);
Expand Down Expand Up @@ -646,7 +643,6 @@ export function ChatShell({ initialViewer }: { initialViewer: AuthViewer }) {
...ownerInfo.bodyField,
chatId: activeChatId,
input: trimmedInput,
model: selectedModel,
attachments: currentAttachments.length > 0 ? currentAttachments : undefined,
}),
});
Expand Down Expand Up @@ -1089,20 +1085,6 @@ export function ChatShell({ initialViewer }: { initialViewer: AuthViewer }) {
{isUploading ? <Loader2 className="size-4 animate-spin" /> : <Paperclip className="size-4" />}
<span>Attach file</span>
</Button>
<div className="relative">
<select
className="h-9 appearance-none rounded-full bg-transparent px-3 pr-9 font-medium text-sm transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
value={selectedModel}
onChange={(event) => setSelectedModel(event.target.value)}
>
{MODEL_OPTIONS.map((model) => (
<option key={model.id} value={model.id}>
{model.label}
</option>
))}
</select>
<ChevronsUpDown className="pointer-events-none absolute top-1/2 right-3 size-4 -translate-y-1/2 text-muted-foreground" />
</div>
</div>
<Button
className="rounded-full"
Expand Down
28 changes: 4 additions & 24 deletions chatbot/lib/ai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,11 @@ import type { AIProvider } from '@/lib/ai/types';

export type { AIProvider, AIChatMessage, UserContentPart, FileParserOptions, StreamCompletionParams } from '@/lib/ai/types';

type ProviderName = 'insforge' | 'vercel';

export function getAIProviderName(): ProviderName {
const raw = process.env.AI_PROVIDER?.trim().toLowerCase();

if (!raw || raw === 'insforge') return 'insforge';
if (raw === 'vercel') return 'vercel';

throw new Error(
`Unknown AI_PROVIDER "${raw}". Supported values: insforge, vercel.`,
);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function createAIProvider(insforgeClient?: any): Promise<AIProvider> {
const name = getAIProviderName();

if (name === 'insforge') {
if (!insforgeClient) {
throw new Error('InsForge client is required for the insforge AI provider.');
}
const { createInsforgeAIProvider } = await import('@/lib/ai/providers/insforge');
return createInsforgeAIProvider(insforgeClient as Parameters<typeof createInsforgeAIProvider>[0]);
if (!insforgeClient) {
throw new Error('InsForge client is required for AI requests.');
}

const { createVercelAIProvider } = await import('@/lib/ai/providers/vercel');
return createVercelAIProvider();
const { createInsforgeAIProvider } = await import('@/lib/ai/providers/insforge');
return createInsforgeAIProvider(insforgeClient as Parameters<typeof createInsforgeAIProvider>[0]);
}
Loading