diff --git a/packages/app/src/components/chat/ChatMessage.vue b/packages/app/src/components/chat/ChatMessage.vue
index 0b4d2d29..071ca4e7 100644
--- a/packages/app/src/components/chat/ChatMessage.vue
+++ b/packages/app/src/components/chat/ChatMessage.vue
@@ -223,6 +223,14 @@
/>
+
+
+
+
{
return extractRunnableCodeBlocks(props.message.content)
})
+const bitcoinAddresses = computed(() => {
+ if (isUser.value) return []
+ return detectBitcoinAddresses(props.message.content)
+})
+
const isCodeResponse = computed(() =>
!isUser.value && props.triggeringQuery.trim().toLowerCase() === '/code'
)
diff --git a/packages/app/src/components/renderers/BitcoinAddressCard.vue b/packages/app/src/components/renderers/BitcoinAddressCard.vue
new file mode 100644
index 00000000..a8686f8d
--- /dev/null
+++ b/packages/app/src/components/renderers/BitcoinAddressCard.vue
@@ -0,0 +1,95 @@
+
+
+
+ Bitcoin Address
+ {{ addressType }}
+
+
+
+
{{ address }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/app/src/composables/useBitcoinDetector.ts b/packages/app/src/composables/useBitcoinDetector.ts
new file mode 100644
index 00000000..20e5c1b0
--- /dev/null
+++ b/packages/app/src/composables/useBitcoinDetector.ts
@@ -0,0 +1,114 @@
+export interface DetectedBitcoinAddress {
+ address: string
+ type: 'bech32' | 'bech32m' | 'p2pkh' | 'p2sh' | 'testnet'
+}
+
+export interface DetectedLightningInvoice {
+ invoice: string
+ amount?: number
+ description?: string
+}
+
+export interface DetectedBolt12Offer {
+ offer: string
+}
+
+export interface DetectedTxId {
+ txid: string
+}
+
+// Bitcoin address patterns
+const BECH32_REGEX = /\b(bc1q[a-z0-9]{38,62})\b/gi
+const BECH32M_REGEX = /\b(bc1p[a-z0-9]{38,62})\b/gi
+const P2PKH_REGEX = /\b(1[13456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{25,34})\b/g
+const P2SH_REGEX = /\b(3[13456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{25,34})\b/g
+const TESTNET_REGEX = /\b(tb1[a-z0-9]{38,62})\b/gi
+
+// Lightning patterns
+const BOLT11_REGEX = /\b(lnbc[a-z0-9]+)\b/gi
+const BOLT12_REGEX = /\b(lno1[a-z0-9]+)\b/gi
+
+// Transaction ID (64 hex chars)
+const TXID_REGEX = /\b([a-f0-9]{64})\b/gi
+
+export function detectBitcoinAddresses(text: string): DetectedBitcoinAddress[] {
+ const addresses: DetectedBitcoinAddress[] = []
+ const seen = new Set()
+
+ for (const match of text.matchAll(BECH32_REGEX)) {
+ if (!seen.has(match[1].toLowerCase())) {
+ seen.add(match[1].toLowerCase())
+ addresses.push({ address: match[1], type: 'bech32' })
+ }
+ }
+ for (const match of text.matchAll(BECH32M_REGEX)) {
+ if (!seen.has(match[1].toLowerCase())) {
+ seen.add(match[1].toLowerCase())
+ addresses.push({ address: match[1], type: 'bech32m' })
+ }
+ }
+ for (const match of text.matchAll(P2PKH_REGEX)) {
+ if (!seen.has(match[1])) {
+ seen.add(match[1])
+ addresses.push({ address: match[1], type: 'p2pkh' })
+ }
+ }
+ for (const match of text.matchAll(P2SH_REGEX)) {
+ if (!seen.has(match[1])) {
+ seen.add(match[1])
+ addresses.push({ address: match[1], type: 'p2sh' })
+ }
+ }
+ for (const match of text.matchAll(TESTNET_REGEX)) {
+ if (!seen.has(match[1].toLowerCase())) {
+ seen.add(match[1].toLowerCase())
+ addresses.push({ address: match[1], type: 'testnet' })
+ }
+ }
+
+ return addresses
+}
+
+export function detectBolt11Invoices(text: string): DetectedLightningInvoice[] {
+ const invoices: DetectedLightningInvoice[] = []
+ const seen = new Set()
+
+ for (const match of text.matchAll(BOLT11_REGEX)) {
+ const lower = match[1].toLowerCase()
+ if (!seen.has(lower)) {
+ seen.add(lower)
+ invoices.push({ invoice: match[1] })
+ }
+ }
+
+ return invoices
+}
+
+export function detectBolt12Offers(text: string): DetectedBolt12Offer[] {
+ const offers: DetectedBolt12Offer[] = []
+ const seen = new Set()
+
+ for (const match of text.matchAll(BOLT12_REGEX)) {
+ const lower = match[1].toLowerCase()
+ if (!seen.has(lower)) {
+ seen.add(lower)
+ offers.push({ offer: match[1] })
+ }
+ }
+
+ return offers
+}
+
+export function detectTxIds(text: string): DetectedTxId[] {
+ const txids: DetectedTxId[] = []
+ const seen = new Set()
+
+ for (const match of text.matchAll(TXID_REGEX)) {
+ if (!seen.has(match[1])) {
+ seen.add(match[1])
+ txids.push({ txid: match[1] })
+ }
+ }
+
+ return txids
+}