-
+
diff --git a/app/components/UserAvatar.vue b/app/components/UserAvatar.vue
new file mode 100644
index 0000000..822ad42
--- /dev/null
+++ b/app/components/UserAvatar.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
![]()
+
{{ avatarInitial(user?.name) }}
+
+
diff --git a/app/composables/useAuth.ts b/app/composables/useAuth.ts
index fe60aa5..7ed96f7 100644
--- a/app/composables/useAuth.ts
+++ b/app/composables/useAuth.ts
@@ -8,6 +8,7 @@ export enum AuthError {
SignInFailed = 'SIGN_IN_FAILED',
SignUpFailed = 'SIGN_UP_FAILED',
SignOutFailed = 'SIGN_OUT_FAILED',
+ UpdateFailed = 'UPDATE_FAILED',
NetworkError = 'NETWORK_ERROR',
}
@@ -166,6 +167,28 @@ export const useAuth = () => {
}
};
+ const updateProfile = async (
+ updates: { name?: string; image?: string | null }
+ ): Promise
> => {
+ try {
+ const { error } = await authClient.updateUser(updates);
+
+ if (error) {
+ console.error('Profile update failed:', error);
+ return Err(AuthError.UpdateFailed);
+ }
+
+ if (user.value) {
+ user.value = { ...user.value, ...updates };
+ }
+
+ return Ok(undefined);
+ } catch (err) {
+ console.error('Profile update error:', err);
+ return Err(AuthError.NetworkError);
+ }
+ };
+
return {
client: authClient,
session,
@@ -174,6 +197,7 @@ export const useAuth = () => {
signIn,
signUp,
signOut,
+ updateProfile,
fetchSession,
};
};
diff --git a/app/composables/useDialog.ts b/app/composables/useDialog.ts
index 73036f8..6241ac4 100644
--- a/app/composables/useDialog.ts
+++ b/app/composables/useDialog.ts
@@ -1,7 +1,6 @@
import { Err, Ok, type Result } from "~~/types/result";
export enum DialogType {
- Settings = 'settings',
Textbox = 'textbox',
QuickSwitcher = 'quick-switcher',
Confirm = 'confirm',
diff --git a/app/pages/settings.vue b/app/pages/settings.vue
new file mode 100644
index 0000000..6e211f8
--- /dev/null
+++ b/app/pages/settings.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
{{ title }}
+
+
+
+
+
+
+
+
diff --git a/app/components/Settings/AppearanceSettings.vue b/app/pages/settings/appearance.vue
similarity index 99%
rename from app/components/Settings/AppearanceSettings.vue
rename to app/pages/settings/appearance.vue
index 5c6f6de..72a3bd1 100644
--- a/app/components/Settings/AppearanceSettings.vue
+++ b/app/pages/settings/appearance.vue
@@ -16,8 +16,6 @@ const updateHinting = (e: Event) => {
const hinting = parseInt((e.target as HTMLInputElement).value);
updateSettings({ appearance: { hinting } });
};
-
-defineEmits(['navigate']);
diff --git a/app/components/Settings/SystemAssistants.vue b/app/pages/settings/assistants.vue
similarity index 98%
rename from app/components/Settings/SystemAssistants.vue
rename to app/pages/settings/assistants.vue
index e7f3bec..98e51d0 100644
--- a/app/components/Settings/SystemAssistants.vue
+++ b/app/pages/settings/assistants.vue
@@ -33,8 +33,6 @@ const getModel = (id: string | null | undefined) => {
return allModels.value.find(m => m.id === id);
}
-
-defineEmits(['navigate']);
diff --git a/app/pages/settings/general.vue b/app/pages/settings/general.vue
new file mode 100644
index 0000000..1431402
--- /dev/null
+++ b/app/pages/settings/general.vue
@@ -0,0 +1,5 @@
+
+
+ Nothing to configure here yet.
+
+
diff --git a/app/pages/settings/index.vue b/app/pages/settings/index.vue
new file mode 100644
index 0000000..3190c9d
--- /dev/null
+++ b/app/pages/settings/index.vue
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/app/pages/settings/profile.vue b/app/pages/settings/profile.vue
new file mode 100644
index 0000000..c03f410
--- /dev/null
+++ b/app/pages/settings/profile.vue
@@ -0,0 +1,240 @@
+
+
+
+
+
diff --git a/app/components/Settings/AIServiceProvider.vue b/app/pages/settings/providers/[id].vue
similarity index 98%
rename from app/components/Settings/AIServiceProvider.vue
rename to app/pages/settings/providers/[id].vue
index 6947fef..34ea39e 100644
--- a/app/components/Settings/AIServiceProvider.vue
+++ b/app/pages/settings/providers/[id].vue
@@ -2,20 +2,19 @@
import { sortByReleaseDate } from '~/utils/sort';
import { encryptData, decrypt, uint8ArrayToBase64, base64ToUint8Array } from '~/utils/crypto';
import { type Model } from '~/types/model';
-import ModelItem from './ModelItem.vue';
-import RowVirtualizerDynamic from '../RowVirtualizerDynamic.vue';
+import ModelItem from '~/components/Settings/ModelItem.vue';
+import RowVirtualizerDynamic from '~/components/RowVirtualizerDynamic.vue';
-const props = defineProps<{
- params?: string;
-}>();
+const route = useRoute();
+const providerId = computed(() => route.params.id as string | undefined);
const { providers, updateProvider, createModel, updateModel } = await useModels();
const scrollContainerRef = ref(null);
const provider = computed(() => {
- if (props.params === undefined) return null;
- return providers.value!.find(p => p.id === props.params);
+ if (providerId.value === undefined) return null;
+ return providers.value!.find(p => p.id === providerId.value);
});
watch(provider, async () => {
@@ -29,8 +28,8 @@ const apiKey = ref('');
const apiProxyUrl = ref(provider.value?.config.apiProxyUrl ?? '');
const modelSearch = ref('');
-watch(() => props.params, () => {
- if (props.params === undefined) return;
+watch(() => providerId.value, () => {
+ if (providerId.value === undefined) return;
apiKey.value = provider.value?.config.apiKey ?? '';
apiProxyUrl.value = provider.value?.config.apiProxyUrl ?? '';
modelSearch.value = '';
@@ -373,8 +372,6 @@ const formatMoney = (value: string) => {
const isFormValid = computed(() => {
return formData.value.name.trim() && formData.value.externalId.trim();
});
-
-defineEmits(['navigate']);
diff --git a/app/components/Settings/ProviderSettings.vue b/app/pages/settings/providers/index.vue
similarity index 91%
rename from app/components/Settings/ProviderSettings.vue
rename to app/pages/settings/providers/index.vue
index b528a85..948b115 100644
--- a/app/components/Settings/ProviderSettings.vue
+++ b/app/pages/settings/providers/index.vue
@@ -3,10 +3,6 @@ import { providerIcons } from '~/utils/model-mapping';
const { providers, updateProvider } = await useModels();
-const props = defineProps<{
- params?: string;
-}>();
-
if (providers.value === undefined) throw new Error('Providers not loaded');
const toggleProvider = async (id: string) => {
@@ -18,7 +14,7 @@ const toggleProvider = async (id: string) => {
});
};
-defineEmits(['navigate']);
+const openProvider = (id: string) => navigateTo(`/settings/providers/${id}`);
@@ -31,7 +27,7 @@ defineEmits(['navigate']);
-