Skip to content
Open
4 changes: 3 additions & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ builds:
goarch: arm64
- goos: windows
goarch: arm64
ldflags:
- -X github.com/mostlygeek/llama-swap/version.Version={{.Version}} -X github.com/mostlygeek/llama-swap/version.Commit={{.Commit}} -X github.com/mostlygeek/llama-swap/version.Date={{.CommitDate}}

archives:
- id: default
Expand All @@ -29,4 +31,4 @@ archives:
# use zip format for windows
- goos: windows
formats:
- zip
- zip
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,18 @@ ui: ui/node_modules
# Build OSX binary
mac: ui
@echo "Building Mac binary..."
GOOS=darwin GOARCH=arm64 go build -ldflags="-X main.commit=${GIT_HASH} -X main.version=local_${GIT_HASH} -X main.date=${BUILD_DATE}" -o $(BUILD_DIR)/$(APP_NAME)-darwin-arm64
GOOS=darwin GOARCH=arm64 go build -ldflags="-X github.com/mostlygeek/llama-swap/version.Commit=${GIT_HASH} -X github.com/mostlygeek/llama-swap/version.Version=local_${GIT_HASH} -X github.com/mostlygeek/llama-swap/version.Date=${BUILD_DATE}" -o $(BUILD_DIR)/$(APP_NAME)-darwin-arm64

# Build Linux binary
linux: ui
@echo "Building Linux binary..."
GOOS=linux GOARCH=amd64 go build -ldflags="-X main.commit=${GIT_HASH} -X main.version=local_${GIT_HASH} -X main.date=${BUILD_DATE}" -o $(BUILD_DIR)/$(APP_NAME)-linux-amd64
GOOS=linux GOARCH=arm64 go build -ldflags="-X main.commit=${GIT_HASH} -X main.version=local_${GIT_HASH} -X main.date=${BUILD_DATE}" -o $(BUILD_DIR)/$(APP_NAME)-linux-arm64
GOOS=linux GOARCH=amd64 go build -ldflags="-X github.com/mostlygeek/llama-swap/version.Commit=${GIT_HASH} -X github.com/mostlygeek/llama-swap/version.Version=local_${GIT_HASH} -X github.com/mostlygeek/llama-swap/version.Date=${BUILD_DATE}" -o $(BUILD_DIR)/$(APP_NAME)-linux-amd64
GOOS=linux GOARCH=arm64 go build -ldflags="-X github.com/mostlygeek/llama-swap/version.Commit=${GIT_HASH} -X github.com/mostlygeek/llama-swap/version.Version=local_${GIT_HASH} -X github.com/mostlygeek/llama-swap/version.Date=${BUILD_DATE}" -o $(BUILD_DIR)/$(APP_NAME)-linux-arm64

# Build Windows binary
windows: ui
@echo "Building Windows binary..."
GOOS=windows GOARCH=amd64 go build -ldflags="-X main.commit=${GIT_HASH} -X main.version=local_${GIT_HASH} -X main.date=${BUILD_DATE}" -o $(BUILD_DIR)/$(APP_NAME)-windows-amd64.exe
GOOS=windows GOARCH=amd64 go build -ldflags="-X github.com/mostlygeek/llama-swap/version.Commit=${GIT_HASH} -X github.com/mostlygeek/llama-swap/version.Version=local_${GIT_HASH} -X github.com/mostlygeek/llama-swap/version.Date=${BUILD_DATE}" -o $(BUILD_DIR)/$(APP_NAME)-windows-amd64.exe

# for testing proxy.Process
simple-responder:
Expand Down
9 changes: 2 additions & 7 deletions llama-swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@ import (
"github.com/mostlygeek/llama-swap/event"
"github.com/mostlygeek/llama-swap/proxy"
"github.com/mostlygeek/llama-swap/proxy/config"
)

var (
version string = "0"
commit string = "abcd1234"
date string = "unknown"
"github.com/mostlygeek/llama-swap/version"
)

func main() {
Expand All @@ -37,7 +32,7 @@ func main() {
flag.Parse() // Parse the command-line flags

if *showVersion {
fmt.Printf("version: %s (%s), built at %s\n", version, commit, date)
fmt.Printf("version: %s (%s), built at %s\n", version.Version, version.Commit, version.Date)
os.Exit(0)
}

Expand Down
11 changes: 11 additions & 0 deletions proxy/proxymanager_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/gin-gonic/gin"
"github.com/mostlygeek/llama-swap/event"
"github.com/mostlygeek/llama-swap/version"
)

type Model struct {
Expand All @@ -28,6 +29,7 @@ func addApiHandlers(pm *ProxyManager) {
apiGroup.POST("/models/unload/*model", pm.apiUnloadSingleModelHandler)
apiGroup.GET("/events", pm.apiSendEvents)
apiGroup.GET("/metrics", pm.apiGetMetrics)
apiGroup.GET("/version", pm.apiGetVersion)
}
}

Expand Down Expand Up @@ -227,3 +229,12 @@ func (pm *ProxyManager) apiUnloadSingleModelHandler(c *gin.Context) {
c.String(http.StatusOK, "OK")
}
}

func (pm *ProxyManager) apiGetVersion(c *gin.Context) {
data, err := json.Marshal(version.VersionJSON)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get version"})
return
}
c.Data(http.StatusOK, "application/json", data)
}
34 changes: 34 additions & 0 deletions proxy/proxymanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1083,3 +1083,37 @@ func TestProxyManager_ProxiedStreamingEndpointReturnsNoBufferingHeader(t *testin
assert.Equal(t, "no", rec.Header().Get("X-Accel-Buffering"))
assert.Contains(t, rec.Header().Get("Content-Type"), "text/event-stream")
}

func TestProxyManager_ApiGetVersion(t *testing.T) {
config := config.AddDefaultGroupToConfig(config.Config{
HealthCheckTimeout: 15,
Models: map[string]config.ModelConfig{
"model1": getTestSimpleResponderConfig("model1"),
},
LogLevel: "error",
})

proxy := New(config)
defer proxy.StopProcesses(StopWaitForInflightRequest)

req := httptest.NewRequest("GET", "/api/version", nil)
w := CreateTestResponseRecorder()

proxy.ServeHTTP(w, req)

assert.Equal(t, http.StatusOK, w.Code)

// Ensure json response
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))

var responseData map[string]interface{}
if err := json.Unmarshal(w.Body.Bytes(), &responseData); err != nil {
t.Fatalf("Failed to parse JSON response: %v", err)
}

// Check for version
_, versionExists := responseData["version"]
assert.True(t, versionExists, "version should exist in the response")
_, ok := responseData["version"].(string)
assert.True(t, ok, "version should be a string")
}
21 changes: 18 additions & 3 deletions ui/src/components/ConnectionStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useAPI } from "../contexts/APIProvider";
import { useMemo } from "react";
import { useMemo, useEffect } from "react";

const ConnectionStatusIcon = () => {
const { connectionStatus } = useAPI();
const { connectionStatus, versionInfo, getVersionInfo } = useAPI();

const eventStatusColor = useMemo(() => {
switch (connectionStatus) {
Expand All @@ -16,8 +16,23 @@ const ConnectionStatusIcon = () => {
}
}, [connectionStatus]);

useEffect(() => {
if (typeof connectionStatus === "string" &&
connectionStatus === "connected") {
getVersionInfo();
}
}, [connectionStatus]);

const title = useMemo(() => {
let baseTitle = `event stream: ${connectionStatus}`;
if (versionInfo) {
baseTitle += `\nVersion: ${versionInfo.version}\nCommit: ${versionInfo.commit}\nDate: ${versionInfo.date}`;
}
return baseTitle;
}, [connectionStatus, versionInfo]);

return (
<div className="flex items-center" title={`event stream: ${connectionStatus}`}>
<div className="flex items-center" title={`${title}`}>
<span className={`inline-block w-3 h-3 rounded-full ${eventStatusColor} mr-2`}></span>
</div>
);
Expand Down
28 changes: 27 additions & 1 deletion ui/src/contexts/APIProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ interface APIProviderType {
upstreamLogs: string;
metrics: Metrics[];
connectionStatus: ConnectionState;
versionInfo: VersionInfo | null;
getVersionInfo: () => Promise<void>;
}

interface Metrics {
Expand All @@ -41,11 +43,18 @@ interface LogData {
source: "upstream" | "proxy";
data: string;
}

interface APIEventEnvelope {
type: "modelStatus" | "logData" | "metrics";
data: string;
}

interface VersionInfo {
version: string;
commit: string;
date: string;
}

const APIContext = createContext<APIProviderType | undefined>(undefined);
type APIProviderProps = {
children: ReactNode;
Expand All @@ -59,6 +68,7 @@ export function APIProvider({ children, autoStartAPIEvents = true }: APIProvider
const [upstreamLogs, setUpstreamLogs] = useState("");
const [metrics, setMetrics] = useState<Metrics[]>([]);
const [connectionStatus, setConnectionState] = useState<ConnectionState>("disconnected");
const [versionInfo, setVersionInfo] = useState<VersionInfo | null>(null);
//const apiEventSource = useRef<EventSource | null>(null);

const [models, setModels] = useState<Model[]>([]);
Expand Down Expand Up @@ -145,6 +155,7 @@ export function APIProvider({ children, autoStartAPIEvents = true }: APIProvider
retryCount++;
const delay = Math.min(initialDelay * Math.pow(2, retryCount - 1), 5000);
setConnectionState("disconnected");
setVersionInfo(null);
setTimeout(connect, delay);
};
};
Expand Down Expand Up @@ -218,6 +229,19 @@ export function APIProvider({ children, autoStartAPIEvents = true }: APIProvider
}
}, []);

const getVersionInfo = useCallback(async () => {
try {
const response = await fetch("/api/version");
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: VersionInfo = await response.json();
setVersionInfo(data);
} catch (error) {
return;
}
}, []);

const value = useMemo(
() => ({
models,
Expand All @@ -230,8 +254,10 @@ export function APIProvider({ children, autoStartAPIEvents = true }: APIProvider
upstreamLogs,
metrics,
connectionStatus,
versionInfo,
getVersionInfo,
}),
[models, listModels, unloadAllModels, loadModel, enableAPIEvents, proxyLogs, upstreamLogs, metrics]
[models, listModels, unloadAllModels, loadModel, enableAPIEvents, proxyLogs, upstreamLogs, metrics, connectionStatus, versionInfo]
);

return <APIContext.Provider value={value}>{children}</APIContext.Provider>;
Expand Down
18 changes: 18 additions & 0 deletions version/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package version

type VersionStruct struct {
Version string `json:"version"`
Commit string `json:"commit"`
Date string `json:"date"`
}

var (
Version string = "0"
Commit string = "abcd1234"
Date string = "unknown"
VersionJSON = VersionStruct{
Version: Version,
Commit: Commit,
Date: Date,
}
)