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** | A Next.js legal assistant chatbot that returns informational summaries, references, next steps, and a legal disclaimer. | Available | [![Live Demo](https://img.shields.io/badge/Live%20Demo-black?style=for-the-badge)](https://legal-drab-three.vercel.app/) | [`/kits/assistant/legal`](./kits/assistant/legal) |
||
| **💬 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/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ASSISTANT_LEGAL_ADVISOR="your-flow-id-here"
LAMATIC_API_URL="https://your-lamatic-api-url"
LAMATIC_PROJECT_ID="your-project-id"
LAMATIC_API_KEY="your-api-key"
28 changes: 28 additions & 0 deletions kits/assistant/legal/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 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*

# env files
.env*
!.env.example

# vercel
.vercel

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

**Agent Kit Legal Assistant** is a Next.js starter kit for a legal assistant chatbot built on Lamatic Flows.

## What This Kit Does and the Problem It Solves

Many users have legal questions but do not know where to begin, which legal area applies, or what next step to take.
This kit provides a structured legal-information assistant that can:
- Classify the likely legal area from a user question.
- Return informational summaries with context-aware references when available.
- Suggest practical next steps and follow-up questions.
- Always show a legal disclaimer so users understand it is not legal advice.

It is designed to:
- Understand user legal questions with optional jurisdiction/context.
- Return an informational legal summary.
- Include references to statutes, case law, or source materials when available.
- Suggest practical next steps.
- Show a clear disclaimer that responses are not legal advice.

## Important Disclaimer

This project is for informational and educational use only.
It is not legal advice, does not create an attorney-client relationship, and should not replace consultation with a licensed attorney.
Do not submit confidential, privileged, or personally identifying information unless you have reviewed and accepted the data retention and logging policies of Lamatic and your configured model provider.

## Prerequisites and Required Providers

- Node.js 20.9+
- npm 9+
Comment on lines +29 to +30
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Mission briefing: resolve Node.js version conflict before launch.

Line 29 documents Node.js 20.9+, but the PR objective says Node.js 18+. This inconsistency can cause avoidable setup failures for users on Node 18. Please align the README with the actual minimum supported runtime (or update the PR/runtime expectation if 20.9+ is truly required).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kits/assistant/legal/README.md` around lines 29 - 30, The README currently
lists "Node.js 20.9+" while the PR objective states "Node.js 18+"—update the
documented minimum runtime to match the actual supported version: either change
the README entry "Node.js 20.9+" to "Node.js 18+" if 18+ is supported, or update
the PR/runtime configuration to require 20.9+ if features demand it; ensure the
change touches the exact README line containing "Node.js 20.9+" so the
documentation and runtime expectations are consistent.

- Lamatic account and deployed flow
- Lamatic API key, project ID, and API URL
- LLM provider configured in Lamatic flow (for example OpenRouter/OpenAI, Anthropic, etc.)

## Lamatic Setup (Pre and Post)

Before running this project, build and deploy your legal flow in Lamatic, then wire its values into this kit.

Pre: Build in Lamatic
1. Sign in at https://lamatic.ai
2. Create a project
3. Create a legal assistant flow
4. Deploy the flow
5. Export your flow files (`config.json`, `inputs.json`, `meta.json`, `README.md`)
6. Copy your project API values and flow ID

Post: Wire into this repo
1. Create `.env.local`
2. Add Lamatic credentials and flow ID
3. Install and run locally

## Required Environment Variables

Create `.env.local`:

```bash
ASSISTANT_LEGAL_ADVISOR="your-flow-id-here"
LAMATIC_API_URL="https://your-lamatic-api-url"
LAMATIC_PROJECT_ID="your-project-id"
LAMATIC_API_KEY="your-api-key"
```

## Setup and Run Instructions

```bash
cd kits/assistant/legal
npm install
npm run dev
# Open http://localhost:3000
```

## Live Preview

https://legal-drab-three.vercel.app/

## Execution Paths

- Client widget path: the bundled `flows/assistant-legal-advisor/config.json` uses a Chat Widget trigger and works with `components/legal-ask-widget.tsx`.
- Server orchestration path: `actions/orchestrate.ts#getLegalGuidance` expects an API Request trigger flow. Replace the bundled flow export with an API trigger flow if you want server-side `executeFlow` calls.

## Usage Examples

Try prompts like:
- "My landlord did not return my security deposit in Goa, India. What can I do?"
- "I was terminated without notice. What steps should I consider?"
- "A contractor did not complete agreed work after payment in Singapore. What are my options?"

Expected behavior:
- Assistant returns an informational response with a disclaimer.
- Assistant asks for missing jurisdiction details when needed.
- Assistant suggests practical next steps without claiming to be a lawyer.

## Screenshots or GIFs (Optional)

![Legal Assistant UI](./public/legal-assistant-ui.png)

## Flow Export Location

Place your Lamatic export in:

```text
kits/assistant/legal/flows/assistant-legal-advisor/
```

If you use a chat-trigger flow, update the flow `domains` allowlist to your actual local and production origins before deployment.

Expected files:
- `config.json`
- `inputs.json`
- `meta.json`
- `README.md`

## Repo Structure

```text
/actions
orchestrate.ts # Legal assistant flow execution and output mapping
/app
page.tsx # Legal assistant UI
/components
header.tsx # Top navigation
/lib
lamatic-client.ts # Lamatic SDK client
/flows
assistant-legal-advisor/
/package.json
/config.json
/.env.example
```

## Contributing

Follow the repository contribution guide in [CONTRIBUTING.md](../../../CONTRIBUTING.md).

Please also follow the community [Code of Conduct](../../../CODE_OF_CONDUCT.md).

## License

MIT License. See [LICENSE](../../../LICENSE).
217 changes: 217 additions & 0 deletions kits/assistant/legal/actions/orchestrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
"use server"

import { getLamaticClient } from "@/lib/lamatic-client"
import { config } from "../orchestrate.js"
import { join } from "node:path"
import { access, readFile } from "node:fs/promises"

const DEFAULT_DISCLAIMER =
"This response is for informational purposes only and is not legal advice. Consult a licensed attorney for advice specific to your situation."

type LegalAssistantResult = {
answer: string
references: string[]
nextSteps: string[]
disclaimer: string
}

type FlowNode = {
data?: {
nodeId?: string
}
}

function toStringArray(value: unknown): string[] {
if (Array.isArray(value)) {
return value
.map((item) => (typeof item === "string" ? item.trim() : String(item).trim()))
.filter(Boolean)
}

if (typeof value === "string") {
return value
.split("\n")
.map((item) => item.replace(/^[-*\d.\s]+/, "").trim())
.filter(Boolean)
}

return []
}

function extractAnswer(result: Record<string, unknown>): string {
const candidates = [
result.answer,
result.response,
result.summary,
result.legalSummary,
result.output,
]

for (const candidate of candidates) {
if (typeof candidate === "string" && candidate.trim()) {
return candidate.trim()
}
}

return ""
}

async function usesChatTriggerExport(): Promise<boolean> {
try {
const candidates = [
join(process.cwd(), "flows", "assistant-legal-advisor", "config.json"),
join(process.cwd(), "kits", "assistant", "legal", "flows", "assistant-legal-advisor", "config.json"),
]

let flowConfigPath: string | undefined
for (const path of candidates) {
try {
await access(path)
flowConfigPath = path
break
} catch {
// Try the next candidate path.
}
}

if (!flowConfigPath) {
return false
}
const raw = await readFile(flowConfigPath, "utf-8")
const parsed = JSON.parse(raw) as { nodes?: FlowNode[] }
const nodes = Array.isArray(parsed.nodes) ? parsed.nodes : []
return nodes.some((node) => node?.data?.nodeId === "chatTriggerNode")
} catch {
return false
}
}

/**
* Server-side legal guidance orchestration.
* This path requires an API Request trigger flow.
* The bundled flows/assistant-legal-advisor export uses Chat Widget trigger
* and is intended for client-side widget usage.
*/
export async function getLegalGuidance(
question: string,
jurisdiction: string,
context: string,
): Promise<{
success: boolean
data?: LegalAssistantResult
error?: string
}> {
try {
if (await usesChatTriggerExport()) {
throw new Error(
"This flow export uses Chat Widget trigger and is not compatible with executeFlow API calls in this kit. Deploy an API Request trigger flow and update ASSISTANT_LEGAL_ADVISOR.",
)
}

const sanitizedQuestion = question.trim()
const sanitizedJurisdiction = jurisdiction.trim()
const sanitizedContext = context.trim()

if (!sanitizedQuestion) {
throw new Error("Question is required")
}

const flows = config.flows
const firstFlowKey = Object.keys(flows)[0]

if (!firstFlowKey) {
throw new Error("No workflows found in configuration")
}

const flow = flows[firstFlowKey as keyof typeof flows] as (typeof flows)[keyof typeof flows]

if (!flow.workflowId) {
throw new Error("Workflow not found in config")
}
Comment on lines +119 to +130
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Agent, your mission: Read the flow ID from environment variables, not config imports.

The briefing clearly states kit server actions must read flow IDs from environment variables. Your current approach imports config from ../orchestrate.js and extracts the workflow ID from config.flows. However, the README documents ASSISTANT_LEGAL_ADVISOR as the env var containing the flow ID.

This message will self-destruct if you choose to accept the fix:

🔧 Proposed fix to use environment variable
-    const flows = config.flows
-    const firstFlowKey = Object.keys(flows)[0]
-
-    if (!firstFlowKey) {
-      throw new Error("No workflows found in configuration")
-    }
-
-    const flow = flows[firstFlowKey as keyof typeof flows] as (typeof flows)[keyof typeof flows]
-
-    if (!flow.workflowId) {
-      throw new Error("Workflow not found in config")
-    }
+    const workflowId = process.env.ASSISTANT_LEGAL_ADVISOR
+
+    if (!workflowId) {
+      throw new Error("ASSISTANT_LEGAL_ADVISOR environment variable is not set")
+    }

Then update line 164:

-    const response = await lamaticClient.executeFlow(flow.workflowId, workflowInput)
+    const response = await lamaticClient.executeFlow(workflowId, workflowInput)

As per coding guidelines: "Kit server action files (e.g., actions/orchestrate.ts) must call Lamatic flows via the SDK and read flow IDs from environment variables."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kits/assistant/legal/actions/orchestrate.ts` around lines 119 - 130, Replace
the current extraction of the workflow ID from the imported config (the
constants flows, firstFlowKey and flow.workflowId) and instead read the flow ID
from the ASSISTANT_LEGAL_ADVISOR environment variable
(process.env.ASSISTANT_LEGAL_ADVISOR), validate it exists and throw a clear
error if missing, and then use that env value wherever flow.workflowId was used;
remove or ignore the config.flows-based lookup so the orchestrate action calls
the Lamatic flow using the environment-provided ID.


const inputAliases: Record<string, unknown> = {
question: sanitizedQuestion,
query: sanitizedQuestion,
userQuery: sanitizedQuestion,
prompt: sanitizedQuestion,
jurisdiction: sanitizedJurisdiction,
region: sanitizedJurisdiction,
context: sanitizedContext,
additionalContext: sanitizedContext,
details: sanitizedContext,
}

const workflowInput: Record<string, unknown> = {}

for (const key of Object.keys(flow.inputSchema || {})) {
const value = inputAliases[key]
if (value !== undefined && value !== "") {
workflowInput[key] = value
}
}

if (Object.keys(workflowInput).length === 0) {
workflowInput.question = sanitizedQuestion
if (sanitizedJurisdiction) {
workflowInput.jurisdiction = sanitizedJurisdiction
}
if (sanitizedContext) {
workflowInput.context = sanitizedContext
}
}
Comment on lines +144 to +161
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Agent, secondary briefing on input mapping.

Once you switch to reading the flow ID from process.env.ASSISTANT_LEGAL_ADVISOR, the flow.inputSchema won't be available. However, your fallback logic (lines 153-161) already handles this case by defaulting to { question, jurisdiction?, context? }. Consider simplifying by removing the inputSchema mapping entirely or documenting that the schema-based mapping is only available when a full config is imported.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kits/assistant/legal/actions/orchestrate.ts` around lines 144 - 161, The
current mapping loop using flow.inputSchema and inputAliases (flow.inputSchema,
inputAliases, workflowInput) is brittle because when the flow is loaded by ID
from process.env.ASSISTANT_LEGAL_ADVISOR the schema may be missing; replace that
logic by removing the schema-based loop and always populate workflowInput with
sanitizedQuestion and the optional sanitizedJurisdiction and sanitizedContext
(i.e., set workflowInput.question = sanitizedQuestion and conditionally add
jurisdiction/context) so fallback behavior is consistent, or alternatively
clearly document that flow.inputSchema-driven mapping only applies when a full
flow config is imported.


const lamaticClient = getLamaticClient()
const response = await lamaticClient.executeFlow(flow.workflowId, workflowInput)

if (response?.status === "error") {
throw new Error(response?.message || "Lamatic flow returned an error")
}

const result = (response?.result ?? {}) as Record<string, unknown>

const answer = extractAnswer(result)
if (!answer) {
throw new Error("No answer found in flow response")
}

const references = toStringArray(
result.references ?? result.citations ?? result.sources ?? result.statutes ?? result.caseLaws,
)
const nextSteps = toStringArray(result.nextSteps ?? result.recommendedActions ?? result.actions ?? result.steps)

const disclaimer =
typeof result.disclaimer === "string" && result.disclaimer.trim()
? result.disclaimer.trim()
: DEFAULT_DISCLAIMER

return {
success: true,
data: {
answer,
references,
nextSteps,
disclaimer,
},
}
} catch (error) {
let errorMessage = "Unknown error occurred"

if (error instanceof Error) {
errorMessage = error.message
if (error.message.includes("fetch failed")) {
errorMessage =
"Network error: Unable to connect to Lamatic service. Check your internet and try again."
} else if (error.message.toLowerCase().includes("timed out")) {
errorMessage =
"Lamatic request timed out. Your flow may be configured as Chat Widget trigger or long-running sync flow. Use an API Request trigger or async flow/polling for this kit."
} else if (error.message.includes("API key")) {
errorMessage = "Authentication error: Check your Lamatic API configuration."
}
}

return {
success: false,
error: errorMessage,
}
}
}
Loading
Loading