feat: add incident delete and clarify AI assistant settings
Release APK / build (push) Has been cancelled
Release APK / build (push) Has been cancelled
Wire deleteIncident() into IncidentDetailScreen with a destructive confirm dialog. Expand the AI settings section with base URL + model fields and an explanatory card documenting the OpenAI-compatible use case (Ollama / OpenAI). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+37
-1
@@ -10,7 +10,7 @@ import {
|
|||||||
import { useLocalSearchParams, router, useNavigation } from "expo-router";
|
import { useLocalSearchParams, router, useNavigation } from "expo-router";
|
||||||
import * as Clipboard from "expo-clipboard";
|
import * as Clipboard from "expo-clipboard";
|
||||||
import * as SecureStore from "expo-secure-store";
|
import * as SecureStore from "expo-secure-store";
|
||||||
import { getIncidentById, updateIncident } from "@/lib/db";
|
import { getIncidentById, updateIncident, deleteIncident } from "@/lib/db";
|
||||||
import { renderMarkdown } from "@/lib/markdown";
|
import { renderMarkdown } from "@/lib/markdown";
|
||||||
import { pushIncidentToGit } from "@/lib/git";
|
import { pushIncidentToGit } from "@/lib/git";
|
||||||
import type { GitProvider } from "@/lib/git";
|
import type { GitProvider } from "@/lib/git";
|
||||||
@@ -54,6 +54,25 @@ export default function IncidentDetailScreen() {
|
|||||||
}
|
}
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
|
async function handleDelete() {
|
||||||
|
if (!incident) return;
|
||||||
|
Alert.alert(
|
||||||
|
"Delete incident?",
|
||||||
|
"This action cannot be undone.",
|
||||||
|
[
|
||||||
|
{ text: "Cancel", style: "cancel" },
|
||||||
|
{
|
||||||
|
text: "Delete",
|
||||||
|
style: "destructive",
|
||||||
|
onPress: async () => {
|
||||||
|
await deleteIncident(incident.id);
|
||||||
|
router.replace("/");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function handleResolve() {
|
async function handleResolve() {
|
||||||
if (!incident) return;
|
if (!incident) return;
|
||||||
await updateIncident(incident.id, { status: "resolved" });
|
await updateIncident(incident.id, { status: "resolved" });
|
||||||
@@ -291,6 +310,23 @@ export default function IncidentDetailScreen() {
|
|||||||
Edit
|
Edit
|
||||||
</Text>
|
</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
||||||
|
<Pressable
|
||||||
|
onPress={handleDelete}
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#ef444410",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: "#ef4444",
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: 14,
|
||||||
|
alignItems: "center",
|
||||||
|
marginTop: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={{ color: "#ef4444", fontWeight: "600", fontSize: 15 }}>
|
||||||
|
Delete Incident
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
|||||||
+51
-9
@@ -17,6 +17,8 @@ const KEYS = {
|
|||||||
HOME_VIEW: "pref_home_view",
|
HOME_VIEW: "pref_home_view",
|
||||||
AI_ENABLED: "pref_ai_enabled",
|
AI_ENABLED: "pref_ai_enabled",
|
||||||
AI_KEY: "pref_ai_key",
|
AI_KEY: "pref_ai_key",
|
||||||
|
AI_BASE_URL: "pref_ai_base_url",
|
||||||
|
AI_MODEL: "pref_ai_model",
|
||||||
MD_TEMPLATE: "md_template",
|
MD_TEMPLATE: "md_template",
|
||||||
GIT_PROVIDER: "git_provider",
|
GIT_PROVIDER: "git_provider",
|
||||||
// Gitea
|
// Gitea
|
||||||
@@ -75,6 +77,8 @@ export default function SettingsScreen() {
|
|||||||
const [homeView, setHomeView] = useState<"dashboard" | "capture">("dashboard");
|
const [homeView, setHomeView] = useState<"dashboard" | "capture">("dashboard");
|
||||||
const [aiEnabled, setAiEnabled] = useState(false);
|
const [aiEnabled, setAiEnabled] = useState(false);
|
||||||
const [aiKey, setAiKey] = useState("");
|
const [aiKey, setAiKey] = useState("");
|
||||||
|
const [aiBaseUrl, setAiBaseUrl] = useState("");
|
||||||
|
const [aiModel, setAiModel] = useState("");
|
||||||
const [mdTemplate, setMdTemplate] = useState(DEFAULT_TEMPLATE);
|
const [mdTemplate, setMdTemplate] = useState(DEFAULT_TEMPLATE);
|
||||||
const [saved, setSaved] = useState(false);
|
const [saved, setSaved] = useState(false);
|
||||||
|
|
||||||
@@ -97,12 +101,14 @@ export default function SettingsScreen() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const [im, hv, ai, key, tpl, gp, gu, gt, go, gr, ghu, ghown, ghrepo, glu, glt, glo, glr] =
|
const [im, hv, ai, key, aiUrl, aiMdl, tpl, gp, gu, gt, go, gr, ghu, ghown, ghrepo, glu, glt, glo, glr] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
SecureStore.getItemAsync(KEYS.INPUT_MODE),
|
SecureStore.getItemAsync(KEYS.INPUT_MODE),
|
||||||
SecureStore.getItemAsync(KEYS.HOME_VIEW),
|
SecureStore.getItemAsync(KEYS.HOME_VIEW),
|
||||||
SecureStore.getItemAsync(KEYS.AI_ENABLED),
|
SecureStore.getItemAsync(KEYS.AI_ENABLED),
|
||||||
SecureStore.getItemAsync(KEYS.AI_KEY),
|
SecureStore.getItemAsync(KEYS.AI_KEY),
|
||||||
|
SecureStore.getItemAsync(KEYS.AI_BASE_URL),
|
||||||
|
SecureStore.getItemAsync(KEYS.AI_MODEL),
|
||||||
SecureStore.getItemAsync(KEYS.MD_TEMPLATE),
|
SecureStore.getItemAsync(KEYS.MD_TEMPLATE),
|
||||||
SecureStore.getItemAsync(KEYS.GIT_PROVIDER),
|
SecureStore.getItemAsync(KEYS.GIT_PROVIDER),
|
||||||
SecureStore.getItemAsync(KEYS.GITEA_URL),
|
SecureStore.getItemAsync(KEYS.GITEA_URL),
|
||||||
@@ -121,6 +127,8 @@ export default function SettingsScreen() {
|
|||||||
if (hv) setHomeView(hv as "dashboard" | "capture");
|
if (hv) setHomeView(hv as "dashboard" | "capture");
|
||||||
if (ai) setAiEnabled(ai === "true");
|
if (ai) setAiEnabled(ai === "true");
|
||||||
if (key) setAiKey(key);
|
if (key) setAiKey(key);
|
||||||
|
if (aiUrl) setAiBaseUrl(aiUrl);
|
||||||
|
if (aiMdl) setAiModel(aiMdl);
|
||||||
if (tpl) setMdTemplate(tpl);
|
if (tpl) setMdTemplate(tpl);
|
||||||
if (gp) setGitProvider(gp as GitProvider);
|
if (gp) setGitProvider(gp as GitProvider);
|
||||||
if (gu) setGiteaUrl(gu);
|
if (gu) setGiteaUrl(gu);
|
||||||
@@ -143,6 +151,8 @@ export default function SettingsScreen() {
|
|||||||
SecureStore.setItemAsync(KEYS.HOME_VIEW, homeView),
|
SecureStore.setItemAsync(KEYS.HOME_VIEW, homeView),
|
||||||
SecureStore.setItemAsync(KEYS.AI_ENABLED, String(aiEnabled)),
|
SecureStore.setItemAsync(KEYS.AI_ENABLED, String(aiEnabled)),
|
||||||
SecureStore.setItemAsync(KEYS.AI_KEY, aiKey),
|
SecureStore.setItemAsync(KEYS.AI_KEY, aiKey),
|
||||||
|
SecureStore.setItemAsync(KEYS.AI_BASE_URL, aiBaseUrl),
|
||||||
|
SecureStore.setItemAsync(KEYS.AI_MODEL, aiModel),
|
||||||
SecureStore.setItemAsync(KEYS.MD_TEMPLATE, mdTemplate),
|
SecureStore.setItemAsync(KEYS.MD_TEMPLATE, mdTemplate),
|
||||||
SecureStore.setItemAsync(KEYS.GIT_PROVIDER, gitProvider),
|
SecureStore.setItemAsync(KEYS.GIT_PROVIDER, gitProvider),
|
||||||
SecureStore.setItemAsync(KEYS.GITEA_URL, giteaUrl),
|
SecureStore.setItemAsync(KEYS.GITEA_URL, giteaUrl),
|
||||||
@@ -300,20 +310,52 @@ export default function SettingsScreen() {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text style={SECTION_TITLE}>AI (optional)</Text>
|
<Text style={SECTION_TITLE}>AI — Root Cause Assistant</Text>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
backgroundColor: Colors.surface,
|
||||||
|
borderRadius: 8,
|
||||||
|
padding: 12,
|
||||||
|
marginBottom: 14,
|
||||||
|
borderLeftWidth: 3,
|
||||||
|
borderLeftColor: Colors.text2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={{ color: Colors.text2, fontSize: 13, lineHeight: 20 }}>
|
||||||
|
When enabled, a button appears on the capture form to suggest a root cause and fix
|
||||||
|
based on the incident title and symptom. Uses any OpenAI-compatible API —
|
||||||
|
set the base URL below to point at a local Ollama instance or leave empty
|
||||||
|
for OpenAI (api.openai.com).
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
<ToggleRow
|
<ToggleRow
|
||||||
label="Enable AI assistance"
|
label="Enable AI assistance"
|
||||||
value={aiEnabled}
|
value={aiEnabled}
|
||||||
onValueChange={setAiEnabled}
|
onValueChange={setAiEnabled}
|
||||||
/>
|
/>
|
||||||
{aiEnabled && (
|
{aiEnabled && (
|
||||||
<Field
|
<>
|
||||||
label="API Key"
|
<Field
|
||||||
value={aiKey}
|
label="API Base URL"
|
||||||
onChange={setAiKey}
|
value={aiBaseUrl}
|
||||||
placeholder="sk-..."
|
onChange={setAiBaseUrl}
|
||||||
secure
|
placeholder="http://192.168.x.x:11434/v1 (empty = OpenAI)"
|
||||||
/>
|
url
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
label="Model"
|
||||||
|
value={aiModel}
|
||||||
|
onChange={setAiModel}
|
||||||
|
placeholder="gpt-4o-mini or llama3.2"
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
label="API Key"
|
||||||
|
value={aiKey}
|
||||||
|
onChange={setAiKey}
|
||||||
|
placeholder="sk-... (leave empty for Ollama)"
|
||||||
|
secure
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Text style={SECTION_TITLE}>Git Repository</Text>
|
<Text style={SECTION_TITLE}>Git Repository</Text>
|
||||||
|
|||||||
Reference in New Issue
Block a user