Skip to content

Commit b785d80

Browse files
committed
feat: File editing supports Dockerfile language
1 parent 254ed95 commit b785d80

5 files changed

Lines changed: 74 additions & 53 deletions

File tree

agent/utils/terminal/ai/config_runtime.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ func ResolveGeneratorConfigFromAgentSettings() (GeneratorConfig, uint, time.Dura
127127
return config, uint(accountID), timeout, err
128128
}
129129

130-
// ResolveGeneratorConfigFromFileSettings loads file-management AI search settings (independent from terminal AI).
131130
func ResolveGeneratorConfigFromFileSettings() (GeneratorConfig, uint, time.Duration, error) {
132131
status, err := loadAgentSettingValue("FileAIStatus")
133132
if err != nil {
@@ -151,7 +150,6 @@ func ResolveGeneratorConfigFromFileSettings() (GeneratorConfig, uint, time.Durat
151150
return config, uint(accountID), timeout, err
152151
}
153152

154-
// LoadFileAIRuntimeConfig returns LLM client config for file-management AI search.
155153
func LoadFileAIRuntimeConfig() (GeneratorConfig, time.Duration, error) {
156154
cfg, _, timeout, err := ResolveGeneratorConfigFromFileSettings()
157155
return cfg, timeout, err

frontend/src/global/mimetype.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ export const Languages = [
4747
label: 'markdown',
4848
value: ['md'],
4949
},
50+
{
51+
label: 'dockerfile',
52+
value: ['dockerfile'],
53+
},
5054
{
5155
label: 'yaml',
5256
value: ['yml', 'yaml'],

frontend/src/utils/file.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Languages } from '@/global/mimetype';
2+
13
const icons = new Map([
24
['.zip', 'p-file-zip'],
35
['.gz', 'p-file-zip'],
@@ -152,3 +154,49 @@ export function downloadWithContent(content: string, fileName: string) {
152154
const event = new MouseEvent('click');
153155
a.dispatchEvent(event);
154156
}
157+
158+
const editorLanguages = new Map(
159+
Languages.flatMap((language) => language.value.map((ext) => [ext.toLowerCase(), language.label] as const)),
160+
);
161+
162+
const specialEditorFileNames = ['dockerfile'];
163+
164+
const normalizeEditorExtension = (value: string) => {
165+
const trimmed = value.trim().toLowerCase();
166+
if (!trimmed) {
167+
return '';
168+
}
169+
return trimmed.startsWith('.') ? trimmed.slice(1) : trimmed;
170+
};
171+
172+
const isSpecialEditorFileName = (value: string) => {
173+
const normalized = value.trim().toLowerCase();
174+
if (!normalized) {
175+
return false;
176+
}
177+
return specialEditorFileNames.some((filename) => normalized === filename || normalized.startsWith(`${filename}.`));
178+
};
179+
180+
export const resolveEditorLanguage = (path: string, extension = '', name = '') => {
181+
if (isSpecialEditorFileName(name)) {
182+
return 'dockerfile';
183+
}
184+
185+
const candidates = [extension, path.split('/').pop() || '']
186+
.map((value) => normalizeEditorExtension(value))
187+
.filter(Boolean);
188+
189+
for (const ext of candidates) {
190+
const language = editorLanguages.get(ext);
191+
if (language) {
192+
return language;
193+
}
194+
}
195+
196+
const fileName = path.split('/').pop() || '';
197+
if (isSpecialEditorFileName(fileName)) {
198+
return 'dockerfile';
199+
}
200+
201+
return 'yaml';
202+
};

frontend/src/views/host/file-management/code-editor/index.vue

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@
187187
<span
188188
v-else
189189
style="display: inline-flex; align-items: center"
190-
@click="getContent(data.path, data.extension)"
190+
@click="getContent(data.path)"
191191
>
192192
<template v-if="isCreate == 'file' && data.id == 'new-file'">
193193
<div class="flex justify-between items-center gap-0.5 pr-2">
@@ -405,6 +405,7 @@ import { MsgError, MsgSuccess, MsgWarning } from '@/utils/message';
405405
import { loadMonacoLanguageSupport, setupMonacoEnvironment } from '@/utils/monaco';
406406
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
407407
import { Languages } from '@/global/mimetype';
408+
import { resolveEditorLanguage } from '@/utils/file';
408409
409410
import type { TabPaneName } from 'element-plus';
410411
import { ElMessageBox, ElTreeV2 } from 'element-plus';
@@ -636,20 +637,20 @@ const removeTab = (targetPath: TabPaneName) => {
636637
updateTabs();
637638
if (fileTabs.value.length > 0) {
638639
saveContent();
639-
getContent(selectTab.value, '');
640+
getContent(selectTab.value);
640641
}
641642
})
642643
.catch(() => {
643644
updateTabs();
644645
isEdit.value = false;
645646
if (fileTabs.value.length > 0) {
646-
getContent(selectTab.value, '');
647+
getContent(selectTab.value);
647648
}
648649
});
649650
} else {
650651
updateTabs();
651652
if (fileTabs.value.length > 0) {
652-
getContent(selectTab.value, '');
653+
getContent(selectTab.value);
653654
}
654655
}
655656
};
@@ -678,7 +679,7 @@ const removeAllTab = (targetPath: string, type: 'left' | 'right' | 'all') => {
678679
selectTab.value = '';
679680
disposeEditor();
680681
} else if (newTabs.length > 0) {
681-
getContent(activeName, '');
682+
getContent(activeName);
682683
}
683684
};
684685
@@ -719,7 +720,7 @@ const removeOtherTab = (targetPath: string) => {
719720
fileTabs.value = [targetTab];
720721
selectTab.value = targetTab.path;
721722
saveTabsToStorage();
722-
getContent(targetTab.path, '');
723+
getContent(targetTab.path);
723724
};
724725
725726
const onConfirm = () => {
@@ -749,7 +750,7 @@ const removeOtherTab = (targetPath: string) => {
749750
750751
const changeTab = (targetPath: TabPaneName) => {
751752
selectTab.value = targetPath.toString();
752-
getContent(targetPath.toString(), '');
753+
getContent(targetPath.toString());
753754
};
754755
755756
const eols = computed(() => [
@@ -1040,6 +1041,7 @@ const acceptParams = async (props: EditProps) => {
10401041
directoryPath.value = getDirectoryPath(props.path);
10411042
fileExtension.value = props.extension;
10421043
fileName.value = props.name;
1044+
config.language = resolveEditorLanguage(props.path, props.extension, props.name);
10431045
10441046
let savedTabs = loadTabsFromStorage();
10451047
const withoutCurrent = savedTabs.filter((tab) => tab.path !== props.path);
@@ -1059,7 +1061,9 @@ const acceptParams = async (props: EditProps) => {
10591061
fileTabs.value = merged.slice(-maxTabs);
10601062
selectTab.value = props.path;
10611063
1062-
config.language = props.language;
1064+
if (props.language) {
1065+
config.language = props.language;
1066+
}
10631067
config.eol = monaco.editor.EndOfLineSequence.LF;
10641068
config.theme = localStorage.getItem(codeThemeKey) || 'vs-dark';
10651069
config.wordWrap = (localStorage.getItem(warpKey) as WordWrapOptions) || 'on';
@@ -1140,7 +1144,7 @@ const getRefresh = (path: string) => {
11401144
}
11411145
};
11421146
1143-
const getContent = (path: string, extension: string, forceReload = false) => {
1147+
const getContent = (path: string, forceReload = false) => {
11441148
if (!forceReload && (form.value.path === path || isCreate.value == 'file')) {
11451149
return;
11461150
}
@@ -1152,31 +1156,15 @@ const getContent = (path: string, extension: string, forceReload = false) => {
11521156
codeReq.path = path;
11531157
codeReq.expand = true;
11541158
1155-
if (extension !== '') {
1156-
Languages.forEach((language) => {
1157-
const ext = extension.substring(1);
1158-
if (language.value.indexOf(ext) > -1) {
1159-
config.language = language.label;
1160-
}
1161-
});
1162-
}
1163-
11641159
getFileContent(codeReq)
11651160
.then((res) => {
11661161
form.value.content = res.data.content;
11671162
oldFileContent.value = res.data.content;
11681163
form.value.path = res.data.path;
11691164
fileExtension.value = res.data.extension;
11701165
fileName.value = res.data.name;
1166+
config.language = resolveEditorLanguage(res.data.path, res.data.extension, res.data.name);
11711167
initEditor();
1172-
if (extension == '') {
1173-
Languages.forEach((language) => {
1174-
const ext = fileExtension.value.substring(1);
1175-
if (language.value.indexOf(ext) > -1) {
1176-
config.language = language.label;
1177-
}
1178-
});
1179-
}
11801168
const exists = fileTabs.value.some((tab) => tab.path === path);
11811169
if (exists) {
11821170
const tab = fileTabs.value.find((t) => t.path === path);
@@ -1215,7 +1203,7 @@ const getContent = (path: string, extension: string, forceReload = false) => {
12151203
12161204
const handleHistoryRestored = (path: string) => {
12171205
if (path === form.value.path) {
1218-
getContent(path, '', true);
1206+
getContent(path, true);
12191207
loadHistoryVersionCount(path);
12201208
}
12211209
};

frontend/src/views/host/file-management/index.vue

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,8 @@ import { dateFormat } from '@/utils/date';
718718
import { downloadFile, getFileType, getIcon, isConvertible } from '@/utils/file';
719719
import { getRandomStr } from '@/utils/id';
720720
import { File } from '@/api/interface/file';
721-
import { Languages, Mimetypes } from '@/global/mimetype';
721+
import { Mimetypes } from '@/global/mimetype';
722+
import { resolveEditorLanguage } from '@/utils/file';
722723
import { useRouter } from 'vue-router';
723724
import { MsgSuccess, MsgWarning } from '@/utils/message';
724725
import { useMultipleSearchable } from './hooks/searchable';
@@ -1383,10 +1384,10 @@ const openView = (item: File.File) => {
13831384
}
13841385
13851386
const actionMap = {
1386-
text: () => openCodeEditor(path, item.extension),
1387+
text: () => openCodeEditor(path),
13871388
};
13881389
1389-
return actionMap[fileType] ? actionMap[fileType](item) : openCodeEditor(path, item.extension);
1390+
return actionMap[fileType] ? actionMap[fileType](item) : openCodeEditor(path);
13901391
};
13911392
13921393
const openPreview = (item: File.File, fileType: string) => {
@@ -1404,37 +1405,18 @@ const openPreview = (item: File.File, fileType: string) => {
14041405
previewRef.value.acceptParams(filePreview);
14051406
};
14061407
1407-
const extensionFromPath = (p: string) => {
1408-
const i = p.lastIndexOf('.');
1409-
if (i <= 0 || i === p.length - 1) {
1410-
return '';
1411-
}
1412-
return p.slice(i);
1413-
};
1414-
14151408
const openPathInCodeEditor = (
14161409
path: string,
14171410
opts?: {
1418-
extension?: string;
14191411
initialLine?: number;
14201412
},
14211413
) => {
14221414
if (!path) {
14231415
return;
14241416
}
1425-
const extension = opts?.extension && opts.extension !== '' ? opts.extension : extensionFromPath(path);
14261417
codeReq.path = path;
14271418
codeReq.expand = true;
14281419
1429-
if (extension !== '') {
1430-
Languages.forEach((language) => {
1431-
const ext = extension.substring(1);
1432-
if (language.value.indexOf(ext) > -1) {
1433-
fileEdit.language = language.label;
1434-
}
1435-
});
1436-
}
1437-
14381420
const line = opts?.initialLine && opts.initialLine > 0 ? Math.floor(opts.initialLine) : undefined;
14391421
14401422
getFileContent(codeReq)
@@ -1443,6 +1425,7 @@ const openPathInCodeEditor = (
14431425
fileEdit.path = res.data.path;
14441426
fileEdit.name = res.data.name;
14451427
fileEdit.extension = res.data.extension;
1428+
fileEdit.language = resolveEditorLanguage(res.data.path, res.data.extension, res.data.name);
14461429
fileEdit.initialLine = line;
14471430
codeEditorRef.value.acceptParams(fileEdit);
14481431
fileEdit.initialLine = undefined;
@@ -1454,8 +1437,8 @@ const onAiSearchOpenEditor = (payload: { path: string; initialLine?: number }) =
14541437
openPathInCodeEditor(payload.path, { initialLine: payload.initialLine });
14551438
};
14561439
1457-
const openCodeEditor = (path: string, extension: string) => {
1458-
openPathInCodeEditor(path, { extension });
1440+
const openCodeEditor = (path: string) => {
1441+
openPathInCodeEditor(path);
14591442
};
14601443
14611444
const openTextPreview = (path: string, name: string) => {

0 commit comments

Comments
 (0)