feat: multi-provider AI assistant, repo URL simplification
Release APK / build (push) Has been cancelled
Release APK / build (push) Has been cancelled
- lib/ai.ts: unified AI client supporting Anthropic (native format) and OpenAI-compatible providers (OpenAI, Perplexity, Ollama, OpenRouter, custom) - settings: 6-provider chip selector, base URL auto-filled on preset select, Anthropic URL hidden (fixed), model/key placeholders per provider - settings: git repo config replaced by single Repo URL field (.git paste) auto-parsed to instance URL + owner + repo at save time - new.tsx: Suggest button between Symptom and Root Cause, visible when AI enabled and title+symptom filled; pre-fills rootCause and fix inline Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+47
@@ -19,6 +19,7 @@ import {
|
||||
} from "@/lib/db";
|
||||
import { Colors } from "@/constants/theme";
|
||||
import { useVoice } from "@/hooks/useVoice";
|
||||
import { callAI } from "@/lib/ai";
|
||||
import type { IncidentDraft } from "@/types/incident";
|
||||
|
||||
const FIELD_STYLE = {
|
||||
@@ -72,6 +73,9 @@ export default function NewIncidentScreen() {
|
||||
});
|
||||
const [tagInput, setTagInput] = useState("");
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [suggesting, setSuggesting] = useState(false);
|
||||
const [suggestError, setSuggestError] = useState("");
|
||||
const [aiEnabled, setAiEnabled] = useState(false);
|
||||
const [knownServices, setKnownServices] = useState<string[]>([]);
|
||||
const [serviceFocused, setServiceFocused] = useState(false);
|
||||
|
||||
@@ -81,6 +85,7 @@ export default function NewIncidentScreen() {
|
||||
|
||||
useEffect(() => {
|
||||
getDistinctServices().then(setKnownServices);
|
||||
SecureStore.getItemAsync("pref_ai_enabled").then((v) => setAiEnabled(v === "true"));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -149,6 +154,19 @@ export default function NewIncidentScreen() {
|
||||
setForm((f) => ({ ...f, tags: f.tags.filter((t) => t !== tag) }));
|
||||
}
|
||||
|
||||
async function handleSuggest() {
|
||||
setSuggesting(true);
|
||||
setSuggestError("");
|
||||
try {
|
||||
const result = await callAI(form.title, form.symptom);
|
||||
setForm((f) => ({ ...f, rootCause: result.rootCause, fix: result.fix }));
|
||||
} catch (e) {
|
||||
setSuggestError(e instanceof Error ? e.message : "AI call failed");
|
||||
} finally {
|
||||
setSuggesting(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!form.title.trim()) {
|
||||
Alert.alert("Required", "Title is required.");
|
||||
@@ -298,6 +316,35 @@ export default function NewIncidentScreen() {
|
||||
returnKeyType="next"
|
||||
/>
|
||||
|
||||
{aiEnabled && form.title.trim() && form.symptom.trim() && (
|
||||
<View style={{ marginBottom: 16 }}>
|
||||
<Pressable
|
||||
onPress={handleSuggest}
|
||||
disabled={suggesting}
|
||||
style={{
|
||||
backgroundColor: suggesting ? Colors.surface2 : Colors.surface,
|
||||
borderRadius: 8,
|
||||
padding: 12,
|
||||
alignItems: "center",
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.primary,
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: Colors.primary, fontSize: 14, fontWeight: "700" }}>
|
||||
{suggesting ? "Analyzing…" : "✦ Suggest root cause & fix"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
{suggestError !== "" && (
|
||||
<Text style={{ color: Colors.danger, fontSize: 12, marginTop: 6 }}>
|
||||
{suggestError}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{labelRow("Root Cause", "rootCause")}
|
||||
<TextInput
|
||||
style={MONO_FIELD_STYLE}
|
||||
|
||||
Reference in New Issue
Block a user