android-midscene-automation 0.1.42 → 0.2.1

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.
Files changed (58) hide show
  1. package/AI/350/257/206/345/210/253/346/223/215/344/275/234/350/257/264/346/230/216.md +442 -0
  2. package/CHANGELOG.md +12 -2
  3. package/FAQ.md +8 -8
  4. package/README.md +156 -19
  5. package/USAGE.md +134 -9
  6. package/package.json +3 -2
  7. package/server/appium-auto-save.test.ts +2 -1
  8. package/server/appium-config.test.ts +48 -1
  9. package/server/appium-recorder/ai-recognition.test.ts +27 -0
  10. package/server/appium-recorder/ai-recognition.ts +16 -4
  11. package/server/appium-recorder/appium-runner.ts +32 -2
  12. package/server/appium-recorder/bounded-loop.test.ts +55 -0
  13. package/server/appium-recorder/bounded-loop.ts +12 -1
  14. package/server/appium-recorder/prompt-optimizer.ts +57 -0
  15. package/server/appium-recorder/report-summary.test.ts +78 -0
  16. package/server/appium-recorder/report-summary.ts +60 -0
  17. package/server/appium-recorder/report.ts +31 -2
  18. package/server/appium-recorder/repository.ts +8 -2
  19. package/server/appium-recorder/routes.ts +53 -3
  20. package/server/config.ts +17 -2
  21. package/server/http-api.ts +8 -4
  22. package/src/App.vue +34 -5
  23. package/src/api.ts +2 -2
  24. package/src/appium-recorder/AppiumPage.vue +126 -62
  25. package/src/appium-recorder/action-descriptions.ts +1 -0
  26. package/src/appium-recorder/ai-recognition.ts +7 -0
  27. package/src/appium-recorder/api.ts +22 -2
  28. package/src/appium-recorder/bounded-loop.ts +9 -1
  29. package/src/appium-recorder/components/AiRecognitionSettings.vue +57 -6
  30. package/src/appium-recorder/components/BreakLoopSettings.vue +19 -3
  31. package/src/appium-recorder/components/ComponentTree.vue +5 -5
  32. package/src/appium-recorder/components/CoordinatePointPicker.vue +23 -0
  33. package/src/appium-recorder/components/FlowActionMenu.vue +50 -20
  34. package/src/appium-recorder/components/FlowStepEditor.vue +10 -2
  35. package/src/appium-recorder/components/NestedConditionBranches.vue +18 -49
  36. package/src/appium-recorder/components/RecordedSteps.vue +16 -5
  37. package/src/appium-recorder/coordinate-preview.ts +37 -0
  38. package/src/appium-recorder/flow-copy.ts +1 -0
  39. package/src/appium-recorder/flow-graph.ts +5 -2
  40. package/src/appium-recorder/flow-labels.ts +4 -2
  41. package/src/appium-recorder/flow-merge.ts +1 -1
  42. package/src/appium-recorder/flow-unmerge.ts +2 -2
  43. package/src/appium-recorder/report-summary.ts +60 -0
  44. package/src/appium-recorder/types.ts +4 -1
  45. package/src/components/config/AiDeduplicationSettings.vue +5 -4
  46. package/src/components/config/AiPromptPresetsSettings.vue +64 -17
  47. package/src/components/config/AppiumConfigPanel.vue +66 -38
  48. package/src/components/config/AppiumModelSettings.vue +45 -0
  49. package/src/components/config/FlowAppearanceSettings.vue +0 -1
  50. package/src/components/config/ReplayReportSummarySettings.vue +118 -0
  51. package/src/components/device/DevicePreviewPanel.vue +18 -3
  52. package/src/components/help/HelpCenterDialog.vue +160 -0
  53. package/src/components/help/MarkdownDocument.vue +139 -0
  54. package/src/config/use-appium-auto-save.ts +12 -4
  55. package/src/pages/ConfigPage.vue +4 -2
  56. package/src/style.css +37 -45
  57. package/src/types.ts +2 -1
  58. package//346/265/201/347/250/213/350/212/202/347/202/271/346/223/215/344/275/234/350/257/264/346/230/216.md +408 -0
@@ -0,0 +1,160 @@
1
+ <script setup lang="ts">
2
+ import { computed, shallowRef } from 'vue';
3
+ import { InfoFilled, QuestionFilled, Tools } from '@element-plus/icons-vue';
4
+ import changelog from '../../../CHANGELOG.md?raw';
5
+ import faq from '../../../FAQ.md?raw';
6
+ import aiRecognitionGuide from '../../../AI识别操作说明.md?raw';
7
+ import appiumGuide from '../../../流程节点操作说明.md?raw';
8
+ import operationGuide from '../../../USAGE.md?raw';
9
+ import packageText from '../../../package.json?raw';
10
+ import MarkdownDocument from './MarkdownDocument.vue';
11
+
12
+ type PackageInfo = { version?: string; dependencies?: Record<string, string> };
13
+ type OperationSection = { id: string; title: string; content: string };
14
+
15
+ const visible = defineModel<boolean>({ required: true });
16
+ const activeTab = shallowRef('about');
17
+ type OperationType = 'midscene' | 'appium' | 'aiRecognition' | 'faq';
18
+
19
+ const operationType = shallowRef<OperationType>('midscene');
20
+ const activeOperation = shallowRef('');
21
+ const packageInfo = JSON.parse(packageText) as PackageInfo;
22
+ const dependencyVersion = (name: string) => packageInfo.dependencies?.[name]?.match(/(\d+\.\d+\.\d+)/)?.[1] ?? '未知';
23
+ const appVersion = packageInfo.version ?? '未知';
24
+ const midsceneVersion = dependencyVersion('@midscene/core');
25
+
26
+ const technologies = computed(() => [
27
+ { name: 'Vue', version: dependencyVersion('vue'), description: '界面与交互框架' },
28
+ { name: 'Element Plus', version: dependencyVersion('element-plus'), description: '界面组件库' },
29
+ { name: 'Appium', version: '2.x', description: 'Android 原生自动化与回放' },
30
+ { name: 'Midscene', version: midsceneVersion, description: 'AI 驱动的界面理解与自动化' },
31
+ { name: 'Vue Flow', version: dependencyVersion('@vue-flow/core'), description: 'Appium 流程画布' },
32
+ { name: 'OpenCV.js', version: dependencyVersion('@techstark/opencv-js'), description: '本地图像检测' },
33
+ { name: 'Vite', version: dependencyVersion('vite'), description: '开发与构建工具' },
34
+ { name: 'TypeScript', version: dependencyVersion('typescript'), description: '类型与工程开发语言' },
35
+ ]);
36
+
37
+ const extractOperationSections = (startHeading: string, endHeading: string) => {
38
+ const start = operationGuide.indexOf(`## ${startHeading}`);
39
+ const end = operationGuide.indexOf(`## ${endHeading}`, start + 1);
40
+ if (start < 0) return [];
41
+ const source = operationGuide.slice(start, end < 0 ? undefined : end);
42
+ const matches = [...source.matchAll(/^###\s+(.+)$/gm)];
43
+ return matches.map<OperationSection>((match, index) => ({
44
+ id: `${startHeading}-${index}`,
45
+ title: match[1]!.trim(),
46
+ content: source.slice(match.index! + match[0].length, matches[index + 1]?.index).trim(),
47
+ }));
48
+ };
49
+
50
+ const extractDocumentSections = (source: string, prefix: string) => {
51
+ const matches = [...source.matchAll(/^##\s+(.+)$/gm)];
52
+ return matches.map<OperationSection>((match, index) => ({
53
+ id: `${prefix}-${index}`,
54
+ title: match[1]!.trim(),
55
+ content: source.slice(match.index! + match[0].length, matches[index + 1]?.index).trim(),
56
+ }));
57
+ };
58
+
59
+ const midsceneOperations = computed(() => extractOperationSections('Midscene 操作说明', 'Appium 使用前准备'));
60
+ const appiumOperations = computed(() => extractDocumentSections(appiumGuide, 'appium'));
61
+ const aiRecognitionOperations = computed(() => extractDocumentSections(aiRecognitionGuide, 'ai-recognition'));
62
+ const faqOperations = computed(() => extractDocumentSections(faq, 'faq'));
63
+ const visibleOperations = computed(() => {
64
+ if (operationType.value === 'midscene') return midsceneOperations.value;
65
+ if (operationType.value === 'appium') return appiumOperations.value;
66
+ if (operationType.value === 'aiRecognition') return aiRecognitionOperations.value;
67
+ return faqOperations.value;
68
+ });
69
+
70
+ const switchOperationType = (value: OperationType) => {
71
+ operationType.value = value;
72
+ activeOperation.value = '';
73
+ };
74
+ </script>
75
+
76
+ <template>
77
+ <el-dialog
78
+ v-model="visible"
79
+ title="帮助与版本"
80
+ width="min(960px, calc(100vw - 48px))"
81
+ class="help-center-dialog"
82
+ top="5vh"
83
+ draggable
84
+ destroy-on-close
85
+ >
86
+ <el-tabs v-model="activeTab" class="help-center">
87
+ <el-tab-pane name="about">
88
+ <template #label><el-icon><InfoFilled /></el-icon><span>关于</span></template>
89
+ <el-scrollbar height="min(620px, calc(100vh - 230px))">
90
+ <div class="help-center__content">
91
+ <div class="help-center__versions">
92
+ <div><span>当前版本</span><strong>v{{ appVersion }}</strong></div>
93
+ <div><span>Midscene 版本</span><strong>v{{ midsceneVersion }}</strong></div>
94
+ </div>
95
+ <h3 class="help-center__section-title">使用技术</h3>
96
+ <div class="help-center__technology-grid">
97
+ <div v-for="item in technologies" :key="item.name" class="help-center__technology">
98
+ <div><strong>{{ item.name }}</strong><el-tag size="small" effect="plain">{{ item.version }}</el-tag></div>
99
+ <p>{{ item.description }}</p>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ </el-scrollbar>
104
+ </el-tab-pane>
105
+ <el-tab-pane name="guide">
106
+ <template #label><el-icon><QuestionFilled /></el-icon><span>操作说明</span></template>
107
+ <el-scrollbar height="min(620px, calc(100vh - 230px))">
108
+ <div class="help-center__operations">
109
+ <el-radio-group :model-value="operationType" class="help-center__operation-types" @change="switchOperationType">
110
+ <el-radio-button value="midscene">Midscene</el-radio-button>
111
+ <el-radio-button value="appium">Appium</el-radio-button>
112
+ <el-radio-button value="aiRecognition">AI 识别</el-radio-button>
113
+ <el-radio-button value="faq">常见问题</el-radio-button>
114
+ </el-radio-group>
115
+ <el-collapse v-model="activeOperation" accordion class="help-center__operation-menu">
116
+ <el-collapse-item v-for="item in visibleOperations" :key="item.id" :name="item.id">
117
+ <template #title><span class="help-center__operation-title">{{ item.title }}</span></template>
118
+ <MarkdownDocument :content="item.content" />
119
+ </el-collapse-item>
120
+ </el-collapse>
121
+ </div>
122
+ </el-scrollbar>
123
+ </el-tab-pane>
124
+ <el-tab-pane name="changelog">
125
+ <template #label><el-icon><Tools /></el-icon><span>更新记录</span></template>
126
+ <el-scrollbar height="min(620px, calc(100vh - 230px))">
127
+ <div class="help-center__document"><MarkdownDocument :content="changelog" /></div>
128
+ </el-scrollbar>
129
+ </el-tab-pane>
130
+ </el-tabs>
131
+ </el-dialog>
132
+ </template>
133
+
134
+ <style scoped>
135
+ .help-center :deep(.el-tabs__header) { margin-bottom: 0; }
136
+ .help-center :deep(.el-tabs__item) { display: inline-flex; align-items: center; gap: 6px; }
137
+ .help-center__content,
138
+ .help-center__document { padding: 22px 6px 18px 2px; }
139
+ .help-center__versions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
140
+ .help-center__versions div { display: flex; align-items: center; justify-content: space-between; padding: 16px 18px; border: 1px solid #e2e8f0; border-radius: 10px; background: #fff; }
141
+ .help-center__versions span { color: #64748b; }
142
+ .help-center__versions strong { color: #1677ff; font-size: 18px; }
143
+ .help-center__section-title { margin: 24px 0 12px; color: #1f2937; font-size: 17px; }
144
+ .help-center__technology-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
145
+ .help-center__technology { padding: 14px 16px; border: 1px solid #e2e8f0; border-radius: 9px; background: #fff; }
146
+ .help-center__technology > div { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
147
+ .help-center__technology p { margin: 6px 0 0; color: #64748b; font-size: 13px; }
148
+ .help-center__operations { padding: 18px 6px 18px 2px; }
149
+ .help-center__operation-types { margin-bottom: 16px; }
150
+ .help-center__operation-menu { border-top: 1px solid #e5e7eb; }
151
+ .help-center__operation-title { color: #334155; font-size: 14px; font-weight: 600; }
152
+ .help-center__operation-menu :deep(.el-collapse-item__header) { min-height: 48px; padding: 0 14px; border-right: 1px solid #e5e7eb; border-left: 1px solid #e5e7eb; }
153
+ .help-center__operation-menu :deep(.el-collapse-item__wrap) { border-right: 1px solid #e5e7eb; border-left: 1px solid #e5e7eb; }
154
+ .help-center__operation-menu :deep(.el-collapse-item__content) { padding: 4px 18px 18px; }
155
+ :global(.help-center-dialog.el-dialog) { margin-top: 5vh; }
156
+ @media (max-width: 760px) {
157
+ .help-center__versions,
158
+ .help-center__technology-grid { grid-template-columns: 1fr; }
159
+ }
160
+ </style>
@@ -0,0 +1,139 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+
4
+ type DocumentBlock =
5
+ | { type: 'heading'; level: number; text: string }
6
+ | { type: 'paragraph'; text: string }
7
+ | { type: 'list'; ordered: boolean; items: string[] }
8
+ | { type: 'code'; text: string }
9
+ | { type: 'table'; headers: string[]; rows: string[][] };
10
+
11
+ const props = defineProps<{ content: string }>();
12
+
13
+ const cleanText = (value: string) => value
14
+ .replace(/\[([^\]]+)]\([^)]+\)/g, '$1')
15
+ .replace(/\*\*([^*]+)\*\*/g, '$1')
16
+ .replace(/`([^`]+)`/g, '$1')
17
+ .trim();
18
+
19
+ const splitTableRow = (line: string) => line
20
+ .replace(/^\||\|$/g, '')
21
+ .split('|')
22
+ .map(cell => cleanText(cell));
23
+
24
+ const blocks = computed<DocumentBlock[]>(() => {
25
+ const lines = props.content.replace(/\r\n/g, '\n').split('\n');
26
+ const result: DocumentBlock[] = [];
27
+ let index = 0;
28
+
29
+ while (index < lines.length) {
30
+ const line = lines[index] ?? '';
31
+ const trimmed = line.trim();
32
+ if (!trimmed) {
33
+ index += 1;
34
+ continue;
35
+ }
36
+
37
+ if (/^([-*_])\1{2,}$/.test(trimmed)) {
38
+ index += 1;
39
+ continue;
40
+ }
41
+
42
+ if (trimmed.startsWith('```')) {
43
+ const code: string[] = [];
44
+ index += 1;
45
+ while (index < lines.length && !lines[index]!.trim().startsWith('```')) {
46
+ code.push(lines[index]!);
47
+ index += 1;
48
+ }
49
+ result.push({ type: 'code', text: code.join('\n') });
50
+ index += 1;
51
+ continue;
52
+ }
53
+
54
+ const heading = /^(#{1,6})\s+(.+)$/.exec(trimmed);
55
+ if (heading) {
56
+ result.push({ type: 'heading', level: heading[1]!.length, text: cleanText(heading[2]!) });
57
+ index += 1;
58
+ continue;
59
+ }
60
+
61
+ if (trimmed.includes('|') && /^\s*\|?\s*:?-{3,}/.test(lines[index + 1] ?? '')) {
62
+ const headers = splitTableRow(trimmed);
63
+ const rows: string[][] = [];
64
+ index += 2;
65
+ while (index < lines.length && lines[index]!.includes('|') && lines[index]!.trim()) {
66
+ rows.push(splitTableRow(lines[index]!));
67
+ index += 1;
68
+ }
69
+ result.push({ type: 'table', headers, rows });
70
+ continue;
71
+ }
72
+
73
+ const listMatch = /^(?:[-*]\s+|(\d+)\.\s+)(.+)$/.exec(trimmed);
74
+ if (listMatch) {
75
+ const ordered = Boolean(listMatch[1]);
76
+ const items: string[] = [];
77
+ while (index < lines.length) {
78
+ const item = /^(?:[-*]\s+|(\d+)\.\s+)(.+)$/.exec(lines[index]!.trim());
79
+ if (!item || Boolean(item[1]) !== ordered) break;
80
+ items.push(cleanText(item[2]!));
81
+ index += 1;
82
+ }
83
+ result.push({ type: 'list', ordered, items });
84
+ continue;
85
+ }
86
+
87
+ const paragraph: string[] = [];
88
+ while (index < lines.length) {
89
+ const next = lines[index]!.trim();
90
+ if (!next || /^(#{1,6})\s+/.test(next) || next.startsWith('```') || /^(?:[-*]\s+|\d+\.\s+)/.test(next)) break;
91
+ if (next.includes('|') && /^\s*\|?\s*:?-{3,}/.test(lines[index + 1] ?? '')) break;
92
+ paragraph.push(cleanText(next));
93
+ index += 1;
94
+ }
95
+ result.push({ type: 'paragraph', text: paragraph.join(' ') });
96
+ }
97
+
98
+ return result;
99
+ });
100
+ </script>
101
+
102
+ <template>
103
+ <article class="markdown-document">
104
+ <template v-for="(block, index) in blocks" :key="`${block.type}-${index}`">
105
+ <h1 v-if="block.type === 'heading' && block.level === 1" class="markdown-document__h1">{{ block.text }}</h1>
106
+ <h2 v-else-if="block.type === 'heading' && block.level === 2" class="markdown-document__h2">{{ block.text }}</h2>
107
+ <h3 v-else-if="block.type === 'heading'" class="markdown-document__h3">{{ block.text }}</h3>
108
+ <p v-else-if="block.type === 'paragraph'" class="markdown-document__paragraph">{{ block.text }}</p>
109
+ <ol v-else-if="block.type === 'list' && block.ordered" class="markdown-document__list">
110
+ <li v-for="item in block.items" :key="item">{{ item }}</li>
111
+ </ol>
112
+ <ul v-else-if="block.type === 'list'" class="markdown-document__list">
113
+ <li v-for="item in block.items" :key="item">{{ item }}</li>
114
+ </ul>
115
+ <pre v-else-if="block.type === 'code'" class="markdown-document__code"><code>{{ block.text }}</code></pre>
116
+ <div v-else-if="block.type === 'table'" class="markdown-document__table-wrap">
117
+ <table class="markdown-document__table">
118
+ <thead><tr><th v-for="header in block.headers" :key="header">{{ header }}</th></tr></thead>
119
+ <tbody><tr v-for="(row, rowIndex) in block.rows" :key="rowIndex"><td v-for="(cell, cellIndex) in row" :key="cellIndex">{{ cell }}</td></tr></tbody>
120
+ </table>
121
+ </div>
122
+ </template>
123
+ </article>
124
+ </template>
125
+
126
+ <style scoped>
127
+ .markdown-document { color: #475569; font-size: 14px; line-height: 1.75; }
128
+ .markdown-document__h1 { margin: 0 0 18px; color: #1f2937; font-size: 24px; }
129
+ .markdown-document__h2 { margin: 24px 0 10px; color: #1f2937; font-size: 19px; }
130
+ .markdown-document__h3 { margin: 18px 0 8px; color: #334155; font-size: 16px; }
131
+ .markdown-document__paragraph { margin: 8px 0; }
132
+ .markdown-document__list { display: grid; gap: 5px; margin: 8px 0; padding-left: 24px; }
133
+ .markdown-document__code { margin: 12px 0; padding: 14px; overflow: auto; border-radius: 8px; background: #f3f6fa; color: #334155; white-space: pre-wrap; }
134
+ .markdown-document__table-wrap { margin: 12px 0; overflow-x: auto; }
135
+ .markdown-document__table { width: 100%; border-collapse: collapse; }
136
+ .markdown-document__table th,
137
+ .markdown-document__table td { padding: 9px 12px; border: 1px solid #e2e8f0; text-align: left; vertical-align: top; }
138
+ .markdown-document__table th { background: #f8fafc; color: #334155; }
139
+ </style>
@@ -1,16 +1,24 @@
1
+ import { resolveReportSummary } from '../appium-recorder/report-summary';
1
2
  import { onScopeDispose, shallowRef, watch } from 'vue';
2
3
  import type { ConfigForm } from '../types';
3
4
  import { isHexColor } from '../appium-recorder/flow-appearance';
4
5
  import { resolveAiDeduplication } from '../appium-recorder/ai-deduplication';
5
6
 
6
7
  // 串行提交最新快照,较慢的旧请求不会在新请求后覆盖配置。
7
- export function useAppiumAutoSave(config: ConfigForm, save: (value: Omit<ConfigForm['appium'], 'model'>) => Promise<unknown>, onError: (error: unknown) => void) {
8
+ export function useAppiumAutoSave(config: ConfigForm, save: (value: Omit<ConfigForm['appium'], 'model' | 'promptOptimizer'>) => Promise<unknown>, onError: (error: unknown) => void) {
8
9
  const saving = shallowRef(false);
9
10
  const status = shallowRef('');
10
11
  let initialized = false, saved = '', pending: string | undefined;
11
12
  let timer: ReturnType<typeof setTimeout> | undefined;
12
13
  // 模型只能通过“测试并保存”提交,自动保存仅观察其余设置。
13
- function snapshotSettings() { const { model: _model, ...settings } = config.appium; return JSON.stringify(settings); }
14
+ function snapshotSettings() {
15
+ const { model: _model, promptOptimizer: _promptOptimizer, reportSummary, ...settings } = config.appium;
16
+ return JSON.stringify({ ...settings, ...(reportSummary ? { reportSummary: {
17
+ enabled: reportSummary.enabled,
18
+ prompt: reportSummary.prompt,
19
+ customPresets: reportSummary.customPresets,
20
+ } } : {}) });
21
+ }
14
22
  function initialize() { saved = snapshotSettings(); initialized = true; }
15
23
  async function flush() {
16
24
  if (saving.value || pending === undefined) return;
@@ -43,8 +51,8 @@ export function useAppiumAutoSave(config: ConfigForm, save: (value: Omit<ConfigF
43
51
  status.value = '颜色格式有误,尚未保存';
44
52
  return;
45
53
  }
46
- try { resolveAiDeduplication(value.aiDeduplication); }
47
- catch { status.value = '去重参数有误,尚未保存'; return; }
54
+ try { resolveAiDeduplication(value.aiDeduplication); resolveReportSummary(value.reportSummary); }
55
+ catch { status.value = '配置参数有误,尚未保存'; return; }
48
56
  pending = snapshot;
49
57
  status.value = '等待自动保存…';
50
58
  timer = setTimeout(() => { void flush(); }, 350);
@@ -14,11 +14,11 @@ defineProps<{
14
14
  isSavingModelConfig: boolean;
15
15
  isSavingAppPreset: boolean;
16
16
  appiumSaveStatus?: string;
17
- modelTestStatus: { midscene: string; scriptOptimizer: string; appium?: string };
17
+ modelTestStatus: { midscene: string; scriptOptimizer: string; appium?: string; promptOptimizer?: string };
18
18
  }>();
19
19
 
20
20
  defineEmits<{
21
- testModel: [key: 'midscene' | 'scriptOptimizer' | 'appium'];
21
+ testModel: [key: 'midscene' | 'scriptOptimizer' | 'appium' | 'promptOptimizer'];
22
22
  saveModelConfig: [];
23
23
  saveAppPreset: [];
24
24
  editAppPreset: [app: AppPreset];
@@ -112,6 +112,8 @@ function handleTabKeydown(event: KeyboardEvent, index: number) {
112
112
  :config-form="configForm"
113
113
  :testing-model-key="testingModelKey"
114
114
  :test-status="modelTestStatus.appium"
115
+ :prompt-optimizer-test-status="modelTestStatus.promptOptimizer"
116
+ @test-prompt-optimizer-model="$emit('testModel', 'promptOptimizer')"
115
117
  :save-status="appiumSaveStatus"
116
118
  @test-model="$emit('testModel', 'appium')"
117
119
  />
package/src/style.css CHANGED
@@ -40,6 +40,9 @@ select {
40
40
  }
41
41
 
42
42
  .layout-sidebar {
43
+ display: flex;
44
+ flex-direction: column;
45
+ min-height: 0;
43
46
  border-right: 1px solid #e4e7ed;
44
47
  background: #001529;
45
48
  }
@@ -64,6 +67,8 @@ select {
64
67
  }
65
68
 
66
69
  .layout-menu {
70
+ flex: 1;
71
+ min-height: 0;
67
72
  border-right: 0;
68
73
  background: transparent;
69
74
  }
@@ -75,6 +80,38 @@ select {
75
80
  --el-menu-active-color: #fff;
76
81
  }
77
82
 
83
+ .layout-sidebar__footer {
84
+ display: flex;
85
+ justify-content: flex-end;
86
+ height: 52px;
87
+ background: #fff;
88
+ }
89
+
90
+ .layout-sidebar__help {
91
+ display: flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ width: 44px;
95
+ height: 100%;
96
+ padding: 0;
97
+ border: 0;
98
+ background: transparent;
99
+ color: #9ca3af;
100
+ cursor: pointer;
101
+ transition: background-color 0.2s, color 0.2s;
102
+ }
103
+
104
+ .layout-sidebar__help:hover,
105
+ .layout-sidebar__help:focus-visible {
106
+ background: transparent;
107
+ color: #6b7280;
108
+ outline: none;
109
+ }
110
+
111
+ .layout-sidebar__help .el-icon {
112
+ font-size: 17px;
113
+ }
114
+
78
115
  .layout-main {
79
116
  display: grid;
80
117
  grid-template-rows: 60px minmax(0, 1fr);
@@ -517,50 +554,6 @@ select {
517
554
  line-height: 18px;
518
555
  }
519
556
 
520
- .appium-activity-lock-alert {
521
- align-items: flex-start;
522
- }
523
-
524
- .appium-activity-lock-alert .el-alert__content {
525
- min-width: 0;
526
- }
527
-
528
- .appium-activity-lock-alert__details {
529
- display: grid;
530
- gap: 5px;
531
- min-width: 0;
532
- margin-top: 6px;
533
- color: #b88230;
534
- font-size: 12px;
535
- line-height: 1.5;
536
- }
537
-
538
- .appium-activity-lock-alert__details > div {
539
- display: grid;
540
- grid-template-columns: 72px minmax(0, 1fr);
541
- gap: 6px;
542
- min-width: 0;
543
- }
544
-
545
- .appium-activity-lock-alert__details strong {
546
- font-weight: 600;
547
- }
548
-
549
- .appium-activity-lock-alert__details code {
550
- min-width: 0;
551
- color: inherit;
552
- font-family: inherit;
553
- overflow-wrap: anywhere;
554
- word-break: break-word;
555
- white-space: normal;
556
- }
557
-
558
- .appium-activity-lock-alert__details p {
559
- min-width: 0;
560
- margin: 0;
561
- overflow-wrap: anywhere;
562
- }
563
-
564
557
  .appium-action-dropdown__item {
565
558
  display: flex;
566
559
  align-items: center;
@@ -1402,7 +1395,6 @@ select {
1402
1395
  .appium-replay-dialog.el-dialog {
1403
1396
  display: flex;
1404
1397
  padding: 16px;
1405
- width: min(820px, calc(100vw - 32px)) !important;
1406
1398
  height: min(620px, calc(100vh - 64px));
1407
1399
  min-width: 420px;
1408
1400
  min-height: 280px;
package/src/types.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { ReportSummaryConfig } from './appium-recorder/report-summary';
1
2
  import type { AiPromptPreset } from './appium-recorder/ai-prompt-presets';
2
3
  import type { ScriptStep } from './script-generator';
3
4
  import type { MidsceneModelProvider } from './config/midscene-model-presets';
@@ -14,7 +15,7 @@ export type GeneratorForm = {
14
15
  };
15
16
 
16
17
  export type ConfigForm = {
17
- appium: { model: AiRecognitionModel; aiPromptPresets?: AiPromptPreset[]; aiDeduplication?: AiDeduplicationConfig; flowBackgroundColor?: string; flowLineColor?: string; screenshotReport?: boolean };
18
+ appium: { reportSummary?: ReportSummaryConfig; model: AiRecognitionModel; promptOptimizer?: { model: AiRecognitionModel }; aiPromptPresets?: AiPromptPreset[]; aiDeduplication?: AiDeduplicationConfig; flowBackgroundColor?: string; flowLineColor?: string; screenshotReport?: boolean };
18
19
  runtime: {
19
20
  androidSdkPath: string;
20
21
  reportOutputPath: string;