refactor: airi card (#105)

* fix: card icon

* style: icon

* refactor: use section component

* chore: i18n

* feat: card detail page

* fix: chat
This commit is contained in:
RainbowBird
2025-03-31 15:56:02 +08:00
committed by GitHub
parent a278937a53
commit f47d794b8e
12 changed files with 337 additions and 379 deletions
+5 -1
View File
@@ -89,7 +89,11 @@ settings:
systemprompt: System Prompt systemprompt: System Prompt
posthistoryinstructions: Post-History Instructions posthistoryinstructions: Post-History Instructions
modules: Modules modules: Modules
voice_id: Voice ID consciousness:
model: Consciousness / Model
speech:
model: Speech / Model
voice: Speech / Voice
cancel: Cancel cancel: Cancel
memory: memory:
description: Where memories got stored, and organized description: Where memories got stored, and organized
+5 -1
View File
@@ -59,7 +59,6 @@ settings:
card: card:
title: Airi 角色卡 title: Airi 角色卡
description: 使用 Airi 角色卡预设 description: 使用 Airi 角色卡预设
voice_id: 声音 ID
upload: 上传 upload: 上传
delete: 删除 delete: 删除
active: 已激活 active: 已激活
@@ -77,6 +76,11 @@ settings:
posthistoryinstructions: 历史提示指令 posthistoryinstructions: 历史提示指令
modules: 模块 modules: 模块
cancel: 取消 cancel: 取消
consciousness:
model: 意识 / 模型
speech:
model: 声音 / 模型
voice: 声音 / 声线
memory: memory:
description: 存放记忆的地方,以及策略 description: 存放记忆的地方,以及策略
title: 记忆体 title: 记忆体
@@ -10,7 +10,7 @@ const model = defineModel<boolean>()
<template> <template>
<label <label
class="setting-bar cursor-pointer text-sm" class="w-full flex cursor-pointer items-center justify-between rounded-lg px-4 py-3 text-sm outline-none transition-all duration-250 ease-in-out"
bg="neutral-50 dark:neutral-800" bg="neutral-50 dark:neutral-800"
hover="bg-neutral-200 dark:bg-neutral-700" hover="bg-neutral-200 dark:bg-neutral-700"
> >
@@ -1,38 +0,0 @@
<script setup lang="ts">
import { Collapsable } from '@proj-airi/stage-ui/components'
withDefaults(defineProps<{
title: string
icon: string
innerClass?: string
expand?: boolean
}>(), {
expand: true,
})
</script>
<template>
<Collapsable :default="expand">
<template #trigger="slotProps">
<button
class="setting-bar"
bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700"
@click="slotProps.setVisible(!slotProps.visible)"
>
<div flex gap-1.5>
<div :class="icon" size-6 />
{{ $t(title) }}
</div>
<div
i-solar:alt-arrow-down-bold-duotone
transition="transform duration-250"
:class="{ 'rotate-180': slotProps.visible }"
/>
</button>
</template>
<div grid gap-4 p-4 :class="innerClass">
<slot />
</div>
</Collapsable>
</template>
@@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { Section } from '@proj-airi/stage-ui/components'
import { Emotion, EmotionNeutralMotionName } from '@proj-airi/stage-ui/constants' import { Emotion, EmotionNeutralMotionName } from '@proj-airi/stage-ui/constants'
import { useSettings } from '@proj-airi/stage-ui/stores' import { useSettings } from '@proj-airi/stage-ui/stores'
import { useFileDialog, useObjectUrl } from '@vueuse/core' import { useFileDialog, useObjectUrl } from '@vueuse/core'
@@ -10,7 +11,6 @@ import { useI18n } from 'vue-i18n'
import ColorPalette from '../Settings/ColorPalette.vue' import ColorPalette from '../Settings/ColorPalette.vue'
import Button from '../Settings/Live2DModelControlButton.vue' import Button from '../Settings/Live2DModelControlButton.vue'
import Section from '../Settings/Section.vue'
defineProps<{ defineProps<{
palette: string[] palette: string[]
@@ -0,0 +1,311 @@
<script setup lang="ts">
import type { AiriCard } from '@proj-airi/stage-ui/stores'
import { Button, Section } from '@proj-airi/stage-ui/components'
import { useAiriCardStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia'
import {
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogOverlay,
AlertDialogPortal,
AlertDialogRoot,
AlertDialogTitle,
AlertDialogTrigger,
} from 'radix-vue'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
const router = useRouter()
const route = useRoute('/settings/airi-card/[id]')
const { t } = useI18n()
const cardStore = useAiriCardStore()
const { getCard, removeCard } = cardStore
const { activeCardId } = storeToRefs(cardStore)
const id = computed(() => route.params.id)
// Get current card
const card = computed<AiriCard | undefined>(() => getCard(id.value as string))
// Get module settings
const moduleSettings = computed(() => {
const airiExt = card.value?.extensions?.airi?.modules
return {
consciousness: airiExt?.consciousness?.model || '',
speech: airiExt?.speech?.model || '',
voice: airiExt?.speech?.voice_id || '',
}
})
// Get character settings
const characterSettings = computed(() => {
if (!card.value)
return {}
return {
personality: card.value.personality,
scenario: card.value.scenario,
systemPrompt: card.value.systemPrompt,
postHistoryInstructions: card.value.postHistoryInstructions,
}
})
// Check if card is active
const isActive = computed(() => id.value === activeCardId.value)
// Activate card
function activateCard() {
activeCardId.value = id.value as string
}
// Animation control for card activation
const isActivating = ref(false)
function handleActivate() {
isActivating.value = true
setTimeout(() => {
activateCard()
isActivating.value = false
activeCardId.value = 'default'
}, 300)
}
// Delete card confirmation
const showDeleteConfirm = ref(false)
function handleDeleteConfirm() {
if (card.value) {
removeCard(id.value as string)
activateCard()
}
showDeleteConfirm.value = false
}
function hightlightTagToHtml(text: string) {
return text?.replace(/\{\{(.*?)\}\}/g, '<span class="bg-primary-500/20 inline-block">{{ $1 }}</span>').trim()
}
</script>
<template>
<div
v-motion
flex="~ row" items-center gap-2
:initial="{ opacity: 0, x: 10 }"
:enter="{ opacity: 1, x: 0 }"
:leave="{ opacity: 0, x: -10 }"
:duration="250"
>
<button @click="router.back()">
<div i-solar:alt-arrow-left-line-duotone text-2xl />
</button>
<h1 relative>
<div absolute left-0 top-0 translate-y="[-80%]">
<span text="neutral-300 dark:neutral-500" text-nowrap>{{ t('settings.title') }}</span>
</div>
<div text-nowrap text-3xl font-semibold>
{{ t('settings.pages.card.title') }}
</div>
</h1>
</div>
<div bg="neutral-50 dark:[rgba(0,0,0,0.3)]" rounded-xl p-4 flex="~ col gap-4">
<div
v-if="card"
bg="neutral-50 dark:[rgba(0,0,0,0.3)]"
rounded-xl p-5 flex="~ col gap-5"
border="~ neutral-200/50 dark:neutral-700/30"
shadow="sm dark:md"
transition="all duration-300"
class="backdrop-blur-sm"
>
<!-- Header -->
<div flex="~ col" gap-3>
<div flex="~ row" items-center justify-between>
<div>
<h1 text-2xl font-bold class="from-primary-500 to-primary-400 bg-gradient-to-r bg-clip-text text-transparent">
{{ card.name }}
</h1>
<div mt-1 text-sm text-neutral-500 dark:text-neutral-400>
v{{ card.version }}
<template v-if="card.creator">
· {{ t('settings.pages.card.created_by') }} <span font-medium>{{ card.creator }}</span>
</template>
</div>
</div>
<!-- Action buttons -->
<div flex="~ row" gap-2>
<!-- Delete button -->
<AlertDialogRoot v-if="id !== 'default'" v-model:open="showDeleteConfirm">
<AlertDialogTrigger as-child>
<Button
variant="danger"
:label="t('settings.pages.card.delete')"
/>
</AlertDialogTrigger>
<AlertDialogPortal>
<AlertDialogOverlay class="fixed inset-0 z-50 bg-black/50" />
<AlertDialogContent
class="fixed left-1/2 top-1/2 z-50 max-w-md w-full border border-neutral-200 rounded-xl bg-white p-6 shadow-xl -translate-x-1/2 -translate-y-1/2 dark:border-neutral-700 dark:bg-neutral-800"
>
<AlertDialogTitle class="mb-4 text-xl font-bold">
{{ t('settings.pages.card.delete_card') }}
</AlertDialogTitle>
<AlertDialogDescription class="mb-6">
{{ t('settings.pages.card.delete_confirmation') }} <b>"{{ card.name }}"</b>
</AlertDialogDescription>
<div class="flex flex-row justify-end gap-3">
<AlertDialogCancel as-child>
<Button
variant="secondary"
:label="t('settings.pages.card.cancel')"
@click="() => showDeleteConfirm = false"
/>
</AlertDialogCancel>
<AlertDialogAction as-child>
<Button
variant="danger"
:label="t('settings.pages.card.delete')"
@click="handleDeleteConfirm"
/>
</AlertDialogAction>
</div>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialogRoot>
<!-- Activation button -->
<Button
variant="primary"
:label="isActive ? t('settings.pages.card.active') : t('settings.pages.card.activate')"
:disabled="isActive"
:class="{ 'animate-pulse': isActivating }"
@click="handleActivate"
/>
</div>
</div>
<!-- Creator notes -->
<Section v-if="card.notes" :title="t('settings.pages.card.creator_notes')" icon="i-solar:notes-linear">
<div
bg="white/60 dark:black/30"
border="~ neutral-200/50 dark:neutral-700/30"
max-h-80 overflow-auto whitespace-pre-line rounded-lg p-4 text-neutral-700 dark:text-neutral-300 transition="all duration-200"
hover="bg-white/80 dark:bg-black/40"
v-html="hightlightTagToHtml(card.notes)"
/>
</Section>
<!-- Description section -->
<Section v-if="card.description" :title="t('settings.pages.card.description_label')" icon="i-solar:document-text-linear">
<div
bg="white/60 dark:black/30"
max-h-80 overflow-auto whitespace-pre-line rounded-lg p-4
text="neutral-600 dark:neutral-300"
border="~ neutral-200/50 dark:neutral-700/30"
v-html="hightlightTagToHtml(card.description)"
/>
</Section>
<!-- Character -->
<template v-if="Object.values(characterSettings).some(value => !!value)">
<Section :title="t('settings.pages.card.character')" icon="i-solar:user-rounded-linear">
<div flex="~ col" gap-4>
<template v-for="(value, key) in characterSettings" :key="key">
<div v-if="value" flex="~ col" gap-2>
<h2 text-lg text-neutral-500 font-medium dark:text-neutral-400>
{{ t(`settings.pages.card.${key.toLowerCase()}`) }}
</h2>
<div
bg="white/60 dark:black/30"
border="~ neutral-200/50 dark:neutral-700/30"
transition="all duration-200"
hover="bg-white/80 dark:bg-black/40"
max-h-80 overflow-auto whitespace-pre-line rounded-lg p-3 text-neutral-700 dark:text-neutral-300
v-html="hightlightTagToHtml(value)"
/>
</div>
</template>
</div>
</Section>
</template>
<!-- Modules -->
<Section :title="t('settings.pages.card.modules')" icon="i-solar:tuning-square-linear">
<div grid="~ cols-1 sm:cols-3" gap-4>
<div
flex="~ col"
bg="white/60 dark:black/30"
gap-1 rounded-lg p-3
border="~ neutral-200/50 dark:neutral-700/30"
transition="all duration-200"
hover="bg-white/80 dark:bg-black/40"
>
<span flex="~ row" items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400>
<div i-lucide:ghost />
{{ t('settings.pages.card.consciousness.model') }}
</span>
<div truncate font-medium>
{{ moduleSettings.consciousness ?? 'default' }}
</div>
</div>
<div
flex="~ col"
bg="white/60 dark:black/30"
gap-2 rounded-lg p-3
border="~ neutral-200/50 dark:neutral-700/30"
transition="all duration-200"
hover="bg-white/80 dark:bg-black/40"
>
<span flex="~ row" items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400>
<div i-lucide:mic />
{{ t('settings.pages.card.speech.model') }}
</span>
<div truncate font-medium>
{{ moduleSettings.speech ?? 'default' }}
</div>
</div>
<div
flex="~ col"
bg="white/60 dark:black/30"
gap-2 rounded-lg p-3
border="~ neutral-200/50 dark:neutral-700/30"
transition="all duration-200"
hover="bg-white/80 dark:bg-black/40"
>
<span flex="~ row" items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400>
<div i-lucide:music />
{{ t('settings.pages.card.speech.voice') }}
</span>
<div truncate font-medium>
{{ moduleSettings.voice ?? 'default' }}
</div>
</div>
</div>
</Section>
</div>
</div>
<div
v-else
bg="neutral-50/50 dark:neutral-900/50"
rounded-xl p-8 text-center
border="~ neutral-200/50 dark:neutral-700/30"
shadow="sm"
>
<div i-solar:card-search-broken mx-auto mb-3 text-6xl text-neutral-400 />
{{ t('settings.pages.card.card_not_found') }}
</div>
</div>
<div text="neutral-200/50 dark:neutral-600/20" pointer-events-none fixed bottom-0 right-0 z--1 translate-x-10 translate-y-10>
<div text="40" i-lucide:id-card />
</div>
</template>
@@ -1,296 +0,0 @@
<script setup lang="ts">
import type { AiriCard } from '@proj-airi/stage-ui/stores'
import { Button, Section } from '@proj-airi/stage-ui/components'
import { useAiriCardStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia'
import {
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogOverlay,
AlertDialogPortal,
AlertDialogRoot,
AlertDialogTitle,
AlertDialogTrigger,
} from 'radix-vue'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
interface Props {
cardId: string
}
interface Emits {
(e: 'activate'): void
(e: 'delete'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const { t } = useI18n()
const cardStore = useAiriCardStore()
const { getCard, removeCard } = cardStore
const { activeCardId } = storeToRefs(cardStore)
// Get current card
const card = computed<AiriCard | undefined>(() => getCard(props.cardId))
// Get module settings
const moduleSettings = computed(() => {
const airiExt = card.value?.extensions?.airi?.modules
return {
consciousness: airiExt?.consciousness?.model || '',
speech: airiExt?.speech?.model || '',
voice: airiExt?.speech?.voice_id || '',
}
})
// Get character settings
const characterSettings = computed(() => {
if (!card.value)
return {}
return {
personality: card.value.personality,
scenario: card.value.scenario,
systemPrompt: card.value.systemPrompt,
postHistoryInstructions: card.value.postHistoryInstructions,
}
})
// Check if card is active
const isActive = computed(() => props.cardId === activeCardId.value)
// Activate card
function activateCard() {
activeCardId.value = props.cardId
emit('activate')
}
// Animation control for card activation
const isActivating = ref(false)
function handleActivate() {
isActivating.value = true
setTimeout(() => {
activateCard()
isActivating.value = false
activeCardId.value = 'default'
}, 300)
}
// Delete card confirmation
const showDeleteConfirm = ref(false)
function handleDeleteConfirm() {
if (card.value) {
removeCard(props.cardId)
emit('delete')
activateCard()
}
showDeleteConfirm.value = false
}
function hightlightTagToHtml(text: string) {
return text?.replace(/\{\{(.*?)\}\}/g, '<span class="bg-primary-500/20 inline-block">{{ $1 }}</span>').trim()
}
</script>
<template>
<div
v-if="card"
bg="neutral-50 dark:[rgba(0,0,0,0.3)]"
rounded-xl p-5 flex="~ col gap-5"
border="~ neutral-200/50 dark:neutral-700/30"
shadow="sm dark:md"
transition="all duration-300"
class="backdrop-blur-sm"
>
<!-- Header -->
<div flex="~ col" gap-3>
<div flex="~ row" items-center justify-between>
<div>
<h1 text-2xl font-bold class="from-primary-500 to-primary-400 bg-gradient-to-r bg-clip-text text-transparent">
{{ card.name }}
</h1>
<div mt-1 text-sm text-neutral-500 dark:text-neutral-400>
v{{ card.version }}
<template v-if="card.creator">
· {{ t('settings.pages.card.created_by') }} <span font-medium>{{ card.creator }}</span>
</template>
</div>
</div>
<!-- Action buttons -->
<div flex="~ row" gap-2>
<!-- Delete button -->
<AlertDialogRoot v-if="props.cardId !== 'default'" v-model:open="showDeleteConfirm">
<AlertDialogTrigger as-child>
<Button
variant="danger"
:label="t('settings.pages.card.delete')"
/>
</AlertDialogTrigger>
<AlertDialogPortal>
<AlertDialogOverlay class="fixed inset-0 z-50 bg-black/50" />
<AlertDialogContent
class="fixed left-1/2 top-1/2 z-50 max-w-md w-full border border-neutral-200 rounded-xl bg-white p-6 shadow-xl -translate-x-1/2 -translate-y-1/2 dark:border-neutral-700 dark:bg-neutral-800"
>
<AlertDialogTitle class="mb-4 text-xl font-bold">
{{ t('settings.pages.card.delete_card') }}
</AlertDialogTitle>
<AlertDialogDescription class="mb-6">
{{ t('settings.pages.card.delete_confirmation') }} <b>"{{ card.name }}"</b>
</AlertDialogDescription>
<div class="flex flex-row justify-end gap-3">
<AlertDialogCancel as-child>
<Button
variant="secondary"
:label="t('settings.pages.card.cancel')"
@click="() => showDeleteConfirm = false"
/>
</AlertDialogCancel>
<AlertDialogAction as-child>
<Button
variant="danger"
:label="t('settings.pages.card.delete')"
@click="handleDeleteConfirm"
/>
</AlertDialogAction>
</div>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialogRoot>
<!-- Activation button -->
<Button
variant="primary"
:label="isActive ? t('settings.pages.card.active') : t('settings.pages.card.activate')"
:disabled="isActive"
:class="{ 'animate-pulse': isActivating }"
@click="handleActivate"
/>
</div>
</div>
<!-- Creator notes -->
<Section v-if="card.notes" :title="t('settings.pages.card.creator_notes')" icon="i-solar:notes-bold-duotone">
<div
bg="white/60 dark:black/30"
whitespace-pre-line rounded-lg p-4
text-neutral-700 dark:text-neutral-300
border="~ neutral-200/50 dark:neutral-700/30"
transition="all duration-200"
hover="bg-white/80 dark:bg-black/40"
v-html="hightlightTagToHtml(card.notes)"
/>
</Section>
<!-- Description section -->
<Section v-if="card.description" :title="t('settings.pages.card.description_label')" icon="i-solar:document-text-bold-duotone">
<div
bg="white/60 dark:black/30"
whitespace-pre-line
rounded-lg
p-4
text="neutral-600 dark:neutral-300"
border="~ neutral-200/50 dark:neutral-700/30"
v-html="hightlightTagToHtml(card.description)"
/>
</Section>
<!-- Character -->
<template v-if="Object.values(characterSettings).some(value => !!value)">
<Section :title="t('settings.pages.card.character')" icon="i-solar:user-rounded-bold-duotone">
<div flex="~ col" gap-4>
<template v-for="(value, key) in characterSettings" :key="key">
<div v-if="value" flex="~ col" gap-2>
<h2 text-lg text-neutral-500 font-medium dark:text-neutral-400>
{{ t(`settings.pages.card.${key.toLowerCase()}`) }}
</h2>
<div
bg="white/60 dark:black/30"
border="~ neutral-200/50 dark:neutral-700/30"
transition="all duration-200"
hover="bg-white/80 dark:bg-black/40"
max-h-60 overflow-auto whitespace-pre-line rounded-lg p-3 text-neutral-700 dark:text-neutral-300
v-html="hightlightTagToHtml(value)"
/>
</div>
</template>
</div>
</Section>
</template>
<!-- Modules -->
<Section :title="t('settings.pages.card.modules')" icon="i-solar:tuning-square-bold-duotone">
<div grid="~ cols-1 sm:cols-3" gap-4>
<div
flex="~ col"
bg="white/60 dark:black/30"
gap-1 rounded-lg p-3
border="~ neutral-200/50 dark:neutral-700/30"
transition="all duration-200"
hover="bg-white/80 dark:bg-black/40"
>
<span flex="~ row" items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400>
<div i-lucide:ghost />
{{ t('settings.pages.modules.consciousness.title') }}
</span>
<div truncate font-medium>
{{ moduleSettings.consciousness }}
</div>
</div>
<div
flex="~ col"
bg="white/60 dark:black/30"
gap-2 rounded-lg p-3
border="~ neutral-200/50 dark:neutral-700/30"
transition="all duration-200"
hover="bg-white/80 dark:bg-black/40"
>
<span flex="~ row" items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400>
<div i-lucide:mic />
{{ t('settings.pages.modules.speech.title') }}
</span>
<div truncate font-medium>
{{ moduleSettings.speech }}
</div>
</div>
<div
flex="~ col"
bg="white/60 dark:black/30"
gap-2 rounded-lg p-3
border="~ neutral-200/50 dark:neutral-700/30"
transition="all duration-200"
hover="bg-white/80 dark:bg-black/40"
>
<span flex="~ row" items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400>
<div i-solar:music-notes-bold-duotone />
{{ t('settings.pages.card.voice_id') }}
</span>
<div truncate font-medium>
{{ moduleSettings.voice }}
</div>
</div>
</div>
</Section>
</div>
</div>
<div
v-else
bg="neutral-50/50 dark:neutral-900/50"
rounded-xl p-8 text-center
border="~ neutral-200/50 dark:neutral-700/30"
shadow="sm"
>
<div i-solar:card-search-broken mx-auto mb-3 text-6xl text-neutral-400 />
{{ t('settings.pages.card.card_not_found') }}
</div>
</template>
@@ -5,12 +5,10 @@ import { Button } from '@proj-airi/stage-ui/components/Button'
import { RadioCardDetailManySelect } from '@proj-airi/stage-ui/components/Form' import { RadioCardDetailManySelect } from '@proj-airi/stage-ui/components/Form'
import { useAiriCardStore } from '@proj-airi/stage-ui/stores' import { useAiriCardStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { computed, ref } from 'vue' import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import AiriCardView from './components/AiriCardView.vue'
const router = useRouter() const router = useRouter()
const { t } = useI18n() const { t } = useI18n()
const cardStore = useAiriCardStore() const cardStore = useAiriCardStore()
@@ -66,21 +64,10 @@ async function handleUpload() {
fileInput.click() fileInput.click()
} }
/** watch(selectedCardId, (cardId) => {
* Sets the selected card as active if (cardId)
*/ router.push(`/settings/airi-card/${cardId}`)
function activateSelectedCard() { })
if (selectedCardId.value)
activeCardId.value = selectedCardId.value
}
/**
* Handles card deletion
*/
function handleCardDelete() {
// Reset selected card ID if it's the one being deleted
selectedCardId.value = cardsArray.value.length > 0 ? cardsArray.value[0].id : ''
}
</script> </script>
<template> <template>
@@ -129,20 +116,11 @@ function handleCardDelete() {
/> />
</template> </template>
</div> </div>
</div>
<!-- Card content area --> <!-- Background decoration -->
<div v-if="selectedCardId && cards.size > 0" mt-6> <div text="neutral-200/50 dark:neutral-600/20" pointer-events-none fixed bottom-0 right-0 z--1 translate-x-10 translate-y-10>
<AiriCardView <div text="40" i-lucide:id-card />
:card-id="selectedCardId"
@activate="activateSelectedCard"
@delete="handleCardDelete"
/>
</div>
<!-- Background decoration -->
<div text="neutral-200/50 dark:neutral-600/20" pointer-events-none fixed bottom-0 right-0 z--1 translate-x-10 translate-y-10>
<div text="40" i-lucide:id-card />
</div>
</div> </div>
</template> </template>
@@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { Section } from '@proj-airi/stage-ui/components'
import { useSettings } from '@proj-airi/stage-ui/stores' import { useSettings } from '@proj-airi/stage-ui/stores'
import { useDark } from '@vueuse/core' import { useDark } from '@vueuse/core'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
@@ -6,7 +7,6 @@ import { useRouter } from 'vue-router'
import CheckBar from '../../../components/Settings/CheckBar.vue' import CheckBar from '../../../components/Settings/CheckBar.vue'
import ColorPalette from '../../../components/Settings/ColorPalette.vue' import ColorPalette from '../../../components/Settings/ColorPalette.vue'
import Section from '../../../components/Settings/Section.vue'
import { useIconAnimation } from '../../../composables/useIconAnimation' import { useIconAnimation } from '../../../composables/useIconAnimation'
import COLOR_PRESETS from './color-presets.json' import COLOR_PRESETS from './color-presets.json'
@@ -55,7 +55,7 @@ watch(() => [settings.usePageSpecificTransitions, settings.disableTransitions],
/> />
<!-- Language Setting --> <!-- Language Setting -->
<div <div
class="setting-bar text-sm" class="w-full flex items-center justify-between rounded-lg px-4 py-3 text-sm outline-none transition-all duration-250 ease-in-out"
bg="neutral-50 dark:neutral-800" bg="neutral-50 dark:neutral-800"
hover="bg-neutral-200 dark:bg-neutral-700" hover="bg-neutral-200 dark:bg-neutral-700"
> >
@@ -129,7 +129,7 @@ watch(() => [settings.usePageSpecificTransitions, settings.disableTransitions],
<Section title="settings.pages.themes.sections.section.theme-presets.title" icon="i-solar:magic-stick-2-bold-duotone"> <Section title="settings.pages.themes.sections.section.theme-presets.title" icon="i-solar:magic-stick-2-bold-duotone">
<div <div
v-for="({ title, description, colors }, i) in $tm('settings.pages.themes.sections.section.theme-presets.presets')" :key="i" v-for="({ title, description, colors }, i) in $tm('settings.pages.themes.sections.section.theme-presets.presets')" :key="i"
class="setting-bar" class="w-full flex items-center justify-between rounded-lg px-4 py-3 outline-none transition-all duration-250 ease-in-out"
bg="neutral-100 dark:neutral-800" bg="neutral-100 dark:neutral-800"
hover="bg-neutral-200 dark:bg-neutral-700" hover="bg-neutral-200 dark:bg-neutral-700"
> >
+1 -1
View File
@@ -63,7 +63,7 @@ const settings = computed(() => [
{ {
title: t('settings.pages.card.title'), title: t('settings.pages.card.title'),
description: t('settings.pages.card.description'), description: t('settings.pages.card.description'),
icon: 'i-lucide:card', icon: 'i-lucide:id-card',
to: '/settings/airi-card', to: '/settings/airi-card',
}, },
{ {
-5
View File
@@ -34,8 +34,3 @@ html.dark {
width: 100%; width: 100%;
height: 2px; height: 2px;
} }
.setting-bar {
--at-apply: transition-all ease-in-out duration-250 w-full flex items-center justify-between rounded-lg px-4 py-3
outline-none;
}
+1 -1
View File
@@ -64,7 +64,7 @@ export const useChatStore = defineStore('chat', () => {
const messages = ref<Array<Message | ErrorMessage>>([ const messages = ref<Array<Message | ErrorMessage>>([
{ {
role: 'system', role: 'system',
content: systemPrompt.value.replace(/\{\{user\}\}/g, 'user'), content: systemPrompt.value, // TODO: compose, replace {{ user }} tag, etc
} satisfies SystemMessage, } satisfies SystemMessage,
]) ])