feat: OpenAI compatibility improvements & build fixes

- Fix hydration mismatches and initialization errors
- Add /v1/models endpoint for OpenAI clients
- Add Codex response translator (Responses → OpenAI)
- Fix circular dependencies and PropTypes
- Add Material Symbols font and CSS fixes
- Update README with deployment guide

Co-merged from PR #18 (14/15 commits, skipped debug)
This commit is contained in:
decolua 2026-01-20 13:16:34 +07:00
parent 0848dd5d13
commit d9b8e48725
15 changed files with 762 additions and 171 deletions

View file

@ -1,31 +1,60 @@
"use client";
import { useEffect } from "react";
import { useEffect, useState, useSyncExternalStore } from "react";
import useThemeStore from "@/store/themeStore";
// Subscribe to system theme changes
function subscribeToSystemTheme(callback) {
if (typeof window === "undefined") return () => {};
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
mediaQuery.addEventListener("change", callback);
return () => mediaQuery.removeEventListener("change", callback);
}
// Get current system theme preference
function getSystemThemeSnapshot() {
if (typeof window === "undefined") return false;
return window.matchMedia("(prefers-color-scheme: dark)").matches;
}
// Server snapshot always returns false
function getServerSnapshot() {
return false;
}
export function useTheme() {
const { theme, setTheme, toggleTheme, initTheme } = useThemeStore();
// Use useSyncExternalStore to safely subscribe to system theme
const systemPrefersDark = useSyncExternalStore(
subscribeToSystemTheme,
getSystemThemeSnapshot,
getServerSnapshot
);
useEffect(() => {
initTheme();
}, [initTheme]);
// Listen for system theme changes when theme is "system"
useEffect(() => {
if (theme !== "system") return;
// Listen for system theme changes
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handleChange = () => {
if (theme === "system") {
initTheme();
}
};
const handleChange = () => initTheme();
mediaQuery.addEventListener("change", handleChange);
return () => mediaQuery.removeEventListener("change", handleChange);
}, [theme, initTheme]);
// Compute isDark from current state (no effect needed)
const isDark = theme === "dark" || (theme === "system" && systemPrefersDark);
return {
theme,
setTheme,
toggleTheme,
isDark: theme === "dark" || (theme === "system" && typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches),
isDark,
};
}