add mode custom
This commit is contained in:
parent
497a9739d7
commit
f282c9f667
@ -30,6 +30,7 @@ import {
|
|||||||
} from '../../core/llm/exception'
|
} from '../../core/llm/exception'
|
||||||
import { regexSearchFiles } from '../../core/ripgrep'
|
import { regexSearchFiles } from '../../core/ripgrep'
|
||||||
import { useChatHistory } from '../../hooks/use-chat-history'
|
import { useChatHistory } from '../../hooks/use-chat-history'
|
||||||
|
import { useCustomModes } from '../../hooks/use-custom-mode'
|
||||||
import { ApplyStatus, ToolArgs } from '../../types/apply'
|
import { ApplyStatus, ToolArgs } from '../../types/apply'
|
||||||
import { ChatMessage, ChatUserMessage } from '../../types/chat'
|
import { ChatMessage, ChatUserMessage } from '../../types/chat'
|
||||||
import {
|
import {
|
||||||
@ -101,6 +102,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
|
|||||||
const { settings, setSettings } = useSettings()
|
const { settings, setSettings } = useSettings()
|
||||||
const { getRAGEngine } = useRAG()
|
const { getRAGEngine } = useRAG()
|
||||||
const diffStrategy = useDiffStrategy()
|
const diffStrategy = useDiffStrategy()
|
||||||
|
const { customModeList, customModePrompts } = useCustomModes()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
createOrUpdateConversation,
|
createOrUpdateConversation,
|
||||||
@ -112,8 +114,8 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
|
|||||||
const { streamResponse, chatModel } = useLLM()
|
const { streamResponse, chatModel } = useLLM()
|
||||||
|
|
||||||
const promptGenerator = useMemo(() => {
|
const promptGenerator = useMemo(() => {
|
||||||
return new PromptGenerator(getRAGEngine, app, settings, diffStrategy)
|
return new PromptGenerator(getRAGEngine, app, settings, diffStrategy, customModePrompts, customModeList)
|
||||||
}, [getRAGEngine, app, settings, diffStrategy])
|
}, [getRAGEngine, app, settings, diffStrategy, customModePrompts, customModeList])
|
||||||
|
|
||||||
const [inputMessage, setInputMessage] = useState<ChatUserMessage>(() => {
|
const [inputMessage, setInputMessage] = useState<ChatUserMessage>(() => {
|
||||||
const newMessage = getNewInputMessage(app, settings.defaultMention)
|
const newMessage = getNewInputMessage(app, settings.defaultMention)
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
import { Plus, Undo2, Settings, Circle, Trash2 } from 'lucide-react';
|
import { ChevronDown, ChevronRight, Plus, Trash2, Undo2 } from 'lucide-react';
|
||||||
import React, { useState, useEffect } from '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 { 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 { modes as buildinModes } from '../../utils/modes';
|
||||||
|
import { openOrCreateMarkdownFile } from '../../utils/obsidian';
|
||||||
const CustomModeView = () => {
|
const CustomModeView = () => {
|
||||||
|
const app = useApp()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
createCustomMode,
|
createCustomMode,
|
||||||
deleteCustomMode,
|
deleteCustomMode,
|
||||||
@ -15,7 +19,7 @@ const CustomModeView = () => {
|
|||||||
// 当前选择的模式
|
// 当前选择的模式
|
||||||
const [selectedMode, setSelectedMode] = useState<string>('ask')
|
const [selectedMode, setSelectedMode] = useState<string>('ask')
|
||||||
const [isBuiltinMode, setIsBuiltinMode] = useState<boolean>(true)
|
const [isBuiltinMode, setIsBuiltinMode] = useState<boolean>(true)
|
||||||
|
const [isAdvancedCollapsed, setIsAdvancedCollapsed] = useState(true);
|
||||||
|
|
||||||
const isNewMode = React.useMemo(() => selectedMode === "add_new_mode", [selectedMode])
|
const isNewMode = React.useMemo(() => selectedMode === "add_new_mode", [selectedMode])
|
||||||
|
|
||||||
@ -46,7 +50,6 @@ const CustomModeView = () => {
|
|||||||
// 自定义指令
|
// 自定义指令
|
||||||
const [customInstructions, setCustomInstructions] = useState<string>('')
|
const [customInstructions, setCustomInstructions] = useState<string>('')
|
||||||
|
|
||||||
|
|
||||||
// 当模式变更时更新表单数据
|
// 当模式变更时更新表单数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// new mode
|
// new mode
|
||||||
@ -63,7 +66,7 @@ const CustomModeView = () => {
|
|||||||
const builtinMode = buildinModes.find(m => m.slug === selectedMode);
|
const builtinMode = buildinModes.find(m => m.slug === selectedMode);
|
||||||
if (builtinMode) {
|
if (builtinMode) {
|
||||||
setIsBuiltinMode(true);
|
setIsBuiltinMode(true);
|
||||||
setModeName(builtinMode.name);
|
setModeName(builtinMode.slug);
|
||||||
setRoleDefinition(builtinMode.roleDefinition);
|
setRoleDefinition(builtinMode.roleDefinition);
|
||||||
setCustomInstructions(builtinMode.customInstructions || '');
|
setCustomInstructions(builtinMode.customInstructions || '');
|
||||||
setSelectedTools(builtinMode.groups as GroupEntry[]);
|
setSelectedTools(builtinMode.groups as GroupEntry[]);
|
||||||
@ -219,9 +222,11 @@ const CustomModeView = () => {
|
|||||||
<div className="infio-custom-modes-section">
|
<div className="infio-custom-modes-section">
|
||||||
<div className="infio-section-header">
|
<div className="infio-section-header">
|
||||||
<h3>角色定义</h3>
|
<h3>角色定义</h3>
|
||||||
<button className="infio-section-btn">
|
{isBuiltinMode && (
|
||||||
<Undo2 size={16} />
|
<button className="infio-section-btn">
|
||||||
</button>
|
<Undo2 size={16} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="infio-section-subtitle">设定专业领域和应答风格</p>
|
<p className="infio-section-subtitle">设定专业领域和应答风格</p>
|
||||||
<textarea
|
<textarea
|
||||||
@ -274,7 +279,7 @@ const CustomModeView = () => {
|
|||||||
checked={selectedTools.includes('research')}
|
checked={selectedTools.includes('research')}
|
||||||
onChange={() => handleToolChange('research')}
|
onChange={() => handleToolChange('research')}
|
||||||
/>
|
/>
|
||||||
浏览器
|
网络搜索
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -298,10 +303,29 @@ const CustomModeView = () => {
|
|||||||
placeholder="输入模式自定义指令..."
|
placeholder="输入模式自定义指令..."
|
||||||
/>
|
/>
|
||||||
<p className="infio-section-footer">
|
<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>
|
</p>
|
||||||
</div>
|
</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">
|
<div className="infio-custom-modes-actions">
|
||||||
<button className="infio-preview-btn">
|
<button className="infio-preview-btn">
|
||||||
预览
|
预览
|
||||||
@ -546,6 +570,17 @@ const CustomModeView = () => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: fit-content;
|
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>
|
</style>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import { ChevronDown, ChevronUp } from 'lucide-react'
|
import { ChevronDown, ChevronUp } from 'lucide-react'
|
||||||
import { useEffect, useState } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
|
|
||||||
import { useSettings } from '../../../contexts/SettingsContext'
|
import { useSettings } from '../../../contexts/SettingsContext'
|
||||||
|
import { useCustomModes } from '../../../hooks/use-custom-mode'
|
||||||
import { modes } from '../../../utils/modes'
|
import { modes } from '../../../utils/modes'
|
||||||
|
|
||||||
export function ModeSelect() {
|
export function ModeSelect() {
|
||||||
@ -10,11 +11,14 @@ export function ModeSelect() {
|
|||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const [mode, setMode] = useState(settings.mode)
|
const [mode, setMode] = useState(settings.mode)
|
||||||
|
|
||||||
|
const { customModeList } = useCustomModes()
|
||||||
|
|
||||||
|
const allModes = useMemo(() => [...modes, ...customModeList], [customModeList])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMode(settings.mode)
|
setMode(settings.mode)
|
||||||
}, [settings.mode])
|
}, [settings.mode])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen}>
|
<DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DropdownMenu.Trigger className="infio-chat-input-model-select">
|
<DropdownMenu.Trigger className="infio-chat-input-model-select">
|
||||||
@ -22,7 +26,7 @@ export function ModeSelect() {
|
|||||||
{isOpen ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
|
{isOpen ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
|
||||||
</div>
|
</div>
|
||||||
<div className="infio-chat-input-model-select__model-name">
|
<div className="infio-chat-input-model-select__model-name">
|
||||||
{modes.find((m) => m.slug === mode)?.name}
|
{allModes.find((m) => m.slug === mode)?.name}
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
|
|
||||||
@ -30,7 +34,7 @@ export function ModeSelect() {
|
|||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
className="infio-popover">
|
className="infio-popover">
|
||||||
<ul>
|
<ul>
|
||||||
{modes.map((mode) => (
|
{allModes.map((mode) => (
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
key={mode.slug}
|
key={mode.slug}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
|
|||||||
@ -152,10 +152,10 @@ ${await addCustomInstructions(this.app, promptComponent?.customInstructions || m
|
|||||||
filesSearchMethod: string = 'regex',
|
filesSearchMethod: string = 'regex',
|
||||||
preferredLanguage?: string,
|
preferredLanguage?: string,
|
||||||
diffStrategy?: DiffStrategy,
|
diffStrategy?: DiffStrategy,
|
||||||
mcpHub?: McpHub,
|
|
||||||
browserViewportSize?: string,
|
|
||||||
customModePrompts?: CustomModePrompts,
|
customModePrompts?: CustomModePrompts,
|
||||||
customModes?: ModeConfig[],
|
customModes?: ModeConfig[],
|
||||||
|
mcpHub?: McpHub,
|
||||||
|
browserViewportSize?: string,
|
||||||
globalCustomInstructions?: string,
|
globalCustomInstructions?: string,
|
||||||
diffEnabled?: boolean,
|
diffEnabled?: boolean,
|
||||||
experiments?: Record<string, boolean>,
|
experiments?: Record<string, boolean>,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
|||||||
import { useApp } from '../contexts/AppContext'
|
import { useApp } from '../contexts/AppContext'
|
||||||
import { CustomModeManager } from '../database/json/custom-mode/CustomModeManager'
|
import { CustomModeManager } from '../database/json/custom-mode/CustomModeManager'
|
||||||
import { CustomMode, GroupEntry } from '../database/json/custom-mode/types'
|
import { CustomMode, GroupEntry } from '../database/json/custom-mode/types'
|
||||||
|
import { CustomModePrompts } from '../utils/modes'
|
||||||
|
|
||||||
type UseCustomModes = {
|
type UseCustomModes = {
|
||||||
createCustomMode: (
|
createCustomMode: (
|
||||||
@ -22,6 +22,7 @@ type UseCustomModes = {
|
|||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
FindCustomModeByName: (name: string) => Promise<CustomMode | undefined>
|
FindCustomModeByName: (name: string) => Promise<CustomMode | undefined>
|
||||||
customModeList: CustomMode[]
|
customModeList: CustomMode[]
|
||||||
|
customModePrompts: CustomModePrompts
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCustomModes(): UseCustomModes {
|
export function useCustomModes(): UseCustomModes {
|
||||||
@ -37,6 +38,16 @@ export function useCustomModes(): UseCustomModes {
|
|||||||
})
|
})
|
||||||
}, [customModeManager])
|
}, [customModeManager])
|
||||||
|
|
||||||
|
const customModePrompts = useMemo(() => {
|
||||||
|
return customModeList.reduce((acc, customMode) => {
|
||||||
|
acc[customMode.slug] = {
|
||||||
|
roleDefinition: customMode.roleDefinition,
|
||||||
|
customInstructions: customMode.customInstructions,
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {} as CustomModePrompts)
|
||||||
|
}, [customModeList])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void fetchCustomModeList()
|
void fetchCustomModeList()
|
||||||
}, [fetchCustomModeList])
|
}, [fetchCustomModeList])
|
||||||
@ -91,5 +102,6 @@ export function useCustomModes(): UseCustomModes {
|
|||||||
updateCustomMode,
|
updateCustomMode,
|
||||||
FindCustomModeByName,
|
FindCustomModeByName,
|
||||||
customModeList,
|
customModeList,
|
||||||
|
customModePrompts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { App, Editor, MarkdownView, TFile, TFolder, Vault, WorkspaceLeaf } from 'obsidian'
|
import { App, Editor, MarkdownView, TFile, TFolder, Vault, WorkspaceLeaf } from 'obsidian'
|
||||||
|
|
||||||
|
import * as path from 'path'
|
||||||
|
|
||||||
import { MentionableBlockData } from '../types/mentionable'
|
import { MentionableBlockData } from '../types/mentionable'
|
||||||
|
|
||||||
export async function readTFileContent(
|
export async function readTFileContent(
|
||||||
@ -61,7 +63,7 @@ export function getOpenFiles(app: App): TFile[] {
|
|||||||
const leaves = app.workspace.getLeavesOfType('markdown')
|
const leaves = app.workspace.getLeavesOfType('markdown')
|
||||||
|
|
||||||
return leaves
|
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
|
v.view instanceof MarkdownView && !!v.view.file
|
||||||
)
|
)
|
||||||
.map((v) => 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
|
MentionableVault
|
||||||
} from '../types/mentionable'
|
} from '../types/mentionable'
|
||||||
import { InfioSettings } from '../types/settings'
|
import { InfioSettings } from '../types/settings'
|
||||||
import { Mode, getFullModeDetails } from "../utils/modes"
|
import { CustomModePrompts, Mode, ModeConfig, getFullModeDetails } from "../utils/modes"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
readTFileContent
|
readTFileContent
|
||||||
@ -116,6 +116,8 @@ export class PromptGenerator {
|
|||||||
private settings: InfioSettings
|
private settings: InfioSettings
|
||||||
private diffStrategy: DiffStrategy
|
private diffStrategy: DiffStrategy
|
||||||
private systemPrompt: SystemPrompt
|
private systemPrompt: SystemPrompt
|
||||||
|
private customModePrompts: CustomModePrompts | null = null
|
||||||
|
private customModeList: ModeConfig[] | null = null
|
||||||
private static readonly EMPTY_ASSISTANT_MESSAGE: RequestMessage = {
|
private static readonly EMPTY_ASSISTANT_MESSAGE: RequestMessage = {
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: '',
|
content: '',
|
||||||
@ -126,12 +128,16 @@ export class PromptGenerator {
|
|||||||
app: App,
|
app: App,
|
||||||
settings: InfioSettings,
|
settings: InfioSettings,
|
||||||
diffStrategy?: DiffStrategy,
|
diffStrategy?: DiffStrategy,
|
||||||
|
customModePrompts?: CustomModePrompts,
|
||||||
|
customModeList?: ModeConfig[],
|
||||||
) {
|
) {
|
||||||
this.getRagEngine = getRagEngine
|
this.getRagEngine = getRagEngine
|
||||||
this.app = app
|
this.app = app
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.diffStrategy = diffStrategy
|
this.diffStrategy = diffStrategy
|
||||||
this.systemPrompt = new SystemPrompt(this.app)
|
this.systemPrompt = new SystemPrompt(this.app)
|
||||||
|
this.customModePrompts = customModePrompts ?? null
|
||||||
|
this.customModeList = customModeList ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
public async generateRequestMessages({
|
public async generateRequestMessages({
|
||||||
@ -473,7 +479,9 @@ export class PromptGenerator {
|
|||||||
mode,
|
mode,
|
||||||
filesSearchMethod,
|
filesSearchMethod,
|
||||||
preferredLanguage,
|
preferredLanguage,
|
||||||
this.diffStrategy
|
this.diffStrategy,
|
||||||
|
this.customModePrompts,
|
||||||
|
this.customModeList,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user