update prompt input view style

This commit is contained in:
duanfuxiang 2025-04-22 22:10:22 +08:00
parent d521184945
commit c40c618311
4 changed files with 290 additions and 148 deletions

View File

@ -37,7 +37,7 @@ function BadgeBase({
onDelete()
}}
>
<X size={10} />
<X size={16} />
</div>
</div>
)

View File

@ -1,7 +1,7 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import Fuse, { FuseResult } from 'fuse.js'
import { ChevronDown, ChevronUp } from 'lucide-react'
import { useEffect, useMemo, useState } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useSettings } from '../../../contexts/SettingsContext'
import { ApiProvider } from '../../../types/llm/model'
@ -138,6 +138,7 @@ export function ModelSelect() {
const [isLoading, setIsLoading] = useState(true)
const [searchTerm, setSearchTerm] = useState("")
const [selectedIndex, setSelectedIndex] = useState(0)
const inputRef = useRef<HTMLInputElement>(null)
const providers = GetAllProviders()
@ -189,138 +190,289 @@ export function ModelSelect() {
}, [searchableItems, searchTerm, fuse])
return (
<DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenu.Trigger className="infio-chat-input-model-select">
<div className="infio-chat-input-model-select__icon">
{isOpen ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
</div>
<div className="infio-chat-input-model-select__model-name">
[{modelProvider}] {chatModelId}
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className="infio-popover infio-llm-setting-combobox-dropdown">
<div className="infio-llm-setting-search-container">
<select
className="infio-llm-setting-provider-switch"
value={modelProvider}
onChange={(e) => {
const newProvider = e.target.value as ApiProvider
setModelProvider(newProvider)
setSearchTerm("")
setSelectedIndex(0)
}}
>
{providers.map((provider) => (
<option
key={provider}
value={provider}
className={`infio-llm-setting-provider-option ${provider === modelProvider ? 'is-active' : ''}`}
>
{provider}
</option>
))}
</select>
{modelIds.length > 0 ? (
<input
type="text"
className="infio-llm-setting-item-search"
placeholder="search model..."
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value)
setSelectedIndex(0)
}}
onKeyDown={(e) => {
switch (e.key) {
case "ArrowDown":
e.preventDefault()
setSelectedIndex((prev) =>
Math.min(prev + 1, filteredOptions.length - 1)
)
break
case "ArrowUp":
e.preventDefault()
setSelectedIndex((prev) => Math.max(prev - 1, 0))
break
case "Enter": {
e.preventDefault()
const selectedOption = filteredOptions[selectedIndex]
if (selectedOption) {
setSettings({
...settings,
chatModelProvider: modelProvider,
chatModelId: selectedOption.id,
})
setChatModelId(selectedOption.id)
setSearchTerm("")
setIsOpen(false)
}
break
}
case "Escape":
e.preventDefault()
setIsOpen(false)
setSearchTerm("")
break
}
}}
/>
) : (
<input
type="text"
className="infio-llm-setting-item-search"
placeholder="input custom model name"
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value)
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault()
setSettings({
...settings,
chatModelProvider: modelProvider,
chatModelId: searchTerm,
})
setChatModelId(searchTerm)
setIsOpen(false)
}
}}
/>
)}
<>
<DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenu.Trigger className="infio-chat-input-model-select">
<div className="infio-chat-input-model-select__icon">
{isOpen ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
</div>
<ul>
{isLoading ? (
<li>Loading...</li>
) : (
filteredOptions.map((option, index) => (
<DropdownMenu.Item
key={option.id}
onSelect={() => {
setSettings({
...settings,
chatModelProvider: modelProvider,
chatModelId: option.id,
})
setChatModelId(option.id)
<div className="infio-chat-input-model-select__model-name">
[{modelProvider}] {chatModelId}
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className="infio-popover infio-llm-setting-combobox-dropdown">
<div className="infio-llm-setting-search-container">
<div className="infio-llm-setting-provider-container">
<select
className="infio-llm-setting-provider-switch"
value={modelProvider}
onChange={(e) => {
const newProvider = e.target.value as ApiProvider
setModelProvider(newProvider)
setSearchTerm("")
setIsOpen(false)
setSelectedIndex(0)
}}
className={`infio-llm-setting-combobox-option ${index === selectedIndex ? 'is-selected' : ''}`}
onMouseEnter={() => setSelectedIndex(index)}
asChild
>
<li>
<HighlightedText segments={option.html} />
</li>
</DropdownMenu.Item>
))
)}
</ul>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
{providers.map((provider) => (
<option
key={provider}
value={provider}
className={`infio-llm-setting-provider-option ${provider === modelProvider ? 'is-active' : ''}`}
>
{provider}
</option>
))}
</select>
</div>
{modelIds.length > 0 ? (
<div className="infio-search-input-container">
<input
type="text"
className="infio-llm-setting-item-search"
placeholder="search model..."
ref={inputRef}
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value)
setSelectedIndex(0)
// 确保下一个渲染循环中仍然聚焦在输入框
setTimeout(() => {
inputRef.current?.focus()
}, 0)
}}
onKeyDown={(e) => {
switch (e.key) {
case "ArrowDown":
e.preventDefault()
setSelectedIndex((prev) =>
Math.min(prev + 1, filteredOptions.length - 1)
)
break
case "ArrowUp":
e.preventDefault()
setSelectedIndex((prev) => Math.max(prev - 1, 0))
break
case "Enter": {
e.preventDefault()
const selectedOption = filteredOptions[selectedIndex]
if (selectedOption) {
setSettings({
...settings,
chatModelProvider: modelProvider,
chatModelId: selectedOption.id,
})
setChatModelId(selectedOption.id)
setSearchTerm("")
setIsOpen(false)
}
break
}
case "Escape":
e.preventDefault()
setIsOpen(false)
setSearchTerm("")
break
}
}}
/>
</div>
) : (
<input
type="text"
className="infio-llm-setting-item-search"
placeholder="input custom model name"
ref={inputRef}
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value)
// 确保下一个渲染循环中仍然聚焦在输入框
setTimeout(() => {
inputRef.current?.focus()
}, 0)
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault()
setSettings({
...settings,
chatModelProvider: modelProvider,
chatModelId: searchTerm,
})
setChatModelId(searchTerm)
setIsOpen(false)
}
}}
/>
)}
</div>
<ul>
{isLoading ? (
<li>Loading...</li>
) : (
filteredOptions.map((option, index) => (
<DropdownMenu.Item
key={option.id}
onSelect={() => {
setSettings({
...settings,
chatModelProvider: modelProvider,
chatModelId: option.id,
})
setChatModelId(option.id)
setSearchTerm("")
setIsOpen(false)
}}
className={`infio-llm-setting-combobox-option ${index === selectedIndex ? 'is-selected' : ''}`}
onMouseEnter={() => setSelectedIndex(index)}
asChild
>
<li
className="infio-llm-setting-model-item"
title={option.id}
>
<div className="infio-model-item-text-wrapper">
<HighlightedText segments={option.html} />
</div>
</li>
</DropdownMenu.Item>
))
)}
</ul>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
<style>
{`
/* 模型项样式 */
.infio-llm-setting-model-item {
display: block;
padding: 0;
transition: background-color 0.2s ease;
}
.infio-llm-setting-combobox-option:hover {
background-color: var(--background-modifier-hover);
}
.infio-llm-setting-combobox-option.is-selected {
background-color: var(--background-modifier-active);
border-left: 3px solid var(--interactive-accent);
}
/* 文本溢出处理 */
.infio-model-item-text-wrapper {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 280px;
display: block;
}
.infio-model-item-text-wrapper span {
display: inline;
}
/* 高亮样式 - 使用紫色而不是主题色 */
.infio-llm-setting-model-item-highlight {
display: inline;
color: #9370DB;
font-weight: 700;
background-color: rgba(147, 112, 219, 0.1);
padding: 0 2px;
border-radius: 2px;
}
/* 搜索容器 */
.infio-llm-setting-search-container {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 5px;
border-bottom: 1px solid var(--background-modifier-border);
padding-bottom: 2px;
}
/* 提供商选择器容器 */
.infio-llm-setting-provider-container {
position: relative;
display: flex;
align-items: center;
flex: 0 0 auto;
width: 26%;
}
/* 移除提供商选择箭头 */
/* 提供商选择器 */
.infio-llm-setting-provider-switch {
width: 100% !important;
margin: 0;
padding: 0;
padding-right: 5px;
text-align: left;
appearance: none;
-webkit-appearance: none;
background-color: var(--background-modifier-form-field);
border: 1px solid var(--background-modifier-border);
font-weight: 500;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
cursor: pointer;
}
.infio-llm-setting-provider-switch:hover {
border-color: var(--interactive-accent-hover);
}
.infio-llm-setting-provider-switch:focus {
border-color: var(--interactive-accent);
box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.2);
}
/* 搜索框容器 */
.infio-search-input-container {
position: relative;
display: flex;
align-items: center;
flex: 1 1 auto;
width: 74%;
}
/* 移除搜索图标 */
/* 搜索输入框 */
.infio-llm-setting-item-search {
width: 100% !important;
border: 1px solid var(--background-modifier-border);
margin: 0;
padding: 0;
border-radius: 0px !important;
background-color: var(--background-modifier-form-field);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
height: 28px;
padding-left: 8px;
}
.infio-llm-setting-item-search:hover {
border-color: var(--interactive-accent-hover);
}
.infio-llm-setting-item-search:focus {
border-color: var(--interactive-accent);
box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.2);
outline: none;
}
/* 下拉菜单容器 */
.infio-llm-setting-combobox-dropdown {
max-height: 350px;
overflow-y: auto;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
border-radius: 8px;
}
`}
</style>
</>
)
}

View File

@ -1,10 +1,10 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import clsx from 'clsx'
import {
$parseSerializedNode,
COMMAND_PRIORITY_NORMAL, SerializedLexicalNode, TextNode
} from 'lexical'
import { Slash } from 'lucide-react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { createPortal } from 'react-dom'
@ -63,8 +63,11 @@ function CommandMenuItem({
onMouseEnter={onMouseEnter}
onClick={onClick}
>
<div className="smtcmp-template-menu-item">
<div className="text">{option.name}</div>
<div className="infio-chat-template-menu-item">
<div className="text">
<Slash size={10} />{' '}
<span>{option.name}</span>
</div>
</div>
</li>
)
@ -157,7 +160,7 @@ export default function CommandPlugin() {
anchorElementRef.current && searchResults.length
? createPortal(
<div
className="smtcmp-popover"
className="infio-popover"
style={{
position: 'fixed',
}}

View File

@ -891,19 +891,6 @@ input[type='text'].infio-chat-list-dropdown-item-title-input {
justify-content: space-between;
gap: var(--size-4-1);
width: 100%;
.infio-chat-template-menu-item-delete {
display: flex;
align-items: center;
padding: var(--size-4-1);
margin: calc(var(--size-4-1) * -1);
opacity: 0.7;
transition: opacity 0.2s;
&:hover {
opacity: 1;
}
}
}
.infio-chat-message-actions {