add mode custom
This commit is contained in:
parent
497a9739d7
commit
f282c9f667
@ -30,6 +30,7 @@ import {
|
||||
} from '../../core/llm/exception'
|
||||
import { regexSearchFiles } from '../../core/ripgrep'
|
||||
import { useChatHistory } from '../../hooks/use-chat-history'
|
||||
import { useCustomModes } from '../../hooks/use-custom-mode'
|
||||
import { ApplyStatus, ToolArgs } from '../../types/apply'
|
||||
import { ChatMessage, ChatUserMessage } from '../../types/chat'
|
||||
import {
|
||||
@ -101,6 +102,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
|
||||
const { settings, setSettings } = useSettings()
|
||||
const { getRAGEngine } = useRAG()
|
||||
const diffStrategy = useDiffStrategy()
|
||||
const { customModeList, customModePrompts } = useCustomModes()
|
||||
|
||||
const {
|
||||
createOrUpdateConversation,
|
||||
@ -112,8 +114,8 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
|
||||
const { streamResponse, chatModel } = useLLM()
|
||||
|
||||
const promptGenerator = useMemo(() => {
|
||||
return new PromptGenerator(getRAGEngine, app, settings, diffStrategy)
|
||||
}, [getRAGEngine, app, settings, diffStrategy])
|
||||
return new PromptGenerator(getRAGEngine, app, settings, diffStrategy, customModePrompts, customModeList)
|
||||
}, [getRAGEngine, app, settings, diffStrategy, customModePrompts, customModeList])
|
||||
|
||||
const [inputMessage, setInputMessage] = useState<ChatUserMessage>(() => {
|
||||
const newMessage = getNewInputMessage(app, settings.defaultMention)
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import { Plus, Undo2, Settings, Circle, Trash2 } from 'lucide-react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ChevronDown, ChevronRight, Plus, Trash2, Undo2 } from 'lucide-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { useApp } from '../../contexts/AppContext';
|
||||
import { CustomMode, GroupEntry, ToolGroup } from '../../database/json/custom-mode/types';
|
||||
import { useCustomModes } from '../../hooks/use-custom-mode';
|
||||
import { CustomMode, ToolGroup, toolGroups, GroupEntry } from '../../database/json/custom-mode/types';
|
||||
import { modes as buildinModes } from '../../utils/modes';
|
||||
import { openOrCreateMarkdownFile } from '../../utils/obsidian';
|
||||
const CustomModeView = () => {
|
||||
const app = useApp()
|
||||
|
||||
const {
|
||||
createCustomMode,
|
||||
deleteCustomMode,
|
||||
@ -15,7 +19,7 @@ const CustomModeView = () => {
|
||||
// 当前选择的模式
|
||||
const [selectedMode, setSelectedMode] = useState<string>('ask')
|
||||
const [isBuiltinMode, setIsBuiltinMode] = useState<boolean>(true)
|
||||
|
||||
const [isAdvancedCollapsed, setIsAdvancedCollapsed] = useState(true);
|
||||
|
||||
const isNewMode = React.useMemo(() => selectedMode === "add_new_mode", [selectedMode])
|
||||
|
||||
@ -46,7 +50,6 @@ const CustomModeView = () => {
|
||||
// 自定义指令
|
||||
const [customInstructions, setCustomInstructions] = useState<string>('')
|
||||
|
||||
|
||||
// 当模式变更时更新表单数据
|
||||
useEffect(() => {
|
||||
// new mode
|
||||
@ -63,7 +66,7 @@ const CustomModeView = () => {
|
||||
const builtinMode = buildinModes.find(m => m.slug === selectedMode);
|
||||
if (builtinMode) {
|
||||
setIsBuiltinMode(true);
|
||||
setModeName(builtinMode.name);
|
||||
setModeName(builtinMode.slug);
|
||||
setRoleDefinition(builtinMode.roleDefinition);
|
||||
setCustomInstructions(builtinMode.customInstructions || '');
|
||||
setSelectedTools(builtinMode.groups as GroupEntry[]);
|
||||
@ -219,9 +222,11 @@ const CustomModeView = () => {
|
||||
<div className="infio-custom-modes-section">
|
||||
<div className="infio-section-header">
|
||||
<h3>角色定义</h3>
|
||||
<button className="infio-section-btn">
|
||||
<Undo2 size={16} />
|
||||
</button>
|
||||
{isBuiltinMode && (
|
||||
<button className="infio-section-btn">
|
||||
<Undo2 size={16} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<p className="infio-section-subtitle">设定专业领域和应答风格</p>
|
||||
<textarea
|
||||
@ -274,7 +279,7 @@ const CustomModeView = () => {
|
||||
checked={selectedTools.includes('research')}
|
||||
onChange={() => handleToolChange('research')}
|
||||
/>
|
||||
浏览器
|
||||
网络搜索
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -298,10 +303,29 @@ const CustomModeView = () => {
|
||||
placeholder="输入模式自定义指令..."
|
||||
/>
|
||||
<p className="infio-section-footer">
|
||||
支持从<a href="#" className="infio-link">_infio_prompts/code-rules/</a>目录读取配置
|
||||
支持从<a href="#" className="infio-link" onClick={() => openOrCreateMarkdownFile(app, `_infio_prompts/${modeName}/rules.md`, 0)}>_infio_prompts/{modeName}/rules</a> 文件中读取配置
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 高级, 覆盖系统提示词 */}
|
||||
<div className="infio-custom-modes-section">
|
||||
<div
|
||||
className="infio-section-header infio-section-header-collapsible"
|
||||
onClick={() => setIsAdvancedCollapsed(!isAdvancedCollapsed)}
|
||||
>
|
||||
<div className="infio-section-header-title-container">
|
||||
{isAdvancedCollapsed ? <ChevronRight size={16} /> : <ChevronDown size={16} />}
|
||||
<h6>覆盖系统提示词</h6>
|
||||
</div>
|
||||
</div>
|
||||
{!isAdvancedCollapsed && (
|
||||
<p className="infio-section-subtitle">
|
||||
您可以通过在工作区创建文件
|
||||
<a href="#" className="infio-link" onClick={() => openOrCreateMarkdownFile(app, `_infio_prompts/${modeName}/system-prompt.md`, 0)}>_infio_prompts/{modeName}/system-prompt</a>
|
||||
,完全替换此模式的系统提示(角色定义和自定义指令除外)。这是一个非常高级的功能,会覆盖工具使用等全部内置提示, 请谨慎操作
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="infio-custom-modes-actions">
|
||||
<button className="infio-preview-btn">
|
||||
预览
|
||||
@ -546,6 +570,17 @@ const CustomModeView = () => {
|
||||
justify-content: center;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.infio-section-header-collapsible {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.infio-section-header-title-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
|
||||
import { useSettings } from '../../../contexts/SettingsContext'
|
||||
import { useCustomModes } from '../../../hooks/use-custom-mode'
|
||||
import { modes } from '../../../utils/modes'
|
||||
|
||||
export function ModeSelect() {
|
||||
@ -10,11 +11,14 @@ export function ModeSelect() {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [mode, setMode] = useState(settings.mode)
|
||||
|
||||
const { customModeList } = useCustomModes()
|
||||
|
||||
const allModes = useMemo(() => [...modes, ...customModeList], [customModeList])
|
||||
|
||||
useEffect(() => {
|
||||
setMode(settings.mode)
|
||||
}, [settings.mode])
|
||||
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DropdownMenu.Trigger className="infio-chat-input-model-select">
|
||||
@ -22,7 +26,7 @@ export function ModeSelect() {
|
||||
{isOpen ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
|
||||
</div>
|
||||
<div className="infio-chat-input-model-select__model-name">
|
||||
{modes.find((m) => m.slug === mode)?.name}
|
||||
{allModes.find((m) => m.slug === mode)?.name}
|
||||
</div>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
@ -30,7 +34,7 @@ export function ModeSelect() {
|
||||
<DropdownMenu.Content
|
||||
className="infio-popover">
|
||||
<ul>
|
||||
{modes.map((mode) => (
|
||||
{allModes.map((mode) => (
|
||||
<DropdownMenu.Item
|
||||
key={mode.slug}
|
||||
onSelect={() => {
|
||||
|
||||
@ -152,10 +152,10 @@ ${await addCustomInstructions(this.app, promptComponent?.customInstructions || m
|
||||
filesSearchMethod: string = 'regex',
|
||||
preferredLanguage?: string,
|
||||
diffStrategy?: DiffStrategy,
|
||||
mcpHub?: McpHub,
|
||||
browserViewportSize?: string,
|
||||
customModePrompts?: CustomModePrompts,
|
||||
customModes?: ModeConfig[],
|
||||
mcpHub?: McpHub,
|
||||
browserViewportSize?: string,
|
||||
globalCustomInstructions?: string,
|
||||
diffEnabled?: boolean,
|
||||
experiments?: Record<string, boolean>,
|
||||
|
||||
@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useApp } from '../contexts/AppContext'
|
||||
import { CustomModeManager } from '../database/json/custom-mode/CustomModeManager'
|
||||
import { CustomMode, GroupEntry } from '../database/json/custom-mode/types'
|
||||
|
||||
import { CustomModePrompts } from '../utils/modes'
|
||||
|
||||
type UseCustomModes = {
|
||||
createCustomMode: (
|
||||
@ -22,6 +22,7 @@ type UseCustomModes = {
|
||||
) => Promise<void>
|
||||
FindCustomModeByName: (name: string) => Promise<CustomMode | undefined>
|
||||
customModeList: CustomMode[]
|
||||
customModePrompts: CustomModePrompts
|
||||
}
|
||||
|
||||
export function useCustomModes(): UseCustomModes {
|
||||
@ -37,6 +38,16 @@ export function useCustomModes(): UseCustomModes {
|
||||
})
|
||||
}, [customModeManager])
|
||||
|
||||
const customModePrompts = useMemo(() => {
|
||||
return customModeList.reduce((acc, customMode) => {
|
||||
acc[customMode.slug] = {
|
||||
roleDefinition: customMode.roleDefinition,
|
||||
customInstructions: customMode.customInstructions,
|
||||
}
|
||||
return acc
|
||||
}, {} as CustomModePrompts)
|
||||
}, [customModeList])
|
||||
|
||||
useEffect(() => {
|
||||
void fetchCustomModeList()
|
||||
}, [fetchCustomModeList])
|
||||
@ -91,5 +102,6 @@ export function useCustomModes(): UseCustomModes {
|
||||
updateCustomMode,
|
||||
FindCustomModeByName,
|
||||
customModeList,
|
||||
customModePrompts,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { App, Editor, MarkdownView, TFile, TFolder, Vault, WorkspaceLeaf } from 'obsidian'
|
||||
|
||||
import * as path from 'path'
|
||||
|
||||
import { MentionableBlockData } from '../types/mentionable'
|
||||
|
||||
export async function readTFileContent(
|
||||
@ -61,7 +63,7 @@ export function getOpenFiles(app: App): TFile[] {
|
||||
const leaves = app.workspace.getLeavesOfType('markdown')
|
||||
|
||||
return leaves
|
||||
.filter((v): v is WorkspaceLeaf & { view: MarkdownView & { file: TFile } } =>
|
||||
.filter((v): v is WorkspaceLeaf & { view: MarkdownView & { file: TFile } } =>
|
||||
v.view instanceof MarkdownView && !!v.view.file
|
||||
)
|
||||
.map((v) => v.view.file)
|
||||
@ -125,3 +127,20 @@ export function openMarkdownFile(
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function openOrCreateMarkdownFile(
|
||||
app: App,
|
||||
filePath: string,
|
||||
startLine?: number,
|
||||
) {
|
||||
const file_exists = await app.vault.adapter.exists(filePath)
|
||||
if (!file_exists) {
|
||||
const dir = path.dirname(filePath)
|
||||
const dir_exists = await app.vault.adapter.exists(dir)
|
||||
if (!dir_exists) {
|
||||
await app.vault.adapter.mkdir(dir)
|
||||
}
|
||||
await app.vault.adapter.write(filePath, '')
|
||||
}
|
||||
openMarkdownFile(app, filePath, startLine)
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ import {
|
||||
MentionableVault
|
||||
} from '../types/mentionable'
|
||||
import { InfioSettings } from '../types/settings'
|
||||
import { Mode, getFullModeDetails } from "../utils/modes"
|
||||
import { CustomModePrompts, Mode, ModeConfig, getFullModeDetails } from "../utils/modes"
|
||||
|
||||
import {
|
||||
readTFileContent
|
||||
@ -116,6 +116,8 @@ export class PromptGenerator {
|
||||
private settings: InfioSettings
|
||||
private diffStrategy: DiffStrategy
|
||||
private systemPrompt: SystemPrompt
|
||||
private customModePrompts: CustomModePrompts | null = null
|
||||
private customModeList: ModeConfig[] | null = null
|
||||
private static readonly EMPTY_ASSISTANT_MESSAGE: RequestMessage = {
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
@ -126,12 +128,16 @@ export class PromptGenerator {
|
||||
app: App,
|
||||
settings: InfioSettings,
|
||||
diffStrategy?: DiffStrategy,
|
||||
customModePrompts?: CustomModePrompts,
|
||||
customModeList?: ModeConfig[],
|
||||
) {
|
||||
this.getRagEngine = getRagEngine
|
||||
this.app = app
|
||||
this.settings = settings
|
||||
this.diffStrategy = diffStrategy
|
||||
this.systemPrompt = new SystemPrompt(this.app)
|
||||
this.customModePrompts = customModePrompts ?? null
|
||||
this.customModeList = customModeList ?? null
|
||||
}
|
||||
|
||||
public async generateRequestMessages({
|
||||
@ -473,7 +479,9 @@ export class PromptGenerator {
|
||||
mode,
|
||||
filesSearchMethod,
|
||||
preferredLanguage,
|
||||
this.diffStrategy
|
||||
this.diffStrategy,
|
||||
this.customModePrompts,
|
||||
this.customModeList,
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user