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
41 changes: 22 additions & 19 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=oxidechat
POSTGRES_USER=postgres
POSTGRES_PASSWORD=your_password_here

HOST=0.0.0.0
PORT=8080
BASE_URL=http://localhost:8080

# OAuth Configuration (optional - can also be configured via admin UI)
OAUTH_GOOGLE_CLIENT_ID=
OAUTH_GOOGLE_CLIENT_SECRET=

OAUTH_DISCORD_CLIENT_ID=
OAUTH_DISCORD_CLIENT_SECRET=

# API Key Encryption (optional - 64 hex chars = 32 bytes)
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=oxidechat
POSTGRES_USER=postgres
POSTGRES_PASSWORD=your_password_here

HOST=0.0.0.0
PORT=8080
BASE_URL=http://localhost:8080

# OAuth Configuration (optional - can also be configured via admin UI)
OAUTH_GOOGLE_CLIENT_ID=
OAUTH_GOOGLE_CLIENT_SECRET=

OAUTH_DISCORD_CLIENT_ID=
OAUTH_DISCORD_CLIENT_SECRET=

# Credential encryption
# A key is generated automatically in OXIDECHAT_DATA_DIR/master.key.
OXIDECHAT_DATA_DIR=./.data
# For containers or multiple replicas, provide the same explicit 64-character hex key to every instance.
# Generate with: openssl rand -hex 32
# ENCRYPTION_KEY=your_64_char_hex_key_here
# ENCRYPTION_KEY=your_64_character_hex_key_here
5 changes: 0 additions & 5 deletions .intent/.gitignore

This file was deleted.

2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

- All request/response structs live in `src/types/`, not in the code files files
- Organized by domain: `i18n.rs`, `base.rs`, etc.
- `mod.rs` re-exports all types for easy importing: `use crate::types::*;`
- `providers_billing` re-exports all types for easy importing: `use crate::types::*;`
- Each file uses section comments for clarity:
- Request Types (structs with `Deserialize`)
- Response Types (structs with `Serialize`)
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/components/settings/DefaultModelPicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
v-for="model in pickerModels"
:key="model.id"
class="px-3 py-2 hover:bg-accent/50 cursor-pointer transition-colors border-b border-border/50 last:border-0"
:class="modelValueFor(model) === modelValue ? 'bg-accent/30' : ''"
:class="modelValueFor(model) === modelValue ? 'bg-accent/30' : ''"
@click="selectModel(model)"
>
<div class="flex items-start gap-3">
Expand Down
18 changes: 9 additions & 9 deletions frontend/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ const checkAuth = async () => {
if (store.bootState !== 'online') {
await store.getBaseData();
}

if (!store.auth.user && !store.auth.isAuthenticated) {
if (!getMePromise) {
getMePromise = store.getMe().finally(() => {
getMePromise = null;
});
}
await getMePromise;
}

if (store.base?.needs_setup) {
if (route.path !== '/auth/setup') {
Expand All @@ -67,15 +76,6 @@ const checkAuth = async () => {
return {authenticated: false};
}

if (!store.auth.user && !store.auth.isAuthenticated) {
if (!getMePromise) {
getMePromise = store.getMe().finally(() => {
getMePromise = null;
});
}
await getMePromise;
}

const isAuthPage = route.path.startsWith('/auth');

if (store.auth.isAuthenticated && isAuthPage) {
Expand Down
20 changes: 11 additions & 9 deletions frontend/pages/settings/admin-config.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
<p class="text-xs text-muted-foreground mt-0.5">{{ store.getTranslation('settings.admin.default_model_hint') }}</p>
</div>
<DefaultModelPicker
:model-value="defaultModelKey"
:model-value="defaultModelId"
:disabled="saving"
endpoint="/api/v1/admin/models"
selected-model-endpoint="/api/v1/admin/models"
value-mode="uuid"
:placeholder="store.getTranslation('settings.teams.use_global_default')"
@update:model-value="setDefaultModel"
/>
Expand All @@ -50,7 +52,7 @@ const {$customFetch} = useNuxtApp();
const saving = ref(false);
const enableProviderSelector = ref(store.base?.enable_provider_selector ?? false);
const allowServerStdioMcp = ref(store.base?.allow_server_stdio_mcp ?? false);
const defaultModelKey = ref<string | null>(store.base?.default_model_key ?? null);
const defaultModelId = ref<string | null>(store.base?.default_model_id ?? null);

async function toggleProviderSelector(val: boolean) {
const previous = enableProviderSelector.value;
Expand Down Expand Up @@ -89,19 +91,19 @@ async function toggleAllowStdioMcp(val: boolean) {
}

async function setDefaultModel(val: string | null) {
const previous = defaultModelKey.value;
const newKey = val;
defaultModelKey.value = newKey;
const previous = defaultModelId.value;
const newId = val;
defaultModelId.value = newId;
saving.value = true;
try {
await $customFetch('/api/v1/admin/config', {
method: 'PATCH',
body: {default_model_key: newKey},
body: {default_model_id: newId},
});
if (store.base) store.base.default_model_key = newKey;
if (store.base) store.base.default_model_id = newId;
} catch (error) {
console.error('Failed to update default model key:', error);
defaultModelKey.value = previous;
console.error('Failed to update default model:', error);
defaultModelId.value = previous;
} finally {
saving.value = false;
}
Expand Down
Loading