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
6 changes: 3 additions & 3 deletions app/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ export default function manifest(): MetadataRoute.Manifest {
short_name: "AIDraw.io",
description:
"Create AWS architecture diagrams, flowcharts, and technical diagrams using AI. Free online tool integrating draw.io with AI assistance for professional diagram creation.",
start_url: "/",
start_url: process.env.NEXT_PUBLIC_BASE_PATH || "/",
display: "standalone",
Copy link
Owner

Choose a reason for hiding this comment

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

I realized that it is better to use getAssetUrl("/favicon-192x192.png") since process.env.NEXT_PUBLIC_BASE_PATH can be undefine.

Also for line 15

background_color: "#f9fafb",
theme_color: "#171d26",
icons: [
{
src: "/favicon-192x192.png",
src: `${process.env.NEXT_PUBLIC_BASE_PATH}/favicon-192x192.png`,
sizes: "192x192",
type: "image/png",
purpose: "any",
},
{
src: "/favicon-512x512.png",
src: `${process.env.NEXT_PUBLIC_BASE_PATH}/favicon-512x512.png`,
sizes: "512x512",
type: "image/png",
purpose: "any",
Expand Down
7 changes: 4 additions & 3 deletions components/chat-example-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Terminal,
Zap,
} from "lucide-react"
import { getAssetUrl } from "@/lib/base-path"

interface ExampleCardProps {
icon: React.ReactNode
Expand Down Expand Up @@ -74,7 +75,7 @@ export default function ExamplePanel({
setInput("Replicate this flowchart.")

try {
const response = await fetch("/example.png")
const response = await fetch(getAssetUrl("/example.png"))
const blob = await response.blob()
const file = new File([blob], "example.png", { type: "image/png" })
setFiles([file])
Expand All @@ -87,7 +88,7 @@ export default function ExamplePanel({
setInput("Replicate this in aws style")

try {
const response = await fetch("/architecture.png")
const response = await fetch(getAssetUrl("/architecture.png"))
const blob = await response.blob()
const file = new File([blob], "architecture.png", {
type: "image/png",
Expand All @@ -102,7 +103,7 @@ export default function ExamplePanel({
setInput("Summarize this paper as a diagram")

try {
const response = await fetch("/chain-of-thought.txt")
const response = await fetch(getAssetUrl("/chain-of-thought.txt"))
const blob = await response.blob()
const file = new File([blob], "chain-of-thought.txt", {
type: "text/plain",
Expand Down
3 changes: 2 additions & 1 deletion components/chat-message-display.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
ReasoningTrigger,
} from "@/components/ai-elements/reasoning"
import { ScrollArea } from "@/components/ui/scroll-area"
import { getApiEndpoint } from "@/lib/base-path"
import {
applyDiagramOperations,
convertToLegalXml,
Expand Down Expand Up @@ -291,7 +292,7 @@ export function ChatMessageDisplay({
setFeedback((prev) => ({ ...prev, [messageId]: value }))

try {
await fetch("/api/log-feedback", {
await fetch(getApiEndpoint("/api/log-feedback"), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
Expand Down
5 changes: 3 additions & 2 deletions components/chat-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ResetWarningModal } from "@/components/reset-warning-modal"
import { SettingsDialog } from "@/components/settings-dialog"
import { useDiagram } from "@/contexts/diagram-context"
import { getAIConfig } from "@/lib/ai-config"
import { getApiEndpoint } from "@/lib/base-path"
import { findCachedResponse } from "@/lib/cached-responses"
import { isPdfFile, isTextFile } from "@/lib/pdf-utils"
import { type FileData, useFileProcessor } from "@/lib/use-file-processor"
Expand Down Expand Up @@ -160,7 +161,7 @@ export default function ChatPanel({

// Check config on mount
useEffect(() => {
fetch("/api/config")
fetch(getApiEndpoint("/api/config"))
.then((res) => res.json())
.then((data) => {
setAccessCodeRequired(data.accessCodeRequired)
Expand Down Expand Up @@ -232,7 +233,7 @@ export default function ChatPanel({
setMessages,
} = useChat({
transport: new DefaultChatTransport({
api: "/api/chat",
api: getApiEndpoint("/api/chat"),
}),
async onToolCall({ toolCall }) {
if (DEBUG) {
Expand Down
16 changes: 10 additions & 6 deletions components/settings-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
SelectValue,
} from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"
import { getApiEndpoint } from "@/lib/base-path"

interface SettingsDialogProps {
open: boolean
Expand Down Expand Up @@ -71,7 +72,7 @@ export function SettingsDialog({
// Only fetch if not cached in localStorage
if (getStoredAccessCodeRequired() !== null) return

fetch("/api/config")
fetch(getApiEndpoint("/api/config"))
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json()
Expand Down Expand Up @@ -119,12 +120,15 @@ export function SettingsDialog({
setIsVerifying(true)

try {
const response = await fetch("/api/verify-access-code", {
method: "POST",
headers: {
"x-access-code": accessCode.trim(),
const response = await fetch(
getApiEndpoint("/api/verify-access-code"),
{
method: "POST",
headers: {
"x-access-code": accessCode.trim(),
},
},
})
)

const data = await response.json()

Expand Down
3 changes: 2 additions & 1 deletion contexts/diagram-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createContext, useContext, useEffect, useRef, useState } from "react"
import type { DrawIoEmbedRef } from "react-drawio"
import { STORAGE_DIAGRAM_XML_KEY } from "@/components/chat-panel"
import type { ExportFormat } from "@/components/save-dialog"
import { getApiEndpoint } from "@/lib/base-path"
import { extractDiagramXML, validateAndFixXml } from "../lib/utils"

interface DiagramContextType {
Expand Down Expand Up @@ -329,7 +330,7 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
sessionId?: string,
) => {
try {
await fetch("/api/log-save", {
await fetch(getApiEndpoint("/api/log-save"), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ filename, format, sessionId }),
Expand Down
5 changes: 5 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ services:
context: .
args:
- NEXT_PUBLIC_DRAWIO_BASE_URL=http://localhost:8080
# Uncomment below for subdirectory deployment
# - NEXT_PUBLIC_BASE_PATH=/nextaidrawio
ports: ["3000:3000"]
env_file: .env
environment:
# For subdirectory deployment, uncomment and set your path:
# NEXT_PUBLIC_BASE_PATH: /nextaidrawio
depends_on: [drawio]
6 changes: 6 additions & 0 deletions env.example
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0
# NEXT_PUBLIC_DRAWIO_BASE_URL=https://embed.diagrams.net # Default: https://embed.diagrams.net
# Use this to point to a self-hosted draw.io instance

# Subdirectory Deployment (Optional)
# For deploying to a subdirectory (e.g., https://example.com/nextaidrawio)
# Set this to your subdirectory path with leading slash (e.g., /nextaidrawio)
# Leave empty for root deployment (default)
# NEXT_PUBLIC_BASE_PATH=/nextaidrawio

# PDF Input Feature (Optional)
# Enable PDF file upload to extract text and generate diagrams
# Enabled by default. Set to "false" to disable.
Expand Down
33 changes: 33 additions & 0 deletions lib/base-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Get the base path for API calls and static assets
* This is used for subdirectory deployment support
*
* Example: If deployed at https://example.com/nextaidrawio, this returns "/nextaidrawio"
* For root deployment, this returns ""
*
* Set NEXT_PUBLIC_BASE_PATH environment variable to your subdirectory path (e.g., /nextaidrawio)
*/
export function getBasePath(): string {
// Read from environment variable (must start with NEXT_PUBLIC_ to be available on client)
return process.env.NEXT_PUBLIC_BASE_PATH || ""
}

/**
* Get full API endpoint URL
* @param endpoint - API endpoint path (e.g., "/api/chat", "/api/config")
* @returns Full API path with base path prefix
*/
export function getApiEndpoint(endpoint: string): string {
const basePath = getBasePath()
return `${basePath}${endpoint}`
}

/**
* Get full static asset URL
* @param assetPath - Asset path (e.g., "/example.png", "/chain-of-thought.txt")
* @returns Full asset path with base path prefix
*/
export function getAssetUrl(assetPath: string): string {
const basePath = getBasePath()
return `${basePath}${assetPath}`
}
3 changes: 3 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import type { NextConfig } from "next"
const nextConfig: NextConfig = {
/* config options here */
output: "standalone",
// Support for subdirectory deployment (e.g., https://example.com/nextaidrawio)
// Set NEXT_PUBLIC_BASE_PATH environment variable to your subdirectory path (e.g., /nextaidrawio)
basePath: process.env.NEXT_PUBLIC_BASE_PATH || "",
}

export default nextConfig
Loading