yonilev commited on
Commit
ef74283
·
verified ·
1 Parent(s): 71df196

Upload discord_bot.py

Browse files
Files changed (1) hide show
  1. discord_bot.py +224 -0
discord_bot.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Text2Receipt — Discord Bot (+5% bonus)
4
+ ========================================
5
+ Run this cell in Google Colab (T4) AFTER the fine-tuned model is loaded.
6
+ The bot wraps the same pipeline as the Gradio Space.
7
+
8
+ Usage in any Discord channel:
9
+ !receipt <Hebrew income note>
10
+
11
+ Example:
12
+ !receipt קיבלתי 1200 שקל ממשה כהן על ייעוץ עסקי
13
+
14
+ Setup:
15
+ 1. Go to https://discord.com/developers/applications → New Application
16
+ 2. Bot tab → Add Bot → copy token
17
+ 3. OAuth2 → URL Generator: scope=bot, permissions=Send Messages
18
+ 4. Paste DISCORD_TOKEN below (or set as Colab env var)
19
+ 5. Run this cell while the model is already loaded in the session
20
+ """
21
+
22
+ import os, json, random, datetime as _dt, asyncio, threading
23
+ import discord
24
+ from discord.ext import commands
25
+
26
+ # ── same pipeline helpers as app.py ──────────────────────────────────────────
27
+ import t2r_core as core
28
+
29
+ DEMO_ISSUER = {
30
+ "name": "מים שקטים",
31
+ "tax_id": "962569844",
32
+ "status": "authorized_dealer",
33
+ }
34
+
35
+ _RNG = random.Random(99)
36
+
37
+ # These are defined here so the bot works standalone (copied from app.py)
38
+ INSTRUCTION = (
39
+ "אתה ממיר הערת הכנסה חופשית בעברית למבנה JSON. "
40
+ "חלץ אך ורק את מה שכתוב בהערה: שם הלקוח (client_name), "
41
+ "האם הלקוח עסק (client_is_business), ורשימת פריטים (items) "
42
+ "כאשר לכל פריט תיאור (description), מחיר ליחידה (unit_price) וכמות (quantity). "
43
+ "החזר JSON תקין בלבד, ללא טקסט נוסף."
44
+ )
45
+
46
+ def _build_prompt(raw_text: str) -> str:
47
+ return f"{INSTRUCTION}\n\nהערה: {raw_text}\n\nJSON:"
48
+
49
+ def _extract_json(text: str):
50
+ s = text.find("{")
51
+ if s < 0:
52
+ return None
53
+ depth = 0
54
+ for i in range(s, len(text)):
55
+ if text[i] == "{":
56
+ depth += 1
57
+ elif text[i] == "}":
58
+ depth -= 1
59
+ if depth == 0:
60
+ try:
61
+ return json.loads(text[s:i+1])
62
+ except Exception:
63
+ return None
64
+ return None
65
+
66
+ def _apply_defaults(parse: dict) -> dict:
67
+ p = dict(parse)
68
+ p.setdefault("doc_type", "receipt")
69
+ p.setdefault("date", _dt.date.today().isoformat())
70
+ p.setdefault("payment_method", "bank_transfer")
71
+ p.setdefault("amount_basis", "net")
72
+ p.setdefault("currency", "ILS")
73
+ p.setdefault("client_tax_id", None)
74
+ p.setdefault("client_is_business", False)
75
+ p.setdefault("items", [])
76
+ return p
77
+
78
+ def _format_document(completed: dict) -> str:
79
+ """Format completed document as Discord-friendly Markdown."""
80
+ c = completed
81
+ issuer = c.get("issuer", {})
82
+ client = c.get("client", {})
83
+ lines = c.get("lines", [])
84
+ vat_pct = int(round(c.get("vat_rate", 0) * 100))
85
+
86
+ line_rows = "\n".join(
87
+ f" • {ln['description']} × {ln['quantity']} → ₪{ln['line_total']:,.2f}"
88
+ for ln in lines
89
+ )
90
+ alloc = (
91
+ f"\n🔖 **מספר הקצאה:** `{c.get('allocation_number')}`"
92
+ if c.get("allocation_required") else ""
93
+ )
94
+
95
+ return (
96
+ f"```\n"
97
+ f"{'━'*38}\n"
98
+ f" {c.get('doc_type_he','מסמך')} | מס׳ {c.get('serial_number','')}\n"
99
+ f" {c.get('issue_date','')}\n"
100
+ f"{'━'*38}\n"
101
+ f" מנפיק : {issuer.get('name','')} (ח.פ. {issuer.get('tax_id','')})\n"
102
+ f" לקוח : {client.get('name','')}"
103
+ f"{' [עסק]' if client.get('is_business') else ''}\n"
104
+ f"{'━'*38}\n"
105
+ f"{line_rows}\n"
106
+ f"{'━'*38}\n"
107
+ f" סכום נטו : ₪{c.get('subtotal',0):>10,.2f}\n"
108
+ f" מע\"מ {vat_pct:2d}% : ₪{c.get('vat_amount',0):>10,.2f}\n"
109
+ f" **סה\"כ** : ₪{c.get('total',0):>10,.2f}\n"
110
+ f" תשלום : {core.PAYMENT_HE.get(c.get('payment_method',''),'—')}\n"
111
+ f"{'━'*38}\n"
112
+ f"```"
113
+ f"{alloc}"
114
+ )
115
+
116
+
117
+ def run_pipeline(raw_text: str, tok, model) -> str:
118
+ """
119
+ Full pipeline: raw Hebrew note → formatted fiscal document string.
120
+ tok and model are the already-loaded objects from the Colab session.
121
+ """
122
+ import torch
123
+
124
+ # 1. Parse
125
+ msgs = [{"role": "user", "content": _build_prompt(raw_text)}]
126
+ prompt = tok.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
127
+ enc = tok(prompt, return_tensors="pt",
128
+ add_special_tokens=False).to(model.device)
129
+ with torch.no_grad():
130
+ out = model.generate(**enc, max_new_tokens=200, do_sample=False,
131
+ pad_token_id=tok.pad_token_id)
132
+ decoded = tok.decode(out[0, enc["input_ids"].shape[1]:],
133
+ skip_special_tokens=True)
134
+ parse = _extract_json(decoded)
135
+
136
+ if parse is None:
137
+ return "❌ לא הצלחתי לחלץ את הפרטים. נסה לנסח מחדש."
138
+
139
+ # 2. Apply defaults + complete
140
+ final_parse = _apply_defaults(parse)
141
+ try:
142
+ completed = core.complete(DEMO_ISSUER, final_parse, _RNG)
143
+ except Exception as e:
144
+ return f"❌ שגיאה בעיבוד: {e}"
145
+
146
+ # 3. Format
147
+ return _format_document(completed)
148
+
149
+
150
+ def start_bot(tok, model, discord_token: str = None):
151
+ """
152
+ Start the Discord bot. Call this from Colab after loading tok + model.
153
+
154
+ Args:
155
+ tok: HuggingFace tokenizer (already loaded)
156
+ model: fine-tuned model (already loaded, on GPU)
157
+ discord_token: Bot token. Falls back to DISCORD_TOKEN env var.
158
+ """
159
+ token = discord_token or os.environ.get("DISCORD_TOKEN", "")
160
+ if not token:
161
+ raise ValueError(
162
+ "No Discord token found. "
163
+ "Pass discord_token= or set os.environ['DISCORD_TOKEN']"
164
+ )
165
+
166
+ intents = discord.Intents.default()
167
+ intents.message_content = True
168
+ bot = commands.Bot(command_prefix="!", intents=intents)
169
+
170
+ @bot.event
171
+ async def on_ready():
172
+ print(f"✅ Discord bot ready: {bot.user} (id: {bot.user.id})")
173
+ print(" Listening for: !receipt <Hebrew note>")
174
+
175
+ @bot.command(name="receipt")
176
+ async def receipt_cmd(ctx, *, note: str):
177
+ """Convert a Hebrew income note to a fiscal document."""
178
+ await ctx.message.add_reaction("⏳")
179
+ try:
180
+ # run blocking inference in executor to not block event loop
181
+ loop = asyncio.get_event_loop()
182
+ result = await loop.run_in_executor(
183
+ None, run_pipeline, note, tok, model
184
+ )
185
+ await ctx.reply(result)
186
+ except Exception as e:
187
+ await ctx.reply(f"❌ שגיאה: {e}")
188
+ finally:
189
+ await ctx.message.remove_reaction("⏳", bot.user)
190
+ await ctx.message.add_reaction("✅")
191
+
192
+ @bot.command(name="help_t2r")
193
+ async def help_cmd(ctx):
194
+ await ctx.reply(
195
+ "**Text2Receipt Bot** 🧾\n"
196
+ "המר הערת הכנסה עברית למסמך פיסקלי ישראלי.\n\n"
197
+ "שימוש: `!receipt <הערה בעברית>`\n\n"
198
+ "דוגמאות:\n"
199
+ "• `!receipt קיבלתי 500 שקל ממשה על ייעוץ`\n"
200
+ "• `!receipt מעגל בע\"מ שילמה 15,000 ש\"ח על פרויקט אתר`"
201
+ )
202
+
203
+ # Run in background thread so Colab cell doesn't block
204
+ def _run():
205
+ asyncio.run(bot.start(token))
206
+
207
+ t = threading.Thread(target=_run, daemon=True)
208
+ t.start()
209
+ print("🤖 Bot thread started. Keep this cell running.")
210
+ return bot
211
+
212
+
213
+ # ═════════════════════════════════════════════════════════════════════════════
214
+ # Colab usage example (paste into a cell after loading tok + model in nb03):
215
+ # ═════════════════════════════════════════════════════════════════════════════
216
+ # import os
217
+ # os.environ["DISCORD_TOKEN"] = "YOUR_BOT_TOKEN_HERE"
218
+ #
219
+ # from discord_bot import start_bot
220
+ # bot = start_bot(tok, ft_model) # tok + ft_model from §5 of nb03
221
+ #
222
+ # # Test locally without Discord:
223
+ # from discord_bot import run_pipeline
224
+ # print(run_pipeline("קיבלתי 1200 שקל ממשה כהן על ייעוץ עסקי", tok, ft_model))