fix: build
This commit is contained in:
@@ -14,7 +14,7 @@ COPY backend/go.mod backend/go.sum ./
|
|||||||
RUN go mod download
|
RUN go mod download
|
||||||
COPY backend/ ./
|
COPY backend/ ./
|
||||||
COPY --from=frontend-builder /app/frontend/public/ ./web/static/
|
COPY --from=frontend-builder /app/frontend/public/ ./web/static/
|
||||||
RUN go build -ldflags="-w -s" -o aethera ./cmd
|
RUN find ./web/static -type d -empty -delete && go build -ldflags="-w -s" -o aethera ./cmd
|
||||||
|
|
||||||
# Stage 3: Minimal Runtime
|
# Stage 3: Minimal Runtime
|
||||||
FROM alpine:3.21
|
FROM alpine:3.21
|
||||||
|
|||||||
22
Makefile
22
Makefile
@@ -1,14 +1,22 @@
|
|||||||
.PHONY: all frontend backend clean dev docker docker-run tests
|
.PHONY: all frontend backend clean dev docker docker-run tests check-static
|
||||||
|
|
||||||
all: frontend backend
|
all: frontend backend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
|
rm -rf frontend/public/dist
|
||||||
cd frontend && bun run build
|
cd frontend && bun run build
|
||||||
|
rm -rf backend/web/static
|
||||||
mkdir -p backend/web/static
|
mkdir -p backend/web/static
|
||||||
cp frontend/public/index.html backend/web/static/ 2>/dev/null || true
|
cp -R frontend/public/. backend/web/static/
|
||||||
cp -r frontend/public/pages backend/web/static/ 2>/dev/null || true
|
find backend/web/static -type d -empty -delete
|
||||||
|
touch backend/web/static/.gitkeep
|
||||||
|
|
||||||
backend:
|
check-static:
|
||||||
|
@test -f backend/web/static/index.html || (echo "missing backend/web/static/index.html; run 'make frontend' first" && exit 1)
|
||||||
|
@test -f backend/web/static/dist/main.js || (echo "missing backend/web/static/dist/main.js; run 'make frontend' first" && exit 1)
|
||||||
|
@test -f backend/web/static/dist/styles.css || (echo "missing backend/web/static/dist/styles.css; run 'make frontend' first" && exit 1)
|
||||||
|
|
||||||
|
backend: check-static
|
||||||
cd backend && go build -o ./dist/aethera ./cmd
|
cd backend && go build -o ./dist/aethera ./cmd
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@@ -17,7 +25,11 @@ clean:
|
|||||||
rm -rf backend/web/static
|
rm -rf backend/web/static
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
cd backend && go run ./cmd --listen 0.0.0.0 &
|
rm -rf frontend/public/dist
|
||||||
|
cd frontend && bun run build
|
||||||
|
cd backend && AETHERA_STATIC_DIR=../frontend/public go run ./cmd --listen 0.0.0.0 & \
|
||||||
|
backend_pid=$$!; \
|
||||||
|
trap 'kill $$backend_pid' INT TERM EXIT; \
|
||||||
cd frontend && bun run dev
|
cd frontend && bun run dev
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ Open your browser and navigate to the URL to begin using Aethera.
|
|||||||
You can customize the server behavior with these command-line flags:
|
You can customize the server behavior with these command-line flags:
|
||||||
|
|
||||||
- `--data-dir`: Directory for storing generated images (default: `data`)
|
- `--data-dir`: Directory for storing generated images (default: `data`)
|
||||||
|
- `--static-dir`: Directory to serve frontend files from instead of embedded assets (useful for development)
|
||||||
- `--listen`: Address to listen on (default: `localhost`)
|
- `--listen`: Address to listen on (default: `localhost`)
|
||||||
- `--port`: Port to listen on (default: `8080`)
|
- `--port`: Port to listen on (default: `8080`)
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type cliParams struct {
|
|||||||
ListenAddr string
|
ListenAddr string
|
||||||
ListenPort int
|
ListenPort int
|
||||||
DataDir string
|
DataDir string
|
||||||
|
StaticDir string
|
||||||
SettingsFile string
|
SettingsFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,5 +42,16 @@ func (p *cliParams) Validate() error {
|
|||||||
return fmt.Errorf("failed to create images directory: %w", err)
|
return fmt.Errorf("failed to create images directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate Static Directory
|
||||||
|
if p.StaticDir != "" {
|
||||||
|
info, err := os.Stat(p.StaticDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to access static directory: %w", err)
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
return fmt.Errorf("static directory is not a directory: %s", p.StaticDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,14 @@ var (
|
|||||||
ListenAddr: getEnvOrDefault("LISTEN", "localhost"),
|
ListenAddr: getEnvOrDefault("LISTEN", "localhost"),
|
||||||
ListenPort: getEnvIntOrDefault("PORT", 8080),
|
ListenPort: getEnvIntOrDefault("PORT", 8080),
|
||||||
DataDir: getEnvOrDefault("DATA_DIR", "./data"),
|
DataDir: getEnvOrDefault("DATA_DIR", "./data"),
|
||||||
|
StaticDir: getEnvOrDefault("STATIC_DIR", ""),
|
||||||
}
|
}
|
||||||
rootCmd = &cobra.Command{Use: "aethera"}
|
rootCmd = &cobra.Command{Use: "aethera"}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringVar(¶ms.DataDir, "data-dir", params.DataDir, "Directory to store generated images (env: AETHERA_DATA_DIR)")
|
rootCmd.PersistentFlags().StringVar(¶ms.DataDir, "data-dir", params.DataDir, "Directory to store generated images (env: AETHERA_DATA_DIR)")
|
||||||
|
rootCmd.PersistentFlags().StringVar(¶ms.StaticDir, "static-dir", params.StaticDir, "Directory to serve static frontend files from instead of embedded assets (env: AETHERA_STATIC_DIR)")
|
||||||
rootCmd.PersistentFlags().StringVar(¶ms.ListenAddr, "listen", params.ListenAddr, "Address to listen on (env: AETHERA_LISTEN)")
|
rootCmd.PersistentFlags().StringVar(¶ms.ListenAddr, "listen", params.ListenAddr, "Address to listen on (env: AETHERA_LISTEN)")
|
||||||
rootCmd.PersistentFlags().IntVar(¶ms.ListenPort, "port", params.ListenPort, "Port to listen on (env: AETHERA_PORT)")
|
rootCmd.PersistentFlags().IntVar(¶ms.ListenPort, "port", params.ListenPort, "Port to listen on (env: AETHERA_PORT)")
|
||||||
}
|
}
|
||||||
@@ -40,7 +42,7 @@ func main() {
|
|||||||
|
|
||||||
// Start Server
|
// Start Server
|
||||||
rootCmd.Run = func(cmd *cobra.Command, args []string) {
|
rootCmd.Run = func(cmd *cobra.Command, args []string) {
|
||||||
server.StartServer(fileStore, params.DataDir, params.ListenAddr, params.ListenPort)
|
server.StartServer(fileStore, params.DataDir, params.StaticDir, params.ListenAddr, params.ListenPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
|||||||
@@ -13,19 +13,24 @@ import (
|
|||||||
"reichard.io/aethera/web"
|
"reichard.io/aethera/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartServer(settingsStore store.Store, dataDir, listenAddress string, listenPort int) {
|
func StartServer(settingsStore store.Store, dataDir, staticDir, listenAddress string, listenPort int) {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
// Create API Instance - use settingsStore as the unified store for both settings and chat
|
// Create API Instance - use settingsStore as the unified store for both settings and chat
|
||||||
logger := logrus.New()
|
logger := logrus.New()
|
||||||
api := api.New(settingsStore, dataDir, logger)
|
api := api.New(settingsStore, dataDir, logger)
|
||||||
|
|
||||||
// Serve embedded static assets
|
// Serve Static Assets
|
||||||
|
if staticDir != "" {
|
||||||
|
logrus.Infof("Serving static assets from directory: %s", staticDir)
|
||||||
|
mux.Handle("GET /", http.FileServer(http.Dir(staticDir)))
|
||||||
|
} else {
|
||||||
staticFS, err := fs.Sub(web.Assets, "static")
|
staticFS, err := fs.Sub(web.Assets, "static")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal("Failed to create static filesystem: ", err)
|
logrus.Fatal("Failed to create static filesystem: ", err)
|
||||||
}
|
}
|
||||||
mux.Handle("GET /", http.FileServer(http.FS(staticFS)))
|
mux.Handle("GET /", http.FileServer(http.FS(staticFS)))
|
||||||
|
}
|
||||||
|
|
||||||
// Serve Generated Data
|
// Serve Generated Data
|
||||||
genFS := http.FileServer(http.Dir(path.Join(dataDir, "generated")))
|
genFS := http.FileServer(http.Dir(path.Join(dataDir, "generated")))
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
# Frontend
|
# Frontend
|
||||||
bun
|
bun
|
||||||
|
typescript-language-server
|
||||||
watchman
|
watchman
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "aethera",
|
"name": "aethera",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "mkdir -p public/dist && bun build src/main.ts --outdir public/dist --target browser --watch & bunx tailwindcss -i styles.css -o public/dist/styles.css --watch",
|
"dev": "mkdir -p public/dist && bun build src/main.ts --outdir public/dist --target browser --watch & bunx tailwindcss -i styles.css -o public/dist/styles.css --watch",
|
||||||
"build": "bun build src/main.ts --outdir public/dist --target browser && bunx tailwindcss -i styles.css -o public/dist/styles.css --minify",
|
"build": "bun build src/main.ts --outdir public/dist --target browser && bunx tailwindcss -i styles.css -o public/dist/styles.css --minify",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
getModels,
|
getModels,
|
||||||
getSettings,
|
getSettings,
|
||||||
} from '../client';
|
} from '../client';
|
||||||
import { ImageRecord } from '../types';
|
import { GenerateImageRequest, ImageRecord } from '../types';
|
||||||
import { applyFilter } from '../utils';
|
import { applyFilter } from '../utils';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
@@ -26,6 +26,12 @@ interface StoredSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Utilities
|
// Utilities
|
||||||
|
const errorMessage = (err: unknown): string => {
|
||||||
|
if (err instanceof Error) return err.message;
|
||||||
|
if (typeof err === 'string') return err;
|
||||||
|
return 'An unexpected error occurred';
|
||||||
|
};
|
||||||
|
|
||||||
const fileToDataURL = (file: File): Promise<string> =>
|
const fileToDataURL = (file: File): Promise<string> =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
@@ -252,8 +258,8 @@ Alpine.data('imageGenerator', () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async buildRequestData() {
|
async buildRequestData(): Promise<GenerateImageRequest> {
|
||||||
const requestData: any = {
|
const requestData: GenerateImageRequest = {
|
||||||
prompt: this.prompt,
|
prompt: this.prompt,
|
||||||
n: parseInt(this.n.toString()),
|
n: parseInt(this.n.toString()),
|
||||||
seed: parseInt(this.seed.toString()),
|
seed: parseInt(this.seed.toString()),
|
||||||
@@ -281,8 +287,8 @@ Alpine.data('imageGenerator', () => {
|
|||||||
const requestData = await this.buildRequestData();
|
const requestData = await this.buildRequestData();
|
||||||
const data = await generateImage(requestData);
|
const data = await generateImage(requestData);
|
||||||
this.generatedImages.unshift(...data);
|
this.generatedImages.unshift(...data);
|
||||||
} catch (err: any) {
|
} catch (err) {
|
||||||
this.error = err;
|
this.error = errorMessage(err);
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
@@ -294,8 +300,8 @@ Alpine.data('imageGenerator', () => {
|
|||||||
this.generatedImages = this.generatedImages.filter(
|
this.generatedImages = this.generatedImages.filter(
|
||||||
(img: ImageRecord) => img.name !== filename,
|
(img: ImageRecord) => img.name !== filename,
|
||||||
);
|
);
|
||||||
} catch (err: any) {
|
} catch (err) {
|
||||||
this.error = err;
|
this.error = errorMessage(err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user