enigma-memory 0.1.18 → 0.1.22
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/README.md +76 -24
- package/apps/cli/bin/enigma-desktop.mjs +140 -0
- package/apps/cli/bin/enigma-terminal.mjs +78 -0
- package/apps/cli/bin/enigma.mjs +1923 -285
- package/apps/desktop/electron-main.cjs +217 -0
- package/apps/desktop/package.json +12 -0
- package/apps/desktop/src/app.js +264 -7
- package/apps/desktop/src/index.html +3514 -1373
- package/apps/desktop/src/launch-electron.mjs +51 -0
- package/apps/desktop/src/server.mjs +2914 -0
- package/apps/desktop/src/styles.css +2972 -260
- package/apps/desktop/src/zk-browser-prove.mjs +53 -0
- package/apps/desktop/src/zk-state.mjs +1789 -0
- package/apps/gateway/bin/enigma-gateway.mjs +102 -5
- package/apps/gateway/src/server.mjs +271 -8
- package/apps/ios/EnigmaCore/Package.swift +12 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/EnigmaAPIClient.swift +227 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/Models.swift +278 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/PKCE.swift +96 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/PrivacyMinimizer.swift +187 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/ToolModels.swift +129 -0
- package/apps/ios/EnigmaCore/Tests/EnigmaCoreTests/EnigmaCoreTests.swift +42 -0
- package/apps/ios/EnigmaIOS/Enigma/AppModel.swift +346 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AccentColor.colorset/Contents.json +12 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/Contents.json +11 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/EnigmaAppIcon.png +0 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/Contents.json +3 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/LaunchBackground.colorset/Contents.json +12 -0
- package/apps/ios/EnigmaIOS/Enigma/ChatView.swift +181 -0
- package/apps/ios/EnigmaIOS/Enigma/CouncilView.swift +78 -0
- package/apps/ios/EnigmaIOS/Enigma/CreateView.swift +152 -0
- package/apps/ios/EnigmaIOS/Enigma/EnigmaApp.swift +52 -0
- package/apps/ios/EnigmaIOS/Enigma/Info.plist +52 -0
- package/apps/ios/EnigmaIOS/Enigma/NaturalLanguagePrivacyTagger.swift +26 -0
- package/apps/ios/EnigmaIOS/Enigma/OAuthClient.swift +321 -0
- package/apps/ios/EnigmaIOS/Enigma/OnboardingView.swift +105 -0
- package/apps/ios/EnigmaIOS/Enigma/PrivateVaultView.swift +275 -0
- package/apps/ios/EnigmaIOS/Enigma/SecureStore.swift +76 -0
- package/apps/ios/EnigmaIOS/Enigma/SettingsView.swift +60 -0
- package/apps/ios/EnigmaIOS/Enigma/Theme.swift +80 -0
- package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/project.pbxproj +211 -0
- package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/xcshareddata/xcschemes/Enigma.xcscheme +23 -0
- package/apps/native-host/README.md +19 -8
- package/apps/native-host/bin/enigma-native-host.mjs +229 -13
- package/apps/relay/bin/enigma-relay.mjs +103 -5
- package/apps/relay/src/federation-runtime.mjs +618 -0
- package/apps/relay/src/server.mjs +310 -9
- package/apps/verifier/bin/enigma-verify.mjs +327 -11
- package/cortex-v3/circuits/build/intent_vk_bytes.json +35 -0
- package/cortex-v3/circuits/build/sale_vk_bytes.json +35 -0
- package/cortex-v3/circuits/build/vk_bytes.json +32 -0
- package/cortex-v3/proving-assets.json +64 -0
- package/cortex-v3/zk/BUILD-CONTRACT.md +87 -0
- package/cortex-v3/zk/action-transition-vk.json +119 -0
- package/cortex-v3/zk/alias-adversarial.test.mjs +220 -0
- package/cortex-v3/zk/groth16-verify-child.mjs +17 -0
- package/cortex-v3/zk/intent-witness.mjs +365 -0
- package/cortex-v3/zk/intent-witness.test.mjs +485 -0
- package/cortex-v3/zk/proving-assets.mjs +203 -0
- package/cortex-v3/zk/sale-witness.mjs +783 -0
- package/cortex-v3/zk/sale-witness.test.mjs +784 -0
- package/cortex-v3/zk/sealed-sale-release-vk.json +119 -0
- package/cortex-v3/zk/settlement-evidence.mjs +722 -0
- package/cortex-v3/zk/setup-intent.mjs +688 -0
- package/cortex-v3/zk/setup-sale.mjs +666 -0
- package/cortex-v3/zk/setup.mjs +594 -0
- package/cortex-v3/zk/witness.mjs +184 -0
- package/cortex-v3/zk/zk-codec.mjs +232 -0
- package/cortex-v3/zk/zk-codec.test.mjs +293 -0
- package/cortex-v3/zk/zk-settle.mjs +370 -0
- package/cortex-v3/zk/zk-tree.mjs +256 -0
- package/cortex-v3/zk/zk-tree.test.mjs +419 -0
- package/deploy/docker-compose.local-production-simulation.yml +36 -0
- package/docs/browser-extension-install.md +8 -6
- package/docs/client-connectors.md +15 -11
- package/docs/developer-ecosystem.md +15 -13
- package/docs/enigma-memory-ready-conformance.md +11 -9
- package/docs/install-anywhere.md +61 -28
- package/docs/installers-and-desktop.md +8 -7
- package/docs/novelty-invention-candidates.md +161 -161
- package/docs/proof-network-claim-boundaries.md +320 -318
- package/examples/01-quickstart-agent/index.mjs +49 -0
- package/examples/01_agent_memory_quickstart.mjs +57 -0
- package/examples/02-multi-agent-swarm/index.mjs +57 -0
- package/examples/02_cross_model_passport.mjs +64 -0
- package/examples/03-langchain-memory/index.mjs +41 -0
- package/examples/03_poseidon_commitment_verification.mjs +71 -0
- package/examples/04-python-trading-agent/trader.py +49 -0
- package/examples/README.md +27 -0
- package/examples/ci/github-actions.yml +7 -2
- package/package.json +142 -11
- package/packages/adapters/PACKAGE_CONTRACT.md +1 -1
- package/packages/connectors/src/index.js +196 -4
- package/packages/connectors/swarm-router.mjs +168 -0
- package/packages/core/src/index.js +248 -1
- package/packages/core/src/version.mjs +7 -0
- package/packages/dev-tools/package.json +19 -0
- package/packages/dev-tools/src/index.js +4 -0
- package/packages/dev-tools/src/memory-benchmark-suite.js +112 -0
- package/packages/dev-tools/src/swarm-simulator.js +101 -0
- package/packages/dev-tools/src/vault-inspector.js +114 -0
- package/packages/dev-tools/src/vector-benchmark.js +100 -0
- package/packages/developer-platform/src/access-credentials.js +341 -0
- package/packages/developer-platform/src/http.js +132 -0
- package/packages/developer-platform/src/index.js +4 -0
- package/packages/developer-platform/src/usage-http.js +60 -0
- package/packages/developer-platform/src/usage.js +295 -0
- package/packages/enclave-runtime/attestation.mjs +159 -0
- package/packages/enclave-runtime/index.mjs +47 -0
- package/packages/enclave-runtime/session-manager.mjs +253 -0
- package/packages/enclave-runtime/zeroization-proof.mjs +227 -0
- package/packages/enigma-reflex/package.json +14 -0
- package/packages/enigma-reflex/src/index.js +204 -0
- package/packages/enigma-reflex/training/generate-dataset.mjs +40 -0
- package/packages/enigma-reflex/training/requirements.txt +8 -0
- package/packages/enigma-reflex/training/train.py +314 -0
- package/packages/enigma-weave/LICENSE +22 -0
- package/packages/enigma-weave/UPSTREAM.json +21 -0
- package/packages/enigma-weave/package.json +14 -0
- package/packages/enigma-weave/src/index.js +286 -0
- package/packages/hosted-cloud/src/index.js +80 -5
- package/packages/importers/src/index.js +432 -0
- package/packages/inference-runtime/src/browser.js +401 -0
- package/packages/inference-runtime/src/chat.js +265 -0
- package/packages/inference-runtime/src/code.js +407 -0
- package/packages/inference-runtime/src/contracts.js +162 -0
- package/packages/inference-runtime/src/http.js +232 -0
- package/packages/inference-runtime/src/image.js +186 -0
- package/packages/inference-runtime/src/index.js +10 -0
- package/packages/inference-runtime/src/model-router.js +320 -0
- package/packages/inference-runtime/src/platform.js +125 -0
- package/packages/inference-runtime/src/privacy.js +400 -0
- package/packages/inference-runtime/src/video.js +253 -0
- package/packages/mcp-server/README.md +22 -6
- package/packages/mcp-server/bin/enigma-mcp.mjs +2 -1
- package/packages/mcp-server/src/index.js +1418 -105
- package/packages/mcp-server/src/oauth.js +561 -0
- package/packages/mcp-server/src/private-handoff.js +84 -0
- package/packages/mcp-server/src/remote-http.js +273 -0
- package/packages/mcp-server/src/remote-policy.js +72 -0
- package/packages/mcp-server/swarm-bridge.mjs +361 -0
- package/packages/mesh/index.d.ts +283 -0
- package/packages/mesh/package.json +23 -0
- package/packages/mesh/src/crypto.js +189 -0
- package/packages/mesh/src/federation-packets.js +353 -0
- package/packages/mesh/src/gossip.js +311 -0
- package/packages/mesh/src/index.js +6 -0
- package/packages/mesh/src/protocol.js +255 -0
- package/packages/mesh/src/router.js +279 -0
- package/packages/mesh/src/transport.js +306 -0
- package/packages/passport/src/index.js +426 -1
- package/packages/private-economy/src/credits-http.js +100 -0
- package/packages/private-economy/src/credits.js +447 -0
- package/packages/private-economy/src/index.js +5 -0
- package/packages/private-economy/src/payments-http.js +120 -0
- package/packages/private-economy/src/payments.js +509 -0
- package/packages/private-economy/src/x402.js +346 -0
- package/packages/proof-network/PACKAGE_CONTRACT.md +21 -0
- package/packages/rag/index.d.ts +182 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/THIRD_PARTY_LICENSES.txt +207 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/config.json +25 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/onnx/model_quantized.onnx +0 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/sha256-manifest.json +28 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer.json +30686 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer_config.json +15 -0
- package/packages/rag/package.json +27 -0
- package/packages/rag/src/blinded-search.js +109 -0
- package/packages/rag/src/bm25.js +169 -0
- package/packages/rag/src/embeddings.js +459 -0
- package/packages/rag/src/hybrid.js +76 -0
- package/packages/rag/src/index.js +38 -0
- package/packages/rag/src/reranker.js +61 -0
- package/packages/rag/src/research.js +107 -0
- package/packages/rag/src/vector-store.js +430 -0
- package/packages/rag/src/verify-model-artifacts.mjs +4 -0
- package/packages/sdk/index.d.ts +760 -0
- package/packages/sdk/package.json +33 -0
- package/packages/sdk/python/README.md +24 -0
- package/packages/sdk/python/enigma_sdk.py +250 -0
- package/packages/sdk/python/pyproject.toml +34 -0
- package/packages/sdk/python/requirements.txt +1 -0
- package/packages/sdk/python/setup.py +20 -0
- package/packages/sdk/src/federation/capability-grant.js +389 -0
- package/packages/sdk/src/federation/federation-router.js +360 -0
- package/packages/sdk/src/federation/ghostmesh-bridge.js +497 -0
- package/packages/sdk/src/federation/index.js +3 -0
- package/packages/sdk/src/index.js +1796 -0
- package/packages/sdk/src/intelligence/contradiction.js +337 -0
- package/packages/sdk/src/intelligence/decision-engine.js +155 -0
- package/packages/sdk/src/intelligence/index.js +4 -0
- package/packages/sdk/src/intelligence/ontology.js +122 -0
- package/packages/sdk/src/intelligence/temporal.js +123 -0
- package/packages/sdk/src/market-client.js +142 -0
- package/packages/sdk/src/mesh-client.js +110 -0
- package/packages/sdk/src/middleware/index.js +3 -0
- package/packages/sdk/src/middleware/langchain.js +159 -0
- package/packages/sdk/src/middleware/llamaindex.js +101 -0
- package/packages/sdk/src/middleware/vercel-ai.js +112 -0
- package/packages/sdk/src/rag-client.js +85 -0
- package/packages/sdk/src/swarm-orchestrator.js +260 -0
- package/packages/settlement/PACKAGE_CONTRACT.md +1 -1
- package/packages/snapcompact/THIRD_PARTY_LICENSES.txt +40 -0
- package/packages/snapcompact/assets/8x13-latin1.bdf +3837 -0
- package/packages/snapcompact/index.d.ts +284 -0
- package/packages/snapcompact/package.json +25 -0
- package/packages/snapcompact/src/index.js +716 -0
- package/packages/storage/PACKAGE_CONTRACT.md +1 -1
- package/packages/terminal-console/animations.mjs +240 -0
- package/packages/terminal-console/auto-anchor.mjs +220 -0
- package/packages/terminal-console/banner.mjs +91 -0
- package/packages/terminal-console/commands.mjs +459 -0
- package/packages/terminal-console/delegation.mjs +152 -0
- package/packages/terminal-console/index.mjs +5 -0
- package/packages/terminal-console/outbox.mjs +143 -0
- package/packages/terminal-console/phantom-bridge.mjs +637 -0
- package/packages/terminal-console/repl.mjs +136 -0
- package/packages/terminal-console/signer-store.mjs +130 -0
- package/packages/terminal-console/solana-rpc.mjs +214 -0
- package/packages/terminal-console/solana-transport.mjs +189 -0
- package/packages/terminal-tui/dashboard.mjs +214 -0
- package/packages/terminal-tui/index.mjs +28 -0
- package/packages/terminal-tui/merkle-tree-renderer.mjs +268 -0
- package/packages/terminal-tui/telemetry-hud.mjs +137 -0
- package/packages/vault/index.d.ts +449 -0
- package/packages/vault/package.json +27 -0
- package/packages/vault/src/e2ee.mjs +393 -0
- package/packages/vault/src/enclave.js +481 -0
- package/packages/vault/src/erasure.js +207 -0
- package/packages/vault/src/index.js +1018 -155
- package/packages/vault/src/persistence.js +307 -0
- package/packages/vault/src/poseidon.js +354 -0
- package/packages/vault/src/receipt.js +459 -0
- package/scripts/benchmark-optical-context.mjs +166 -0
- package/scripts/bootstrap-enigma.mjs +502 -0
- package/scripts/build-edge-backend-workers.mjs +20 -5
- package/scripts/build-goal-completion-audit.mjs +72 -25
- package/scripts/build-hosted-api-key-lifecycle.mjs +26 -8
- package/scripts/build-hosted-customer-lifecycle.mjs +20 -3
- package/scripts/build-hosted-probe-worker.mjs +19 -4
- package/scripts/build-installer-assets.mjs +41 -21
- package/scripts/build-operator-evidence-starter.mjs +59 -1
- package/scripts/build-production-backend-env-kit.mjs +2 -0
- package/scripts/build-production-unblocker.mjs +3 -0
- package/scripts/check.mjs +17 -3
- package/scripts/collect-hosted-backend-live-evidence.mjs +49 -12
- package/scripts/install-enigma-local.mjs +18 -5
- package/scripts/release-audit.mjs +65 -109
- package/scripts/release-provenance.mjs +6 -0
- package/scripts/run-backend-readiness-smoke.mjs +112 -10
- package/scripts/scan-secrets.mjs +1 -0
- package/scripts/simulate-production-env.mjs +7 -2
- package/scripts/validate-hosted-backend-live.mjs +112 -1
- package/specs/antibody-pack-v1.schema.json +95 -0
- package/specs/antigen-envelope-v1.schema.json +81 -0
- package/specs/boundary-manifest-v1.schema.json +35 -35
- package/specs/capsule-v1.schema.json +55 -55
- package/specs/claim-boundary-manifest-v1.schema.json +22 -22
- package/specs/claim-ledger-v1.schema.json +291 -0
- package/specs/context-passport-v1.schema.json +59 -0
- package/specs/deletion-tombstone-v1.schema.json +26 -26
- package/specs/evidence-packet-v1.schema.json +177 -0
- package/specs/hosted-backend-live-evidence-v1.schema.json +72 -3
- package/specs/immune-scan-report-v1.schema.json +112 -0
- package/specs/lifecycle-receipt-log-v1.schema.json +67 -0
- package/specs/memory-atom-v1.schema.json +59 -0
- package/specs/memory-event-v1.schema.json +42 -42
- package/specs/passport-v1.schema.json +50 -50
- package/specs/proof-of-non-use-v1.schema.json +65 -0
- package/specs/quarantine-record-v1.schema.json +126 -0
- package/specs/receipt-v1.schema.json +61 -61
- package/specs/state-checkpoint-v1.schema.json +37 -37
- package/specs/trust-bundle-v1.schema.json +56 -56
- package/specs/trust-card-v1.schema.json +119 -0
- package/docs/proof-network-launch-plan.md +0 -421
- package/packages/metering/PACKAGE_CONTRACT.md +0 -20
- package/scripts/build-ai-orchestration-plan.mjs +0 -248
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import EnigmaCore
|
|
2
|
+
import Foundation
|
|
3
|
+
import SwiftUI
|
|
4
|
+
|
|
5
|
+
struct CouncilOpinion: Identifiable, Sendable {
|
|
6
|
+
let id = UUID()
|
|
7
|
+
let model: String
|
|
8
|
+
let answer: String
|
|
9
|
+
let error: String?
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@MainActor
|
|
13
|
+
final class AppModel: ObservableObject {
|
|
14
|
+
@Published var serverAddress: String
|
|
15
|
+
@Published var oauthIssuer: String
|
|
16
|
+
@Published var oauthClientID: String
|
|
17
|
+
@Published var oauthResource: String
|
|
18
|
+
@Published var isConnected = false
|
|
19
|
+
@Published var isBusy = false
|
|
20
|
+
@Published var errorMessage: String?
|
|
21
|
+
@Published var platform: PlatformResponse?
|
|
22
|
+
@Published var models: [PlatformModel] = []
|
|
23
|
+
@Published var selectedModel = "enigma-auto"
|
|
24
|
+
@Published var privacyMode: PrivacyMode = .smart
|
|
25
|
+
@Published var messages: [EnigmaMessage] = []
|
|
26
|
+
@Published var lastPrivacyReceipt: PrivacyReceipt?
|
|
27
|
+
@Published var imageModels: [PlatformModel] = []
|
|
28
|
+
@Published var videoModels: [PlatformModel] = []
|
|
29
|
+
@Published var councilOpinions: [CouncilOpinion] = []
|
|
30
|
+
@Published var generatedImages: [GeneratedImageAsset] = []
|
|
31
|
+
@Published var creditsVault: CreditsVault?
|
|
32
|
+
@Published var recoveryCodeShownOnce: String?
|
|
33
|
+
@Published var videoJob: VideoJob?
|
|
34
|
+
@Published var videoAsset: VideoAsset?
|
|
35
|
+
@Published var codeOutput = ""
|
|
36
|
+
@Published var codeSession: CodeSession?
|
|
37
|
+
@Published var browserSession: BrowserSession?
|
|
38
|
+
@Published var browserViewerOrigin: String
|
|
39
|
+
@Published var creditPackages: [CreditPackage] = []
|
|
40
|
+
@Published var usage: UsageAggregate?
|
|
41
|
+
|
|
42
|
+
private let broker = CredentialBroker()
|
|
43
|
+
private let privacy = PrivacyMinimizer(semanticTagger: NaturalLanguagePrivacyTagger())
|
|
44
|
+
private let secureStore = SecureStore()
|
|
45
|
+
private var client: EnigmaAPIClient?
|
|
46
|
+
|
|
47
|
+
init(defaults: UserDefaults = .standard) {
|
|
48
|
+
serverAddress = defaults.string(forKey: "enigma.server") ?? ""
|
|
49
|
+
oauthIssuer = defaults.string(forKey: "enigma.oauth.issuer") ?? ""
|
|
50
|
+
oauthClientID = defaults.string(forKey: "enigma.oauth.client") ?? ""
|
|
51
|
+
oauthResource = defaults.string(forKey: "enigma.oauth.resource") ?? ""
|
|
52
|
+
browserViewerOrigin = defaults.string(forKey: "enigma.browser.viewerOrigin") ?? ""
|
|
53
|
+
configureClient()
|
|
54
|
+
Task { await restoreConnection() }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var hasServer: Bool { client != nil }
|
|
58
|
+
var displayModel: String { selectedModel == "enigma-auto" ? "Auto" : selectedModel }
|
|
59
|
+
|
|
60
|
+
func configure(server: String, issuer: String, clientID: String, resource: String) {
|
|
61
|
+
serverAddress = server.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
62
|
+
oauthIssuer = issuer.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
63
|
+
oauthClientID = clientID.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
64
|
+
oauthResource = resource.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
65
|
+
let defaults = UserDefaults.standard
|
|
66
|
+
defaults.set(serverAddress, forKey: "enigma.server")
|
|
67
|
+
defaults.set(oauthIssuer, forKey: "enigma.oauth.issuer")
|
|
68
|
+
defaults.set(oauthClientID, forKey: "enigma.oauth.client")
|
|
69
|
+
defaults.set(oauthResource, forKey: "enigma.oauth.resource")
|
|
70
|
+
configureClient()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func connectWithOAuth() async {
|
|
74
|
+
guard let issuer = URL(string: oauthIssuer), let resource = URL(string: oauthResource) else {
|
|
75
|
+
errorMessage = "Enter the OAuth issuer and protected resource URL."
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
await perform {
|
|
79
|
+
let credential = try await OAuthClient().authorize(
|
|
80
|
+
issuer: issuer,
|
|
81
|
+
clientID: self.oauthClientID.isEmpty ? nil : self.oauthClientID,
|
|
82
|
+
resource: resource
|
|
83
|
+
)
|
|
84
|
+
self.oauthClientID = credential.clientID
|
|
85
|
+
UserDefaults.standard.set(credential.clientID, forKey: "enigma.oauth.client")
|
|
86
|
+
try await self.broker.save(credential)
|
|
87
|
+
self.isConnected = true
|
|
88
|
+
try await self.loadInitialData()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
func setBrowserViewerOrigin(_ value: String) {
|
|
92
|
+
browserViewerOrigin = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
93
|
+
UserDefaults.standard.set(browserViewerOrigin, forKey: "enigma.browser.viewerOrigin")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func connectWithToken(_ token: String) async {
|
|
97
|
+
let value = token.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
98
|
+
guard !value.isEmpty else { errorMessage = "Paste a direct Enigma token."; return }
|
|
99
|
+
await perform {
|
|
100
|
+
try await self.broker.saveDirectToken(value)
|
|
101
|
+
self.isConnected = true
|
|
102
|
+
try await self.loadInitialData()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func disconnect() async {
|
|
107
|
+
do { try await broker.clear() } catch { errorMessage = error.localizedDescription }
|
|
108
|
+
isConnected = false
|
|
109
|
+
platform = nil
|
|
110
|
+
models = []
|
|
111
|
+
clearConversation()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func send(_ prompt: String) async {
|
|
115
|
+
let value = prompt.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
116
|
+
guard !value.isEmpty, let client else { return }
|
|
117
|
+
let user = EnigmaMessage(role: .user, content: value)
|
|
118
|
+
messages.append(user)
|
|
119
|
+
isBusy = true
|
|
120
|
+
errorMessage = nil
|
|
121
|
+
let protected = privacy.minimize(messages, mode: privacyMode)
|
|
122
|
+
defer {
|
|
123
|
+
protected.dispose()
|
|
124
|
+
isBusy = false
|
|
125
|
+
}
|
|
126
|
+
do {
|
|
127
|
+
let response = try await client.complete(.init(messages: protected.messages, model: selectedModel, privacyMode: "off"))
|
|
128
|
+
messages.append(.init(role: .assistant, content: protected.restore(response.answer)))
|
|
129
|
+
lastPrivacyReceipt = protected.receipt
|
|
130
|
+
} catch {
|
|
131
|
+
errorMessage = error.localizedDescription
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
func runCouncil(prompt: String, modelIDs: [String]) async {
|
|
136
|
+
let value = prompt.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
137
|
+
let selected = Array(Set(modelIDs.filter { !$0.isEmpty })).prefix(3)
|
|
138
|
+
guard !value.isEmpty, selected.count >= 2, let client else {
|
|
139
|
+
errorMessage = "Choose at least two distinct models."
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
isBusy = true
|
|
143
|
+
errorMessage = nil
|
|
144
|
+
councilOpinions = []
|
|
145
|
+
let privacy = self.privacy
|
|
146
|
+
let mode = privacyMode
|
|
147
|
+
councilOpinions = await withTaskGroup(of: CouncilOpinion.self, returning: [CouncilOpinion].self) { group in
|
|
148
|
+
for model in selected {
|
|
149
|
+
group.addTask {
|
|
150
|
+
let session = privacy.minimize([EnigmaMessage(role: .user, content: value)], mode: mode)
|
|
151
|
+
defer { session.dispose() }
|
|
152
|
+
do {
|
|
153
|
+
let response = try await client.complete(.init(messages: session.messages, model: model, privacyMode: "off"))
|
|
154
|
+
return CouncilOpinion(model: model, answer: session.restore(response.answer), error: nil)
|
|
155
|
+
} catch {
|
|
156
|
+
return CouncilOpinion(model: model, answer: "", error: error.localizedDescription)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
var results: [CouncilOpinion] = []
|
|
161
|
+
for await result in group { results.append(result) }
|
|
162
|
+
return results.sorted { $0.model < $1.model }
|
|
163
|
+
}
|
|
164
|
+
isBusy = false
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
func generateImage(prompt: String, model: String) async {
|
|
168
|
+
guard let client else { return }
|
|
169
|
+
let value = prompt.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
170
|
+
guard !value.isEmpty, !model.isEmpty else { errorMessage = "Choose an image model and enter a prompt."; return }
|
|
171
|
+
await perform {
|
|
172
|
+
let session = self.privacy.minimize([EnigmaMessage(role: .user, content: value)], mode: self.privacyMode)
|
|
173
|
+
defer { session.dispose() }
|
|
174
|
+
let response = try await client.generateImage(.init(prompt: session.messages[0].content, model: model))
|
|
175
|
+
self.generatedImages = response.assets
|
|
176
|
+
self.lastPrivacyReceipt = session.receipt
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
func createCreditsVault() async {
|
|
181
|
+
guard let client else { return }
|
|
182
|
+
await perform {
|
|
183
|
+
let session = try await client.createCreditsVault()
|
|
184
|
+
try self.secureStore.setString(session.accessToken, account: "credits-access")
|
|
185
|
+
self.creditsVault = session.vault
|
|
186
|
+
self.recoveryCodeShownOnce = session.recoveryCode
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
func queueVideo(prompt: String, model: String) async {
|
|
191
|
+
guard let client else { return }
|
|
192
|
+
let value = prompt.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
193
|
+
guard !value.isEmpty, !model.isEmpty else { errorMessage = "Choose a video model and enter a prompt."; return }
|
|
194
|
+
await perform {
|
|
195
|
+
let session = self.privacy.minimize([EnigmaMessage(role: .user, content: value)], mode: self.privacyMode)
|
|
196
|
+
defer { session.dispose() }
|
|
197
|
+
self.videoJob = try await client.queueVideo(.init(prompt: session.messages[0].content, model: model))
|
|
198
|
+
self.videoAsset = nil
|
|
199
|
+
self.lastPrivacyReceipt = session.receipt
|
|
200
|
+
for _ in 0..<120 {
|
|
201
|
+
try await Task.sleep(for: .seconds(2))
|
|
202
|
+
guard let id = self.videoJob?.id else { return }
|
|
203
|
+
let result = try await client.retrieveVideo(id: id)
|
|
204
|
+
self.videoJob = result.job
|
|
205
|
+
if result.job.status == "succeeded" {
|
|
206
|
+
self.videoAsset = result.asset
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
if ["failed", "cancelled", "expired"].contains(result.job.status) { return }
|
|
210
|
+
}
|
|
211
|
+
throw EnigmaAPIError.server(status: 408, code: "video_poll_timeout", message: "Video generation is still running. Reopen this screen to poll again.")
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
func executeCode(_ source: String) async {
|
|
216
|
+
guard let client else { return }
|
|
217
|
+
await perform {
|
|
218
|
+
let session = try await client.createCodeSession()
|
|
219
|
+
self.codeSession = session
|
|
220
|
+
do {
|
|
221
|
+
try await client.uploadCode(sessionID: session.id, source: source)
|
|
222
|
+
let execution = try await client.executeCode(sessionID: session.id)
|
|
223
|
+
let events = try await client.codeEvents(sessionID: session.id)
|
|
224
|
+
self.codeOutput = events.compactMap { event in
|
|
225
|
+
guard event.type == "command.output", let data = event.data?.data else { return nil }
|
|
226
|
+
return data
|
|
227
|
+
}.joined()
|
|
228
|
+
if self.codeOutput.isEmpty { self.codeOutput = execution.ok ? "Completed without output." : "Execution failed." }
|
|
229
|
+
_ = try await client.burnCodeSession(id: session.id)
|
|
230
|
+
self.codeSession = nil
|
|
231
|
+
} catch {
|
|
232
|
+
do {
|
|
233
|
+
_ = try await client.burnCodeSession(id: session.id)
|
|
234
|
+
self.codeSession = nil
|
|
235
|
+
} catch let cleanupError {
|
|
236
|
+
self.codeOutput = "Cleanup failed. Retry is required before trusting disposal: \(cleanupError.localizedDescription)"
|
|
237
|
+
}
|
|
238
|
+
throw error
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
func startPrivateBrowser() async {
|
|
244
|
+
guard let client else { return }
|
|
245
|
+
await perform {
|
|
246
|
+
let created = try await client.createBrowserSession()
|
|
247
|
+
let connected = try await client.connectBrowserSession(id: created.id)
|
|
248
|
+
guard connected.privacy.privateNetworkBlocked, connected.streamUrl != nil else {
|
|
249
|
+
_ = try? await client.burnBrowserSession(id: created.id)
|
|
250
|
+
throw EnigmaAPIError.server(status: 503, code: "browser_proof_missing", message: "The browser broker did not prove network-level private-address blocking.")
|
|
251
|
+
}
|
|
252
|
+
self.browserSession = connected
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
func navigatePrivateBrowser(_ value: String) async {
|
|
257
|
+
guard let client, let id = browserSession?.id else { return }
|
|
258
|
+
await perform { self.browserSession = try await client.navigateBrowserSession(id: id, url: value) }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
func burnPrivateBrowser() async {
|
|
262
|
+
guard let client, let id = browserSession?.id else { return }
|
|
263
|
+
await perform {
|
|
264
|
+
_ = try await client.burnBrowserSession(id: id)
|
|
265
|
+
self.browserSession = nil
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
func recoverCreditsVault(_ code: String) async {
|
|
270
|
+
guard let client else { return }
|
|
271
|
+
let value = code.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
272
|
+
guard !value.isEmpty else { errorMessage = "Enter a recovery code."; return }
|
|
273
|
+
await perform {
|
|
274
|
+
let session = try await client.recoverCreditsVault(code: value)
|
|
275
|
+
try self.secureStore.setString(session.accessToken, account: "credits-access")
|
|
276
|
+
self.creditsVault = session.vault
|
|
277
|
+
self.recoveryCodeShownOnce = session.recoveryCode
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
func refreshCredits() async {
|
|
282
|
+
guard let client, let token = try? secureStore.string(account: "credits-access") else { return }
|
|
283
|
+
do { creditsVault = try await client.creditsStatus(accessToken: token) }
|
|
284
|
+
catch {
|
|
285
|
+
try? secureStore.remove(account: "credits-access")
|
|
286
|
+
creditsVault = nil
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
func forgetCreditsSession() {
|
|
291
|
+
try? secureStore.remove(account: "credits-access")
|
|
292
|
+
creditsVault = nil
|
|
293
|
+
recoveryCodeShownOnce = nil
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
func dismissRecoveryCode() { recoveryCodeShownOnce = nil }
|
|
297
|
+
|
|
298
|
+
func refreshUsage() async {
|
|
299
|
+
guard let client else { return }
|
|
300
|
+
do { usage = try await client.usage() }
|
|
301
|
+
catch { errorMessage = error.localizedDescription }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
func clearConversation() {
|
|
305
|
+
messages.removeAll(keepingCapacity: false)
|
|
306
|
+
lastPrivacyReceipt = nil
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private func configureClient() {
|
|
310
|
+
guard let url = URL(string: serverAddress) else { client = nil; return }
|
|
311
|
+
let broker = self.broker
|
|
312
|
+
client = try? EnigmaAPIClient(baseURL: url) { try await broker.accessToken() }
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private func restoreConnection() async {
|
|
316
|
+
guard client != nil else { return }
|
|
317
|
+
do {
|
|
318
|
+
isConnected = try await broker.accessToken() != nil
|
|
319
|
+
if isConnected { try await loadInitialData() }
|
|
320
|
+
} catch {
|
|
321
|
+
errorMessage = error.localizedDescription
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private func loadInitialData() async throws {
|
|
326
|
+
guard let client else { throw EnigmaAPIError.invalidBaseURL }
|
|
327
|
+
async let platform = client.platform()
|
|
328
|
+
async let models = client.models()
|
|
329
|
+
self.platform = try await platform
|
|
330
|
+
self.models = try await models
|
|
331
|
+
if self.platform?.capabilities.images == true { imageModels = (try? await client.imageModels()) ?? [] }
|
|
332
|
+
if self.platform?.capabilities.videos == true { videoModels = (try? await client.videoModels()) ?? [] }
|
|
333
|
+
if selectedModel != "enigma-auto" && !self.models.contains(where: { $0.id == selectedModel }) { selectedModel = "enigma-auto" }
|
|
334
|
+
creditPackages = (try? await client.creditPackages()) ?? []
|
|
335
|
+
await refreshCredits()
|
|
336
|
+
await refreshUsage()
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private func perform(_ operation: () async throws -> Void) async {
|
|
340
|
+
isBusy = true
|
|
341
|
+
errorMessage = nil
|
|
342
|
+
defer { isBusy = false }
|
|
343
|
+
do { try await operation() }
|
|
344
|
+
catch { errorMessage = error.localizedDescription }
|
|
345
|
+
}
|
|
346
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import EnigmaCore
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
struct ChatView: View {
|
|
5
|
+
@EnvironmentObject private var model: AppModel
|
|
6
|
+
@State private var prompt = ""
|
|
7
|
+
@State private var showPrivacyReceipt = false
|
|
8
|
+
|
|
9
|
+
var body: some View {
|
|
10
|
+
ScrollViewReader { proxy in
|
|
11
|
+
ScrollView {
|
|
12
|
+
LazyVStack(spacing: 0) {
|
|
13
|
+
if model.messages.isEmpty { emptyState }
|
|
14
|
+
else {
|
|
15
|
+
ForEach(model.messages) { message in
|
|
16
|
+
MessageRow(message: message)
|
|
17
|
+
.id(message.id)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
.padding(.horizontal, 20)
|
|
22
|
+
.padding(.bottom, 130)
|
|
23
|
+
}
|
|
24
|
+
.scrollDismissesKeyboard(.interactively)
|
|
25
|
+
.onChange(of: model.messages.count) {
|
|
26
|
+
if let last = model.messages.last { withAnimation { proxy.scrollTo(last.id, anchor: .bottom) } }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
.navigationTitle("new chat")
|
|
30
|
+
.navigationBarTitleDisplayMode(.inline)
|
|
31
|
+
.toolbar {
|
|
32
|
+
ToolbarItem(placement: .topBarLeading) {
|
|
33
|
+
HStack(spacing: 7) { EnigmaMark(size: 17); Text("Enigma.").font(.system(.headline, design: .serif)) }
|
|
34
|
+
}
|
|
35
|
+
ToolbarItemGroup(placement: .topBarTrailing) {
|
|
36
|
+
Button { showPrivacyReceipt = true } label: { Image(systemName: "checkmark.shield") }
|
|
37
|
+
.disabled(model.lastPrivacyReceipt == nil)
|
|
38
|
+
Menu {
|
|
39
|
+
Button("Clear local conversation", role: .destructive) { model.clearConversation() }
|
|
40
|
+
} label: { Image(systemName: "ellipsis") }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
.safeAreaInset(edge: .bottom) { composer }
|
|
44
|
+
.sheet(isPresented: $showPrivacyReceipt) { PrivacyReceiptView(receipt: model.lastPrivacyReceipt) }
|
|
45
|
+
.enigmaCanvas()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private var emptyState: some View {
|
|
49
|
+
VStack(spacing: 0) {
|
|
50
|
+
Spacer(minLength: 74)
|
|
51
|
+
EnigmaMark(size: 28).padding(.bottom, 18)
|
|
52
|
+
SectionEyebrow(text: "PRIVATE AI WORKSPACE").padding(.bottom, 7)
|
|
53
|
+
Text("What would you like\nto work on?")
|
|
54
|
+
.font(.system(size: 34, weight: .medium, design: .serif))
|
|
55
|
+
.multilineTextAlignment(.center)
|
|
56
|
+
.tracking(-0.8)
|
|
57
|
+
Text("Enigma Auto chooses an explicit route. No training. No silent chat retention.")
|
|
58
|
+
.font(.footnote)
|
|
59
|
+
.foregroundStyle(EnigmaTheme.secondary)
|
|
60
|
+
.multilineTextAlignment(.center)
|
|
61
|
+
.padding(.top, 10)
|
|
62
|
+
.padding(.horizontal, 24)
|
|
63
|
+
VStack(spacing: 8) {
|
|
64
|
+
suggestion("explain e2e encryption →", prompt: "Explain end-to-end encryption and where metadata can still leak.")
|
|
65
|
+
suggestion("tor / i2p / mixnets →", prompt: "Compare Tor, I2P, and mixnets for private agent traffic.")
|
|
66
|
+
suggestion("plan from verified context →", prompt: "Draft an implementation plan grounded only in retrieved Enigma context.")
|
|
67
|
+
}
|
|
68
|
+
.padding(.top, 26)
|
|
69
|
+
VStack(spacing: 3) {
|
|
70
|
+
Text("· Conversation memory stays in this app process.")
|
|
71
|
+
Text("· Context leaves only when you send.")
|
|
72
|
+
Text("· Smart Privacy minimizes identity anchors first.")
|
|
73
|
+
}
|
|
74
|
+
.font(.caption2)
|
|
75
|
+
.foregroundStyle(EnigmaTheme.muted)
|
|
76
|
+
.padding(.top, 18)
|
|
77
|
+
Spacer(minLength: 70)
|
|
78
|
+
}
|
|
79
|
+
.frame(maxWidth: .infinity, minHeight: 520)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private func suggestion(_ label: String, prompt value: String) -> some View {
|
|
83
|
+
Button { prompt = value } label: {
|
|
84
|
+
Text(label).font(.footnote).foregroundStyle(EnigmaTheme.secondary)
|
|
85
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
86
|
+
.padding(.horizontal, 14).padding(.vertical, 12)
|
|
87
|
+
.background(.white.opacity(0.74))
|
|
88
|
+
.overlay(RoundedRectangle(cornerRadius: 9).stroke(EnigmaTheme.line))
|
|
89
|
+
}
|
|
90
|
+
.buttonStyle(.plain)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private var composer: some View {
|
|
94
|
+
VStack(spacing: 9) {
|
|
95
|
+
HStack(alignment: .bottom, spacing: 10) {
|
|
96
|
+
TextField("Ask anything privately…", text: $prompt, axis: .vertical)
|
|
97
|
+
.lineLimit(1...5)
|
|
98
|
+
.font(EnigmaTheme.body)
|
|
99
|
+
.padding(.horizontal, 12)
|
|
100
|
+
.padding(.vertical, 10)
|
|
101
|
+
Button {
|
|
102
|
+
let outgoing = prompt
|
|
103
|
+
prompt = ""
|
|
104
|
+
Task { await model.send(outgoing) }
|
|
105
|
+
} label: {
|
|
106
|
+
Image(systemName: "arrow.up").font(.headline.weight(.bold)).foregroundStyle(.white)
|
|
107
|
+
.frame(width: 38, height: 38).background(EnigmaTheme.ember).clipShape(Circle())
|
|
108
|
+
}
|
|
109
|
+
.disabled(prompt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || model.isBusy)
|
|
110
|
+
.padding(5)
|
|
111
|
+
}
|
|
112
|
+
.background(.white)
|
|
113
|
+
.overlay(RoundedRectangle(cornerRadius: 15).stroke(EnigmaTheme.line))
|
|
114
|
+
.clipShape(RoundedRectangle(cornerRadius: 15))
|
|
115
|
+
HStack {
|
|
116
|
+
Menu(model.displayModel) {
|
|
117
|
+
Button("Auto") { model.selectedModel = "enigma-auto" }
|
|
118
|
+
ForEach(model.models) { route in Button(route.id) { model.selectedModel = route.id } }
|
|
119
|
+
}
|
|
120
|
+
Menu("Privacy: \(model.privacyMode.rawValue.capitalized)") {
|
|
121
|
+
ForEach(PrivacyMode.allCases, id: \.self) { mode in Button(mode.rawValue.capitalized) { model.privacyMode = mode } }
|
|
122
|
+
}
|
|
123
|
+
Spacer()
|
|
124
|
+
Text("Local conversation").font(.caption2).foregroundStyle(EnigmaTheme.muted)
|
|
125
|
+
}
|
|
126
|
+
.font(.caption)
|
|
127
|
+
}
|
|
128
|
+
.padding(.horizontal, 14)
|
|
129
|
+
.padding(.top, 8)
|
|
130
|
+
.padding(.bottom, 5)
|
|
131
|
+
.background(.ultraThinMaterial)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private struct MessageRow: View {
|
|
136
|
+
let message: EnigmaMessage
|
|
137
|
+
|
|
138
|
+
var body: some View {
|
|
139
|
+
HStack(alignment: .top, spacing: 12) {
|
|
140
|
+
Text(message.role == .user ? "YOU" : "ENIGMA")
|
|
141
|
+
.font(EnigmaTheme.label)
|
|
142
|
+
.foregroundStyle(message.role == .assistant ? EnigmaTheme.ember : EnigmaTheme.muted)
|
|
143
|
+
.frame(width: 54, alignment: .leading)
|
|
144
|
+
Text(message.content)
|
|
145
|
+
.font(EnigmaTheme.body)
|
|
146
|
+
.foregroundStyle(EnigmaTheme.ink)
|
|
147
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
148
|
+
.textSelection(.enabled)
|
|
149
|
+
}
|
|
150
|
+
.padding(.vertical, 18)
|
|
151
|
+
.overlay(alignment: .bottom) { Rectangle().fill(EnigmaTheme.line).frame(height: 0.6) }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private struct PrivacyReceiptView: View {
|
|
156
|
+
let receipt: PrivacyReceipt?
|
|
157
|
+
@Environment(\.dismiss) private var dismiss
|
|
158
|
+
|
|
159
|
+
var body: some View {
|
|
160
|
+
NavigationStack {
|
|
161
|
+
List {
|
|
162
|
+
if let receipt {
|
|
163
|
+
Section("Boundary") {
|
|
164
|
+
LabeledContent("Mode", value: receipt.mode.rawValue.capitalized)
|
|
165
|
+
LabeledContent("Input", value: "\(receipt.inputCharacters) characters")
|
|
166
|
+
LabeledContent("Output", value: "\(receipt.outputCharacters) characters")
|
|
167
|
+
LabeledContent("Reference", value: String(receipt.receiptReference.uuidString.prefix(12)) + "…")
|
|
168
|
+
}
|
|
169
|
+
Section("Actions") {
|
|
170
|
+
ForEach(receipt.summary, id: \.self) { item in
|
|
171
|
+
LabeledContent(item.category.rawValue, value: "\(item.action) × \(item.count)")
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
Section { Text("Receipt metadata contains no prompt, output, or deterministic prompt hash.") }
|
|
175
|
+
} else { Text("No protected turn yet.") }
|
|
176
|
+
}
|
|
177
|
+
.navigationTitle("Smart Privacy")
|
|
178
|
+
.toolbar { Button("Done") { dismiss() } }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
struct CouncilView: View {
|
|
4
|
+
@EnvironmentObject private var model: AppModel
|
|
5
|
+
@State private var prompt = ""
|
|
6
|
+
@State private var seats = ["", "", ""]
|
|
7
|
+
|
|
8
|
+
var body: some View {
|
|
9
|
+
ScrollView {
|
|
10
|
+
VStack(alignment: .leading, spacing: 22) {
|
|
11
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
12
|
+
SectionEyebrow(text: "MULTI-MODEL COUNCIL")
|
|
13
|
+
Text("Independent answers.\nOne shared brief.")
|
|
14
|
+
.font(EnigmaTheme.display)
|
|
15
|
+
.tracking(-0.8)
|
|
16
|
+
Text("Each seat receives the same locally minimized question. Answers remain separate; Enigma does not hide a synthesizer behind the panel.")
|
|
17
|
+
.font(.subheadline)
|
|
18
|
+
.foregroundStyle(EnigmaTheme.secondary)
|
|
19
|
+
}
|
|
20
|
+
VStack(spacing: 0) {
|
|
21
|
+
ForEach(0..<3, id: \.self) { index in
|
|
22
|
+
HStack {
|
|
23
|
+
Text("SEAT 0\(index + 1)").font(EnigmaTheme.label).foregroundStyle(EnigmaTheme.muted)
|
|
24
|
+
Spacer()
|
|
25
|
+
Picker("Seat \(index + 1)", selection: $seats[index]) {
|
|
26
|
+
Text("Choose route").tag("")
|
|
27
|
+
ForEach(model.models) { route in Text(route.id).tag(route.id) }
|
|
28
|
+
}
|
|
29
|
+
.labelsHidden()
|
|
30
|
+
}
|
|
31
|
+
.padding(.vertical, 14)
|
|
32
|
+
if index < 2 { Divider().overlay(EnigmaTheme.line) }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
.enigmaSurface(padding: 16)
|
|
36
|
+
|
|
37
|
+
VStack(alignment: .leading, spacing: 10) {
|
|
38
|
+
Text("BRIEF").font(EnigmaTheme.label).foregroundStyle(EnigmaTheme.muted)
|
|
39
|
+
TextField("Place one question before the council…", text: $prompt, axis: .vertical)
|
|
40
|
+
.lineLimit(4...9)
|
|
41
|
+
.padding(12)
|
|
42
|
+
.background(.white)
|
|
43
|
+
.overlay(RoundedRectangle(cornerRadius: 9).stroke(EnigmaTheme.line))
|
|
44
|
+
Button("Convene council") {
|
|
45
|
+
Task { await model.runCouncil(prompt: prompt, modelIDs: seats) }
|
|
46
|
+
}
|
|
47
|
+
.buttonStyle(PrimaryButtonStyle())
|
|
48
|
+
.disabled(model.isBusy || prompt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
ForEach(model.councilOpinions) { opinion in
|
|
52
|
+
VStack(alignment: .leading, spacing: 10) {
|
|
53
|
+
HStack {
|
|
54
|
+
Text(opinion.model).font(.subheadline.weight(.semibold))
|
|
55
|
+
Spacer()
|
|
56
|
+
Circle().fill(opinion.error == nil ? EnigmaTheme.success : .red).frame(width: 7, height: 7)
|
|
57
|
+
}
|
|
58
|
+
Text(opinion.error ?? opinion.answer)
|
|
59
|
+
.font(EnigmaTheme.body)
|
|
60
|
+
.foregroundStyle(opinion.error == nil ? EnigmaTheme.ink : .red)
|
|
61
|
+
.textSelection(.enabled)
|
|
62
|
+
}
|
|
63
|
+
.enigmaSurface(padding: 16)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
.padding(20)
|
|
67
|
+
.padding(.bottom, 34)
|
|
68
|
+
}
|
|
69
|
+
.navigationTitle("Council")
|
|
70
|
+
.navigationBarTitleDisplayMode(.inline)
|
|
71
|
+
.enigmaCanvas()
|
|
72
|
+
.onAppear {
|
|
73
|
+
if seats.allSatisfy(\.isEmpty) {
|
|
74
|
+
for (index, route) in model.models.prefix(3).enumerated() { seats[index] = route.id }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|