Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Each kit includes configuration instructions, environment variables/lamatic-conf
||
| **🧑‍💼 Assistant Kits** | Create context-aware helpers for users, customers, and team members | | | [`/kits/assistant`](./kits/assistant) |
| **Grammar Assistant** | A chrome extension to check grammar corrections across your selection. | Available | | [`/kits/assistant/grammar-extension`](./kits/assistant/grammar-extension) |
| **Legal Assistant** | Research legal questions against a Lamatic-connected legal corpus with citations, next steps, and a standing disclaimer. | Available | | [`/kits/assistant/legal-assistant`](./kits/assistant/legal-assistant) |
||
| **💬 Embed Kits** | Seamlessly integrate AI agents into apps, websites, and workflows | | | [`/kits/embed`](./kits/embed) |
| **Chatbot** | A Next.js starter kit for chatbot using Lamatic Flows. | Available | [![Live Demo](https://img.shields.io/badge/Live%20Demo-black?style=for-the-badge)](https://agent-kit-embedded-chat.vercel.app) | [`/kits/embed/chat`](./kits/embed/chat) |
Expand Down
4 changes: 4 additions & 0 deletions kits/assistant/legal-assistant/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ASSISTANT_LEGAL_CHATBOT=ASSISTANT_LEGAL_CHATBOT_FLOW_ID
LAMATIC_API_KEY=LAMATIC_API_KEY
LAMATIC_API_URL=LAMATIC_API_URL
LAMATIC_PROJECT_ID=LAMATIC_PROJECT_ID
31 changes: 31 additions & 0 deletions kits/assistant/legal-assistant/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules

# next.js
/.next/
/out/

# production
/build

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
.next-dev*.log

# env files
.env
.env.local
.env.*.local
!.env.example

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
92 changes: 92 additions & 0 deletions kits/assistant/legal-assistant/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Legal Assistant

Legal Assistant is a Lamatic-powered research kit for answering legal questions against a connected legal corpus. It is built for statutes, regulations, case summaries, internal policy libraries, or legal memos that you have already indexed in Lamatic.

The UI asks for 3 things:

1. **Jurisdiction** so the answer is framed in the right legal system.
2. **Context** so the assistant sees the facts and procedural posture.
3. **Question** so the flow can return a research-style answer.

Every response is framed as informational only and pushes for citations plus practical next steps.

## Prerequisites

- Node.js 18+
- A Lamatic project with the bundled legal RAG flow imported and deployed
- A Lamatic-backed legal knowledge base or vector store connected to that flow

## Environment Variables

Create a `.env` file in this directory:

```bash
ASSISTANT_LEGAL_CHATBOT="your-deployed-flow-id"
LAMATIC_API_URL="https://your-org.lamatic.dev"
LAMATIC_PROJECT_ID="your-project-id"
LAMATIC_API_KEY="your-api-key"
```

## Lamatic Setup

Import `flows/legal-rag-chatbot/` into Lamatic Studio, connect the RAG node to your legal corpus, then deploy it and copy the deployed flow ID into `ASSISTANT_LEGAL_CHATBOT`.

The flow is meant to sit on top of legal source material such as:

- public statutes and regulations
- internal policy manuals
- legal knowledge bases
- case summaries or research notes

## Run Locally

```bash
cd kits/assistant/legal-assistant
npm install
cp .env.example .env
npm run dev
```

Then open `http://localhost:3000`.

## API Route

The frontend posts to `POST /api/legal` with:

```json
{
"jurisdiction": "California",
"context": "Commercial lease, 2 months unpaid rent, no prior default notices.",
"question": "What notice is typically required before termination?"
}
```

The route packages that into a Lamatic `chatMessage`, executes the deployed legal RAG flow, and returns:

```json
{
"answer": "Research-style legal response...",
"disclaimer": "Informational only, not legal advice...",
"jurisdiction": "California"
}
```

## Project Structure

```text
kits/assistant/legal-assistant/
├── app/
│ ├── api/legal/route.ts
│ ├── layout.tsx
│ └── page.tsx
├── flows/
│ └── legal-rag-chatbot/
├── .env.example
├── config.json
├── package.json
└── README.md
```

## Important Note

This kit is for legal research support. It should not be presented as legal advice, and the bundled UI keeps that disclaimer visible by default.
209 changes: 209 additions & 0 deletions kits/assistant/legal-assistant/app/api/legal/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { NextRequest, NextResponse } from "next/server"

const query = `
query ExecuteWorkflow($workflowId: String!, $chatMessage: String) {
executeWorkflow(
workflowId: $workflowId
payload: { chatMessage: $chatMessage }
) {
status
result
}
}
`

const DISCLAIMER =
"Informational only, not legal advice. Verify the answer against current law in your jurisdiction and consult a qualified attorney before acting."

type LamaticIssue = {
message?: string
}

type LamaticGraphqlResponse = {
data?: {
executeWorkflow?: {
status?: string
result?: unknown
}
}
errors?: LamaticIssue[]
message?: string
}

function getGraphqlUrl(apiUrl: string) {
const trimmed = apiUrl.trim().replace(/\/+$/, "")
return trimmed.endsWith("/graphql") ? trimmed : `${trimmed}/graphql`
}

function extractAnswer(result: unknown): string {
if (typeof result === "string") {
return result.trim()
}

if (!result || typeof result !== "object") {
return ""
}

const record = result as Record<string, unknown>
const candidateKeys = [
"answer",
"response",
"content",
"generatedResponse",
"modelResponse",
]

for (const key of candidateKeys) {
const value = record[key]
if (typeof value === "string" && value.trim()) {
return value.trim()
}
}

return JSON.stringify(result, null, 2)
}

export async function POST(req: NextRequest) {
try {
const body = await req.json()
const question = typeof body?.question === "string" ? body.question.trim() : ""
const jurisdiction =
typeof body?.jurisdiction === "string" ? body.jurisdiction.trim() : ""
const context = typeof body?.context === "string" ? body.context.trim() : ""

if (!question) {
return NextResponse.json(
{ error: "A legal question is required." },
{ status: 400 }
)
}

const lamaticApiKey = process.env.LAMATIC_API_KEY?.trim()
const lamaticApiUrl = process.env.LAMATIC_API_URL?.trim()
const lamaticProjectId = process.env.LAMATIC_PROJECT_ID?.trim()
const workflowId = process.env.ASSISTANT_LEGAL_CHATBOT?.trim()

const missingEnv = [
!lamaticApiKey && "LAMATIC_API_KEY",
!lamaticApiUrl && "LAMATIC_API_URL",
!lamaticProjectId && "LAMATIC_PROJECT_ID",
!workflowId && "ASSISTANT_LEGAL_CHATBOT",
].filter(Boolean)

if (missingEnv.length > 0) {
return NextResponse.json(
{
error: `Missing required Lamatic environment variables: ${missingEnv.join(
", "
)}.`,
},
{ status: 500 }
)
}

const combinedPrompt = [
jurisdiction ? `Jurisdiction: ${jurisdiction}` : "Jurisdiction: unspecified",
context ? `Context: ${context}` : null,
`Question: ${question}`,
"Answer for informational purposes only. Cite the relevant law, statute, regulation, or source when possible. Close with practical next steps and a short reminder that this is not legal advice.",
]
.filter(Boolean)
.join("\n\n")

const res = await fetch(getGraphqlUrl(lamaticApiUrl), {
method: "POST",
headers: {
Authorization: `Bearer ${lamaticApiKey}`,
"Content-Type": "application/json",
"x-project-id": lamaticProjectId,
},
body: JSON.stringify({
query,
variables: {
workflowId,
chatMessage: combinedPrompt,
},
}),
signal: AbortSignal.timeout(60_000),
})

const raw = await res.text()
const trimmed = raw.trim()
let data: LamaticGraphqlResponse | null = null

if (trimmed) {
try {
data = JSON.parse(trimmed) as LamaticGraphqlResponse
} catch {
data = null
}
}

if (!res.ok) {
const upstreamMessage =
data?.errors
?.map((error) => error?.message)
.filter(Boolean)
.join("; ") ||
data?.message ||
(trimmed.startsWith("<")
? "Lamatic returned HTML instead of JSON."
: "Lamatic returned an unsuccessful response.")

return NextResponse.json(
{ error: `Lamatic request failed (${res.status}): ${upstreamMessage}` },
{ status: 502 }
)
}

if (!data || typeof data !== "object") {
return NextResponse.json(
{ error: "Lamatic returned an invalid non-JSON response." },
{ status: 502 }
)
}

if (Array.isArray(data.errors) && data.errors.length > 0) {
const message = data.errors
.map((error) => error?.message)
.filter(Boolean)
.join("; ")

return NextResponse.json(
{ error: message || "Lamatic returned GraphQL errors." },
{ status: 502 }
)
}

const execution = data.data?.executeWorkflow

if (execution?.status?.toLowerCase() === "error") {
return NextResponse.json(
{ error: "Lamatic reported an execution error." },
{ status: 502 }
)
}

const answer = extractAnswer(execution?.result)

if (!answer) {
return NextResponse.json(
{ error: "Lamatic returned an empty legal response." },
{ status: 502 }
)
}

return NextResponse.json({
answer,
disclaimer: DISCLAIMER,
jurisdiction: jurisdiction || "Unspecified",
})
} catch (error) {
console.error("Legal route failed", error)

return NextResponse.json(
{ error: "The legal assistant route failed before it could complete the request." },
{ status: 500 }
)
}
}
11 changes: 11 additions & 0 deletions kits/assistant/legal-assistant/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}

body {
font-family: sans-serif;
background: #fff;
color: #111;
}
19 changes: 19 additions & 0 deletions kits/assistant/legal-assistant/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Metadata } from "next"
import "./globals.css"

export const metadata: Metadata = {
title: "Legal Assistant",
description: "Lamatic-powered legal research assistant with citations, next steps, and a standing disclaimer.",
}

export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
Loading
Loading