loopat 0.1.54 → 0.1.55
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/server/src/git-host.ts +5 -1
- package/server/src/github.ts +21 -0
- package/server/src/loops.ts +14 -0
- package/web/dist/assets/{Editor-BN7Q5g4N.js → Editor-onKcF7Hk.js} +1 -1
- package/web/dist/assets/{Markdown-DvFvJx3d.js → Markdown-TRpyMaSJ.js} +1 -1
- package/web/dist/assets/{MilkdownEditor-DsqsnlSK.js → MilkdownEditor-CPzvRlCr.js} +1 -1
- package/web/dist/assets/{architectureDiagram-3BPJPVTR-BiL4yrd-.js → architectureDiagram-3BPJPVTR-BqWvl6_Z.js} +1 -1
- package/web/dist/assets/{chunk-727SXJPM-DB5VAPjb.js → chunk-727SXJPM-DgAepYue.js} +1 -1
- package/web/dist/assets/{chunk-AQP2D5EJ-zKFdEFYM.js → chunk-AQP2D5EJ-DSPZ2zOm.js} +1 -1
- package/web/dist/assets/{classDiagram-4FO5ZUOK-DGIJJBY5.js → classDiagram-4FO5ZUOK-CRIzZW-i.js} +1 -1
- package/web/dist/assets/{classDiagram-v2-Q7XG4LA2-BdsZBmZ_.js → classDiagram-v2-Q7XG4LA2-CWOJ0KP_.js} +1 -1
- package/web/dist/assets/{diagram-2AECGRRQ-DE80i5fc.js → diagram-2AECGRRQ-aYV3991Q.js} +1 -1
- package/web/dist/assets/{diagram-5GNKFQAL-Egr34zuZ.js → diagram-5GNKFQAL-430D0ULK.js} +1 -1
- package/web/dist/assets/{diagram-LMA3HP47-f9ekzEBK.js → diagram-LMA3HP47-B_1CjDrM.js} +1 -1
- package/web/dist/assets/{diagram-OG6HWLK6-CJhcoTKU.js → diagram-OG6HWLK6-DS7uDEpu.js} +1 -1
- package/web/dist/assets/{erDiagram-TEJ5UH35-CYNgf9or.js → erDiagram-TEJ5UH35-KifhsjrY.js} +1 -1
- package/web/dist/assets/{flowDiagram-I6XJVG4X-BXTQ7QRg.js → flowDiagram-I6XJVG4X-CUF2AkfX.js} +1 -1
- package/web/dist/assets/{index-BbAx7IhR.js → index-B60_8DVa.js} +6 -6
- package/web/dist/assets/{infoDiagram-5YYISTIA-Be0OcURm.js → infoDiagram-5YYISTIA-BWBMucTX.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-YF4QCWOH-BFyPbPqD.js → ishikawaDiagram-YF4QCWOH-B08GNYsG.js} +1 -1
- package/web/dist/assets/{kanban-definition-UN3LZRKU-C5BAKIQX.js → kanban-definition-UN3LZRKU-B5M0uLn9.js} +1 -1
- package/web/dist/assets/{mindmap-definition-RKZ34NQL-DqgCcW-r.js → mindmap-definition-RKZ34NQL-BcWz14bu.js} +1 -1
- package/web/dist/assets/{pieDiagram-4H26LBE5-BP9tC1HT.js → pieDiagram-4H26LBE5-BeDVs-my.js} +1 -1
- package/web/dist/assets/{requirementDiagram-4Y6WPE33-B_O_cEpd.js → requirementDiagram-4Y6WPE33-AQfIOy53.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-3UESZ5HK-DBBZS2WD.js → sequenceDiagram-3UESZ5HK--egU0Cm3.js} +1 -1
- package/web/dist/assets/{stateDiagram-AJRCARHV-BaH2s_og.js → stateDiagram-AJRCARHV-CbyXuf3R.js} +1 -1
- package/web/dist/assets/{stateDiagram-v2-BHNVJYJU-Ryor5k_i.js → stateDiagram-v2-BHNVJYJU-CeJTs4x0.js} +1 -1
- package/web/dist/assets/{timeline-definition-PNZ67QCA-BNvy_0DN.js → timeline-definition-PNZ67QCA-DvSJjt5p.js} +1 -1
- package/web/dist/assets/{vennDiagram-CIIHVFJN-D5RSWRYm.js → vennDiagram-CIIHVFJN-BFBGrdGc.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-YWT4CUSO-5PntQ1A_.js → wardleyDiagram-YWT4CUSO-9QmHh5AB.js} +1 -1
- package/web/dist/assets/{xychartDiagram-2RQKCTM6-B_1m_ITe.js → xychartDiagram-2RQKCTM6-DwH6-8zP.js} +1 -1
- package/web/dist/index.html +1 -1
package/package.json
CHANGED
package/server/src/git-host.ts
CHANGED
|
@@ -25,12 +25,16 @@ export type OnboardingField = {
|
|
|
25
25
|
type?: "password" | "text"
|
|
26
26
|
help?: string
|
|
27
27
|
placeholder?: string
|
|
28
|
+
/** Prefill value (e.g. the seeded baseUrl/model) — user can edit. */
|
|
29
|
+
value?: string
|
|
28
30
|
/**
|
|
29
31
|
* - "vault-env": store the submitted value in the vault under `name`.
|
|
30
32
|
* - "personal-repo-token": use the submitted value as the git token to
|
|
31
33
|
* provision + import the user's personal repo.
|
|
34
|
+
* - "provider-field": write to the default provider's `name` field
|
|
35
|
+
* (e.g. baseUrl, model) in personal config.json.
|
|
32
36
|
*/
|
|
33
|
-
action: "vault-env" | "personal-repo-token"
|
|
37
|
+
action: "vault-env" | "personal-repo-token" | "provider-field"
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export type OnboardingForm = {
|
package/server/src/github.ts
CHANGED
|
@@ -193,6 +193,27 @@ export const githubProvider: GitHostProvider = {
|
|
|
193
193
|
},
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
|
+
// Step 2: need at least one usable AI key. Judge by resolved providers (key
|
|
197
|
+
// already expanded from vault) — repo seeded anthropic with a baseUrl/model.
|
|
198
|
+
const providers = (ctx.config?.providers ?? {}) as Record<string, any>
|
|
199
|
+
const hasKey = Object.values(providers).some((p) => p && typeof p.apiKey === "string" && p.apiKey.trim().length > 0)
|
|
200
|
+
const anthropic = providers.anthropic ?? {}
|
|
201
|
+
if (!hasKey) {
|
|
202
|
+
return {
|
|
203
|
+
done: false,
|
|
204
|
+
show: {
|
|
205
|
+
kind: "form",
|
|
206
|
+
title: "配置 AI Provider",
|
|
207
|
+
description: "填一个 API key 才能开始。URL 和 model 可改(默认 Anthropic)。key 存在你自己的加密 vault 里,不上服务器。",
|
|
208
|
+
submitLabel: "保存并开始",
|
|
209
|
+
fields: [
|
|
210
|
+
{ name: "ANTHROPIC_API_KEY", label: "API Key", type: "password", action: "vault-env" },
|
|
211
|
+
{ name: "baseUrl", label: "API URL", type: "text", action: "provider-field", value: anthropic.baseUrl ?? "https://api.anthropic.com" },
|
|
212
|
+
{ name: "model", label: "Model", type: "text", action: "provider-field", value: anthropic.model ?? "claude-opus-4-7" },
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
}
|
|
196
217
|
return { done: true }
|
|
197
218
|
},
|
|
198
219
|
async authenticate(cred) {
|
package/server/src/loops.ts
CHANGED
|
@@ -772,6 +772,7 @@ export async function submitOnboarding(
|
|
|
772
772
|
// user acting on the real page, then onboarding() re-checks.
|
|
773
773
|
if (cur.show.kind !== "form") return { ok: true, next: cur }
|
|
774
774
|
let wroteVaultEnv = false
|
|
775
|
+
const providerFields: Record<string, string> = {}
|
|
775
776
|
for (const field of cur.show.fields) {
|
|
776
777
|
const raw = values[field.name]
|
|
777
778
|
const value = typeof raw === "string" ? raw.trim() : ""
|
|
@@ -780,6 +781,8 @@ export async function submitOnboarding(
|
|
|
780
781
|
const r = await writeVaultEnv(userId, vault, field.name, value)
|
|
781
782
|
if (!r.ok) return { ok: false, error: `couldn't save ${field.label}: ${r.error}` }
|
|
782
783
|
wroteVaultEnv = true
|
|
784
|
+
} else if (field.action === "provider-field") {
|
|
785
|
+
providerFields[field.name] = value
|
|
783
786
|
} else if (field.action === "personal-repo-token") {
|
|
784
787
|
const r = await setupPersonalViaProvider({
|
|
785
788
|
userId,
|
|
@@ -791,6 +794,17 @@ export async function submitOnboarding(
|
|
|
791
794
|
if (!r.ok) return { ok: false, error: r.error }
|
|
792
795
|
}
|
|
793
796
|
}
|
|
797
|
+
// Write provider-field edits (baseUrl / model) into the default provider.
|
|
798
|
+
if (Object.keys(providerFields).length > 0) {
|
|
799
|
+
const { readPersonalDiskRaw, savePersonalDisk } = await import("./config")
|
|
800
|
+
const disk = await readPersonalDiskRaw(userId)
|
|
801
|
+
const def = (disk.providers?.default as string) || "anthropic"
|
|
802
|
+
const p = { ...(disk.providers?.[def] as any) }
|
|
803
|
+
if (providerFields.baseUrl) p.baseUrl = providerFields.baseUrl
|
|
804
|
+
if (providerFields.model) { p.model = providerFields.model; p.models = [{ id: providerFields.model, enabled: true }] }
|
|
805
|
+
await savePersonalDisk(userId, { providers: { ...disk.providers, [def]: p } as any })
|
|
806
|
+
wroteVaultEnv = true // ensure the config change is pushed below
|
|
807
|
+
}
|
|
794
808
|
// Persist vault edits (e.g. an api key) to the personal remote — committed but
|
|
795
809
|
// unpushed secrets are lost on re-import / another machine.
|
|
796
810
|
if (wroteVaultEnv) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{i as e}from"./chunk-62oNxeRG.js";import{n as t,t as n}from"./jsx-runtime-ZP0XCtgh.js";import{d as r,f as i,u as a}from"./index-
|
|
1
|
+
import{i as e}from"./chunk-62oNxeRG.js";import{n as t,t as n}from"./jsx-runtime-ZP0XCtgh.js";import{d as r,f as i,u as a}from"./index-B60_8DVa.js";import{CodeEditor as o}from"./CodeEditor-ROIg2ylA.js";var s=e(t(),1),c=n();function l(){try{if(localStorage.getItem(`loopat:editor:wordWrap`)===`0`)return!1}catch{}return!0}function u({loopId:e,path:t,onSelectionChange:n}){let[u,d]=(0,s.useState)(``),[f,p]=(0,s.useState)(``),[m,h]=(0,s.useState)(!1),[g,_]=(0,s.useState)(!1),[v,y]=(0,s.useState)(l);(0,s.useEffect)(()=>{if(!t){d(``),p(``);return}h(!0),a(e,t).then(e=>{let t=e?.content??``;d(t),p(t)}).finally(()=>h(!1))},[e,t]);let b=t&&f!==u,x=async()=>{if(!(!t||g)){_(!0);try{await r(e,t,f)&&d(f)}finally{_(!1)}}};return t?(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(`div`,{className:`flex-1 min-h-0 relative`,onKeyDown:e=>{(e.metaKey||e.ctrlKey)&&e.key===`s`&&(e.preventDefault(),x())},children:m?(0,c.jsx)(`div`,{className:`h-full w-full flex items-center justify-center text-[12px] text-gray-400`,children:`loading…`}):(0,c.jsx)(o,{path:t,value:f,onChange:p,wordWrap:v,onSelectionChange:n})}),(0,c.jsxs)(`div`,{className:`border-t border-gray-200 px-3 py-1.5 text-[11px] text-gray-500 flex items-center gap-3`,children:[(0,c.jsx)(`span`,{className:`truncate`,children:t}),b&&(0,c.jsx)(`button`,{onClick:x,className:`text-orange-600 hover:underline`,title:`ctrl/⌘+S`,children:g?`saving…`:`unsaved · save`}),(0,c.jsx)(`span`,{className:`flex-1`}),(0,c.jsx)(`button`,{onClick:()=>{let e=!v;y(e);try{localStorage.setItem(`loopat:editor:wordWrap`,e?`1`:`0`)}catch{}},className:`flex items-center gap-1 hover:text-gray-700 transition-colors ${v?`text-gray-500`:`text-gray-300`}`,title:v?`word wrap: on`:`word wrap: off`,children:(0,c.jsx)(i,{size:13})}),(0,c.jsx)(`span`,{children:`utf-8 · LF`})]})]}):(0,c.jsx)(`div`,{className:`flex-1 min-h-0 flex items-center justify-center text-[13px] text-gray-500 px-8 text-center`,children:`没打开文件 · 在 ▤ workdir 里点一个`})}export{u as Editor};
|