refactor(ui): 统一全局确认框交互

This commit is contained in:
tbphp committed 2026-08-01 15:20:58 +08:00
1 parent 8fd4b6ec83
commit 30d51de088
17 files changed
+275 -302

No files matched your search

@@ -0,0 +1,66 @@
<script setup lang="ts">
import AppButton from './AppButton.vue'
import AppDialog from './AppDialog.vue'
withDefaults(
defineProps<{
open: boolean
title: string
description: string
closeLabel: string
cancelLabel: string
confirmLabel: string
tone?: 'default' | 'danger'
pending?: boolean
confirmDisabled?: boolean
dismissible?: boolean
preventCloseAutoFocus?: boolean
}>(),
{
tone: 'default',
pending: false,
confirmDisabled: false,
dismissible: true,
preventCloseAutoFocus: false,
},
)
const emit = defineEmits<{
'update:open': [open: boolean]
confirm: []
}>()
</script>
<template>
<AppDialog
:open="open"
:title="title"
:description="description"
:close-label="closeLabel"
:dismissible="dismissible && !pending"
:prevent-close-auto-focus="preventCloseAutoFocus"
@update:open="emit('update:open', $event)"
>
<template v-if="$slots.trigger" #trigger><slot name="trigger" /></template>
<template v-if="$slots.default" #body><slot /></template>
<template #footer>
<AppButton
variant="secondary"
size="compact"
:disabled="pending"
@click="emit('update:open', false)"
>
{{ cancelLabel }}
</AppButton>
<AppButton
:variant="tone === 'danger' ? 'danger' : 'primary'"
size="compact"
:busy="pending"
:disabled="confirmDisabled"
@click="emit('confirm')"
>
<slot name="confirm-icon" />
{{ confirmLabel }}
</AppButton>
</template>
</AppDialog>
</template>
+27 -11
View File
@@ -52,7 +52,8 @@ function guardDismiss(event: Event): void {
</DialogClose>
</header>
<DialogDescription class="app-dialog__description">{{ description }}</DialogDescription>
<div class="app-dialog__body"><slot /></div>
<div v-if="$slots.body" class="app-dialog__body"><slot name="body" /></div>
<footer v-if="$slots.footer" class="app-dialog__footer"><slot name="footer" /></footer>
</DialogContent>
</DialogPortal>
</DialogRoot>
@@ -71,7 +72,7 @@ function guardDismiss(event: Event): void {
top: 50%;
left: 50%;
display: grid;
width: min(calc(100vw - 32px), 460px);
width: min(calc(100vw - 32px), 420px);
max-height: calc(100vh - 32px);
max-height: calc(100dvh - 32px);
transform: translate(-50%, -50%);
@@ -81,23 +82,26 @@ function guardDismiss(event: Event): void {
background: var(--color-surface);
box-shadow: var(--shadow-overlay);
color: var(--color-text);
padding: var(--space-5);
padding: 0;
}
.app-dialog__header {
display: flex;
align-items: flex-start;
min-height: 58px;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
border-bottom: 1px solid var(--color-border-subtle);
padding: 10px 16px 10px 18px;
}
.app-dialog__title {
font-size: 1.0625rem;
font-size: var(--text-meta);
font-weight: 700;
}
.app-dialog__close {
display: inline-flex;
width: 44px;
height: 44px;
flex: 0 0 44px;
width: 36px;
height: 36px;
flex: 0 0 36px;
align-items: center;
justify-content: center;
border: 0;
@@ -111,13 +115,25 @@ function guardDismiss(event: Event): void {
opacity: 0.55;
}
.app-dialog__description {
margin: var(--space-2) 0 0;
color: var(--color-text-muted);
margin: 0;
padding: 12px 18px 0;
color: var(--color-text-faint);
font-size: var(--text-sm);
line-height: var(--line-normal);
}
.app-dialog__body {
min-height: 0;
margin-top: var(--space-5);
overflow-y: auto;
overscroll-behavior: contain;
padding: 14px 18px 16px;
}
.app-dialog__footer {
display: flex;
min-height: 58px;
align-items: center;
justify-content: flex-end;
gap: var(--space-2);
border-top: 1px solid var(--color-border-subtle);
padding: 10px 18px;
}
</style>
+24 -8
View File
@@ -154,20 +154,36 @@ function guardDismiss(event: Event): void {
padding: 10px 18px;
}
@keyframes app-drawer-overlay-in {
from { opacity: 0; }
to { opacity: 1; }
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes app-drawer-overlay-out {
from { opacity: 1; }
to { opacity: 0; }
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes app-drawer-content-in {
from { transform: translateX(100%); }
to { transform: translateX(0); }
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@keyframes app-drawer-content-out {
from { transform: translateX(0); }
to { transform: translateX(100%); }
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
@media (max-width: 480px) {
.app-drawer__content {
+1 -6
View File
@@ -10,12 +10,7 @@ const current = toast.current
<template>
<Teleport to="body">
<Transition name="app-toast" mode="out-in">
<InlineFeedback
v-if="current"
:key="current.id"
:tone="current.tone"
appearance="toast"
>
<InlineFeedback v-if="current" :key="current.id" :tone="current.tone" appearance="toast">
{{ current.message }}
</InlineFeedback>
</Transition>
@@ -0,0 +1,64 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{
id: string
label: string
modelValue: string
disabled?: boolean
}>()
const emit = defineEmits<{ 'update:modelValue': [value: string] }>()
const input = ref<HTMLInputElement>()
function focus(): void {
input.value?.focus()
}
defineExpose({ focus })
</script>
<template>
<label class="app-typed-confirmation" :for="id">
<span>{{ label }}</span>
<input
:id="id"
ref="input"
:value="modelValue"
type="text"
autocomplete="off"
spellcheck="false"
:disabled="disabled"
@input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
/>
</label>
</template>
<style scoped>
.app-typed-confirmation {
display: grid;
gap: 6px;
color: var(--color-text-muted);
font-size: var(--text-sm);
font-weight: 600;
}
.app-typed-confirmation input {
width: 100%;
min-height: var(--control-md);
border: 1px solid var(--color-border-control);
border-radius: var(--radius-control);
outline: 0;
background: var(--color-surface-sunken);
color: var(--color-text);
padding: var(--space-2) var(--space-3);
font: inherit;
font-weight: 400;
}
.app-typed-confirmation input:focus {
border-color: var(--color-action);
box-shadow: var(--focus-ring);
}
.app-typed-confirmation input:disabled {
cursor: not-allowed;
opacity: 0.6;
}
</style>
@@ -242,7 +242,6 @@ onBeforeUnmount(() => {
overflow-wrap: anywhere;
white-space: normal;
}
}
@media (max-width: 860px) {
@@ -8,7 +8,8 @@ import { deleteAccessKey } from '@/app/resources/access-keys'
import type { AccessKeyDto } from '@/api/control/types'
import { RequestCancelledError } from '@/api/errors'
import AppButton from '@/components/ui/AppButton.vue'
import AppDialog from '@/components/ui/AppDialog.vue'
import AppConfirmDialog from '@/components/ui/AppConfirmDialog.vue'
import AppTypedConfirmation from '@/components/ui/AppTypedConfirmation.vue'
import InlineFeedback from '@/components/ui/InlineFeedback.vue'
const props = defineProps<{ accessKey: AccessKeyDto; total: number }>()
@@ -17,7 +18,7 @@ const client = useApiClient()
const { t } = useI18n()
const open = ref(false)
const typedName = ref('')
const nameInput = ref<HTMLInputElement>()
const nameInput = ref<InstanceType<typeof AppTypedConfirmation>>()
const pending = ref(false)
const failed = ref(false)
let controller: AbortController | undefined
@@ -65,13 +66,18 @@ onBeforeUnmount(() => controller?.abort())
</script>
<template>
<AppDialog
<AppConfirmDialog
:open="open"
:title="t('accessKeys.delete.title')"
:description="t('accessKeys.delete.description', { name: accessKey.name })"
:close-label="t('accessKeys.delete.close')"
:dismissible="!pending"
:cancel-label="t('common.cancel')"
:confirm-label="t('accessKeys.delete.confirm')"
tone="danger"
:pending="pending"
:confirm-disabled="!confirmed"
@update:open="setOpen"
@confirm="confirmDelete"
>
<template #trigger>
<AppButton variant="danger" size="compact" @click="setOpen(true)">
@@ -81,98 +87,53 @@ onBeforeUnmount(() => controller?.abort())
</template>
<div class="access-key-delete__body">
<InlineFeedback v-if="total === 1" tone="warning">
<div class="access-key-delete__summary">
<strong>{{ accessKey.name }}</strong>
<code>{{ accessKey.masked_key }}</code>
</div>
<InlineFeedback v-if="total === 1" tone="warning" appearance="hint">
{{ t('accessKeys.delete.lastWarning') }}
</InlineFeedback>
<dl class="access-key-delete__details">
<dt>{{ t('accessKeys.delete.name') }}</dt>
<dd>{{ accessKey.name }}</dd>
<dt>{{ t('accessKeys.delete.key') }}</dt>
<dd>
<code>{{ accessKey.masked_key }}</code>
</dd>
</dl>
<InlineFeedback tone="warning">
<InlineFeedback tone="warning" appearance="hint">
{{ t('accessKeys.delete.impact') }}
</InlineFeedback>
<label class="access-key-delete__label" for="access-key-delete-name">{{
t('accessKeys.delete.typeName', { name: accessKey.name })
}}</label>
<input
<AppTypedConfirmation
id="access-key-delete-name"
ref="nameInput"
v-model="typedName"
type="text"
autocomplete="off"
spellcheck="false"
:label="t('accessKeys.delete.typeName', { name: accessKey.name })"
:disabled="pending"
/>
<InlineFeedback v-if="failed" tone="danger">{{
t('accessKeys.delete.failed')
}}</InlineFeedback>
<div class="access-key-delete__actions">
<AppButton variant="secondary" :disabled="pending" @click="setOpen(false)">
{{ t('common.cancel') }}
</AppButton>
<AppButton
class="access-key-delete__confirm"
variant="danger"
:busy="pending"
:disabled="!confirmed"
@click="confirmDelete"
>
{{ t('accessKeys.delete.confirm') }}
</AppButton>
</div>
</div>
</AppDialog>
</AppConfirmDialog>
</template>
<style scoped>
.access-key-delete__body {
display: grid;
gap: var(--space-4);
gap: 12px;
}
.access-key-delete__label {
margin-bottom: calc(var(--space-3) * -1);
font-weight: 650;
}
.access-key-delete__details {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: var(--space-2) var(--space-3);
margin: 0;
padding: var(--space-3);
.access-key-delete__summary {
display: flex;
min-width: 0;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-control);
background: var(--color-surface-sunken);
padding: 9px 10px;
}
.access-key-delete__details dt {
color: var(--color-text-muted);
.access-key-delete__summary strong {
font-size: var(--text-sm);
}
.access-key-delete__details dd {
.access-key-delete__summary code {
min-width: 0;
margin: 0;
color: var(--color-code);
font-size: var(--text-label-xs);
overflow-wrap: anywhere;
}
.access-key-delete__details code {
color: var(--color-code);
}
input {
width: 100%;
min-height: var(--touch-target);
border: 1px solid var(--color-border-control);
border-radius: var(--radius-control);
background: var(--color-surface-sunken);
color: var(--color-text);
padding: var(--space-2) var(--space-3);
font: inherit;
}
.access-key-delete__actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: var(--space-2);
}
</style>
@@ -708,11 +708,7 @@ onBeforeUnmount(clearLocalState)
<template #footer>
<div v-if="editing && base" class="access-key-drawer__delete">
<AccessKeyDeleteDialog
:access-key="base"
:total="total"
@deleted="handleDeleted"
/>
<AccessKeyDeleteDialog :access-key="base" :total="total" @deleted="handleDeleted" />
</div>
<AppButton variant="secondary" :disabled="closeBlocked" @click="setOpen(false)">
{{ t('common.cancel') }}
@@ -45,10 +45,7 @@ function optionDisabled(dimension: 'protocols' | 'models'): boolean {
return props.disabled || props.modes[dimension] !== 'restricted' || catalogUnavailable()
}
function requestScopeMode(
dimension: AccessKeyScopeDimension,
mode: AccessKeyScopeMode,
): void {
function requestScopeMode(dimension: AccessKeyScopeDimension, mode: AccessKeyScopeMode): void {
emit('setScopeMode', dimension, mode)
}
@@ -78,7 +75,6 @@ function toggleProtocol(protocol: AccessProtocol, event: Event): void {
: props.filters.protocols.filter((value) => value !== protocol)
emit('update:protocols', next)
}
</script>
<template>
@@ -164,11 +160,7 @@ function toggleProtocol(protocol: AccessProtocol, event: Event): void {
<i aria-hidden="true" />{{ t('accessKeys.drawer.allProtocolsAllowed') }}
</div>
<div v-else class="protocol-options">
<label
v-for="protocol in protocolOptions"
:key="protocol"
class="access-key-drawer__check"
>
<label v-for="protocol in protocolOptions" :key="protocol" class="access-key-drawer__check">
<input
type="checkbox"
:checked="filters.protocols.includes(protocol)"
+10 -32
View File
@@ -15,7 +15,7 @@ import {
import { applyInvalidationPlan, mutationInvalidationPlans } from '@/app/resources/invalidation'
import { groupDetailLocation } from '@/app/route-locations'
import AppButton from '@/components/ui/AppButton.vue'
import AppDialog from '@/components/ui/AppDialog.vue'
import AppConfirmDialog from '@/components/ui/AppConfirmDialog.vue'
import DataTable from '@/components/ui/DataTable.vue'
import EmptyState from '@/components/ui/EmptyState.vue'
import QueryFeedback from '@/components/ui/QueryFeedback.vue'
@@ -290,13 +290,17 @@ const effectiveLabels = computed(() => ({
>
{{ t('group.keys.saveWeight') }}
</AppButton>
<AppDialog
<AppConfirmDialog
:open="deleteKeyId === key.id"
:title="t('group.keys.deleteTitle')"
:description="t('group.keys.deleteDescription', { mask: key.mask })"
:close-label="t('group.keys.closeDialog')"
:dismissible="!pending(key.id)"
:cancel-label="t('group.keys.cancel')"
:confirm-label="t('group.keys.confirmDelete')"
tone="danger"
:pending="pending(key.id)"
@update:open="setDeleteDialog($event, key.id)"
@confirm="confirmDelete(key)"
>
<template #trigger>
<AppButton
@@ -308,25 +312,7 @@ const effectiveLabels = computed(() => ({
{{ t('group.keys.delete') }}
</AppButton>
</template>
<div class="group-keys__dialog-actions">
<AppButton
variant="secondary"
:disabled="pending(key.id)"
@click="setDeleteDialog(false, key.id)"
>
{{ t('group.keys.cancel') }}
</AppButton>
<AppButton
class="group-keys__confirm-delete"
variant="secondary"
:busy="pending(key.id)"
:disabled="pending(key.id)"
@click="confirmDelete(key)"
>
{{ t('group.keys.confirmDelete') }}
</AppButton>
</div>
</AppDialog>
</AppConfirmDialog>
</div>
</td>
</tr>
@@ -381,8 +367,7 @@ const effectiveLabels = computed(() => ({
font: inherit;
cursor: pointer;
}
.group-keys__actions,
.group-keys__dialog-actions {
.group-keys__actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
@@ -392,16 +377,9 @@ const effectiveLabels = computed(() => ({
font-size: 0.8125rem;
}
.group-keys__delete :deep(button),
.group-keys__delete,
.group-keys__confirm-delete {
.group-keys__delete {
color: var(--color-danger);
}
.group-keys__confirm-delete {
border-color: var(--color-danger);
}
.group-keys__dialog-actions {
justify-content: flex-end;
}
.group-keys__action-error {
margin: 0;
border-radius: var(--radius-control);
@@ -19,7 +19,7 @@ import ModelDraftEditor, {
type ModelDraftEditorItem,
} from '@/components/config/ModelDraftEditor.vue'
import AppButton from '@/components/ui/AppButton.vue'
import AppDialog from '@/components/ui/AppDialog.vue'
import AppConfirmDialog from '@/components/ui/AppConfirmDialog.vue'
import InlineFeedback from '@/components/ui/InlineFeedback.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue'
@@ -219,14 +219,18 @@ onBeforeUnmount(() => {
>
<RefreshCw :size="16" aria-hidden="true" />{{ t('group.modelEditor.rediscover') }}
</AppButton>
<AppDialog
<AppConfirmDialog
v-if="emptySelection"
:open="emptyConfirmOpen"
:title="t('group.modelEditor.emptyConfirm.title')"
:description="t('group.modelEditor.emptyConfirm.description')"
:close-label="t('group.modelEditor.emptyConfirm.close')"
:dismissible="!pending"
:cancel-label="t('group.modelEditor.emptyConfirm.cancel')"
:confirm-label="t('group.modelEditor.emptyConfirm.confirm')"
tone="danger"
:pending="pending"
@update:open="setEmptyConfirmOpen"
@confirm="confirmEmptyReplace"
>
<template #trigger>
<AppButton
@@ -240,21 +244,7 @@ onBeforeUnmount(() => {
<InlineFeedback v-if="saveError" tone="danger">
{{ t('group.modelEditor.saveFailed') }}
</InlineFeedback>
<div class="group-models__dialog-actions">
<AppButton variant="secondary" :disabled="pending" @click="setEmptyConfirmOpen(false)">
{{ t('group.modelEditor.emptyConfirm.cancel') }}
</AppButton>
<AppButton
class="group-models__confirm-empty"
variant="secondary"
:busy="pending"
:disabled="pending"
@click="confirmEmptyReplace"
>
{{ t('group.modelEditor.emptyConfirm.confirm') }}
</AppButton>
</div>
</AppDialog>
</AppConfirmDialog>
<AppButton
v-else
:busy="pendingAction === 'save'"
@@ -333,8 +323,7 @@ onBeforeUnmount(() => {
color: var(--color-text-muted);
}
.group-models__actions,
.group-models__guidance-actions,
.group-models__dialog-actions {
.group-models__guidance-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
@@ -358,13 +347,6 @@ onBeforeUnmount(() => {
.group-models__guidance-actions .button-link {
min-height: 44px;
}
.group-models__dialog-actions {
justify-content: flex-end;
}
.group-models__confirm-empty {
border-color: var(--color-danger);
color: var(--color-danger);
}
@media (max-width: 640px) {
.group-models__header {
align-items: stretch;
@@ -12,7 +12,8 @@ import { controlQueryKeys } from '@/app/query-keys'
import { applyInvalidationPlan, mutationInvalidationPlans } from '@/app/resources/invalidation'
import { homeLocation } from '@/app/route-locations'
import AppButton from '@/components/ui/AppButton.vue'
import AppDialog from '@/components/ui/AppDialog.vue'
import AppConfirmDialog from '@/components/ui/AppConfirmDialog.vue'
import AppTypedConfirmation from '@/components/ui/AppTypedConfirmation.vue'
import InlineFeedback from '@/components/ui/InlineFeedback.vue'
import GroupInUseFeedback from './GroupInUseFeedback.vue'
@@ -24,7 +25,7 @@ const router = useRouter()
const { t } = useI18n()
const open = ref(false)
const typedName = ref('')
const nameInput = ref<HTMLInputElement>()
const nameInput = ref<InstanceType<typeof AppTypedConfirmation>>()
const pending = ref(false)
const genericError = ref(false)
const references = ref<AccessKeyReferenceDto[]>([])
@@ -90,13 +91,18 @@ onBeforeUnmount(() => controller?.abort())
</script>
<template>
<AppDialog
<AppConfirmDialog
:open="open"
:title="t('group.settings.delete.title')"
:description="t('group.settings.delete.description', { name: groupName })"
:close-label="t('group.settings.delete.close')"
:dismissible="!pending"
:cancel-label="t('group.settings.delete.cancel')"
:confirm-label="t('group.settings.delete.confirm')"
tone="danger"
:pending="pending"
:confirm-disabled="!confirmed"
@update:open="setOpen"
@confirm="confirmDelete"
>
<template #trigger>
<AppButton class="group-delete__open" variant="secondary" @click="setOpen(true)">
@@ -105,43 +111,23 @@ onBeforeUnmount(() => controller?.abort())
</template>
<div class="group-delete__body">
<label for="group-delete-name">{{
t('group.settings.delete.typeName', { name: groupName })
}}</label>
<input
<AppTypedConfirmation
id="group-delete-name"
ref="nameInput"
v-model="typedName"
type="text"
autocomplete="off"
spellcheck="false"
:label="t('group.settings.delete.typeName', { name: groupName })"
:disabled="pending"
/>
<GroupInUseFeedback v-if="references.length" :references="references" />
<InlineFeedback v-else-if="genericError" tone="danger">
{{ t('group.settings.delete.failed') }}
</InlineFeedback>
<div class="group-delete__actions">
<AppButton variant="secondary" :disabled="pending" @click="setOpen(false)">
{{ t('group.settings.delete.cancel') }}
</AppButton>
<AppButton
class="group-delete__confirm"
variant="secondary"
:busy="pending"
:disabled="!confirmed"
@click="confirmDelete"
>
{{ t('group.settings.delete.confirm') }}
</AppButton>
</div>
</div>
</AppDialog>
</AppConfirmDialog>
</template>
<style scoped>
.group-delete__open,
.group-delete__confirm {
.group-delete__open {
border-color: var(--color-danger);
color: var(--color-danger);
}
@@ -153,23 +139,4 @@ onBeforeUnmount(() => controller?.abort())
display: grid;
gap: var(--space-3);
}
label {
font-weight: 650;
}
input {
width: 100%;
min-height: 44px;
border: 1px solid var(--color-border-control);
border-radius: var(--radius-control);
background: var(--color-surface-sunken);
color: var(--color-text);
padding: var(--space-2) var(--space-3);
font: inherit;
}
.group-delete__actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: var(--space-2);
}
</style>
@@ -19,7 +19,7 @@ import { groupDetailLocation } from '@/app/route-locations'
import { useUnsavedChanges } from '@/app/unsaved-changes'
import HeaderRulesEditor from '@/components/config/HeaderRulesEditor.vue'
import AppButton from '@/components/ui/AppButton.vue'
import AppDialog from '@/components/ui/AppDialog.vue'
import AppConfirmDialog from '@/components/ui/AppConfirmDialog.vue'
import InlineFeedback from '@/components/ui/InlineFeedback.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue'
import SurfaceCard from '@/components/ui/SurfaceCard.vue'
@@ -394,29 +394,18 @@ onBeforeUnmount(() => {
<GroupDeleteDialog :group-id="groupId" :group-name="savedGroup.name" />
</SurfaceCard>
<AppDialog
<AppConfirmDialog
:open="urlConfirmOpen"
:title="t('group.settings.urlConfirm.title')"
:description="t('group.settings.urlConfirm.description')"
:close-label="t('group.settings.urlConfirm.close')"
:dismissible="!pending"
:cancel-label="t('group.settings.urlConfirm.cancel')"
:confirm-label="t('group.settings.urlConfirm.confirm')"
:pending="pending"
prevent-close-auto-focus
@update:open="setURLConfirmOpen"
>
<div class="group-settings__dialog-actions">
<AppButton variant="secondary" :disabled="pending" @click="setURLConfirmOpen(false)">
{{ t('group.settings.urlConfirm.cancel') }}
</AppButton>
<AppButton
class="group-settings__confirm-url"
variant="secondary"
:busy="pending"
@click="runSave(true)"
>
{{ t('group.settings.urlConfirm.confirm') }}
</AppButton>
</div>
</AppDialog>
@confirm="runSave(true)"
/>
</section>
</template>
@@ -428,8 +417,7 @@ onBeforeUnmount(() => {
}
.group-settings__header,
.group-settings__section-heading,
.group-settings__runtime-row,
.group-settings__dialog-actions {
.group-settings__runtime-row {
display: flex;
}
.group-settings__header,
@@ -538,15 +526,6 @@ small {
.group-settings__danger {
border-color: color-mix(in srgb, var(--color-danger) 38%, var(--color-border-subtle));
}
.group-settings__dialog-actions {
flex-wrap: wrap;
justify-content: flex-end;
gap: var(--space-2);
}
.group-settings__confirm-url {
border-color: var(--color-warning);
color: var(--color-warning);
}
@media (max-width: 759px) {
.group-settings__header {
align-items: stretch;
+7 -23
View File
@@ -9,7 +9,7 @@ import type { HomeBaseDto } from '@/app/resources/home'
import { accessKeysLocation } from '@/app/route-locations'
import { revealAccessKey } from '@/app/resources/access-keys'
import AppButton from '@/components/ui/AppButton.vue'
import AppDialog from '@/components/ui/AppDialog.vue'
import AppConfirmDialog from '@/components/ui/AppConfirmDialog.vue'
import AppSelect from '@/components/ui/AppSelect.vue'
import CodeBlock from '@/components/ui/CodeBlock.vue'
import CopyAction from '@/components/ui/CopyAction.vue'
@@ -455,26 +455,16 @@ onBeforeUnmount(() => {
{{ feedbackMessage }}
</InlineFeedback>
<AppDialog
<AppConfirmDialog
v-model:open="nextChatConfirmationOpen"
:title="t('home.ledger.connection.nextChatConfirmTitle')"
:description="t('home.ledger.connection.nextChatConfirmDescription')"
:close-label="t('common.close')"
:dismissible="!actionBusy"
>
<div class="gateway-connection__dialog-actions">
<AppButton
variant="secondary"
:disabled="actionBusy"
@click="nextChatConfirmationOpen = false"
>
{{ t('common.cancel') }}
</AppButton>
<AppButton :busy="actionBusy" @click="openNextChat">
{{ t('home.ledger.connection.openNextChat') }}
</AppButton>
</div>
</AppDialog>
:cancel-label="t('common.cancel')"
:confirm-label="t('home.ledger.connection.openNextChat')"
:pending="actionBusy"
@confirm="openNextChat"
/>
</section>
</template>
@@ -603,12 +593,6 @@ onBeforeUnmount(() => {
color: var(--color-text-muted);
}
.gateway-connection__dialog-actions {
display: flex;
justify-content: flex-end;
gap: var(--space-2);
}
@media (max-width: 860px) {
.gateway-connection__toolbar {
grid-template-columns: 1fr;
@@ -8,8 +8,7 @@ import { useApiClient } from '@/api/client-context'
import { resetModelPrice, type ModelPriceRuleDto } from '@/app/resources/model-prices'
import { applyInvalidationPlan, mutationInvalidationPlans } from '@/app/resources/invalidation'
import { RequestCancelledError } from '@/api/errors'
import AppButton from '@/components/ui/AppButton.vue'
import AppDialog from '@/components/ui/AppDialog.vue'
import AppConfirmDialog from '@/components/ui/AppConfirmDialog.vue'
import InlineFeedback from '@/components/ui/InlineFeedback.vue'
const props = defineProps<{ rule: ModelPriceRuleDto }>()
@@ -71,13 +70,17 @@ onBeforeUnmount(clearRequest)
</script>
<template>
<AppDialog
<AppConfirmDialog
:open="open"
:title="t('modelPrices.reset.title')"
:description="t('modelPrices.reset.description', { pattern: rule.pattern })"
:close-label="t('modelPrices.reset.close')"
:dismissible="!pending"
:cancel-label="t('common.cancel')"
:confirm-label="t('modelPrices.reset.confirm')"
tone="danger"
:pending="pending"
@update:open="setOpen"
@confirm="confirmReset"
>
<template #trigger>
<button type="button" class="model-price-reset__trigger" @click="setOpen(true)">
@@ -90,21 +93,8 @@ onBeforeUnmount(clearRequest)
<InlineFeedback v-if="failed" tone="danger">
{{ t('modelPrices.reset.failed') }}
</InlineFeedback>
<div class="model-price-reset__actions">
<AppButton variant="secondary" :disabled="pending" @click="setOpen(false)">
{{ t('common.cancel') }}
</AppButton>
<AppButton
class="model-price-reset__confirm"
variant="secondary"
:busy="pending"
@click="confirmReset"
>
{{ t('modelPrices.reset.confirm') }}
</AppButton>
</div>
</div>
</AppDialog>
</AppConfirmDialog>
</template>
<style scoped>
@@ -124,14 +114,4 @@ onBeforeUnmount(clearRequest)
display: grid;
gap: var(--space-4);
}
.model-price-reset__actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: var(--space-2);
}
.model-price-reset__confirm {
border-color: var(--color-danger);
color: var(--color-danger);
}
</style>
@@ -57,10 +57,7 @@ async function setDrawerOpen(open: boolean): Promise<void> {
<ArrowLeft :size="16" aria-hidden="true" />{{ t('modelPrices.back') }}
</RouterLink>
<PageHeader
id="model-prices-title"
:title="t('modelPrices.title')"
>
<PageHeader id="model-prices-title" :title="t('modelPrices.title')">
<template #actions>
<AppButton @click="addOverride">
<Plus :size="16" aria-hidden="true" />{{ t('modelPrices.add') }}
+2 -1
View File
@@ -92,7 +92,8 @@ export default {
'Edit routing filters and the RPM limit. Plaintext is masked unless explicitly revealed.',
close: 'Close AccessKey editor',
basicInformation: 'Basic information',
basicInformationDescription: 'The name identifies the client and is not used for authentication.',
basicInformationDescription:
'The name identifies the client and is not used for authentication.',
permissionScope: 'Permission scope',
permissionScopeDescription: 'Values within a dimension use OR; all three dimensions use AND.',
scopeLogic: 'Permission logic',