mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 08:00:53 +00:00
feat: enhance AI search functionality with response language support (#12460)
This commit is contained in:
@@ -63,6 +63,9 @@ func (b *BaseApi) FileAISearch(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.ResponseLanguage) == "" {
|
||||
req.ResponseLanguage = strings.TrimSpace(c.GetHeader("Accept-Language"))
|
||||
}
|
||||
res, err := fileService.AISearch(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
|
||||
@@ -10,18 +10,19 @@ type FileOption struct {
|
||||
}
|
||||
|
||||
type FileAISearch struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Query string `json:"query" validate:"required"`
|
||||
ContainSub *bool `json:"containSub,omitempty"`
|
||||
MaxItems int `json:"maxItems" validate:"omitempty,min=1,max=2000"`
|
||||
MatchCase bool `json:"matchCase"`
|
||||
WholeWord bool `json:"wholeWord"`
|
||||
UseRegex bool `json:"useRegex"`
|
||||
Extensions []string `json:"extensions,omitempty"`
|
||||
MinSize int64 `json:"minSize"`
|
||||
MaxSize int64 `json:"maxSize"`
|
||||
ModifiedAfter string `json:"modifiedAfter,omitempty"`
|
||||
ModifiedBefore string `json:"modifiedBefore,omitempty"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Query string `json:"query" validate:"required"`
|
||||
ResponseLanguage string `json:"responseLanguage,omitempty"`
|
||||
ContainSub *bool `json:"containSub,omitempty"`
|
||||
MaxItems int `json:"maxItems" validate:"omitempty,min=1,max=2000"`
|
||||
MatchCase bool `json:"matchCase"`
|
||||
WholeWord bool `json:"wholeWord"`
|
||||
UseRegex bool `json:"useRegex"`
|
||||
Extensions []string `json:"extensions,omitempty"`
|
||||
MinSize int64 `json:"minSize"`
|
||||
MaxSize int64 `json:"maxSize"`
|
||||
ModifiedAfter string `json:"modifiedAfter,omitempty"`
|
||||
ModifiedBefore string `json:"modifiedBefore,omitempty"`
|
||||
|
||||
MaxScanFiles int `json:"maxScanFiles"`
|
||||
MaxFileBytes int64 `json:"maxFileBytes"`
|
||||
|
||||
@@ -1167,9 +1167,15 @@ func (f *FileService) AISearch(req request.FileAISearch) (*response.FileAISearch
|
||||
defer cancel()
|
||||
|
||||
llmMaxOut := searchOpts.LlmMaxOutputTokens
|
||||
summary, usage, err := files.RunFileAISearchLLM(runCtx, cfg, clientTimeout, root, query, llmItems, truncated, preFiltered, contentHits, scannedFiles, hitsTrunc, matchDesc, searchOpts.ContentHitsPromptMaxBytes, llmMaxOut)
|
||||
summary, usage, err := files.RunFileAISearchLLM(runCtx, cfg, clientTimeout, root, query, req.ResponseLanguage, llmItems, truncated, preFiltered, contentHits, scannedFiles, hitsTrunc, matchDesc, searchOpts.ContentHitsPromptMaxBytes, llmMaxOut)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
result.Mode = "grep"
|
||||
result.Summary = ""
|
||||
result.Duration = time.Since(start).Round(time.Millisecond).String()
|
||||
if errors.Is(err, context.DeadlineExceeded) || strings.Contains(strings.ToLower(err.Error()), "timeout") {
|
||||
return result, nil
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
result.Summary = summary
|
||||
result.PromptTokens = usage.PromptTokens
|
||||
|
||||
@@ -11,24 +11,33 @@ import (
|
||||
|
||||
const fileAISearchMaxPathRunes = 240
|
||||
|
||||
func buildFileAISearchSystemPrompt() string {
|
||||
return strings.Join([]string{
|
||||
func buildFileAISearchSystemPrompt(responseLanguage string) string {
|
||||
lines := []string{
|
||||
"You are a file browser assistant for a server panel.",
|
||||
"Answer using Markdown (headings, bullet lists).",
|
||||
"Only mention files and directories that appear in the provided inventory. Do not invent paths.",
|
||||
"If a \"Content line matches\" section is present, you may reference those lines and numbers only; never invent line numbers or snippets that are not listed there.",
|
||||
"If the inventory was truncated or incomplete, say so and suggest narrowing the directory or increasing limits.",
|
||||
"Group or rank results by relevance to the user's question when helpful.",
|
||||
"Prefer responding in the same language as the user's query (e.g. Chinese if the query is in Chinese).",
|
||||
}, "\n")
|
||||
}
|
||||
if lang := normalizeFileAISearchLanguage(responseLanguage); lang != "" {
|
||||
lines = append(lines, "Respond in "+lang+".")
|
||||
} else {
|
||||
lines = append(lines, "Prefer responding in the same language as the user's query (e.g. Chinese if the query is in Chinese).")
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func buildFileAISearchUserPrompt(root, query string, items []AISearchInventoryItem, truncated, preFiltered bool, contentHits []FileAIContentHit, contentScannedFiles int, contentHitsTruncated bool, matchDesc string, promptHitMaxBytes int) string {
|
||||
func buildFileAISearchUserPrompt(root, query, responseLanguage string, items []AISearchInventoryItem, truncated, preFiltered bool, contentHits []FileAIContentHit, contentScannedFiles int, contentHitsTruncated bool, matchDesc string, promptHitMaxBytes int) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("User question:\n")
|
||||
b.WriteString(strings.TrimSpace(query))
|
||||
b.WriteString("\n\nRoot directory:\n")
|
||||
b.WriteString(root)
|
||||
if lang := normalizeFileAISearchLanguage(responseLanguage); lang != "" {
|
||||
b.WriteString("\n\nPanel reply language:\n")
|
||||
b.WriteString(lang)
|
||||
}
|
||||
b.WriteString("\n\nInventory notes:\n")
|
||||
if truncated {
|
||||
b.WriteString("- Listing was truncated; not all files under the root were included.\n")
|
||||
@@ -80,7 +89,7 @@ func buildFileAISearchUserPrompt(root, query string, items []AISearchInventoryIt
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func RunFileAISearchLLM(ctx context.Context, cfg terminalai.GeneratorConfig, clientTimeout time.Duration, root, query string, items []AISearchInventoryItem, truncated, preFiltered bool, contentHits []FileAIContentHit, contentScannedFiles int, contentHitsTruncated bool, matchDesc string, promptHitMaxBytes, llmMaxOutputTokens int) (string, terminalai.ResponseUsage, error) {
|
||||
func RunFileAISearchLLM(ctx context.Context, cfg terminalai.GeneratorConfig, clientTimeout time.Duration, root, query, responseLanguage string, items []AISearchInventoryItem, truncated, preFiltered bool, contentHits []FileAIContentHit, contentScannedFiles int, contentHitsTruncated bool, matchDesc string, promptHitMaxBytes, llmMaxOutputTokens int) (string, terminalai.ResponseUsage, error) {
|
||||
timeout := clientTimeout
|
||||
if timeout <= 0 {
|
||||
timeout = 2 * time.Minute
|
||||
@@ -109,8 +118,8 @@ func RunFileAISearchLLM(ctx context.Context, cfg terminalai.GeneratorConfig, cli
|
||||
}
|
||||
resp, err := client.ChatCompletion(ctx, terminalai.ChatCompletionRequest{
|
||||
Messages: []terminalai.ChatMessage{
|
||||
{Role: "system", Content: buildFileAISearchSystemPrompt()},
|
||||
{Role: "user", Content: buildFileAISearchUserPrompt(root, query, items, truncated, preFiltered, contentHits, contentScannedFiles, contentHitsTruncated, matchDesc, promptHitMaxBytes)},
|
||||
{Role: "system", Content: buildFileAISearchSystemPrompt(responseLanguage)},
|
||||
{Role: "user", Content: buildFileAISearchUserPrompt(root, query, responseLanguage, items, truncated, preFiltered, contentHits, contentScannedFiles, contentHitsTruncated, matchDesc, promptHitMaxBytes)},
|
||||
},
|
||||
MaxTokens: outTokens,
|
||||
})
|
||||
@@ -123,3 +132,33 @@ func RunFileAISearchLLM(ctx context.Context, cfg terminalai.GeneratorConfig, cli
|
||||
}
|
||||
return summary, resp.Usage, nil
|
||||
}
|
||||
|
||||
func normalizeFileAISearchLanguage(lang string) string {
|
||||
lang = strings.TrimSpace(strings.ToLower(lang))
|
||||
switch {
|
||||
case lang == "", lang == "*":
|
||||
return "English"
|
||||
case strings.HasPrefix(lang, "zh-hant"), strings.HasPrefix(lang, "zh-tw"), strings.HasPrefix(lang, "zh-hk"):
|
||||
return "Traditional Chinese"
|
||||
case strings.HasPrefix(lang, "zh"):
|
||||
return "Simplified Chinese"
|
||||
case strings.HasPrefix(lang, "en"):
|
||||
return "English"
|
||||
case strings.HasPrefix(lang, "ja"):
|
||||
return "Japanese"
|
||||
case strings.HasPrefix(lang, "ko"):
|
||||
return "Korean"
|
||||
case strings.HasPrefix(lang, "ru"):
|
||||
return "Russian"
|
||||
case strings.HasPrefix(lang, "ms"):
|
||||
return "Malay"
|
||||
case strings.HasPrefix(lang, "tr"):
|
||||
return "Turkish"
|
||||
case strings.HasPrefix(lang, "pt-br"):
|
||||
return "Brazilian Portuguese"
|
||||
case strings.HasPrefix(lang, "es"):
|
||||
return "Spanish"
|
||||
default:
|
||||
return lang
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ export namespace File {
|
||||
export interface FileAISearchReq {
|
||||
path: string;
|
||||
query: string;
|
||||
responseLanguage?: string;
|
||||
containSub?: boolean;
|
||||
maxItems?: number;
|
||||
matchCase?: boolean;
|
||||
|
||||
@@ -11,7 +11,7 @@ export const getFilesList = (params: File.ReqFile) => {
|
||||
};
|
||||
|
||||
export const fileAiSearch = (params: File.FileAISearchReq) => {
|
||||
return http.post<File.FileAISearchResult>('files/ai-search', params, TimeoutEnum.T_5M);
|
||||
return http.post<File.FileAISearchResult>('files/ai-search', params, TimeoutEnum.T_10M);
|
||||
};
|
||||
|
||||
export const getFilesListByNode = (params: File.ReqNodeFile) => {
|
||||
|
||||
+85
-85
@@ -137,7 +137,9 @@
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button @click="emit('pick-directory')">
|
||||
<el-button
|
||||
@click="emit('pick-directory', (aiSearchPath || props.listPath || '').trim())"
|
||||
>
|
||||
<el-icon><Folder /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -225,114 +227,104 @@
|
||||
<div class="grid grid-cols-1 items-center gap-x-4 gap-y-3 min-[480px]:grid-cols-2">
|
||||
<div class="ai-search-field-pair flex min-w-0 items-center gap-2">
|
||||
<span
|
||||
class="w-28 shrink-0 text-sm leading-8 text-[var(--el-text-color-secondary)] sm:w-32"
|
||||
class="w-32 shrink-0 text-sm leading-8 text-[var(--el-text-color-secondary)] sm:w-36"
|
||||
>
|
||||
{{ $t('file.aiSearchLimitMaxItems') }}
|
||||
</span>
|
||||
<el-tooltip :content="$t('file.aiSearchMaxItems')" placement="top">
|
||||
<el-input-number
|
||||
v-model="aiSearchMaxItems"
|
||||
:min="1"
|
||||
:max="2000"
|
||||
:controls="true"
|
||||
class="min-w-0 flex-1 !max-w-full sm:!w-44"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="pr-1 text-xs text-[var(--el-text-color-secondary)]">
|
||||
{{ $t('file.aiSearchLimitUnitEntries') }}
|
||||
</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</el-tooltip>
|
||||
<el-input-number
|
||||
v-model="aiSearchMaxItems"
|
||||
:min="1"
|
||||
:max="2000"
|
||||
:controls="true"
|
||||
class="min-w-0 flex-1 !max-w-full sm:!w-52"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="pr-1 text-xs text-[var(--el-text-color-secondary)]">
|
||||
{{ $t('file.aiSearchLimitUnitEntries') }}
|
||||
</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</div>
|
||||
<div class="ai-search-field-pair flex min-w-0 items-center gap-2">
|
||||
<span
|
||||
class="w-28 shrink-0 text-sm leading-8 text-[var(--el-text-color-secondary)] sm:w-32"
|
||||
class="w-32 shrink-0 text-sm leading-8 text-[var(--el-text-color-secondary)] sm:w-36"
|
||||
>
|
||||
{{ $t('file.aiSearchLimitMaxScan') }}
|
||||
</span>
|
||||
<el-tooltip :content="$t('file.aiSearchMaxScanFiles')" placement="top">
|
||||
<el-input-number
|
||||
v-model="aiSearchMaxScanFiles"
|
||||
:min="1"
|
||||
:max="500"
|
||||
:controls="true"
|
||||
class="min-w-0 flex-1 !max-w-full sm:!w-44"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="pr-1 text-xs text-[var(--el-text-color-secondary)]">
|
||||
{{ $t('file.aiSearchLimitUnitFiles') }}
|
||||
</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</el-tooltip>
|
||||
<el-input-number
|
||||
v-model="aiSearchMaxScanFiles"
|
||||
:min="1"
|
||||
:max="500"
|
||||
:controls="true"
|
||||
class="min-w-0 flex-1 !max-w-full sm:!w-52"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="pr-1 text-xs text-[var(--el-text-color-secondary)]">
|
||||
{{ $t('file.aiSearchLimitUnitFiles') }}
|
||||
</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</div>
|
||||
<div class="ai-search-field-pair flex min-w-0 items-center gap-2">
|
||||
<span
|
||||
class="w-28 shrink-0 text-sm leading-8 text-[var(--el-text-color-secondary)] sm:w-32"
|
||||
class="w-32 shrink-0 text-sm leading-8 text-[var(--el-text-color-secondary)] sm:w-36"
|
||||
>
|
||||
{{ $t('file.aiSearchLimitMaxBytes') }}
|
||||
</span>
|
||||
<el-tooltip :content="$t('file.aiSearchMaxFileBytes')" placement="top">
|
||||
<el-input-number
|
||||
v-model="aiSearchMaxFileBytes"
|
||||
:min="1024"
|
||||
:max="4194304"
|
||||
:step="1024"
|
||||
:controls="true"
|
||||
class="min-w-0 flex-1 !max-w-full sm:!w-52"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="pr-1 text-xs text-[var(--el-text-color-secondary)]">
|
||||
{{ $t('file.aiSearchLimitUnitBytes') }}
|
||||
</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</el-tooltip>
|
||||
<el-input-number
|
||||
v-model="aiSearchMaxFileBytes"
|
||||
:min="1024"
|
||||
:max="4194304"
|
||||
:step="1024"
|
||||
:controls="true"
|
||||
class="min-w-0 flex-1 !max-w-full sm:!w-52"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="pr-1 text-xs text-[var(--el-text-color-secondary)]">
|
||||
{{ $t('file.aiSearchLimitUnitBytes') }}
|
||||
</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</div>
|
||||
<div class="ai-search-field-pair flex min-w-0 items-center gap-2">
|
||||
<span
|
||||
class="w-28 shrink-0 text-sm leading-8 text-[var(--el-text-color-secondary)] sm:w-32"
|
||||
class="w-32 shrink-0 text-sm leading-8 text-[var(--el-text-color-secondary)] sm:w-36"
|
||||
>
|
||||
{{ $t('file.aiSearchLimitHitsPerFile') }}
|
||||
</span>
|
||||
<el-tooltip :content="$t('file.aiSearchMaxHitsPerFile')" placement="top">
|
||||
<el-input-number
|
||||
v-model="aiSearchMaxHitsPerFile"
|
||||
:min="1"
|
||||
:max="200"
|
||||
:controls="true"
|
||||
class="min-w-0 flex-1 !max-w-full sm:!w-44"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="pr-1 text-xs text-[var(--el-text-color-secondary)]">
|
||||
{{ $t('file.aiSearchLimitUnitLines') }}
|
||||
</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</el-tooltip>
|
||||
<el-input-number
|
||||
v-model="aiSearchMaxHitsPerFile"
|
||||
:min="1"
|
||||
:max="200"
|
||||
:controls="true"
|
||||
class="min-w-0 flex-1 !max-w-full sm:!w-52"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="pr-1 text-xs text-[var(--el-text-color-secondary)]">
|
||||
{{ $t('file.aiSearchLimitUnitLines') }}
|
||||
</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</div>
|
||||
<div class="ai-search-field-pair flex min-w-0 items-center gap-2">
|
||||
<span
|
||||
class="w-28 shrink-0 text-sm leading-8 text-[var(--el-text-color-secondary)] sm:w-32"
|
||||
class="w-32 shrink-0 text-sm leading-8 text-[var(--el-text-color-secondary)] sm:w-36"
|
||||
>
|
||||
{{ $t('file.aiSearchLimitTotalHits') }}
|
||||
</span>
|
||||
<el-tooltip :content="$t('file.aiSearchMaxTotalHits')" placement="top">
|
||||
<el-input-number
|
||||
v-model="aiSearchMaxTotalHits"
|
||||
:min="1"
|
||||
:max="2000"
|
||||
:controls="true"
|
||||
class="min-w-0 flex-1 !max-w-full sm:!w-44"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="pr-1 text-xs text-[var(--el-text-color-secondary)]">
|
||||
{{ $t('file.aiSearchLimitUnitLines') }}
|
||||
</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</el-tooltip>
|
||||
<el-input-number
|
||||
v-model="aiSearchMaxTotalHits"
|
||||
:min="1"
|
||||
:max="2000"
|
||||
:controls="true"
|
||||
class="min-w-0 flex-1 !max-w-full sm:!w-52"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="pr-1 text-xs text-[var(--el-text-color-secondary)]">
|
||||
{{ $t('file.aiSearchLimitUnitLines') }}
|
||||
</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -499,6 +491,7 @@ import { fileAiSearch } from '@/api/modules/files';
|
||||
import { getAgentFileManageAIInfo, updateAgentFileManageAIInfo } from '@/api/modules/setting';
|
||||
import { pageAgentAccounts } from '@/api/modules/ai';
|
||||
import { File } from '@/api/interface/file';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
import MkdownEditor from '@/components/mkdown-editor/index.vue';
|
||||
@@ -522,9 +515,11 @@ const props = defineProps<{
|
||||
listPath: string;
|
||||
}>();
|
||||
|
||||
const { globalStore } = useGlobalStore();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', v: boolean): void;
|
||||
(e: 'pick-directory'): void;
|
||||
(e: 'pick-directory', path: string): void;
|
||||
(e: 'open-editor', payload: { path: string; initialLine?: number }): void;
|
||||
}>();
|
||||
|
||||
@@ -814,6 +809,7 @@ const submitAiSearch = async () => {
|
||||
const payload: File.FileAISearchReq = {
|
||||
path: rootPath,
|
||||
query: q,
|
||||
responseLanguage: globalStore.language,
|
||||
containSub: aiSearchContainSub.value,
|
||||
matchCase: aiSearchMatchCase.value,
|
||||
wholeWord: aiSearchWholeWord.value,
|
||||
@@ -851,8 +847,12 @@ const submitAiSearch = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
function applyPathFromPicker(path: string) {
|
||||
aiSearchPath.value = path;
|
||||
function applyPathFromPicker(path: string | string[]) {
|
||||
if (Array.isArray(path)) {
|
||||
aiSearchPath.value = (path[0] || '').trim();
|
||||
return;
|
||||
}
|
||||
aiSearchPath.value = (path || '').trim();
|
||||
}
|
||||
|
||||
function resetAiSearchLimitsToDefaults() {
|
||||
@@ -438,14 +438,45 @@ interface TreeNode {
|
||||
}
|
||||
|
||||
const pendingInitialLine = ref(0);
|
||||
let lineHighlightDecorationIds: string[] = [];
|
||||
|
||||
const clearPendingLineHighlight = () => {
|
||||
if (!editor) {
|
||||
lineHighlightDecorationIds = [];
|
||||
return;
|
||||
}
|
||||
lineHighlightDecorationIds = editor.deltaDecorations(lineHighlightDecorationIds, []);
|
||||
};
|
||||
|
||||
const revealPendingInitialLine = () => {
|
||||
const line = pendingInitialLine.value;
|
||||
if (!editor || line < 1) {
|
||||
return;
|
||||
}
|
||||
editor.setPosition({ lineNumber: line, column: 1 });
|
||||
editor.revealLineInCenter(line);
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
const targetLine = Math.min(line, model.getLineCount());
|
||||
editor.setSelection({
|
||||
startLineNumber: targetLine,
|
||||
startColumn: 1,
|
||||
endLineNumber: targetLine,
|
||||
endColumn: 1,
|
||||
});
|
||||
editor.setPosition({ lineNumber: targetLine, column: 1 });
|
||||
editor.revealLineInCenter(targetLine);
|
||||
lineHighlightDecorationIds = editor.deltaDecorations(lineHighlightDecorationIds, [
|
||||
{
|
||||
range: new monaco.Range(targetLine, 1, targetLine, model.getLineMaxColumn(targetLine)),
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
className: 'ai-search-target-line',
|
||||
linesDecorationsClassName: 'ai-search-target-line-gutter',
|
||||
},
|
||||
},
|
||||
]);
|
||||
editor.focus();
|
||||
pendingInitialLine.value = 0;
|
||||
};
|
||||
|
||||
@@ -722,6 +753,7 @@ const handleClose = () => {
|
||||
fileTabs.value = [];
|
||||
isEdit.value = false;
|
||||
if (editor) {
|
||||
clearPendingLineHighlight();
|
||||
editor.dispose();
|
||||
}
|
||||
em('close', open.value);
|
||||
@@ -942,6 +974,12 @@ const acceptParams = async (props: EditProps) => {
|
||||
saveTabsToStorage();
|
||||
nextTick(() => {
|
||||
if (editor) {
|
||||
editor.setValue(form.value.content);
|
||||
const model = editor.getModel();
|
||||
if (model) {
|
||||
monaco.editor.setModelLanguage(model, config.language);
|
||||
}
|
||||
isEdit.value = false;
|
||||
revealPendingInitialLine();
|
||||
}
|
||||
});
|
||||
@@ -1427,4 +1465,12 @@ defineExpose({ acceptParams });
|
||||
:deep(.el-input__inner:focus) {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
:deep(.monaco-editor .ai-search-target-line) {
|
||||
background-color: rgba(64, 158, 255, 0.14);
|
||||
}
|
||||
|
||||
:deep(.monaco-editor .ai-search-target-line-gutter) {
|
||||
border-left: 3px solid var(--el-color-primary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -666,7 +666,7 @@
|
||||
ref="aiSearchDrawerRef"
|
||||
v-model="aiSearchDrawerVisible"
|
||||
:list-path="req.path"
|
||||
@pick-directory="fileRef.acceptParams({ dir: false, multiple: true })"
|
||||
@pick-directory="openAiSearchPathPicker"
|
||||
@open-editor="onAiSearchOpenEditor"
|
||||
/>
|
||||
<FileList ref="fileRef" @choose="getSearchPath" />
|
||||
@@ -733,7 +733,7 @@ import Preview from './preview/index.vue';
|
||||
import TextPreview from './text-preview/index.vue';
|
||||
import VscodeOpenDialog from '@/components/vscode-open/index.vue';
|
||||
import Convert from './convert/index.vue';
|
||||
import FileAiSearchDrawer from './file-ai-search-drawer.vue';
|
||||
import FileAiSearchDrawer from './ai-search/file-ai-search-drawer.vue';
|
||||
import FileShare from './share/index.vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
import TerminalDialog from './terminal/index.vue';
|
||||
@@ -837,10 +837,14 @@ const openAiSearchDrawer = () => {
|
||||
aiSearchDrawerVisible.value = true;
|
||||
};
|
||||
|
||||
const getSearchPath = (path: string) => {
|
||||
const getSearchPath = (path: string | string[]) => {
|
||||
aiSearchDrawerRef.value?.applyPathFromPicker(path);
|
||||
};
|
||||
|
||||
const openAiSearchPathPicker = (path?: string) => {
|
||||
fileRef.value.acceptParams({ path: path || req.path, dir: true, multiple: false });
|
||||
};
|
||||
|
||||
const createRef = ref();
|
||||
const roleRef = ref();
|
||||
const detailRef = ref();
|
||||
|
||||
Reference in New Issue
Block a user