android-midscene-automation 0.1.24 → 0.1.26

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/src/style.css CHANGED
@@ -120,6 +120,52 @@ select {
120
120
  padding: 6px 16px 14px;
121
121
  }
122
122
 
123
+ .action-dialog__footer-actions {
124
+ display: flex;
125
+ justify-content: flex-end;
126
+ gap: 8px;
127
+ width: 100%;
128
+ }
129
+
130
+ .action-dialog__footer-button.el-button {
131
+ min-width: 88px;
132
+ height: 34px;
133
+ padding: 0 16px;
134
+ border-radius: 7px;
135
+ font-size: 14px;
136
+ font-weight: 700;
137
+ }
138
+
139
+ .action-dialog__footer-button.el-button + .action-dialog__footer-button.el-button {
140
+ margin-left: 0;
141
+ }
142
+
143
+ .action-dialog__footer-button--secondary.el-button {
144
+ border-color: #e5e7eb;
145
+ background: #fff;
146
+ color: #8b95a5;
147
+ }
148
+
149
+ .action-dialog__footer-button--secondary.el-button:hover,
150
+ .action-dialog__footer-button--secondary.el-button:focus {
151
+ border-color: #d7dce5;
152
+ background: #f8fafc;
153
+ color: #6b7280;
154
+ }
155
+
156
+ .action-dialog__footer-button--primary.el-button {
157
+ border-color: #ffd436;
158
+ background: #ffd436;
159
+ color: #111827;
160
+ }
161
+
162
+ .action-dialog__footer-button--primary.el-button:hover,
163
+ .action-dialog__footer-button--primary.el-button:focus {
164
+ border-color: #ffca1f;
165
+ background: #ffca1f;
166
+ color: #111827;
167
+ }
168
+
123
169
  @keyframes action-dialog-spin {
124
170
  from {
125
171
  transform: rotate(0deg);
@@ -149,6 +195,10 @@ select {
149
195
  gap: 12px;
150
196
  }
151
197
 
198
+ .layout-header__button--loading .el-icon {
199
+ animation: action-dialog-spin 1s linear infinite;
200
+ }
201
+
152
202
  .page-body {
153
203
  height: 100%;
154
204
  min-height: 0;
@@ -192,7 +242,7 @@ select {
192
242
  .appium-recorder-layout {
193
243
  display: grid;
194
244
  flex: 1 1 auto;
195
- grid-template-columns: minmax(0, 3fr) minmax(320px, 2.8fr) minmax(0, 4.2fr);
245
+ grid-template-columns: minmax(320px, 2.8fr) minmax(0, 3fr) minmax(0, 4.2fr);
196
246
  gap: 12px;
197
247
  align-items: stretch;
198
248
  height: auto;
@@ -1197,6 +1247,20 @@ select {
1197
1247
  gap: 8px;
1198
1248
  }
1199
1249
 
1250
+ .appium-flow-editor__grid--swipe {
1251
+ grid-template-columns: repeat(3, minmax(0, 1fr));
1252
+ }
1253
+
1254
+ .appium-swipe-message-box {
1255
+ width: min(560px, calc(100vw - 32px));
1256
+ }
1257
+
1258
+ .appium-swipe-dialog-grid {
1259
+ display: grid;
1260
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1261
+ gap: 10px 12px;
1262
+ }
1263
+
1200
1264
  .appium-flow-insert {
1201
1265
  position: relative;
1202
1266
  z-index: 1;
@@ -1609,137 +1673,6 @@ select {
1609
1673
  gap: 18px;
1610
1674
  }
1611
1675
 
1612
- .model-usage-chart {
1613
- margin-top: 20px;
1614
- padding-top: 18px;
1615
- border-top: 1px solid #ebeef5;
1616
- }
1617
-
1618
- .model-usage-chart__body {
1619
- display: grid;
1620
- gap: 10px;
1621
- }
1622
-
1623
- .model-usage-chart__summary,
1624
- .model-usage-chart__legend,
1625
- .model-usage-chart__record {
1626
- display: flex;
1627
- align-items: center;
1628
- gap: 12px;
1629
- min-width: 0;
1630
- color: #64748b;
1631
- font-size: 12px;
1632
- }
1633
-
1634
- .model-usage-chart__summary {
1635
- flex-wrap: wrap;
1636
- }
1637
-
1638
- .model-usage-chart__svg {
1639
- width: 100%;
1640
- height: 180px;
1641
- display: block;
1642
- border: 1px solid #ebeef5;
1643
- border-radius: 6px;
1644
- background: #fbfdff;
1645
- }
1646
-
1647
- .model-usage-chart__axis {
1648
- stroke: #d8dee9;
1649
- stroke-width: 1;
1650
- }
1651
-
1652
- .model-usage-chart__line {
1653
- fill: none;
1654
- stroke-linecap: round;
1655
- stroke-linejoin: round;
1656
- stroke-width: 2.5;
1657
- }
1658
-
1659
- .model-usage-chart__line--prompt,
1660
- .model-usage-chart__point--prompt {
1661
- stroke: #2563eb;
1662
- }
1663
-
1664
- .model-usage-chart__line--completion,
1665
- .model-usage-chart__point--completion {
1666
- stroke: #d97706;
1667
- }
1668
-
1669
- .model-usage-chart__line--duration,
1670
- .model-usage-chart__point--duration {
1671
- stroke: #16a34a;
1672
- }
1673
-
1674
- .model-usage-chart__point {
1675
- fill: #fff;
1676
- stroke-width: 2;
1677
- }
1678
-
1679
- .model-usage-chart__legend {
1680
- flex-wrap: wrap;
1681
- }
1682
-
1683
- .model-usage-chart__legend span {
1684
- display: inline-flex;
1685
- align-items: center;
1686
- gap: 6px;
1687
- }
1688
-
1689
- .model-usage-chart__swatch {
1690
- width: 18px;
1691
- height: 3px;
1692
- border-radius: 999px;
1693
- }
1694
-
1695
- .model-usage-chart__swatch--prompt {
1696
- background: #2563eb;
1697
- }
1698
-
1699
- .model-usage-chart__swatch--completion {
1700
- background: #d97706;
1701
- }
1702
-
1703
- .model-usage-chart__swatch--duration {
1704
- background: #16a34a;
1705
- }
1706
-
1707
- .model-usage-chart__records {
1708
- display: grid;
1709
- gap: 6px;
1710
- height: 220px;
1711
- align-content: start;
1712
- overflow-y: auto;
1713
- padding-right: 4px;
1714
- }
1715
-
1716
- .model-usage-chart__record {
1717
- display: grid;
1718
- grid-template-columns: 86px 64px minmax(120px, 1fr) 84px 84px 84px 72px;
1719
- justify-content: initial;
1720
- min-height: 38px;
1721
- padding: 6px 8px;
1722
- border-radius: 6px;
1723
- background: #f8fafc;
1724
- }
1725
-
1726
- .model-usage-chart__record--head {
1727
- position: sticky;
1728
- top: 0;
1729
- z-index: 1;
1730
- min-height: 30px;
1731
- background: #eef2f7;
1732
- color: #475569;
1733
- font-weight: 700;
1734
- }
1735
-
1736
- .model-usage-chart__record span {
1737
- min-width: 0;
1738
- overflow: hidden;
1739
- text-overflow: ellipsis;
1740
- white-space: nowrap;
1741
- }
1742
-
1743
1676
  .app-preset-list {
1744
1677
  display: grid;
1745
1678
  gap: 8px;
@@ -2101,6 +2034,7 @@ select {
2101
2034
  display: grid;
2102
2035
  grid-template-columns: 420px minmax(0, 1fr);
2103
2036
  gap: 16px;
2037
+ height: 100%;
2104
2038
  min-height: 0;
2105
2039
  }
2106
2040
 
@@ -2126,12 +2060,19 @@ select {
2126
2060
 
2127
2061
  .execution-card > .el-card__body {
2128
2062
  display: flex;
2063
+ flex: 1 1 auto;
2064
+ min-height: 0;
2065
+ }
2066
+
2067
+ .execution-card.el-card {
2068
+ height: 100%;
2129
2069
  }
2130
2070
 
2131
2071
  .execution-card .el-form {
2132
2072
  display: flex;
2133
2073
  flex: 1;
2134
2074
  width: 100%;
2075
+ height: 100%;
2135
2076
  min-width: 0;
2136
2077
  min-height: 0;
2137
2078
  flex-direction: column;
@@ -2141,12 +2082,17 @@ select {
2141
2082
  flex: 0 0 auto;
2142
2083
  }
2143
2084
 
2085
+ .execution-card .execution-script-item {
2086
+ margin-bottom: 12px;
2087
+ }
2088
+
2144
2089
  .execution-card .execution-output-item {
2145
2090
  display: flex;
2146
2091
  flex-direction: column;
2147
2092
  align-items: stretch;
2148
2093
  flex: 1 1 auto;
2149
2094
  min-height: 0;
2095
+ margin-bottom: 0;
2150
2096
  }
2151
2097
 
2152
2098
  .execution-card .execution-output-item .el-form-item__content {
@@ -2574,11 +2520,13 @@ select {
2574
2520
  flex: 1 1 auto;
2575
2521
  width: 100%;
2576
2522
  min-width: 0;
2577
- height: auto;
2523
+ height: 100%;
2524
+ min-height: 0;
2578
2525
  padding: 0;
2579
- border: 1px solid #e4e7ed;
2526
+ border: 1px solid #263244;
2580
2527
  border-radius: 6px;
2581
- background: #fff;
2528
+ background: #101722;
2529
+ scrollbar-color: #526174 #101722;
2582
2530
  }
2583
2531
 
2584
2532
  .execution-output-header {
@@ -2623,15 +2571,30 @@ select {
2623
2571
  border: 0;
2624
2572
  outline: 0;
2625
2573
  resize: none;
2626
- background: #fff;
2627
- color: #475569;
2574
+ background: #101722;
2575
+ color: #d7e0ea;
2628
2576
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
2629
2577
  font-size: 12px;
2630
2578
  line-height: 1.6;
2631
2579
  overflow: auto;
2632
- white-space: pre;
2580
+ white-space: pre-wrap;
2633
2581
  overflow-wrap: normal;
2634
- word-break: normal;
2582
+ word-break: break-word;
2583
+ }
2584
+
2585
+ .execution-log__output::-webkit-scrollbar {
2586
+ width: 10px;
2587
+ height: 10px;
2588
+ }
2589
+
2590
+ .execution-log__output::-webkit-scrollbar-track {
2591
+ background: #101722;
2592
+ }
2593
+
2594
+ .execution-log__output::-webkit-scrollbar-thumb {
2595
+ border: 2px solid #101722;
2596
+ border-radius: 6px;
2597
+ background: #526174;
2635
2598
  }
2636
2599
 
2637
2600
  .execution-process {
@@ -2745,12 +2708,18 @@ select {
2745
2708
 
2746
2709
  .automation-layout {
2747
2710
  height: auto;
2711
+ min-height: calc(100vh - 108px);
2748
2712
  }
2749
2713
 
2750
2714
  .automation-stage {
2751
2715
  grid-template-columns: 1fr;
2752
2716
  }
2753
2717
 
2718
+ .execution-card.el-card {
2719
+ height: min(760px, calc(100vh - 120px));
2720
+ min-height: 560px;
2721
+ }
2722
+
2754
2723
  .appium-recorder-layout {
2755
2724
  grid-template-columns: 1fr;
2756
2725
  height: auto;
package/src/types.ts CHANGED
@@ -34,20 +34,6 @@ export type ConfigForm = {
34
34
  };
35
35
  };
36
36
 
37
- export type ModelUsageRecord = {
38
- id: string;
39
- modelKey: 'midscene' | 'scriptOptimizer';
40
- provider: string;
41
- modelName: string;
42
- family: string;
43
- success: boolean;
44
- durationMs: number;
45
- promptTokens?: number;
46
- completionTokens?: number;
47
- totalTokens?: number;
48
- createdAt: string;
49
- };
50
-
51
37
  export type SavedScript = {
52
38
  id: string;
53
39
  name: string;
@@ -1,108 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { appDataPath } from './paths';
4
- import { saveModelUsageRecord } from './model-usage-repository';
5
-
6
- type MidsceneModelConfig = {
7
- provider?: string;
8
- name?: string;
9
- family?: string;
10
- };
11
-
12
- type ModelCallUsage = {
13
- prompt_tokens?: number;
14
- completion_tokens?: number;
15
- total_tokens?: number;
16
- time_cost?: number;
17
- model_name?: string;
18
- response_model_name?: string;
19
- model_description?: string;
20
- };
21
-
22
- type ModelCallRecord = {
23
- timestamp?: string;
24
- type?: string;
25
- modelFamily?: string;
26
- provider?: string;
27
- final?: {
28
- usage?: ModelCallUsage;
29
- timeCost?: number;
30
- };
31
- };
32
-
33
- function modelRequestDir() {
34
- return appDataPath('midscene_run', 'model-requests');
35
- }
36
-
37
- function numberValue(value: unknown) {
38
- return typeof value === 'number' && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : undefined;
39
- }
40
-
41
- function listModelRequestFilesSince(sinceMs: number) {
42
- const dir = modelRequestDir();
43
- if (!fs.existsSync(dir)) return [];
44
-
45
- return fs
46
- .readdirSync(dir)
47
- .filter((name) => name.endsWith('.jsonl'))
48
- .map((name) => path.join(dir, name))
49
- .filter((filePath) => {
50
- const stat = fs.statSync(filePath);
51
- return stat.mtimeMs >= sinceMs - 1_000;
52
- });
53
- }
54
-
55
- function recordsFromFile(filePath: string) {
56
- return fs
57
- .readFileSync(filePath, 'utf8')
58
- .split(/\r?\n/)
59
- .map((line) => line.trim())
60
- .filter(Boolean)
61
- .flatMap((line) => {
62
- try {
63
- return [JSON.parse(line) as ModelCallRecord];
64
- } catch {
65
- return [];
66
- }
67
- });
68
- }
69
-
70
- export function importMidsceneModelUsage(input: {
71
- sinceMs: number;
72
- model: MidsceneModelConfig;
73
- success: boolean;
74
- }) {
75
- let imported = 0;
76
- const files = listModelRequestFilesSince(input.sinceMs);
77
-
78
- for (const filePath of files) {
79
- for (const record of recordsFromFile(filePath)) {
80
- if (record.type !== 'response') continue;
81
- const usage = record.final?.usage;
82
- if (!usage) continue;
83
-
84
- const promptTokens = numberValue(usage.prompt_tokens);
85
- const completionTokens = numberValue(usage.completion_tokens);
86
- const totalTokens = numberValue(usage.total_tokens) ?? numberValue((promptTokens || 0) + (completionTokens || 0));
87
-
88
- saveModelUsageRecord({
89
- modelKey: 'midscene',
90
- provider: input.model.provider || (record.provider === 'codex-app-server' ? 'codex' : 'custom'),
91
- modelName: usage.model_name || usage.response_model_name || input.model.name || '',
92
- family: input.model.family || record.modelFamily || '',
93
- success: input.success,
94
- durationMs: numberValue(usage.time_cost) ?? numberValue(record.final?.timeCost) ?? 0,
95
- promptTokens,
96
- completionTokens,
97
- totalTokens,
98
- createdAt: record.timestamp,
99
- });
100
- imported += 1;
101
- }
102
- }
103
-
104
- return {
105
- imported,
106
- files,
107
- };
108
- }
@@ -1,131 +0,0 @@
1
- import {
2
- createId,
3
- querySql,
4
- runSql,
5
- sqlNullableString,
6
- sqlString,
7
- } from './storage/sqlite';
8
-
9
- export type ModelUsageRecord = {
10
- id: string;
11
- modelKey: 'midscene' | 'scriptOptimizer';
12
- provider: string;
13
- modelName: string;
14
- family: string;
15
- success: boolean;
16
- durationMs: number;
17
- promptTokens?: number;
18
- completionTokens?: number;
19
- totalTokens?: number;
20
- errorMessage?: string;
21
- createdAt: string;
22
- };
23
-
24
- type ModelUsageRow = {
25
- id: string;
26
- model_key: 'midscene' | 'scriptOptimizer';
27
- provider: string;
28
- model_name: string;
29
- family: string | null;
30
- success: 0 | 1;
31
- duration_ms: number;
32
- prompt_tokens: number | null;
33
- completion_tokens: number | null;
34
- total_tokens: number | null;
35
- error_message: string | null;
36
- created_at: string;
37
- };
38
-
39
- let initialized = false;
40
-
41
- function sqlNullableNumber(value: number | null | undefined) {
42
- return typeof value === 'number' && Number.isFinite(value) ? String(Math.max(0, Math.floor(value))) : 'NULL';
43
- }
44
-
45
- function rowToRecord(row: ModelUsageRow): ModelUsageRecord {
46
- return {
47
- id: row.id,
48
- modelKey: row.model_key,
49
- provider: row.provider,
50
- modelName: row.model_name,
51
- family: row.family || '',
52
- success: row.success === 1,
53
- durationMs: row.duration_ms,
54
- promptTokens: row.prompt_tokens ?? undefined,
55
- completionTokens: row.completion_tokens ?? undefined,
56
- totalTokens: row.total_tokens ?? undefined,
57
- errorMessage: row.error_message || undefined,
58
- createdAt: row.created_at,
59
- };
60
- }
61
-
62
- export function initModelUsageRepository() {
63
- if (initialized) return;
64
- initialized = true;
65
- runSql(`
66
- PRAGMA journal_mode = WAL;
67
- CREATE TABLE IF NOT EXISTS model_usage_records (
68
- id TEXT PRIMARY KEY,
69
- model_key TEXT NOT NULL,
70
- provider TEXT NOT NULL,
71
- model_name TEXT NOT NULL,
72
- family TEXT NULL,
73
- success INTEGER NOT NULL,
74
- duration_ms INTEGER NOT NULL,
75
- prompt_tokens INTEGER NULL,
76
- completion_tokens INTEGER NULL,
77
- total_tokens INTEGER NULL,
78
- error_message TEXT NULL,
79
- created_at TEXT NOT NULL
80
- );
81
- CREATE INDEX IF NOT EXISTS idx_model_usage_records_created_at ON model_usage_records(created_at DESC);
82
- `);
83
- }
84
-
85
- export function saveModelUsageRecord(input: Omit<ModelUsageRecord, 'id' | 'createdAt'> & { createdAt?: string }) {
86
- initModelUsageRepository();
87
- const id = createId('usage');
88
- const createdAt = input.createdAt || new Date().toISOString();
89
- runSql(`
90
- INSERT INTO model_usage_records (
91
- id,
92
- model_key,
93
- provider,
94
- model_name,
95
- family,
96
- success,
97
- duration_ms,
98
- prompt_tokens,
99
- completion_tokens,
100
- total_tokens,
101
- error_message,
102
- created_at
103
- )
104
- VALUES (
105
- ${sqlString(id)},
106
- ${sqlString(input.modelKey)},
107
- ${sqlString(input.provider || 'custom')},
108
- ${sqlString(input.modelName || '')},
109
- ${sqlNullableString(input.family)},
110
- ${input.success ? 1 : 0},
111
- ${sqlNullableNumber(input.durationMs)},
112
- ${sqlNullableNumber(input.promptTokens)},
113
- ${sqlNullableNumber(input.completionTokens)},
114
- ${sqlNullableNumber(input.totalTokens)},
115
- ${sqlNullableString(input.errorMessage)},
116
- ${sqlString(createdAt)}
117
- );
118
- `);
119
- return listModelUsageRecords(1)[0];
120
- }
121
-
122
- export function listModelUsageRecords(limit = 50) {
123
- initModelUsageRepository();
124
- const boundedLimit = Math.max(1, Math.min(200, Math.floor(limit)));
125
- return querySql<ModelUsageRow>(`
126
- SELECT *
127
- FROM model_usage_records
128
- ORDER BY datetime(created_at) DESC
129
- LIMIT ${boundedLimit};
130
- `).map(rowToRecord).reverse();
131
- }