android-midscene-automation 0.1.41 → 0.1.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI_RECOGNITION_GUIDE.md +370 -0
- package/CHANGELOG.md +16 -0
- package/README.md +35 -3
- package/USAGE.md +18 -3
- package/package.json +2 -1
- package/remote-agent/index.ts +1 -0
- package/server/appium-auto-save.test.ts +61 -0
- package/server/appium-config.test.ts +165 -0
- package/server/appium-recorder/ai-deduplication.test.ts +80 -0
- package/server/appium-recorder/ai-deduplication.ts +74 -0
- package/server/appium-recorder/ai-observation.test.ts +125 -0
- package/server/appium-recorder/ai-observation.ts +125 -0
- package/server/appium-recorder/ai-recognition.ts +118 -22
- package/server/appium-recorder/appium-runner.ts +79 -14
- package/server/appium-recorder/bounded-loop.test.ts +55 -0
- package/server/appium-recorder/bounded-loop.ts +12 -1
- package/server/appium-recorder/history-capture.ts +1 -0
- package/server/appium-recorder/image-check.ts +1 -1
- package/server/appium-recorder/managed-appium.ts +15 -0
- package/server/appium-recorder/managed-cleanup.test.ts +82 -0
- package/server/appium-recorder/prompt-optimizer.ts +57 -0
- package/server/appium-recorder/replay-log.ts +13 -0
- package/server/appium-recorder/report-summary.test.ts +78 -0
- package/server/appium-recorder/report-summary.ts +60 -0
- package/server/appium-recorder/report.ts +45 -14
- package/server/appium-recorder/repository.ts +7 -0
- package/server/appium-recorder/routes.ts +78 -4
- package/server/appium-recorder/screenshot.ts +1 -0
- package/server/config.ts +34 -4
- package/server/http-api.ts +31 -5
- package/server/model-tester.ts +2 -1
- package/src/App.vue +53 -30
- package/src/api.ts +11 -4
- package/src/appium-recorder/AppiumPage.vue +167 -68
- package/src/appium-recorder/RunHistoryDialog.vue +1 -1
- package/src/appium-recorder/action-descriptions.ts +2 -1
- package/src/appium-recorder/ai-deduplication.ts +34 -0
- package/src/appium-recorder/ai-prompt-presets.ts +34 -0
- package/src/appium-recorder/ai-recognition.ts +27 -2
- package/src/appium-recorder/api.ts +36 -2
- package/src/appium-recorder/bounded-loop.ts +9 -1
- package/src/appium-recorder/components/AiRecognitionPromptInput.vue +41 -0
- package/src/appium-recorder/components/AiRecognitionSettings.vue +167 -0
- package/src/appium-recorder/components/AiRecognitionTestDialog.vue +86 -0
- package/src/appium-recorder/components/BreakLoopSettings.vue +19 -3
- package/src/appium-recorder/components/ComponentTree.vue +5 -5
- package/src/appium-recorder/components/CoordinatePointPicker.vue +23 -0
- package/src/appium-recorder/components/FlowActionMenu.vue +50 -20
- package/src/appium-recorder/components/FlowCanvas.vue +3 -2
- package/src/appium-recorder/components/FlowNodeCard.vue +2 -2
- package/src/appium-recorder/components/FlowStepEditor.vue +17 -3
- package/src/appium-recorder/components/NestedConditionBranches.vue +25 -50
- package/src/appium-recorder/components/RecordedSteps.vue +23 -6
- package/src/appium-recorder/coordinate-preview.ts +37 -0
- package/src/appium-recorder/flow-appearance.ts +12 -1
- package/src/appium-recorder/flow-copy.ts +1 -0
- package/src/appium-recorder/flow-graph.ts +7 -4
- package/src/appium-recorder/flow-labels.ts +9 -5
- package/src/appium-recorder/flow-merge.ts +1 -1
- package/src/appium-recorder/flow-unmerge.ts +2 -2
- package/src/appium-recorder/report-summary.ts +60 -0
- package/src/appium-recorder/types.ts +6 -0
- package/src/components/config/AiDeduplicationSettings.vue +60 -0
- package/src/components/config/AiPromptPresetsSettings.vue +63 -0
- package/src/components/config/AppiumConfigPanel.vue +76 -3
- package/src/components/config/AppiumModelSettings.vue +45 -0
- package/src/components/config/BaseConfigPanel.vue +20 -8
- package/src/components/config/FlowAppearanceSettings.vue +51 -32
- package/src/components/config/MidsceneConfigPanel.vue +84 -86
- package/src/components/config/ReplayReportSummarySettings.vue +118 -0
- package/src/components/device/DevicePreviewPanel.vue +18 -3
- package/src/config/midscene-model-presets.ts +37 -9
- package/src/config/use-appium-auto-save.ts +62 -0
- package/src/pages/ConfigPage.vue +8 -6
- package/src/style.css +5 -55
- package/src/types.ts +4 -1
- package/src/ui-refresh.css +17 -4
package/server/http-api.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveReportSummary } from '../src/appium-recorder/report-summary';
|
|
1
2
|
// The Midscene Admin HTTP API — extracted from vite.config.ts so the exact
|
|
2
3
|
// same middleware serves the standalone dev/preview flow AND the DSH plugin.
|
|
3
4
|
// It is a Connect-style handler: (req, res, next). Callers that have no next
|
|
@@ -716,9 +717,17 @@ export function createApiMiddleware() {
|
|
|
716
717
|
return;
|
|
717
718
|
}
|
|
718
719
|
|
|
719
|
-
if (req.url === '/api/config' && req.method === 'POST') {
|
|
720
|
+
if ((req.url === '/api/config' || req.url === '/api/config/appium') && req.method === 'POST') {
|
|
720
721
|
try {
|
|
721
|
-
const
|
|
722
|
+
const parsed = await readBody<Partial<AppConfig>>(req);
|
|
723
|
+
if (req.url === '/api/config/appium' && (!parsed.appium || typeof parsed.appium !== 'object' || Array.isArray(parsed.appium))) {
|
|
724
|
+
throw new ConfigValidationError('Appium 配置无效');
|
|
725
|
+
}
|
|
726
|
+
const current = loadConfig();
|
|
727
|
+
const config = req.url === '/api/config/appium'
|
|
728
|
+
? { ...current, appium: { ...current.appium, ...parsed.appium, model: current.appium.model, promptOptimizer: current.appium.promptOptimizer,
|
|
729
|
+
reportSummary: { ...resolveReportSummary(current.appium.reportSummary), ...parsed.appium?.reportSummary } } }
|
|
730
|
+
: { ...current, runtime: parsed.runtime ?? current.runtime };
|
|
722
731
|
saveConfig(config);
|
|
723
732
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
724
733
|
res.end(JSON.stringify({ success: true }));
|
|
@@ -1089,9 +1098,10 @@ export function createApiMiddleware() {
|
|
|
1089
1098
|
if (req.url === '/api/test-model' && req.method === 'POST') {
|
|
1090
1099
|
try {
|
|
1091
1100
|
const parsed = await readBody<{
|
|
1092
|
-
|
|
1101
|
+
save?: boolean;
|
|
1102
|
+
modelKey?: 'midscene' | 'scriptOptimizer' | 'appium' | 'promptOptimizer';
|
|
1093
1103
|
model?: {
|
|
1094
|
-
provider?: '
|
|
1104
|
+
provider?: import('../src/config/midscene-model-presets').MidsceneModelProvider;
|
|
1095
1105
|
baseUrl?: string;
|
|
1096
1106
|
apiKey?: string;
|
|
1097
1107
|
name?: string;
|
|
@@ -1100,6 +1110,13 @@ export function createApiMiddleware() {
|
|
|
1100
1110
|
}>(req);
|
|
1101
1111
|
const model = parsed.model || {};
|
|
1102
1112
|
|
|
1113
|
+
if (parsed.save && !['midscene', 'scriptOptimizer', 'appium', 'promptOptimizer'].includes(parsed.modelKey || '')) {
|
|
1114
|
+
throw new ConfigValidationError('请选择有效的模型配置');
|
|
1115
|
+
}
|
|
1116
|
+
if (parsed.save && parsed.modelKey === 'midscene' && !model.family?.trim()) {
|
|
1117
|
+
throw new ConfigValidationError('请填写 Model Family');
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1103
1120
|
const result = await testModelConnection({
|
|
1104
1121
|
provider: model.provider,
|
|
1105
1122
|
baseUrl: model.baseUrl || '',
|
|
@@ -1107,8 +1124,17 @@ export function createApiMiddleware() {
|
|
|
1107
1124
|
name: model.name || '',
|
|
1108
1125
|
family: model.family || '',
|
|
1109
1126
|
});
|
|
1127
|
+
if (parsed.save) {
|
|
1128
|
+
if (!result.ok) throw new Error('模型测试失败,未保存配置');
|
|
1129
|
+
const key = parsed.modelKey!;
|
|
1130
|
+
// 测试期间其他配置可能已保存,此时重新读取,只更新已通过测试的模型。
|
|
1131
|
+
const current = loadConfig();
|
|
1132
|
+
if (key === 'promptOptimizer') {
|
|
1133
|
+
saveConfig({ ...current, appium: { ...current.appium, promptOptimizer: { model: { baseUrl: model.baseUrl || '', apiKey: model.apiKey || '', name: model.name || '' } } } });
|
|
1134
|
+
} else saveConfig({ ...current, [key]: { ...current[key], model: { ...current[key].model, ...model } } });
|
|
1135
|
+
}
|
|
1110
1136
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
1111
|
-
res.end(JSON.stringify(result));
|
|
1137
|
+
res.end(JSON.stringify({ ...result, saved: parsed.save === true }));
|
|
1112
1138
|
} catch (error) {
|
|
1113
1139
|
res.statusCode = 500;
|
|
1114
1140
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
package/server/model-tester.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import OpenAI from 'openai';
|
|
3
|
+
import type { MidsceneModelProvider } from '../src/config/midscene-model-presets';
|
|
3
4
|
|
|
4
5
|
type ModelInput = {
|
|
5
|
-
provider?:
|
|
6
|
+
provider?: MidsceneModelProvider;
|
|
6
7
|
baseUrl: string;
|
|
7
8
|
apiKey: string;
|
|
8
9
|
name: string;
|
package/src/App.vue
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { resolveReportSummary } from './appium-recorder/report-summary';
|
|
2
3
|
import { isAiRecognitionModelConfigured } from './appium-recorder/ai-recognition';
|
|
4
|
+
import { resolveAiDeduplication } from './appium-recorder/ai-deduplication';
|
|
5
|
+
import { resolveAiPromptPresets } from './appium-recorder/ai-prompt-presets';
|
|
3
6
|
import { computed, onMounted, onUnmounted, reactive, ref, shallowRef, watch } from 'vue';
|
|
4
7
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
5
8
|
import {
|
|
@@ -27,13 +30,14 @@ import {
|
|
|
27
30
|
type ScriptStep,
|
|
28
31
|
} from './script-generator';
|
|
29
32
|
import {
|
|
30
|
-
|
|
33
|
+
selectMidsceneModelProvider,
|
|
31
34
|
type MidsceneModelProvider,
|
|
32
35
|
} from './config/midscene-model-presets';
|
|
33
36
|
import * as api from './api';
|
|
37
|
+
import { useAppiumAutoSave } from './config/use-appium-auto-save';
|
|
34
38
|
import AutomationPage from './pages/AutomationPage.vue';
|
|
35
39
|
import AppiumPage from './appium-recorder/AppiumPage.vue';
|
|
36
|
-
import {
|
|
40
|
+
import { normalizeFlowBackground, normalizeFlowLineColor } from './appium-recorder/flow-appearance';
|
|
37
41
|
import ConfigPage from './pages/ConfigPage.vue';
|
|
38
42
|
import GeneratorPage from './pages/GeneratorPage.vue';
|
|
39
43
|
import type {
|
|
@@ -102,6 +106,7 @@ const modelTestStatus = reactive({
|
|
|
102
106
|
midscene: '',
|
|
103
107
|
scriptOptimizer: '',
|
|
104
108
|
appium: '',
|
|
109
|
+
promptOptimizer: '',
|
|
105
110
|
});
|
|
106
111
|
const aiRecognitionModelConfigured = shallowRef(false);
|
|
107
112
|
const actionDialog = reactive({
|
|
@@ -153,7 +158,7 @@ const appPresetForm = reactive({
|
|
|
153
158
|
});
|
|
154
159
|
|
|
155
160
|
const configForm = reactive<ConfigForm>({
|
|
156
|
-
appium: { model: { baseUrl: '', apiKey: '', name: '' } },
|
|
161
|
+
appium: { reportSummary: resolveReportSummary(), model: { baseUrl: '', apiKey: '', name: '' }, promptOptimizer: { model: { baseUrl: '', apiKey: '', name: '' } }, aiDeduplication: resolveAiDeduplication(), screenshotReport: false },
|
|
157
162
|
runtime: {
|
|
158
163
|
androidSdkPath: '',
|
|
159
164
|
reportOutputPath: '',
|
|
@@ -183,6 +188,12 @@ const lastCustomMidsceneModel = reactive({
|
|
|
183
188
|
family: '',
|
|
184
189
|
});
|
|
185
190
|
|
|
191
|
+
const appiumAutoSave = useAppiumAutoSave(configForm, async value => {
|
|
192
|
+
await api.saveAppiumConfig(value);
|
|
193
|
+
}, error => {
|
|
194
|
+
void ElMessageBox.alert(error instanceof Error ? error.message : 'Appium 配置自动保存失败', '自动保存失败', { type: 'error' }).catch(() => {});
|
|
195
|
+
});
|
|
196
|
+
|
|
186
197
|
const menuItems = [
|
|
187
198
|
{ key: 'generator', label: '测试脚本生成', icon: Operation },
|
|
188
199
|
{ key: 'automation', label: '自动化测试', icon: Monitor },
|
|
@@ -662,6 +673,14 @@ const loadConfig = async () => {
|
|
|
662
673
|
Object.assign(configForm.midscene.env, payload.midscene.env || {});
|
|
663
674
|
Object.assign(configForm.scriptOptimizer.model, payload.scriptOptimizer.model);
|
|
664
675
|
Object.assign(configForm.appium.model, payload.appium?.model || { baseUrl: '', apiKey: '', name: '' });
|
|
676
|
+
Object.assign(configForm.appium.promptOptimizer!.model, payload.appium?.promptOptimizer?.model || { baseUrl: '', apiKey: '', name: '' });
|
|
677
|
+
configForm.appium.screenshotReport = payload.appium?.screenshotReport === true;
|
|
678
|
+
configForm.appium.reportSummary = resolveReportSummary(payload.appium?.reportSummary);
|
|
679
|
+
configForm.appium.aiDeduplication = resolveAiDeduplication(payload.appium?.aiDeduplication);
|
|
680
|
+
configForm.appium.aiPromptPresets = resolveAiPromptPresets(payload.appium?.aiPromptPresets);
|
|
681
|
+
configForm.appium.flowBackgroundColor = normalizeFlowBackground(payload.appium?.flowBackgroundColor);
|
|
682
|
+
configForm.appium.flowLineColor = normalizeFlowLineColor(payload.appium?.flowLineColor);
|
|
683
|
+
appiumAutoSave.initialize();
|
|
665
684
|
aiRecognitionModelConfigured.value = isAiRecognitionModelConfigured(configForm.appium.model);
|
|
666
685
|
if (configForm.midscene.model.provider !== 'codex') {
|
|
667
686
|
rememberCustomMidsceneModel();
|
|
@@ -677,15 +696,10 @@ const rememberCustomMidsceneModel = () => {
|
|
|
677
696
|
};
|
|
678
697
|
|
|
679
698
|
const updateMidsceneModelProvider = (provider: MidsceneModelProvider) => {
|
|
680
|
-
if (provider
|
|
699
|
+
if (configForm.midscene.model.provider !== 'codex') {
|
|
681
700
|
rememberCustomMidsceneModel();
|
|
682
|
-
Object.assign(configForm.midscene.model, codexMidsceneModel);
|
|
683
|
-
} else {
|
|
684
|
-
Object.assign(configForm.midscene.model, {
|
|
685
|
-
provider: 'custom',
|
|
686
|
-
...lastCustomMidsceneModel,
|
|
687
|
-
});
|
|
688
701
|
}
|
|
702
|
+
Object.assign(configForm.midscene.model, selectMidsceneModelProvider(provider, configForm.midscene.model, lastCustomMidsceneModel));
|
|
689
703
|
modelTestStatus.midscene = '';
|
|
690
704
|
};
|
|
691
705
|
|
|
@@ -699,22 +713,15 @@ const getMidsceneModelConfigError = () => {
|
|
|
699
713
|
if (model.provider !== 'codex' && !model.apiKey.trim()) missing.push('API Key');
|
|
700
714
|
|
|
701
715
|
if (!missing.length) return '';
|
|
702
|
-
return `Midscene 模型配置不完整:缺少 ${missing.join('、')}。请在“参数配置 > Midscene配置 > Midscene 模型”中选择“使用 Codex
|
|
716
|
+
return `Midscene 模型配置不完整:缺少 ${missing.join('、')}。请在“参数配置 > Midscene配置 > Midscene 模型”中选择“使用 Codex”,或补齐自定义提供方后测试并保存。`;
|
|
703
717
|
};
|
|
704
718
|
|
|
705
719
|
const saveModelConfig = async () => {
|
|
706
|
-
if (!isHexColor(configForm.appium.flowBackgroundColor ?? DEFAULT_FLOW_BACKGROUND)) {
|
|
707
|
-
ElMessage.warning('流程背景色格式错误,请输入 #RGB 或 #RRGGBB');
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
720
|
isSavingModelConfig.value = true;
|
|
711
721
|
errorMessage.value = '';
|
|
712
722
|
openActionDialog('保存参数配置');
|
|
713
723
|
try {
|
|
714
|
-
|
|
715
|
-
const snapshot = JSON.parse(JSON.stringify(configForm)) as ConfigForm;
|
|
716
|
-
await api.saveConfig(snapshot);
|
|
717
|
-
aiRecognitionModelConfigured.value = isAiRecognitionModelConfigured(snapshot.appium.model);
|
|
724
|
+
await api.saveConfig({ runtime: { ...configForm.runtime } });
|
|
718
725
|
closeActionDialog();
|
|
719
726
|
ElMessage.success('参数配置已保存');
|
|
720
727
|
} catch (error) {
|
|
@@ -744,9 +751,10 @@ const resetAppPresetForm = () => {
|
|
|
744
751
|
};
|
|
745
752
|
|
|
746
753
|
const saveAppPreset = async () => {
|
|
754
|
+
if (isSavingAppPreset.value) return;
|
|
755
|
+
const editing = Boolean(appPresetForm.id);
|
|
747
756
|
isSavingAppPreset.value = true;
|
|
748
757
|
errorMessage.value = '';
|
|
749
|
-
openActionDialog('保存 App 配置');
|
|
750
758
|
try {
|
|
751
759
|
const payload = await api.saveAppPreset({
|
|
752
760
|
id: appPresetForm.id || undefined,
|
|
@@ -758,11 +766,10 @@ const saveAppPreset = async () => {
|
|
|
758
766
|
form.appPresetId = payload.app.id;
|
|
759
767
|
}
|
|
760
768
|
resetAppPresetForm();
|
|
761
|
-
|
|
769
|
+
ElMessage.success(editing ? 'App 配置已更新' : 'App 已添加');
|
|
762
770
|
} catch (error) {
|
|
763
771
|
const message = error instanceof Error ? error.message : 'App 配置保存失败';
|
|
764
|
-
|
|
765
|
-
failActionDialog(message);
|
|
772
|
+
void ElMessageBox.alert(message, 'App 配置保存失败', { type: 'error' }).catch(() => {});
|
|
766
773
|
} finally {
|
|
767
774
|
isSavingAppPreset.value = false;
|
|
768
775
|
}
|
|
@@ -785,22 +792,33 @@ const removeAppPreset = async (id: string) => {
|
|
|
785
792
|
if (form.appPresetId === id) {
|
|
786
793
|
form.appPresetId = '';
|
|
787
794
|
}
|
|
795
|
+
if (appPresetForm.id === id) resetAppPresetForm();
|
|
788
796
|
} catch (error) {
|
|
789
|
-
|
|
797
|
+
void ElMessageBox.alert(error instanceof Error ? error.message : 'App 配置删除失败', '删除失败', { type: 'error' }).catch(() => {});
|
|
790
798
|
}
|
|
791
799
|
};
|
|
792
800
|
|
|
793
|
-
const testModel = async (key: 'midscene' | 'scriptOptimizer' | 'appium') => {
|
|
801
|
+
const testModel = async (key: 'midscene' | 'scriptOptimizer' | 'appium' | 'promptOptimizer') => {
|
|
802
|
+
if (testingModelKey.value) return;
|
|
794
803
|
testingModelKey.value = key;
|
|
795
804
|
errorMessage.value = '';
|
|
796
805
|
modelTestStatus[key] = '';
|
|
797
|
-
const model =
|
|
806
|
+
const model = { ...(key === 'promptOptimizer'
|
|
807
|
+
? configForm.appium.promptOptimizer!.model
|
|
808
|
+
: configForm[key].model) };
|
|
798
809
|
|
|
799
810
|
try {
|
|
800
|
-
const payload = await api.testModel({ modelKey: key, model });
|
|
801
|
-
|
|
811
|
+
const payload = await api.testModel({ modelKey: key, model, save: true });
|
|
812
|
+
if (!payload.saved) throw new Error('模型未保存,请重试');
|
|
813
|
+
const successMessage = `测试通过,配置已保存${payload.content ? `:${payload.content}` : ''}`;
|
|
814
|
+
modelTestStatus[key] = '';
|
|
815
|
+
if (key === 'appium') aiRecognitionModelConfigured.value = isAiRecognitionModelConfigured(model);
|
|
816
|
+
void ElMessageBox.alert(successMessage, '模型测试成功', {
|
|
817
|
+
type: 'success',
|
|
818
|
+
confirmButtonText: '确定',
|
|
819
|
+
}).catch(() => {});
|
|
802
820
|
} catch (error) {
|
|
803
|
-
modelTestStatus[key] = error instanceof Error ? error.message : '测试失败'
|
|
821
|
+
modelTestStatus[key] = `未保存:${error instanceof Error ? error.message : '测试失败'}`;
|
|
804
822
|
} finally {
|
|
805
823
|
testingModelKey.value = '';
|
|
806
824
|
}
|
|
@@ -1485,7 +1503,7 @@ onUnmounted(() => {
|
|
|
1485
1503
|
'page-body--appium': activeMenu === 'appium',
|
|
1486
1504
|
}"
|
|
1487
1505
|
>
|
|
1488
|
-
<el-alert v-if="activeMenu !== 'appium' && errorMessage" type="error" :closable="false" show-icon class="page-alert">
|
|
1506
|
+
<el-alert v-if="activeMenu !== 'appium' && activeMenu !== 'config' && errorMessage" type="error" :closable="false" show-icon class="page-alert">
|
|
1489
1507
|
<template #title>{{ errorMessage }}</template>
|
|
1490
1508
|
</el-alert>
|
|
1491
1509
|
|
|
@@ -1526,11 +1544,13 @@ onUnmounted(() => {
|
|
|
1526
1544
|
:is-saving-model-config="isSavingModelConfig"
|
|
1527
1545
|
:is-saving-app-preset="isSavingAppPreset"
|
|
1528
1546
|
:model-test-status="modelTestStatus"
|
|
1547
|
+
:appium-save-status="appiumAutoSave.status.value"
|
|
1529
1548
|
@test-model="testModel"
|
|
1530
1549
|
@save-model-config="saveModelConfig"
|
|
1531
1550
|
@save-app-preset="saveAppPreset"
|
|
1532
1551
|
@edit-app-preset="editAppPreset"
|
|
1533
1552
|
@delete-app-preset="removeAppPreset"
|
|
1553
|
+
@cancel-app-preset-edit="resetAppPresetForm"
|
|
1534
1554
|
@update-midscene-model-provider="updateMidsceneModelProvider"
|
|
1535
1555
|
/>
|
|
1536
1556
|
|
|
@@ -1569,7 +1589,10 @@ onUnmounted(() => {
|
|
|
1569
1589
|
:active="activeMenu === 'appium'"
|
|
1570
1590
|
:app-presets="appPresets"
|
|
1571
1591
|
:ai-recognition-model-configured="aiRecognitionModelConfigured"
|
|
1592
|
+
:ai-prompt-presets="configForm.appium.aiPromptPresets"
|
|
1593
|
+
:report-summary="configForm.appium.reportSummary"
|
|
1572
1594
|
:flow-background-color="configForm.appium.flowBackgroundColor"
|
|
1595
|
+
:flow-line-color="configForm.appium.flowLineColor"
|
|
1573
1596
|
:device-actions="deviceActions"
|
|
1574
1597
|
:playground-available="playgroundAvailable"
|
|
1575
1598
|
:playground-device-id="playgroundDeviceId"
|
package/src/api.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ScriptStep } from './script-generator';
|
|
2
|
+
import type { MidsceneModelProvider } from './config/midscene-model-presets';
|
|
2
3
|
import type {
|
|
3
4
|
AndroidDevice,
|
|
4
5
|
AppPreset,
|
|
@@ -109,16 +110,21 @@ export async function getConfig() {
|
|
|
109
110
|
return readJson<ConfigForm>(response);
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
//
|
|
113
|
-
export function saveConfig(input: ConfigForm) {
|
|
113
|
+
// 运行配置独立保存,不携带尚未测试的模型草稿。
|
|
114
|
+
export function saveConfig(input: Pick<ConfigForm, 'runtime'>) {
|
|
114
115
|
return postJson<{ success?: boolean } | ConfigForm>(`${APP_BASE}/api/config`, input);
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
export function saveAppiumConfig(appium: Omit<ConfigForm['appium'], 'model' | 'promptOptimizer'>) {
|
|
119
|
+
return postJson<{ success: boolean }>(`${APP_BASE}/api/config/appium`, { appium });
|
|
120
|
+
}
|
|
121
|
+
|
|
117
122
|
// 测试指定模型配置是否可用,通常用于保存前验证 baseUrl/apiKey/model。
|
|
118
123
|
export function testModel(input: {
|
|
119
|
-
|
|
124
|
+
save?: boolean;
|
|
125
|
+
modelKey: 'midscene' | 'scriptOptimizer' | 'appium' | 'promptOptimizer';
|
|
120
126
|
model: {
|
|
121
|
-
provider?:
|
|
127
|
+
provider?: MidsceneModelProvider;
|
|
122
128
|
baseUrl: string;
|
|
123
129
|
apiKey: string;
|
|
124
130
|
name: string;
|
|
@@ -126,6 +132,7 @@ export function testModel(input: {
|
|
|
126
132
|
};
|
|
127
133
|
}) {
|
|
128
134
|
return postJson<{
|
|
135
|
+
saved?: boolean;
|
|
129
136
|
content?: string;
|
|
130
137
|
durationMs?: number;
|
|
131
138
|
usage?: {
|