161 lines
7.3 KiB
JavaScript
161 lines
7.3 KiB
JavaScript
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { dirname, resolve } from "node:path";
|
|
|
|
const repoRoot = resolve(new URL("..", import.meta.url).pathname);
|
|
const requestPath = resolve(repoRoot, "Saved/Chat/Maria/LastRequest.json");
|
|
const outDir = resolve(repoRoot, "Saved/Chat/Maria");
|
|
const transcriptPath = resolve(outDir, "RPLiveClosenessTranscript.json");
|
|
const readablePath = resolve(outDir, "RPLiveClosenessTranscript.md");
|
|
|
|
const apiKey = process.env.DEEPSEEK_API_KEY;
|
|
if (!apiKey) {
|
|
console.error("DEEPSEEK_API_KEY is required.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const apiBase = process.env.DEEPSEEK_API_BASE || "https://api.deepseek.com";
|
|
const model = process.env.DEEPSEEK_MODEL || "deepseek-v4-flash";
|
|
const temperature = Number(process.env.DEEPSEEK_TEMPERATURE || "1.15");
|
|
|
|
const baseRequest = JSON.parse(await readFile(requestPath, "utf8"));
|
|
|
|
function patchPrompt(content) {
|
|
let patched = content.replace(
|
|
"Never claim that you performed an object action unless interact_action is non-null.",
|
|
"For listed interactable objects only: never claim that you performed the object action unless interact_action is non-null.",
|
|
);
|
|
|
|
if (patched.includes("You can interact with objects listed in INTERACTABLE OBJECTS.") &&
|
|
!patched.includes("ordinary unlisted background behavior")) {
|
|
patched = patched.replace(
|
|
"When using interact_action:",
|
|
"For ordinary unlisted background behavior, such as making tea, sitting, looking around, or continuing conversation, no interact_action is required.\n\nWhen using interact_action:",
|
|
);
|
|
}
|
|
|
|
if (patched.includes("Respond only with a valid JSON object.") &&
|
|
!patched.includes("spoken dialogue only")) {
|
|
patched = patched.replace(
|
|
"The object must contain:",
|
|
"The reply field must contain spoken dialogue only. Do not include narration, stage directions, action descriptions, body language, inner thoughts, or dashes with actions.\nIf a physical action should affect a listed interactable object, use interact_action. Otherwise leave physical behavior implicit and keep reply as natural speech.\nThe object must contain:",
|
|
);
|
|
}
|
|
|
|
return patched;
|
|
}
|
|
|
|
const messages = baseRequest.messages
|
|
.filter((message) => message.role === "system")
|
|
.map((message) => ({ ...message, content: patchPrompt(message.content) }));
|
|
|
|
const playerTurns = [
|
|
"Привет. Я Марк, новый сосед из дома напротив. Просто решил зайти познакомиться, без вторжения.",
|
|
"Спасибо. Я ненадолго, правда. Дом пока пустой, но я уже начинаю привыкать к этому месту.",
|
|
"Если не против, я иногда буду заходить поздороваться. Тут тихо, но с живым человеком всё равно спокойнее.",
|
|
"Прошло несколько дней. Привет. Я принёс печенье к чаю, как соседскую взятку за то, что пару вечеров шумел с ремонтом.",
|
|
"У тебя тут правда уютно. Не как картинка, а как место, где человек живёт.",
|
|
"Мне с тобой легко разговаривать. Даже странно, мы вроде недавно знакомы, а тишина рядом с тобой не неловкая.",
|
|
"Можно я сяду ближе к столу? Не к тебе вплотную, просто там теплее.",
|
|
"Мне нравится, как ты иногда подкалываешь. У тебя это получается мягко, но метко.",
|
|
"Прошло ещё немного времени. Кажется, я уже жду этих вечерних чаёв сильнее, чем надо бы.",
|
|
"Можно я скажу честно? Ты мне нравишься. Не хочу давить, просто не хочу делать вид, что это только соседские разговоры.",
|
|
"Если тебе некомфортно, я отступлю. Но если нет... можно взять тебя за руку?",
|
|
"Спасибо. Я не хочу торопиться. Мне просто хорошо рядом.",
|
|
"Можно обнять тебя? Просто обнять, без попытки утащить момент дальше.",
|
|
"Я правда к тебе привязался. И мне важно не сломать это спешкой.",
|
|
"Можно я тебя поцелую? Только если ты сама хочешь.",
|
|
"Я хочу остаться с тобой сегодня. Не потому что мне негде, а потому что не хочется уходить. Но решаешь ты.",
|
|
"Если ты хочешь остановиться, мы остановимся. Если хочешь просто посидеть рядом, я буду рядом.",
|
|
"Я хочу быть ближе. Медленно. С тобой. Скажи мне, если ты тоже этого хочешь.",
|
|
"Тогда давай не будем торопить слова. Просто останемся вместе.",
|
|
];
|
|
|
|
function parseReply(raw) {
|
|
try {
|
|
return JSON.parse(raw);
|
|
} catch {
|
|
return { reply: raw, interact_action: null, emotion: null };
|
|
}
|
|
}
|
|
|
|
async function callMaria() {
|
|
const response = await fetch(`${apiBase.replace(/\/+$/, "")}/chat/completions`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${apiKey}`,
|
|
},
|
|
body: JSON.stringify({
|
|
model,
|
|
messages,
|
|
temperature,
|
|
top_p: 1,
|
|
max_tokens: 512,
|
|
response_format: { type: "json_object" },
|
|
stream: false,
|
|
}),
|
|
});
|
|
|
|
const data = await response.json().catch(() => ({}));
|
|
if (!response.ok) {
|
|
throw new Error(data?.error?.message || `HTTP ${response.status}`);
|
|
}
|
|
|
|
return {
|
|
raw: data?.choices?.[0]?.message?.content || "",
|
|
usage: data?.usage || null,
|
|
};
|
|
}
|
|
|
|
const transcript = {
|
|
created_at: new Date().toISOString(),
|
|
model,
|
|
temperature,
|
|
requestPath,
|
|
turns: [],
|
|
};
|
|
|
|
for (const player of playerTurns) {
|
|
messages.push({ role: "user", content: player });
|
|
const result = await callMaria();
|
|
const maria = parseReply(result.raw);
|
|
|
|
transcript.turns.push({
|
|
player,
|
|
maria,
|
|
maria_raw: result.raw,
|
|
usage: result.usage,
|
|
});
|
|
|
|
messages.push({ role: "assistant", content: result.raw });
|
|
console.log(`M: ${maria.reply}`);
|
|
}
|
|
|
|
await mkdir(dirname(transcriptPath), { recursive: true });
|
|
await writeFile(transcriptPath, `${JSON.stringify(transcript, null, 2)}\n`, "utf8");
|
|
|
|
const lines = [
|
|
"# RP Live Closeness Transcript",
|
|
"",
|
|
`Model: ${model}`,
|
|
`Temperature: ${temperature}`,
|
|
"",
|
|
];
|
|
|
|
for (const [index, turn] of transcript.turns.entries()) {
|
|
lines.push(`## ${index + 1}`);
|
|
lines.push("");
|
|
lines.push(`**Марк:** ${turn.player}`);
|
|
lines.push("");
|
|
lines.push(`**Мария:** ${turn.maria.reply}`);
|
|
lines.push("");
|
|
lines.push(`emotion: ${JSON.stringify(turn.maria.emotion ?? null)}`);
|
|
lines.push(`interact_action: ${JSON.stringify(turn.maria.interact_action ?? null)}`);
|
|
lines.push("");
|
|
}
|
|
|
|
await writeFile(readablePath, `${lines.join("\n")}\n`, "utf8");
|
|
|
|
console.log(`Transcript saved: ${transcriptPath}`);
|
|
console.log(`Readable transcript saved: ${readablePath}`);
|