niahere 0.3.8 → 0.3.10
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
CHANGED
|
@@ -105,13 +105,15 @@ def encode_file(path: str) -> str:
|
|
|
105
105
|
return base64.b64encode(f.read()).decode("utf-8")
|
|
106
106
|
|
|
107
107
|
|
|
108
|
-
def resolve_output_path(output: str | None, ext: str = ".png") -> Path:
|
|
108
|
+
def resolve_output_path(output: str | None, ext: str = ".png", index: int | None = None) -> Path:
|
|
109
|
+
# For multi-image output, append _1, _2, ... before the extension.
|
|
110
|
+
suffix = f"_{index + 1}" if index is not None else ""
|
|
109
111
|
if output:
|
|
110
112
|
out = Path(output).expanduser()
|
|
111
113
|
if out.suffix:
|
|
112
|
-
return out
|
|
113
|
-
return out / f"image_{time.strftime(TIMESTAMP_FORMAT)}{ext}"
|
|
114
|
-
return Path(f"/tmp/image_{time.strftime(TIMESTAMP_FORMAT)}{ext}")
|
|
114
|
+
return out.with_name(f"{out.stem}{suffix}{out.suffix}")
|
|
115
|
+
return out / f"image_{time.strftime(TIMESTAMP_FORMAT)}{suffix}{ext}"
|
|
116
|
+
return Path(f"/tmp/image_{time.strftime(TIMESTAMP_FORMAT)}{suffix}{ext}")
|
|
115
117
|
|
|
116
118
|
|
|
117
119
|
def read_config_key(key: str) -> str:
|
|
@@ -159,14 +161,14 @@ def generate_openai(
|
|
|
159
161
|
quality: str,
|
|
160
162
|
reference_path: str | None = None,
|
|
161
163
|
n: int = 1,
|
|
162
|
-
) -> tuple[bytes, str]:
|
|
163
|
-
"""Generate image via OpenAI Images API."""
|
|
164
|
+
) -> list[tuple[bytes, str]]:
|
|
165
|
+
"""Generate image(s) via OpenAI Images API. Returns one entry per image."""
|
|
164
166
|
if reference_path and Path(reference_path).is_file():
|
|
165
167
|
return _openai_edit(api_key, prompt, reference_path, model, size, quality, n)
|
|
166
168
|
return _openai_generate(api_key, prompt, model, size, quality, n)
|
|
167
169
|
|
|
168
170
|
|
|
169
|
-
def _openai_generate(api_key: str, prompt: str, model: str, size: str, quality: str, n: int) -> tuple[bytes, str]:
|
|
171
|
+
def _openai_generate(api_key: str, prompt: str, model: str, size: str, quality: str, n: int) -> list[tuple[bytes, str]]:
|
|
170
172
|
url = "https://api.openai.com/v1/images/generations"
|
|
171
173
|
payload: dict = {
|
|
172
174
|
"model": model,
|
|
@@ -191,7 +193,7 @@ def _openai_generate(api_key: str, prompt: str, model: str, size: str, quality:
|
|
|
191
193
|
|
|
192
194
|
def _openai_edit(
|
|
193
195
|
api_key: str, prompt: str, reference_path: str, model: str, size: str, quality: str, n: int
|
|
194
|
-
) -> tuple[bytes, str]:
|
|
196
|
+
) -> list[tuple[bytes, str]]:
|
|
195
197
|
"""Use OpenAI images/edits endpoint with a reference image."""
|
|
196
198
|
import io
|
|
197
199
|
|
|
@@ -235,7 +237,7 @@ def _openai_edit(
|
|
|
235
237
|
return _openai_request(req)
|
|
236
238
|
|
|
237
239
|
|
|
238
|
-
def _openai_request(req: urllib.request.Request) -> tuple[bytes, str]:
|
|
240
|
+
def _openai_request(req: urllib.request.Request) -> list[tuple[bytes, str]]:
|
|
239
241
|
try:
|
|
240
242
|
with urllib.request.urlopen(req, timeout=180) as resp:
|
|
241
243
|
response = json.loads(resp.read().decode("utf-8"))
|
|
@@ -247,11 +249,11 @@ def _openai_request(req: urllib.request.Request) -> tuple[bytes, str]:
|
|
|
247
249
|
if not data_list:
|
|
248
250
|
raise RuntimeError(f"No data in OpenAI response: {json.dumps(response, indent=2)}")
|
|
249
251
|
|
|
250
|
-
|
|
251
|
-
if not
|
|
252
|
+
images = [(base64.b64decode(item["b64_json"]), "image/png") for item in data_list if item.get("b64_json")]
|
|
253
|
+
if not images:
|
|
252
254
|
raise RuntimeError("No b64_json in OpenAI response.")
|
|
253
255
|
|
|
254
|
-
return
|
|
256
|
+
return images
|
|
255
257
|
|
|
256
258
|
|
|
257
259
|
# --- Gemini Generation ---
|
|
@@ -422,7 +424,7 @@ Examples:
|
|
|
422
424
|
raise SystemExit(f"2K is only supported on gpt-image-2 (got --model {model}).")
|
|
423
425
|
size_map = OPENAI_SIZE_MAP_2K if args.resolution == "2K" else OPENAI_SIZE_MAP_1K
|
|
424
426
|
size = size_map.get(args.aspect_ratio, size_map["1:1"])
|
|
425
|
-
|
|
427
|
+
images = generate_openai(
|
|
426
428
|
api_key=api_key,
|
|
427
429
|
prompt=args.prompt,
|
|
428
430
|
model=model,
|
|
@@ -432,21 +434,27 @@ Examples:
|
|
|
432
434
|
n=args.n,
|
|
433
435
|
)
|
|
434
436
|
else:
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
437
|
+
images = [
|
|
438
|
+
generate_gemini(
|
|
439
|
+
api_key=api_key,
|
|
440
|
+
prompt=args.prompt,
|
|
441
|
+
model=model,
|
|
442
|
+
aspect_ratio=args.aspect_ratio,
|
|
443
|
+
resolution=args.resolution,
|
|
444
|
+
reference_path=ref,
|
|
445
|
+
)
|
|
446
|
+
]
|
|
447
|
+
|
|
448
|
+
multiple = len(images) > 1
|
|
449
|
+
for idx, (image_data, mime) in enumerate(images):
|
|
450
|
+
ext = ".png" if "png" in mime else ".jpg"
|
|
451
|
+
out = resolve_output_path(args.output, ext, index=idx if multiple else None)
|
|
452
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
453
|
+
out.write_bytes(image_data)
|
|
454
|
+
print(f"Saved: {out}")
|
|
455
|
+
print(
|
|
456
|
+
f"Provider: {provider} | Model: {model} | Ratio: {args.aspect_ratio} | Resolution: {args.resolution} | Images: {len(images)}"
|
|
457
|
+
)
|
|
450
458
|
except Exception as exc:
|
|
451
459
|
print(f"Error: {exc}", file=sys.stderr)
|
|
452
460
|
raise SystemExit(1) from exc
|
package/src/channels/slack.ts
CHANGED
|
@@ -136,6 +136,7 @@ class SlackChannel implements Channel {
|
|
|
136
136
|
});
|
|
137
137
|
|
|
138
138
|
let botUserId: string | undefined;
|
|
139
|
+
let botId: string | undefined;
|
|
139
140
|
|
|
140
141
|
const watchReloader = new SlackWatchReloader();
|
|
141
142
|
const attachmentCache = new SlackAttachmentCache(botToken);
|
|
@@ -237,7 +238,7 @@ class SlackChannel implements Channel {
|
|
|
237
238
|
inclusive: true,
|
|
238
239
|
});
|
|
239
240
|
const parentMsg = parent.messages?.[0];
|
|
240
|
-
if (parentMsg && (parentMsg.user === botUserId || parentMsg.bot_id)) {
|
|
241
|
+
if (parentMsg && (parentMsg.user === botUserId || (botId && parentMsg.bot_id === botId))) {
|
|
241
242
|
isActiveThread = true;
|
|
242
243
|
log.debug(
|
|
243
244
|
{ channel: msg.channel, thread_ts: msg.thread_ts },
|
|
@@ -493,7 +494,8 @@ class SlackChannel implements Channel {
|
|
|
493
494
|
try {
|
|
494
495
|
const auth = await app.client.auth.test();
|
|
495
496
|
botUserId = auth.user_id as string | undefined;
|
|
496
|
-
|
|
497
|
+
botId = auth.bot_id as string | undefined;
|
|
498
|
+
log.info({ botUserId, botId }, "slack bot authenticated");
|
|
497
499
|
} catch (err) {
|
|
498
500
|
log.warn({ err }, "could not get slack bot user ID");
|
|
499
501
|
}
|