fix: UI polish and provider fixes
- Add animate-pulse CSS animation for loading states - Add Ctrl+I/Ctrl+B markdown shortcuts in chat input - Improve chat input resize with mirror element - Add reasoning duration display and shimmer effect - Add confirmation dialog for deleting all models - Simplify RowVirtualizerDynamic resize handling - Fix DialogType imports (type -> value) - Fix ollama cloud model name resolution - Make vllm auth header optional - Various icon and styling fixes
This commit is contained in:
@@ -224,6 +224,45 @@ button.accent:hover {
|
|||||||
animation: blink 1s step-end infinite;
|
animation: blink 1s step-end infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.animate-pulse {
|
||||||
|
background: linear-gradient(90deg,
|
||||||
|
currentColor 0%,
|
||||||
|
currentColor 35%,
|
||||||
|
rgba(255, 255, 255, 0.65) 50%,
|
||||||
|
currentColor 65%,
|
||||||
|
currentColor 100%);
|
||||||
|
background-size: 300% 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
animation:
|
||||||
|
pulse-shimmer 2s linear infinite,
|
||||||
|
pulse-opacity 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: 100% 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: 0% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-opacity {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes blink {
|
@keyframes blink {
|
||||||
|
|
||||||
0%,
|
0%,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ watch(files, (newFiles) => {
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
submit: [value: BaseMessage, model: ModelWithProvider | null];
|
submit: [value: BaseMessage, model: ModelWithProvider | null];
|
||||||
cancel: [];
|
cancel: [];
|
||||||
|
resize: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -147,11 +148,50 @@ const handleSubmit = () => {
|
|||||||
files.value = [];
|
files.value = [];
|
||||||
textAreaValue.value = '';
|
textAreaValue.value = '';
|
||||||
}
|
}
|
||||||
// Reset height after sending
|
};
|
||||||
if (inputRef.value) inputRef.value.style.height = 'auto';
|
|
||||||
|
const toggleMarkdown = (marker: '*' | '**') => {
|
||||||
|
const textarea = inputRef.value;
|
||||||
|
if (!textarea) return;
|
||||||
|
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
if (start === end) return;
|
||||||
|
|
||||||
|
const text = textAreaValue.value;
|
||||||
|
const len = marker.length;
|
||||||
|
|
||||||
|
let countBefore = 0;
|
||||||
|
for (let i = start - 1; i >= 0 && text[i] === '*'; i--) countBefore++;
|
||||||
|
let countAfter = 0;
|
||||||
|
for (let i = end; i < text.length && text[i] === '*'; i++) countAfter++;
|
||||||
|
|
||||||
|
const shouldRemove = len === 1
|
||||||
|
? countBefore >= 1 && countAfter >= 1 && countBefore % 2 === 1 && countAfter % 2 === 1
|
||||||
|
: countBefore >= 2 && countAfter >= 2;
|
||||||
|
|
||||||
|
if (shouldRemove) {
|
||||||
|
textAreaValue.value = text.slice(0, start - len) + text.slice(start, end) + text.slice(end + len);
|
||||||
|
nextTick(() => textarea.setSelectionRange(start - len, end - len));
|
||||||
|
} else {
|
||||||
|
textAreaValue.value = text.slice(0, start) + marker + text.slice(start, end) + marker + text.slice(end);
|
||||||
|
nextTick(() => textarea.setSelectionRange(start + len, end + len));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = async (event: KeyboardEvent) => {
|
const handleKeyDown = async (event: KeyboardEvent) => {
|
||||||
|
if (event.ctrlKey && event.key.toLowerCase() === 'i') {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
toggleMarkdown('*');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.ctrlKey && event.key.toLowerCase() === 'b') {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
toggleMarkdown('**');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
if (event.shiftKey) return;
|
if (event.shiftKey) return;
|
||||||
if (event.ctrlKey || event.metaKey) {
|
if (event.ctrlKey || event.metaKey) {
|
||||||
@@ -195,16 +235,43 @@ const handleWindowKeyDown = async (event: KeyboardEvent) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mirrorRef = ref<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const resizeTextArea = () => {
|
const resizeTextArea = () => {
|
||||||
const textarea = inputRef.value;
|
const textarea = inputRef.value;
|
||||||
if (!textarea) return;
|
const mirror = mirrorRef.value;
|
||||||
|
if (!textarea || !mirror) return;
|
||||||
|
|
||||||
inputHeight.value = 'auto';
|
emit('resize');
|
||||||
nextTick().then(() => {
|
nextTick().then(() => {
|
||||||
|
mirror.textContent = (textarea.value || '\u200b') + '\n';
|
||||||
|
|
||||||
|
const textareaStyles = getComputedStyle(textarea);
|
||||||
|
mirror.style.width = textareaStyles.width;
|
||||||
|
mirror.style.fontFamily = textareaStyles.fontFamily;
|
||||||
|
mirror.style.fontSize = textareaStyles.fontSize;
|
||||||
|
mirror.style.lineHeight = textareaStyles.lineHeight;
|
||||||
|
mirror.style.letterSpacing = textareaStyles.letterSpacing;
|
||||||
|
mirror.style.wordSpacing = textareaStyles.wordSpacing;
|
||||||
|
mirror.style.textAlign = textareaStyles.textAlign;
|
||||||
|
mirror.style.paddingTop = textareaStyles.paddingTop;
|
||||||
|
mirror.style.paddingBottom = textareaStyles.paddingBottom;
|
||||||
|
mirror.style.paddingLeft = textareaStyles.paddingLeft;
|
||||||
|
mirror.style.paddingRight = textareaStyles.paddingRight;
|
||||||
|
mirror.style.borderTopWidth = textareaStyles.borderTopWidth;
|
||||||
|
mirror.style.borderBottomWidth = textareaStyles.borderBottomWidth;
|
||||||
|
mirror.style.borderLeftWidth = textareaStyles.borderLeftWidth;
|
||||||
|
mirror.style.borderRightWidth = textareaStyles.borderRightWidth;
|
||||||
|
mirror.style.whiteSpace = 'pre-wrap';
|
||||||
|
mirror.style.wordWrap = 'break-word';
|
||||||
|
mirror.style.overflow = 'hidden';
|
||||||
|
|
||||||
const lineHeight = 24;
|
const lineHeight = 24;
|
||||||
|
const minLines = 2;
|
||||||
const maxLines = 10;
|
const maxLines = 10;
|
||||||
|
const minHeight = minLines * lineHeight;
|
||||||
const maxHeight = maxLines * lineHeight;
|
const maxHeight = maxLines * lineHeight;
|
||||||
const height = Math.min(textarea.scrollHeight, maxHeight);
|
const height = Math.min(Math.max(mirror.scrollHeight, minHeight), maxHeight);
|
||||||
inputHeight.value = `${height}px`;
|
inputHeight.value = `${height}px`;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -249,6 +316,8 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="['w-full flex max-h-full', $attrs.class]">
|
<div :class="['w-full flex max-h-full', $attrs.class]">
|
||||||
|
<div ref="mirrorRef" aria-hidden="true"
|
||||||
|
class="absolute top-0 left-0 pointer-events-none invisible overflow-hidden h-0"></div>
|
||||||
<div class="relative w-full flex flex-shrink-1 flex-col gap-3 p-2 rounded-2xl border transition-border ease-in-out duration-300 bg-[var(--bg-container)]
|
<div class="relative w-full flex flex-shrink-1 flex-col gap-3 p-2 rounded-2xl border transition-border ease-in-out duration-300 bg-[var(--bg-container)]
|
||||||
border-[var(--color-border)] focus-within:border-[var(--color-border-active)]">
|
border-[var(--color-border)] focus-within:border-[var(--color-border-active)]">
|
||||||
<div v-if="files.length > 0" class="flex-1 flex gap-2 pt-2 px-2 pb-1 overflow-x-auto flex-wrap">
|
<div v-if="files.length > 0" class="flex-1 flex gap-2 pt-2 px-2 pb-1 overflow-x-auto flex-wrap">
|
||||||
|
|||||||
@@ -53,6 +53,20 @@ const toggleReasoning = async () => {
|
|||||||
scrollToBottom('instant');
|
scrollToBottom('instant');
|
||||||
handleScroll();
|
handleScroll();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatDuration = (ms: number) => {
|
||||||
|
const seconds = ms / 1000;
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}h ${minutes % 60}m ${(seconds % 60).toFixed(1)}s`;
|
||||||
|
} else if (minutes > 0) {
|
||||||
|
return `${minutes}m ${(seconds % 60).toFixed(1)}s`;
|
||||||
|
} else {
|
||||||
|
return `${seconds.toFixed(1)}s`;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -63,11 +77,17 @@ const toggleReasoning = async () => {
|
|||||||
]">
|
]">
|
||||||
<span class="flex items-center gap-1">
|
<span class="flex items-center gap-1">
|
||||||
<span
|
<span
|
||||||
class="w-[24px] h-[24px] flex-shrink-0 rounded-lg overflow-hidden bg-[var(--bg-surface)] flex items-center justify-center">
|
class="w-6 h-6 flex-shrink-0 rounded-lg overflow-hidden bg-[var(--bg-surface)] flex items-center justify-center">
|
||||||
<span class="i-mynaui-atom text-2.5 text-[var(--reasoning-accent)]"></span>
|
<span class="i-mynaui-atom text-3 text-[var(--reasoning-accent)]"></span>
|
||||||
</span>
|
</span>
|
||||||
|
<span v-if="!part.finished" class="animate-pulse">
|
||||||
Deep Thinking
|
Deep Thinking
|
||||||
</span>
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
Deeply Thought (in {{ formatDuration(new Date(part.lastUpdatedAt).getTime() - new
|
||||||
|
Date(part.createdAt).getTime()) }})
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
<span class="i-mynaui-chevron-down w-4 h-4 text-[var(--text-secondary)]"
|
<span class="i-mynaui-chevron-down w-4 h-4 text-[var(--text-secondary)]"
|
||||||
:class="reasoningOpen ? '' : '-rotate-90'"></span>
|
:class="reasoningOpen ? '' : '-rotate-90'"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, nextTick } from 'vue';
|
import { ref, computed, watch, nextTick } from 'vue';
|
||||||
import { useFloating, offset, flip, shift, autoUpdate, size, hide } from '@floating-ui/vue';
|
import { useFloating, offset, flip, shift, autoUpdate, size, hide } from '@floating-ui/vue';
|
||||||
import type { DialogType } from '~/composables/useDialog';
|
import { DialogType } from '~/composables/useDialog';
|
||||||
import type { Model, ModelWithProvider, Provider, ProviderWithModels } from '~/composables/useModels';
|
import type { Model, ModelWithProvider, Provider, ProviderWithModels } from '~/composables/useModels';
|
||||||
import { sortByReleaseDate } from '~/utils/sort';
|
import { sortByReleaseDate } from '~/utils/sort';
|
||||||
import RowVirtualizerFixed from './RowVirtualizerFixed.vue';
|
import RowVirtualizerFixed from './RowVirtualizerFixed.vue';
|
||||||
|
|||||||
@@ -33,29 +33,15 @@ const rowVirtualizer = useVirtualizer(computed(() => ({
|
|||||||
const virtualRows = computed(() => rowVirtualizer.value.getVirtualItems());
|
const virtualRows = computed(() => rowVirtualizer.value.getVirtualItems());
|
||||||
const totalSize = computed(() => rowVirtualizer.value.getTotalSize());
|
const totalSize = computed(() => rowVirtualizer.value.getTotalSize());
|
||||||
|
|
||||||
let resizeRafId: number | undefined = undefined;
|
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
|
||||||
if (resizeRafId) return;
|
|
||||||
|
|
||||||
resizeRafId = requestAnimationFrame(() => {
|
|
||||||
resizeRafId = undefined;
|
|
||||||
rowVirtualizer.value.measure();
|
|
||||||
updateScrollMargin();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
resizeObserver.observe(containerRef.value!);
|
window.addEventListener('resize', updateScrollMargin);
|
||||||
updateScrollMargin();
|
updateScrollMargin();
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
resizeObserver.disconnect();
|
window.removeEventListener('resize', updateScrollMargin);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Keep the measure function simple
|
|
||||||
const measureElement = (el: any) => {
|
const measureElement = (el: any) => {
|
||||||
if (el) {
|
if (el) {
|
||||||
rowVirtualizer.value.measureElement(el);
|
rowVirtualizer.value.measureElement(el);
|
||||||
@@ -75,7 +61,6 @@ watch(() => props.items, () => {
|
|||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
minHeight: `${virtualRow.size}px`,
|
|
||||||
transform: `translateY(${virtualRow.start - scrollMargin}px)`,
|
transform: `translateY(${virtualRow.start - scrollMargin}px)`,
|
||||||
}">
|
}">
|
||||||
<slot :item="props.items[virtualRow.index]" :index="virtualRow.index" />
|
<slot :item="props.items[virtualRow.index]" :index="virtualRow.index" />
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ const openSettings = () => {
|
|||||||
</span>
|
</span>
|
||||||
<button @click="adjustMaxResults(1)"
|
<button @click="adjustMaxResults(1)"
|
||||||
class="h-7 w-7 flex items-center justify-center rounded-lg @hover:bg-[var(--color-hover)] text-[var(--text-secondary)] @hover:text-[var(--text-primary)] transition-colors duration-150">
|
class="h-7 w-7 flex items-center justify-center rounded-lg @hover:bg-[var(--color-hover)] text-[var(--text-secondary)] @hover:text-[var(--text-primary)] transition-colors duration-150">
|
||||||
<span class="text-sm i-mynaui-plus"></span>
|
<span class="text-sm i-mynaui-plus-solid"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -422,11 +422,33 @@ defineEmits(['navigate']);
|
|||||||
<h4 class="whitespace-nowrap m-0 flex gap-x-2 items-start">
|
<h4 class="whitespace-nowrap m-0 flex gap-x-2 items-start">
|
||||||
Model List
|
Model List
|
||||||
<span class="text-sm text-[var(--text-secondary)] font-normal text-xs flex items-center gap-1">
|
<span class="text-sm text-[var(--text-secondary)] font-normal text-xs flex items-center gap-1">
|
||||||
{{ provider?.models.length }} models available <button
|
{{ provider?.models.length }} models available
|
||||||
class="p-0.5 @hover:bg-[var(--color-hover)] rounded transition-colors duration-200"
|
<Dropdown placement="bottom">
|
||||||
@click="deleteModels">
|
<template #default="{ toggle, setRef }">
|
||||||
|
<button :ref="setRef" @click="toggle"
|
||||||
|
class="p-0.5 @hover:bg-[var(--color-hover)] rounded transition-colors duration-200">
|
||||||
<span class="i-mynaui-x-solid"></span>
|
<span class="i-mynaui-x-solid"></span>
|
||||||
</button>
|
</button>
|
||||||
|
</template>
|
||||||
|
<template #dropdown="{ close }">
|
||||||
|
<div class="px-3 py-2.5 w-64 flex flex-col gap-3">
|
||||||
|
<p class="text-sm text-[var(--text-primary)] m-0 leading-snug text-center">
|
||||||
|
Are you sure you want to delete all models? This will remove
|
||||||
|
<strong>all</strong> fetched models for this provider
|
||||||
|
</p>
|
||||||
|
<div class="flex gap-2 justify-end">
|
||||||
|
<button @click="close()"
|
||||||
|
class="px-3 py-1.5 text-sm text-[var(--text-secondary)] @hover:text-[var(--text-primary)] @hover:bg-[var(--color-hover)] rounded-md transition-colors duration-200 border border-[var(--color-border)] @hover:border-transparent">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button @click="deleteModels(); close()"
|
||||||
|
class="px-3 py-1.5 text-sm bg-red-500 text-white rounded-md @hover:bg-red-600 transition-colors duration-200">
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dropdown>
|
||||||
</span>
|
</span>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
@@ -449,7 +471,7 @@ defineEmits(['navigate']);
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<button @click="openAddPanel"
|
<button @click="openAddPanel"
|
||||||
class="whitespace-nowrap flex bg-[var(--bg-container)] @hover:bg-[var(--color-hover)] text-sm rounded-l-md items-center px-2 py-0.5 gap-2 transition-colors duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
|
class="whitespace-nowrap flex bg-[var(--bg-container)] @hover:bg-[var(--color-hover)] text-sm rounded-l-md items-center px-2 py-0.5 gap-2 transition-colors duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
|
||||||
<span class="i-mynaui-plus text-5"></span>
|
<span class="i-mynaui-plus-solid text-5"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DialogType } from '~/composables/useDialog';
|
import { DialogType } from '~/composables/useDialog';
|
||||||
|
|
||||||
const { user, signOut } = useAuth();
|
const { user, signOut } = useAuth();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { navigateTo } from '#app';
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { agents, getAgent } = await useAgents();
|
const { agents, getAgent } = await useAgents();
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<div data-action="toggle-dropdown"
|
<div data-action="toggle-dropdown"
|
||||||
class="text-[var(--text-secondary)] shrink-0 w-0 max-w-0 opacity-0 overflow-hidden p-1 max-md:w-auto max-md:max-w-none max-md:opacity-100 flex items-center justify-center rounded-md @hover:bg-[var(--color-hover)] focus-visible:bg-[var(--color-hover)]"
|
class="text-[var(--text-secondary)] shrink-0 w-0 max-w-0 opacity-0 overflow-hidden p-1 max-md:w-auto max-md:max-w-none max-md:opacity-100 flex items-center justify-center rounded-md @hover:bg-[var(--color-hover)] focus-visible:bg-[var(--color-hover)]"
|
||||||
:class="route.params.topicId === topic.id ? 'w-auto max-w-none opacity-100' : 'group-hover:w-auto group-hover:max-w-none group-hover:opacity-100'">
|
:class="(route.params.topicId === topic.id || (dropdownOpen && activeMenuTopicId === topic.id)) ? 'w-auto max-w-none opacity-100' : 'group-hover:w-auto group-hover:max-w-none group-hover:opacity-100'">
|
||||||
<span class="pointer-events-none h-6 w-6 md:h-4.5 md:w-4.5 i-tabler-dots"></span>
|
<span class="pointer-events-none h-6 w-6 md:h-4.5 md:w-4.5 i-tabler-dots"></span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const currentOption = computed(
|
|||||||
<button :ref="setRef" @click="toggle"
|
<button :ref="setRef" @click="toggle"
|
||||||
class="flex items-center justify-center rounded-lg @hover:bg-[var(--color-hover)] focus-visible:bg-[var(--color-hover)] transition-colors active:text-[var(--text-primary)]"
|
class="flex items-center justify-center rounded-lg @hover:bg-[var(--color-hover)] focus-visible:bg-[var(--color-hover)] transition-colors active:text-[var(--text-primary)]"
|
||||||
:class="{
|
:class="{
|
||||||
'h-9 w-9 md:h-7 md:w-7 text-5 md:text-6': size === 'small',
|
'h-9 w-9 md:h-7 md:w-7 md:text-5 text-6': size === 'small',
|
||||||
'h-9 w-9 text-6': size === 'medium',
|
'h-9 w-9 text-6': size === 'medium',
|
||||||
'h-11 w-11 text-7': size === 'large',
|
'h-11 w-11 text-7': size === 'large',
|
||||||
}">
|
}">
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error - shut up
|
||||||
await db.insert(agents).values(result.data).onConflictDoNothing();
|
await db.insert(agents).values(result.data).onConflictDoNothing();
|
||||||
userEvents.emit(userId, 'agents', {
|
userEvents.emit(userId, 'agents', {
|
||||||
op: 'create',
|
op: 'create',
|
||||||
|
|||||||
@@ -103,12 +103,13 @@ export default {
|
|||||||
normalizedId = normalizedId.replace(/:cloud$/, '');
|
normalizedId = normalizedId.replace(/:cloud$/, '');
|
||||||
normalizedId = normalizedId.replace(/-cloud$/, '');
|
normalizedId = normalizedId.replace(/-cloud$/, '');
|
||||||
normalizedId = normalizedId.replace(/:latest$/, '');
|
normalizedId = normalizedId.replace(/:latest$/, '');
|
||||||
|
console.log(normalizedId);
|
||||||
const modelData = getModelData(normalizedId, 'ollama-cloud', modelsDevData);
|
const modelData = getModelData(normalizedId, 'ollama-cloud', modelsDevData);
|
||||||
|
|
||||||
models.set(model.name, {
|
models.set(model.name, {
|
||||||
...modelData,
|
...modelData,
|
||||||
|
name: modelData.name || model.name,
|
||||||
id: model.name,
|
id: model.name,
|
||||||
name: model.name,
|
|
||||||
attributes: {
|
attributes: {
|
||||||
...modelData.attributes,
|
...modelData.attributes,
|
||||||
inputModalities: mergeSets(
|
inputModalities: mergeSets(
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default {
|
|||||||
async fetchModels({ baseURL, apiKey }) {
|
async fetchModels({ baseURL, apiKey }) {
|
||||||
const res = await fetch(`${baseURL}/models`, {
|
const res = await fetch(`${baseURL}/models`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: { Authorization: `Bearer ${apiKey}` },
|
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user