@agentprojectcontext/apx 1.32.2 → 1.33.0
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/package.json +1 -1
- package/src/core/agent/prompts/action-discipline.md +12 -5
- package/src/core/agent/prompts/channels/telegram.md +9 -5
- package/src/core/stores/code-sessions.js +4 -1
- package/src/host/daemon/api/artifacts.js +25 -0
- package/src/host/daemon/api/code.js +14 -1
- package/src/host/daemon/api/exec.js +17 -2
- package/src/host/daemon/plugins/telegram/index.js +2 -14
- package/src/interfaces/web/dist/assets/index-7dVT2O1S.css +1 -0
- package/src/interfaces/web/dist/assets/index-DWsE_8Nz.js +602 -0
- package/src/interfaces/web/dist/assets/index-DWsE_8Nz.js.map +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +3 -3
- package/src/interfaces/web/src/App.tsx +3 -1
- package/src/interfaces/web/src/components/UiSelect.tsx +12 -2
- package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +253 -111
- package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +10 -8
- package/src/interfaces/web/src/components/code/CodeComposer.tsx +20 -17
- package/src/interfaces/web/src/components/code/CodeContextTab.tsx +43 -18
- package/src/interfaces/web/src/components/code/CodeFileTree.tsx +212 -0
- package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +121 -0
- package/src/interfaces/web/src/components/code/CodeSessionList.tsx +30 -26
- package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +23 -19
- package/src/interfaces/web/src/components/code/CodeTerminal.tsx +140 -0
- package/src/interfaces/web/src/components/common/TabLayout.tsx +3 -3
- package/src/interfaces/web/src/components/ui/chat-input.tsx +17 -6
- package/src/interfaces/web/src/hooks/useChat.ts +1 -0
- package/src/interfaces/web/src/hooks/useNavCollapseCtx.tsx +25 -1
- package/src/interfaces/web/src/i18n/es.ts +1 -1
- package/src/interfaces/web/src/lib/api/agents.ts +1 -1
- package/src/interfaces/web/src/lib/api/artifacts.ts +10 -0
- package/src/interfaces/web/src/lib/api/code.ts +4 -2
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +423 -79
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +7 -10
- package/src/core/util/text-similarity.js +0 -52
- package/src/interfaces/web/dist/assets/index-34U_Mp1M.css +0 -1
- package/src/interfaces/web/dist/assets/index-BkybwwRn.js +0 -570
- package/src/interfaces/web/dist/assets/index-BkybwwRn.js.map +0 -1
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
<link rel="apple-touch-icon" href="/favicon/dark/apple-touch-icon.png" media="(prefers-color-scheme: dark)" />
|
|
19
19
|
<link rel="manifest" href="/favicon/white/site.webmanifest" media="(prefers-color-scheme: light)" />
|
|
20
20
|
<link rel="manifest" href="/favicon/dark/site.webmanifest" media="(prefers-color-scheme: dark)" />
|
|
21
|
-
<script type="module" crossorigin src="/assets/index-
|
|
22
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
21
|
+
<script type="module" crossorigin src="/assets/index-DWsE_8Nz.js"></script>
|
|
22
|
+
<link rel="stylesheet" crossorigin href="/assets/index-7dVT2O1S.css">
|
|
23
23
|
</head>
|
|
24
24
|
<body class="bg-background text-foreground antialiased">
|
|
25
25
|
<div id="root"></div>
|
|
@@ -2236,9 +2236,9 @@
|
|
|
2236
2236
|
}
|
|
2237
2237
|
},
|
|
2238
2238
|
"node_modules/caniuse-lite": {
|
|
2239
|
-
"version": "1.0.
|
|
2240
|
-
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.
|
|
2241
|
-
"integrity": "sha512-
|
|
2239
|
+
"version": "1.0.30001799",
|
|
2240
|
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
|
|
2241
|
+
"integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
|
|
2242
2242
|
"dev": true,
|
|
2243
2243
|
"funding": [
|
|
2244
2244
|
{
|
|
@@ -17,7 +17,7 @@ import { TooltipProvider } from "./components/ui/tooltip";
|
|
|
17
17
|
import { useTheme } from "./hooks/useTheme";
|
|
18
18
|
import { useProjects } from "./hooks/useProjects";
|
|
19
19
|
import { useTokenBootstrap } from "./hooks/useTokenBootstrap";
|
|
20
|
-
import { NavCollapseProvider, useNavCollapseCtx, usePageLabel } from "./hooks/useNavCollapseCtx";
|
|
20
|
+
import { NavCollapseProvider, useNavCollapseCtx, usePageActions, usePageLabel } from "./hooks/useNavCollapseCtx";
|
|
21
21
|
import { NavToggle } from "./components/common/TabNav";
|
|
22
22
|
import { t } from "./i18n";
|
|
23
23
|
|
|
@@ -131,6 +131,7 @@ function TopBar({
|
|
|
131
131
|
: project ? `${projectKindLabel(project.kind)} · ${project.path}` : "")
|
|
132
132
|
: "";
|
|
133
133
|
const nav = useNavCollapseCtx();
|
|
134
|
+
const pageActions = usePageActions();
|
|
134
135
|
return (
|
|
135
136
|
<header className="flex h-10 shrink-0 items-center gap-2 border-b border-border/50 px-3">
|
|
136
137
|
{nav && <NavToggle collapsed={nav.collapsed} onToggle={nav.toggle} />}
|
|
@@ -138,6 +139,7 @@ function TopBar({
|
|
|
138
139
|
{crumb}
|
|
139
140
|
{subtitle && <span className="text-muted-fg/50"> · {subtitle}</span>}
|
|
140
141
|
</span>
|
|
142
|
+
{pageActions}
|
|
141
143
|
<button
|
|
142
144
|
type="button"
|
|
143
145
|
onClick={onToggleTheme}
|
|
@@ -25,6 +25,7 @@ export function UiSelect({
|
|
|
25
25
|
placeholder = "— elegir —",
|
|
26
26
|
disabled,
|
|
27
27
|
className,
|
|
28
|
+
showIcon = false,
|
|
28
29
|
}: {
|
|
29
30
|
value: string;
|
|
30
31
|
onChange: (value: string) => void;
|
|
@@ -32,13 +33,22 @@ export function UiSelect({
|
|
|
32
33
|
placeholder?: string;
|
|
33
34
|
disabled?: boolean;
|
|
34
35
|
className?: string;
|
|
36
|
+
showIcon?: boolean;
|
|
35
37
|
}) {
|
|
36
38
|
return (
|
|
37
39
|
<Select value={value} onValueChange={(v) => onChange((v as string) ?? "")} disabled={disabled}>
|
|
38
40
|
<SelectTrigger className={cn("h-9 w-full", className)}>
|
|
39
|
-
{/* Show the option's label in the trigger, not the raw value key. */}
|
|
40
41
|
<SelectValue placeholder={placeholder}>
|
|
41
|
-
{(val) =>
|
|
42
|
+
{(val) => {
|
|
43
|
+
const opt = options.find((o) => o.value === val);
|
|
44
|
+
const Icon = showIcon ? opt?.icon : undefined;
|
|
45
|
+
return (
|
|
46
|
+
<span className="flex min-w-0 items-center gap-1.5">
|
|
47
|
+
{Icon && <Icon className="size-3.5 shrink-0" />}
|
|
48
|
+
<span className="truncate">{opt?.label ?? (val as string)}</span>
|
|
49
|
+
</span>
|
|
50
|
+
);
|
|
51
|
+
}}
|
|
42
52
|
</SelectValue>
|
|
43
53
|
</SelectTrigger>
|
|
44
54
|
{/* side=bottom + alignItemWithTrigger=false → dropdown sits BELOW the
|
|
@@ -1,37 +1,63 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
2
|
import useSWR from "swr";
|
|
3
|
-
import {
|
|
3
|
+
import { Copy, RefreshCw, Trash2, FileCode2, Play, Pencil, Eye, SquarePen } from "lucide-react";
|
|
4
4
|
import { cn } from "../../lib/cn";
|
|
5
5
|
import { t } from "../../i18n";
|
|
6
6
|
import { Empty, Spinner } from "../ui";
|
|
7
7
|
import { Artifacts, type ArtifactEntry, type ArtifactRunResult } from "../../lib/api/artifacts";
|
|
8
8
|
import { useToast } from "../Toast";
|
|
9
|
+
import {
|
|
10
|
+
Dialog,
|
|
11
|
+
DialogContent,
|
|
12
|
+
DialogHeader,
|
|
13
|
+
DialogTitle,
|
|
14
|
+
DialogFooter,
|
|
15
|
+
DialogClose,
|
|
16
|
+
} from "../ui/dialog";
|
|
17
|
+
import { Tip } from "../ui/tip";
|
|
9
18
|
|
|
10
19
|
interface Props {
|
|
11
20
|
pid: string;
|
|
21
|
+
onRunInTerminal?: (cmd: string) => void;
|
|
22
|
+
onEditArtifact?: (name: string) => void;
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
function ArtifactRow({
|
|
15
26
|
pid,
|
|
16
27
|
entry,
|
|
17
28
|
onDeleted,
|
|
29
|
+
onRenamed,
|
|
30
|
+
onRunInTerminal,
|
|
31
|
+
onEditArtifact,
|
|
18
32
|
}: {
|
|
19
33
|
pid: string;
|
|
20
34
|
entry: ArtifactEntry;
|
|
21
35
|
onDeleted: () => void;
|
|
36
|
+
onRenamed: () => void;
|
|
37
|
+
onRunInTerminal?: (cmd: string) => void;
|
|
38
|
+
onEditArtifact?: (name: string) => void;
|
|
22
39
|
}) {
|
|
23
|
-
const [open, setOpen] = useState(false);
|
|
24
40
|
const [running, setRunning] = useState(false);
|
|
25
41
|
const [runResult, setRunResult] = useState<ArtifactRunResult | null>(null);
|
|
26
42
|
const toast = useToast();
|
|
27
|
-
const detail = useSWR(open ? ["artifact", pid, entry.name] : null, () =>
|
|
28
|
-
Artifacts.read(pid, entry.name),
|
|
29
|
-
);
|
|
30
43
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
// Rename state
|
|
45
|
+
const [renaming, setRenaming] = useState(false);
|
|
46
|
+
const [renameValue, setRenameValue] = useState(entry.name);
|
|
47
|
+
const renameInputRef = useRef<HTMLInputElement>(null);
|
|
48
|
+
|
|
49
|
+
// View dialog state
|
|
50
|
+
const [viewOpen, setViewOpen] = useState(false);
|
|
51
|
+
// Delete confirmation dialog
|
|
52
|
+
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
53
|
+
const [deleting, setDeleting] = useState(false);
|
|
54
|
+
|
|
55
|
+
// Load detail only when the view dialog is open
|
|
56
|
+
const detailKey = viewOpen ? ["artifact", pid, entry.name] : null;
|
|
57
|
+
const detail = useSWR(detailKey, () => Artifacts.read(pid, entry.name), {
|
|
58
|
+
revalidateOnFocus: false,
|
|
59
|
+
});
|
|
60
|
+
|
|
35
61
|
const looksRunnable = !detail.data?.content || detail.data.content.startsWith("#!");
|
|
36
62
|
|
|
37
63
|
const copy = async (text: string) => {
|
|
@@ -59,137 +85,249 @@ function ArtifactRow({
|
|
|
59
85
|
};
|
|
60
86
|
|
|
61
87
|
const remove = async () => {
|
|
62
|
-
|
|
88
|
+
setDeleting(true);
|
|
63
89
|
try {
|
|
64
90
|
await Artifacts.remove(pid, entry.name);
|
|
91
|
+
setDeleteOpen(false);
|
|
65
92
|
onDeleted();
|
|
66
93
|
} catch (e) {
|
|
67
94
|
toast.error((e as Error).message);
|
|
95
|
+
} finally {
|
|
96
|
+
setDeleting(false);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const startRename = () => {
|
|
101
|
+
setRenameValue(entry.name);
|
|
102
|
+
setRenaming(true);
|
|
103
|
+
// Focus after paint
|
|
104
|
+
requestAnimationFrame(() => renameInputRef.current?.select());
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const commitRename = async () => {
|
|
108
|
+
const trimmed = renameValue.trim();
|
|
109
|
+
setRenaming(false);
|
|
110
|
+
if (!trimmed || trimmed === entry.name) return;
|
|
111
|
+
try {
|
|
112
|
+
await Artifacts.rename(pid, entry.name, trimmed);
|
|
113
|
+
onRenamed();
|
|
114
|
+
} catch (e) {
|
|
115
|
+
toast.error((e as Error).message);
|
|
68
116
|
}
|
|
69
117
|
};
|
|
70
118
|
|
|
71
119
|
return (
|
|
72
120
|
<li className="rounded-md border border-border">
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
onClick={() => setOpen((v) => !v)}
|
|
76
|
-
className="flex w-full items-center gap-2 px-2 py-1.5 text-left text-xs hover:bg-accent/40"
|
|
77
|
-
>
|
|
78
|
-
<ChevronRight
|
|
79
|
-
className={cn(
|
|
80
|
-
"size-3 shrink-0 transition-transform",
|
|
81
|
-
open && "rotate-90",
|
|
82
|
-
)}
|
|
83
|
-
/>
|
|
121
|
+
{/* Row header: file icon + name (or rename input) + size */}
|
|
122
|
+
<div className="flex w-full items-center gap-2 px-2 py-1.5 text-xs">
|
|
84
123
|
<FileCode2 className="size-3.5 shrink-0 text-emerald-600 dark:text-emerald-400" />
|
|
85
|
-
|
|
124
|
+
{renaming ? (
|
|
125
|
+
<input
|
|
126
|
+
ref={renameInputRef}
|
|
127
|
+
value={renameValue}
|
|
128
|
+
onChange={(e) => setRenameValue(e.target.value)}
|
|
129
|
+
onBlur={() => void commitRename()}
|
|
130
|
+
onKeyDown={(e) => {
|
|
131
|
+
if (e.key === "Enter") void commitRename();
|
|
132
|
+
if (e.key === "Escape") setRenaming(false);
|
|
133
|
+
}}
|
|
134
|
+
autoFocus
|
|
135
|
+
className="min-w-0 flex-1 rounded border border-border bg-background px-1 py-0.5 font-mono text-xs outline-none focus:ring-1 focus:ring-ring"
|
|
136
|
+
/>
|
|
137
|
+
) : (
|
|
138
|
+
<span className="min-w-0 flex-1 truncate font-mono">{entry.name}</span>
|
|
139
|
+
)}
|
|
140
|
+
<Tip content="Renombrar">
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
onClick={startRename}
|
|
144
|
+
className="shrink-0 rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
145
|
+
>
|
|
146
|
+
<Pencil className="size-3" />
|
|
147
|
+
</button>
|
|
148
|
+
</Tip>
|
|
86
149
|
<span className="shrink-0 font-mono text-[10px] text-muted-foreground">
|
|
87
150
|
{entry.size}b
|
|
88
151
|
</span>
|
|
89
|
-
</
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
type="button"
|
|
99
|
-
onClick={() => void run()}
|
|
100
|
-
disabled={running}
|
|
101
|
-
title={t("code_module.artifacts_run")}
|
|
102
|
-
className={cn(
|
|
103
|
-
"inline-flex items-center gap-1 rounded px-1.5 py-1 text-[10px] font-medium",
|
|
104
|
-
running
|
|
105
|
-
? "bg-muted text-muted-foreground"
|
|
106
|
-
: "bg-emerald-500/15 text-emerald-700 hover:bg-emerald-500/25 dark:text-emerald-300",
|
|
107
|
-
)}
|
|
108
|
-
>
|
|
109
|
-
{running ? <Spinner size={10} /> : <Play className="size-3" />}
|
|
110
|
-
{t("code_module.artifacts_run")}
|
|
111
|
-
</button>
|
|
112
|
-
)}
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{/* Action bar — always visible */}
|
|
155
|
+
<div className="space-y-2 border-t border-border p-2">
|
|
156
|
+
<div className="flex w-full min-w-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5">
|
|
157
|
+
<code className="min-w-0 flex-1 truncate font-mono text-[10px] text-muted-foreground">
|
|
158
|
+
{entry.path}
|
|
159
|
+
</code>
|
|
160
|
+
<Tip content={t("code_module.artifacts_copy_path")}>
|
|
113
161
|
<button
|
|
114
162
|
type="button"
|
|
115
163
|
onClick={() => void copy(entry.path)}
|
|
116
|
-
|
|
117
|
-
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
164
|
+
className="shrink-0 rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
118
165
|
>
|
|
119
166
|
<Copy className="size-3" />
|
|
120
167
|
</button>
|
|
168
|
+
</Tip>
|
|
169
|
+
</div>
|
|
170
|
+
<div className="flex flex-wrap items-center gap-1 mt-1">
|
|
171
|
+
{/* Ver button */}
|
|
172
|
+
<Dialog open={viewOpen} onOpenChange={setViewOpen}>
|
|
173
|
+
<Tip content="Ver contenido">
|
|
174
|
+
<button
|
|
175
|
+
type="button"
|
|
176
|
+
onClick={() => setViewOpen(true)}
|
|
177
|
+
className="inline-flex items-center gap-1 rounded px-1.5 py-1 text-[10px] font-medium bg-blue-500/15 text-blue-700 hover:bg-blue-500/25 dark:text-blue-300"
|
|
178
|
+
>
|
|
179
|
+
<Eye className="size-3" />
|
|
180
|
+
Ver
|
|
181
|
+
</button>
|
|
182
|
+
</Tip>
|
|
183
|
+
<DialogContent className="sm:max-w-lg">
|
|
184
|
+
<DialogHeader>
|
|
185
|
+
<DialogTitle className="font-mono text-sm">{entry.name}</DialogTitle>
|
|
186
|
+
</DialogHeader>
|
|
187
|
+
{detail.isLoading ? (
|
|
188
|
+
<div className="flex justify-center py-6">
|
|
189
|
+
<Spinner size={16} />
|
|
190
|
+
</div>
|
|
191
|
+
) : (
|
|
192
|
+
<pre className="max-h-96 overflow-auto rounded bg-muted/50 p-3 font-mono text-[11px] leading-tight whitespace-pre-wrap break-all">
|
|
193
|
+
{detail.data?.content ?? ""}
|
|
194
|
+
</pre>
|
|
195
|
+
)}
|
|
196
|
+
<DialogFooter showCloseButton />
|
|
197
|
+
</DialogContent>
|
|
198
|
+
</Dialog>
|
|
199
|
+
|
|
200
|
+
{/* Editar — opens as a file tab in the main panel */}
|
|
201
|
+
<Tip content="Editar contenido">
|
|
121
202
|
<button
|
|
122
203
|
type="button"
|
|
123
|
-
onClick={() =>
|
|
124
|
-
|
|
125
|
-
className="rounded p-1 text-rose-600 hover:bg-rose-50 dark:text-rose-400 dark:hover:bg-rose-950"
|
|
204
|
+
onClick={() => onEditArtifact?.(entry.name)}
|
|
205
|
+
className="inline-flex items-center gap-1 rounded px-1.5 py-1 text-[10px] font-medium bg-violet-500/15 text-violet-700 hover:bg-violet-500/25 dark:text-violet-300"
|
|
126
206
|
>
|
|
127
|
-
<
|
|
207
|
+
<SquarePen className="size-3" />
|
|
208
|
+
Editar
|
|
128
209
|
</button>
|
|
129
|
-
</
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
<
|
|
210
|
+
</Tip>
|
|
211
|
+
|
|
212
|
+
{/* Run button */}
|
|
213
|
+
{looksRunnable && (
|
|
214
|
+
<Tip content={t("code_module.artifacts_run")}>
|
|
215
|
+
<button
|
|
216
|
+
type="button"
|
|
217
|
+
onClick={() => onRunInTerminal?.(`apx artifact run ${entry.name}`)}
|
|
218
|
+
className="inline-flex items-center gap-1 rounded px-1.5 py-1 text-[10px] font-medium bg-emerald-500/15 text-emerald-700 hover:bg-emerald-500/25 dark:text-emerald-300"
|
|
219
|
+
>
|
|
220
|
+
<Play className="size-3" />
|
|
221
|
+
{t("code_module.artifacts_run")}
|
|
222
|
+
</button>
|
|
223
|
+
</Tip>
|
|
224
|
+
)}
|
|
225
|
+
|
|
226
|
+
{/* Eliminar — confirmation dialog */}
|
|
227
|
+
<Dialog open={deleteOpen} onOpenChange={setDeleteOpen}>
|
|
228
|
+
<Tip content={t("code_module.artifacts_delete")}>
|
|
229
|
+
<button
|
|
230
|
+
type="button"
|
|
231
|
+
onClick={() => setDeleteOpen(true)}
|
|
232
|
+
className="ml-auto rounded p-1 text-rose-600 hover:bg-rose-50 dark:text-rose-400 dark:hover:bg-rose-950"
|
|
233
|
+
>
|
|
234
|
+
<Trash2 className="size-3" />
|
|
235
|
+
</button>
|
|
236
|
+
</Tip>
|
|
237
|
+
<DialogContent className="sm:max-w-sm">
|
|
238
|
+
<DialogHeader>
|
|
239
|
+
<DialogTitle className="font-mono text-sm">
|
|
240
|
+
{t("code_module.artifacts_delete")} — {entry.name}
|
|
241
|
+
</DialogTitle>
|
|
242
|
+
</DialogHeader>
|
|
243
|
+
<p className="px-1 text-sm text-muted-foreground">
|
|
244
|
+
{t("code_module.artifacts_delete_confirm")}
|
|
245
|
+
</p>
|
|
246
|
+
<DialogFooter>
|
|
247
|
+
<DialogClose
|
|
248
|
+
render={
|
|
249
|
+
<button
|
|
250
|
+
type="button"
|
|
251
|
+
className="rounded px-3 py-1.5 text-xs font-medium hover:bg-accent"
|
|
252
|
+
/>
|
|
253
|
+
}
|
|
254
|
+
>
|
|
255
|
+
Cancelar
|
|
256
|
+
</DialogClose>
|
|
257
|
+
<button
|
|
258
|
+
type="button"
|
|
259
|
+
onClick={() => void remove()}
|
|
260
|
+
disabled={deleting}
|
|
140
261
|
className={cn(
|
|
141
|
-
"
|
|
142
|
-
|
|
143
|
-
? "bg-
|
|
144
|
-
: "bg-rose-500/15 text-rose-700 dark:text-rose-300",
|
|
262
|
+
"inline-flex items-center gap-1.5 rounded px-3 py-1.5 text-xs font-medium",
|
|
263
|
+
deleting
|
|
264
|
+
? "bg-muted text-muted-foreground"
|
|
265
|
+
: "bg-rose-500/15 text-rose-700 hover:bg-rose-500/25 dark:text-rose-300",
|
|
145
266
|
)}
|
|
146
267
|
>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
268
|
+
{deleting && <Spinner size={10} />}
|
|
269
|
+
Eliminar
|
|
270
|
+
</button>
|
|
271
|
+
</DialogFooter>
|
|
272
|
+
</DialogContent>
|
|
273
|
+
</Dialog>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<div className="mt-1 text-[10px] text-muted-foreground">
|
|
277
|
+
{t("code_module.artifacts_run_hint")}{" "}
|
|
278
|
+
<code className="rounded bg-muted px-1 font-mono">
|
|
279
|
+
apx artifact run {entry.name}
|
|
280
|
+
</code>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
{/* Run result display */}
|
|
284
|
+
{runResult && (
|
|
285
|
+
<div className="space-y-1">
|
|
286
|
+
<div className="flex items-center gap-2 text-[10px]">
|
|
287
|
+
<span
|
|
288
|
+
className={cn(
|
|
289
|
+
"rounded px-1.5 py-0.5 font-mono",
|
|
290
|
+
runResult.ok
|
|
291
|
+
? "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300"
|
|
292
|
+
: "bg-rose-500/15 text-rose-700 dark:text-rose-300",
|
|
158
293
|
)}
|
|
159
|
-
|
|
160
|
-
|
|
294
|
+
>
|
|
295
|
+
exit {runResult.exitCode ?? runResult.signal ?? "?"}
|
|
296
|
+
</span>
|
|
297
|
+
{runResult.timedOut && (
|
|
298
|
+
<span className="rounded bg-amber-500/15 px-1.5 py-0.5 font-mono text-amber-700 dark:text-amber-300">
|
|
299
|
+
timeout
|
|
161
300
|
</span>
|
|
162
|
-
</div>
|
|
163
|
-
{runResult.stdout && (
|
|
164
|
-
<pre className="max-h-32 overflow-auto rounded bg-background/60 p-2 text-[10px] leading-tight">
|
|
165
|
-
{runResult.stdout}
|
|
166
|
-
</pre>
|
|
167
301
|
)}
|
|
168
|
-
{runResult.
|
|
169
|
-
<
|
|
170
|
-
|
|
171
|
-
</
|
|
302
|
+
{runResult.truncated && (
|
|
303
|
+
<span className="rounded bg-amber-500/15 px-1.5 py-0.5 font-mono text-amber-700 dark:text-amber-300">
|
|
304
|
+
truncated
|
|
305
|
+
</span>
|
|
172
306
|
)}
|
|
307
|
+
<span className="font-mono text-muted-foreground">
|
|
308
|
+
{runResult.durationMs}ms
|
|
309
|
+
</span>
|
|
173
310
|
</div>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
311
|
+
{runResult.stdout && (
|
|
312
|
+
<pre className="max-h-32 overflow-auto rounded bg-background/60 p-2 text-[10px] leading-tight">
|
|
313
|
+
{runResult.stdout}
|
|
314
|
+
</pre>
|
|
315
|
+
)}
|
|
316
|
+
{runResult.stderr && (
|
|
317
|
+
<pre className="max-h-32 overflow-auto rounded bg-rose-500/5 p-2 text-[10px] leading-tight text-rose-700 dark:text-rose-300">
|
|
318
|
+
{runResult.stderr}
|
|
319
|
+
</pre>
|
|
320
|
+
)}
|
|
321
|
+
</div>
|
|
322
|
+
)}
|
|
323
|
+
</div>
|
|
186
324
|
</li>
|
|
187
325
|
);
|
|
188
326
|
}
|
|
189
327
|
|
|
190
328
|
// Artifacts tab: managed files stored under <project>/artifacts/. The agent
|
|
191
329
|
// puts reusable scripts here so the user can run them from a terminal.
|
|
192
|
-
export function CodeArtifactsTab({ pid }: Props) {
|
|
330
|
+
export function CodeArtifactsTab({ pid, onRunInTerminal, onEditArtifact }: Props) {
|
|
193
331
|
const list = useSWR(pid ? ["artifacts", pid] : null, () => Artifacts.list(pid));
|
|
194
332
|
const entries = list.data || [];
|
|
195
333
|
return (
|
|
@@ -200,14 +338,15 @@ export function CodeArtifactsTab({ pid }: Props) {
|
|
|
200
338
|
? t("code_module.artifacts_count", { n: entries.length })
|
|
201
339
|
: ""}
|
|
202
340
|
</span>
|
|
203
|
-
<
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
341
|
+
<Tip content="Recargar">
|
|
342
|
+
<button
|
|
343
|
+
type="button"
|
|
344
|
+
onClick={() => void list.mutate()}
|
|
345
|
+
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
346
|
+
>
|
|
347
|
+
{list.isLoading ? <Spinner size={12} /> : <RefreshCw className="size-3" />}
|
|
348
|
+
</button>
|
|
349
|
+
</Tip>
|
|
211
350
|
</div>
|
|
212
351
|
<div className="min-h-0 flex-1 overflow-y-auto px-3 pb-3">
|
|
213
352
|
{entries.length === 0 ? (
|
|
@@ -220,6 +359,9 @@ export function CodeArtifactsTab({ pid }: Props) {
|
|
|
220
359
|
pid={pid}
|
|
221
360
|
entry={a}
|
|
222
361
|
onDeleted={() => void list.mutate()}
|
|
362
|
+
onRenamed={() => void list.mutate()}
|
|
363
|
+
onRunInTerminal={onRunInTerminal}
|
|
364
|
+
onEditArtifact={onEditArtifact}
|
|
223
365
|
/>
|
|
224
366
|
))}
|
|
225
367
|
</ul>
|
|
@@ -3,6 +3,7 @@ import { ChevronRight, FilePlus2, FilePen, FileX2, RefreshCw } from "lucide-reac
|
|
|
3
3
|
import { cn } from "../../lib/cn";
|
|
4
4
|
import { t } from "../../i18n";
|
|
5
5
|
import { Empty, Spinner } from "../ui";
|
|
6
|
+
import { Tip } from "../ui/tip";
|
|
6
7
|
import { DiffView } from "./DiffView";
|
|
7
8
|
import type { CodeChanges, CodeFileChange } from "../../lib/api/code";
|
|
8
9
|
|
|
@@ -60,14 +61,15 @@ export function CodeChangesTab({ changes, loading, onRefresh }: Props) {
|
|
|
60
61
|
<span className="text-[11px] text-muted-foreground">
|
|
61
62
|
{files.length > 0 ? t("code_module.changes_files", { n: files.length }) : ""}
|
|
62
63
|
</span>
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
<Tip content="Recargar">
|
|
65
|
+
<button
|
|
66
|
+
type="button"
|
|
67
|
+
onClick={onRefresh}
|
|
68
|
+
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
69
|
+
>
|
|
70
|
+
{loading ? <Spinner size={12} /> : <RefreshCw className="size-3" />}
|
|
71
|
+
</button>
|
|
72
|
+
</Tip>
|
|
71
73
|
</div>
|
|
72
74
|
<div className="min-h-0 flex-1 overflow-y-auto px-3 pb-3">
|
|
73
75
|
{changes && !changes.git ? (
|
|
@@ -2,6 +2,7 @@ import { Hammer, ClipboardList } from "lucide-react";
|
|
|
2
2
|
import { cn } from "../../lib/cn";
|
|
3
3
|
import { t } from "../../i18n";
|
|
4
4
|
import { ChatInput } from "../ui/chat-input";
|
|
5
|
+
import { Tip } from "../ui/tip";
|
|
5
6
|
import { ModelPicker } from "../chat/ModelPicker";
|
|
6
7
|
import type { CodeMode } from "../../lib/api/code";
|
|
7
8
|
|
|
@@ -29,22 +30,23 @@ function ModeToggle({
|
|
|
29
30
|
disabled?: boolean;
|
|
30
31
|
}) {
|
|
31
32
|
const item = (m: CodeMode, label: string, hint: string, Icon: typeof Hammer) => (
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
33
|
+
<Tip content={hint}>
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
disabled={disabled}
|
|
37
|
+
data-testid={`code-mode-${m}`}
|
|
38
|
+
aria-pressed={mode === m}
|
|
39
|
+
onClick={() => onChange(m)}
|
|
40
|
+
className={cn(
|
|
41
|
+
"flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-colors disabled:opacity-50",
|
|
42
|
+
mode === m
|
|
43
|
+
? "bg-background text-foreground shadow-sm"
|
|
44
|
+
: "text-muted-foreground hover:text-foreground",
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
<Icon className="size-3.5" /> {label}
|
|
48
|
+
</button>
|
|
49
|
+
</Tip>
|
|
48
50
|
);
|
|
49
51
|
return (
|
|
50
52
|
<div className="flex items-center gap-0.5 rounded-lg border border-border bg-muted/60 p-0.5">
|
|
@@ -75,7 +77,8 @@ export function CodeComposer({
|
|
|
75
77
|
busy={busy}
|
|
76
78
|
disabled={disabled}
|
|
77
79
|
placeholder={t("code_module.placeholder")}
|
|
78
|
-
|
|
80
|
+
minRows={1}
|
|
81
|
+
maxRows={6}
|
|
79
82
|
footer={
|
|
80
83
|
<div className="flex items-center gap-2">
|
|
81
84
|
<ModeToggle mode={mode} onChange={onModeChange} disabled={busy} />
|