update prompt input view style
This commit is contained in:
parent
d521184945
commit
c40c618311
@ -37,7 +37,7 @@ function BadgeBase({
|
|||||||
onDelete()
|
onDelete()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<X size={10} />
|
<X size={16} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import Fuse, { FuseResult } from 'fuse.js'
|
import Fuse, { FuseResult } from 'fuse.js'
|
||||||
import { ChevronDown, ChevronUp } from 'lucide-react'
|
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 { useSettings } from '../../../contexts/SettingsContext'
|
||||||
import { ApiProvider } from '../../../types/llm/model'
|
import { ApiProvider } from '../../../types/llm/model'
|
||||||
@ -138,6 +138,7 @@ export function ModelSelect() {
|
|||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [searchTerm, setSearchTerm] = useState("")
|
const [searchTerm, setSearchTerm] = useState("")
|
||||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
const providers = GetAllProviders()
|
const providers = GetAllProviders()
|
||||||
|
|
||||||
@ -189,138 +190,289 @@ export function ModelSelect() {
|
|||||||
}, [searchableItems, searchTerm, fuse])
|
}, [searchableItems, searchTerm, fuse])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen}>
|
<>
|
||||||
<DropdownMenu.Trigger className="infio-chat-input-model-select">
|
<DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<div className="infio-chat-input-model-select__icon">
|
<DropdownMenu.Trigger className="infio-chat-input-model-select">
|
||||||
{isOpen ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
|
<div className="infio-chat-input-model-select__icon">
|
||||||
</div>
|
{isOpen ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
|
||||||
<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)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<div className="infio-chat-input-model-select__model-name">
|
||||||
{isLoading ? (
|
[{modelProvider}] {chatModelId}
|
||||||
<li>Loading...</li>
|
</div>
|
||||||
) : (
|
</DropdownMenu.Trigger>
|
||||||
filteredOptions.map((option, index) => (
|
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Portal>
|
||||||
key={option.id}
|
<DropdownMenu.Content className="infio-popover infio-llm-setting-combobox-dropdown">
|
||||||
onSelect={() => {
|
<div className="infio-llm-setting-search-container">
|
||||||
setSettings({
|
<div className="infio-llm-setting-provider-container">
|
||||||
...settings,
|
<select
|
||||||
chatModelProvider: modelProvider,
|
className="infio-llm-setting-provider-switch"
|
||||||
chatModelId: option.id,
|
value={modelProvider}
|
||||||
})
|
onChange={(e) => {
|
||||||
setChatModelId(option.id)
|
const newProvider = e.target.value as ApiProvider
|
||||||
|
setModelProvider(newProvider)
|
||||||
setSearchTerm("")
|
setSearchTerm("")
|
||||||
setIsOpen(false)
|
setSelectedIndex(0)
|
||||||
}}
|
}}
|
||||||
className={`infio-llm-setting-combobox-option ${index === selectedIndex ? 'is-selected' : ''}`}
|
|
||||||
onMouseEnter={() => setSelectedIndex(index)}
|
|
||||||
asChild
|
|
||||||
>
|
>
|
||||||
<li>
|
{providers.map((provider) => (
|
||||||
<HighlightedText segments={option.html} />
|
<option
|
||||||
</li>
|
key={provider}
|
||||||
</DropdownMenu.Item>
|
value={provider}
|
||||||
))
|
className={`infio-llm-setting-provider-option ${provider === modelProvider ? 'is-active' : ''}`}
|
||||||
)}
|
>
|
||||||
</ul>
|
{provider}
|
||||||
</DropdownMenu.Content>
|
</option>
|
||||||
</DropdownMenu.Portal>
|
))}
|
||||||
</DropdownMenu.Root>
|
</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>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
|
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import {
|
import {
|
||||||
$parseSerializedNode,
|
$parseSerializedNode,
|
||||||
COMMAND_PRIORITY_NORMAL, SerializedLexicalNode, TextNode
|
COMMAND_PRIORITY_NORMAL, SerializedLexicalNode, TextNode
|
||||||
} from 'lexical'
|
} from 'lexical'
|
||||||
|
import { Slash } from 'lucide-react'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
|
|
||||||
@ -63,8 +63,11 @@ function CommandMenuItem({
|
|||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<div className="smtcmp-template-menu-item">
|
<div className="infio-chat-template-menu-item">
|
||||||
<div className="text">{option.name}</div>
|
<div className="text">
|
||||||
|
<Slash size={10} />{' '}
|
||||||
|
<span>{option.name}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
@ -157,7 +160,7 @@ export default function CommandPlugin() {
|
|||||||
anchorElementRef.current && searchResults.length
|
anchorElementRef.current && searchResults.length
|
||||||
? createPortal(
|
? createPortal(
|
||||||
<div
|
<div
|
||||||
className="smtcmp-popover"
|
className="infio-popover"
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
}}
|
}}
|
||||||
|
|||||||
13
styles.css
13
styles.css
@ -891,19 +891,6 @@ input[type='text'].infio-chat-list-dropdown-item-title-input {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: var(--size-4-1);
|
gap: var(--size-4-1);
|
||||||
width: 100%;
|
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 {
|
.infio-chat-message-actions {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user