-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6a9939a
commit 395bf9e
Showing
43 changed files
with
1,142 additions
and
905 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
github: chihebnabil | ||
github: chihebnabil |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,41 @@ | ||
<template> | ||
<form class="mt-8 space-y-6" @submit.prevent="onSubmit"> | ||
<slot /> | ||
<div class="flex items-center justify-between"> | ||
<UCheckbox v-model="rememberMe" label="Remember me" name="remember-me" /> | ||
<div class="text-sm"> | ||
<UButton v-if="linkPath" variant="link" :to="linkPath"> | ||
{{ linkText }} | ||
</UButton> | ||
</div> | ||
</div> | ||
<div> | ||
<UButton type="submit" block color="primary" :loading="isLoading" size="lg"> | ||
<template #leading> | ||
<UIcon name="i-heroicons-lock-closed" /> | ||
</template> | ||
{{ buttonText }} | ||
</UButton> | ||
</div> | ||
</form> | ||
<form class="mt-8 space-y-6" @submit.prevent="onSubmit"> | ||
<slot /> | ||
<div class="flex items-center justify-between"> | ||
<UCheckbox v-model="rememberMe" label="Remember me" name="remember-me" /> | ||
<div class="text-sm"> | ||
<UButton v-if="linkPath" variant="link" :to="linkPath"> | ||
{{ linkText }} | ||
</UButton> | ||
</div> | ||
</div> | ||
<div> | ||
<UButton | ||
type="submit" | ||
block | ||
color="primary" | ||
:loading="isLoading" | ||
size="lg" | ||
> | ||
<template #leading> | ||
<UIcon name="i-heroicons-lock-closed" /> | ||
</template> | ||
{{ buttonText }} | ||
</UButton> | ||
</div> | ||
</form> | ||
</template> | ||
|
||
<script setup> | ||
import { ref } from 'vue' | ||
import { ref } from "vue"; | ||
defineProps({ | ||
onSubmit: Function, | ||
buttonText: String, | ||
linkText: String, | ||
linkPath: String | ||
onSubmit: Function, | ||
buttonText: String, | ||
linkText: String, | ||
linkPath: String, | ||
}); | ||
const rememberMe = ref(false); | ||
const isLoading = ref(false); | ||
</script> | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,71 +1,84 @@ | ||
<template> | ||
<div :class="[ | ||
'flex mb-4 max-w-full', | ||
message.role === 'user' ? 'justify-end' : 'justify-start' | ||
]"> | ||
<div :class="[ | ||
'flex items-start max-w-[85%]', | ||
message.role === 'user' ? 'flex-row-reverse' : '' | ||
]"> | ||
<UAvatar :alt="message.role === 'user' ? 'User' : 'AI'" size="sm" class="flex-shrink-0" /> | ||
<div class="flex flex-col mx-2"> | ||
<div :class="[ | ||
'px-4 py-2 rounded-lg break-words', | ||
message.role === 'user' | ||
? 'bg-primary text-white dark:bg-primary-600' | ||
: 'bg-gray-200 dark:bg-gray-700 dark:text-gray-100' | ||
]" v-html="$mdRenderer.render(message.content)" /> | ||
<span :class="[ | ||
'text-xs mt-1', | ||
message.role === 'user' ? 'text-right' : 'text-left', | ||
'text-gray-500 dark:text-gray-400' | ||
]"> | ||
{{ relativeTime }} | ||
</span> | ||
</div> | ||
</div> | ||
<div | ||
:class="[ | ||
'flex mb-4 max-w-full', | ||
message.role === 'user' ? 'justify-end' : 'justify-start', | ||
]" | ||
> | ||
<div | ||
:class="[ | ||
'flex items-start max-w-[85%]', | ||
message.role === 'user' ? 'flex-row-reverse' : '', | ||
]" | ||
> | ||
<UAvatar | ||
:alt="message.role === 'user' ? 'User' : 'AI'" | ||
size="sm" | ||
class="flex-shrink-0" | ||
/> | ||
<div class="flex flex-col mx-2"> | ||
<div | ||
:class="[ | ||
'px-4 py-2 rounded-lg break-words', | ||
message.role === 'user' | ||
? 'bg-primary text-white dark:bg-primary-600' | ||
: 'bg-gray-200 dark:bg-gray-700 dark:text-gray-100', | ||
]" | ||
v-html="$mdRenderer.render(message.content)" | ||
/> | ||
<span | ||
:class="[ | ||
'text-xs mt-1', | ||
message.role === 'user' ? 'text-right' : 'text-left', | ||
'text-gray-500 dark:text-gray-400', | ||
]" | ||
> | ||
{{ relativeTime }} | ||
</span> | ||
</div> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script setup> | ||
import { computed } from 'vue' | ||
const { $mdRenderer } = useNuxtApp() | ||
import { computed } from "vue"; | ||
const { $mdRenderer } = useNuxtApp(); | ||
const props = defineProps({ | ||
message: { | ||
type: Object, | ||
required: true, | ||
validator: (value) => { | ||
return value.role && value.content && value.createdAt | ||
} | ||
} | ||
}) | ||
message: { | ||
type: Object, | ||
required: true, | ||
validator: (value) => { | ||
return value.role && value.content && value.createdAt; | ||
}, | ||
}, | ||
}); | ||
const relativeTime = computed(() => { | ||
const now = new Date() | ||
const createdAt = new Date(props.message.createdAt) | ||
const diffInSeconds = Math.floor((now - createdAt) / 1000) | ||
const now = new Date(); | ||
const createdAt = new Date(props.message.createdAt); | ||
const diffInSeconds = Math.floor((now - createdAt) / 1000); | ||
if (diffInSeconds < 60) { | ||
return 'just now' | ||
} | ||
if (diffInSeconds < 60) { | ||
return "just now"; | ||
} | ||
const diffInMinutes = Math.floor(diffInSeconds / 60) | ||
if (diffInMinutes < 60) { | ||
return `${diffInMinutes}m ago` | ||
} | ||
const diffInMinutes = Math.floor(diffInSeconds / 60); | ||
if (diffInMinutes < 60) { | ||
return `${diffInMinutes}m ago`; | ||
} | ||
const diffInHours = Math.floor(diffInMinutes / 60) | ||
if (diffInHours < 24) { | ||
return `${diffInHours}h ago` | ||
} | ||
const diffInHours = Math.floor(diffInMinutes / 60); | ||
if (diffInHours < 24) { | ||
return `${diffInHours}h ago`; | ||
} | ||
const diffInDays = Math.floor(diffInHours / 24) | ||
if (diffInDays < 7) { | ||
return `${diffInDays}d ago` | ||
} | ||
const diffInDays = Math.floor(diffInHours / 24); | ||
if (diffInDays < 7) { | ||
return `${diffInDays}d ago`; | ||
} | ||
// For older messages, show the actual date | ||
return createdAt.toLocaleDateString() | ||
}) | ||
</script> | ||
// For older messages, show the actual date | ||
return createdAt.toLocaleDateString(); | ||
}); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,99 @@ | ||
<template> | ||
<UPopover mode="hover" :popper="{ strategy: 'absolute' }" :ui="{ width: 'w-[156px]' }"> | ||
<template #default="{ open }"> | ||
<UButton | ||
color="gray" variant="ghost" square :class="[open && 'bg-gray-50 dark:bg-gray-800']" | ||
aria-label="Color picker"> | ||
<UIcon name="i-heroicons-swatch-20-solid" class="w-5 h-5 text-primary-500 dark:text-primary-400" /> | ||
</UButton> | ||
</template> | ||
|
||
<template #panel> | ||
<div class="p-2"> | ||
<div class="grid grid-cols-5 gap-px"> | ||
<ColorPickerPill | ||
v-for="color in primaryColors" :key="color.value" :color="color" | ||
:selected="primary" @select="primary = color" /> | ||
</div> | ||
|
||
<hr class="border-gray-200 dark:border-gray-800 my-2"> | ||
|
||
<div class="grid grid-cols-5 gap-px"> | ||
<ColorPickerPill | ||
v-for="color in grayColors" :key="color.value" :color="color" :selected="gray" | ||
@select="gray = color" /> | ||
</div> | ||
</div> | ||
</template> | ||
</UPopover> | ||
<UPopover | ||
mode="hover" | ||
:popper="{ strategy: 'absolute' }" | ||
:ui="{ width: 'w-[156px]' }" | ||
> | ||
<template #default="{ open }"> | ||
<UButton | ||
color="gray" | ||
variant="ghost" | ||
square | ||
:class="[open && 'bg-gray-50 dark:bg-gray-800']" | ||
aria-label="Color picker" | ||
> | ||
<UIcon | ||
name="i-heroicons-swatch-20-solid" | ||
class="w-5 h-5 text-primary-500 dark:text-primary-400" | ||
/> | ||
</UButton> | ||
</template> | ||
|
||
<template #panel> | ||
<div class="p-2"> | ||
<div class="grid grid-cols-5 gap-px"> | ||
<ColorPickerPill | ||
v-for="color in primaryColors" | ||
:key="color.value" | ||
:color="color" | ||
:selected="primary" | ||
@select="primary = color" | ||
/> | ||
</div> | ||
|
||
<hr class="border-gray-200 dark:border-gray-800 my-2" /> | ||
|
||
<div class="grid grid-cols-5 gap-px"> | ||
<ColorPickerPill | ||
v-for="color in grayColors" | ||
:key="color.value" | ||
:color="color" | ||
:selected="gray" | ||
@select="gray = color" | ||
/> | ||
</div> | ||
</div> | ||
</template> | ||
</UPopover> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import colors from '#tailwind-config/theme/colors' | ||
import colors from "#tailwind-config/theme/colors"; | ||
const appConfig = useAppConfig() | ||
const colorMode = useColorMode() | ||
const appConfig = useAppConfig(); | ||
const colorMode = useColorMode(); | ||
// Computed | ||
const primaryColors = computed(() => appConfig.ui.colors.filter(color => color !== 'primary').map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] }))) | ||
const primaryColors = computed(() => | ||
appConfig.ui.colors | ||
.filter((color) => color !== "primary") | ||
.map((color) => ({ | ||
value: color, | ||
text: color, | ||
hex: colors[color][colorMode.value === "dark" ? 400 : 500], | ||
})), | ||
); | ||
const primary = computed({ | ||
get() { | ||
return primaryColors.value.find(option => option.value === appConfig.ui.primary) | ||
}, | ||
set(option) { | ||
appConfig.ui.primary = option.value | ||
get() { | ||
return primaryColors.value.find( | ||
(option) => option.value === appConfig.ui.primary, | ||
); | ||
}, | ||
set(option) { | ||
appConfig.ui.primary = option.value; | ||
window.localStorage.setItem('nuxt-ui-primary', appConfig.ui.primary) | ||
} | ||
}) | ||
window.localStorage.setItem("nuxt-ui-primary", appConfig.ui.primary); | ||
}, | ||
}); | ||
const grayColors = computed(() => ['slate', 'cool', 'zinc', 'neutral', 'stone'].map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] }))) | ||
const grayColors = computed(() => | ||
["slate", "cool", "zinc", "neutral", "stone"].map((color) => ({ | ||
value: color, | ||
text: color, | ||
hex: colors[color][colorMode.value === "dark" ? 400 : 500], | ||
})), | ||
); | ||
const gray = computed({ | ||
get() { | ||
return grayColors.value.find(option => option.value === appConfig.ui.gray) | ||
}, | ||
set(option) { | ||
appConfig.ui.gray = option.value | ||
window.localStorage.setItem('nuxt-ui-gray', appConfig.ui.gray) | ||
} | ||
}) | ||
</script> | ||
get() { | ||
return grayColors.value.find( | ||
(option) => option.value === appConfig.ui.gray, | ||
); | ||
}, | ||
set(option) { | ||
appConfig.ui.gray = option.value; | ||
window.localStorage.setItem("nuxt-ui-gray", appConfig.ui.gray); | ||
}, | ||
}); | ||
</script> |
Oops, something went wrong.