mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-20 14:56:17 -04:00
feat(new tool) AI prompt splitter
This commit is contained in:
parent
0b1b98f93e
commit
c93ebb5ef0
5 changed files with 301 additions and 0 deletions
1
components.d.ts
vendored
1
components.d.ts
vendored
|
@ -11,6 +11,7 @@ declare module '@vue/runtime-core' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
'404.page': typeof import('./src/pages/404.page.vue')['default']
|
'404.page': typeof import('./src/pages/404.page.vue')['default']
|
||||||
About: typeof import('./src/pages/About.vue')['default']
|
About: typeof import('./src/pages/About.vue')['default']
|
||||||
|
AiPromptSplitter: typeof import('./src/tools/ai-prompt-splitter/ai-prompt-splitter.vue')['default']
|
||||||
App: typeof import('./src/App.vue')['default']
|
App: typeof import('./src/App.vue')['default']
|
||||||
AsciiTextDrawer: typeof import('./src/tools/ascii-text-drawer/ascii-text-drawer.vue')['default']
|
AsciiTextDrawer: typeof import('./src/tools/ascii-text-drawer/ascii-text-drawer.vue')['default']
|
||||||
'Base.layout': typeof import('./src/layouts/base.layout.vue')['default']
|
'Base.layout': typeof import('./src/layouts/base.layout.vue')['default']
|
||||||
|
|
|
@ -392,3 +392,7 @@ tools:
|
||||||
text-to-binary:
|
text-to-binary:
|
||||||
title: Text to ASCII binary
|
title: Text to ASCII binary
|
||||||
description: Convert text to its ASCII binary representation and vice-versa.
|
description: Convert text to its ASCII binary representation and vice-versa.
|
||||||
|
|
||||||
|
ai-prompt-splitter:
|
||||||
|
title: AI prompt splitter
|
||||||
|
description: Split up large prompts into smaller, manageable segments based on a set character limit for AI chat prompts.
|
||||||
|
|
282
src/tools/ai-prompt-splitter/ai-prompt-splitter.vue
Normal file
282
src/tools/ai-prompt-splitter/ai-prompt-splitter.vue
Normal file
|
@ -0,0 +1,282 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
const prompt = ref<string>('');
|
||||||
|
const characterLimit = ref<number>(15000);
|
||||||
|
const characterLimitInput = ref<number>(characterLimit.value);
|
||||||
|
const splitPrompts = ref<string[]>([]); // Array to store the split prompts
|
||||||
|
const formattedPrompt = ref<string>(''); // To store the entire formatted prompt
|
||||||
|
const clickedParts = ref<boolean[]>([]); // Array to keep track of which parts have been clicked
|
||||||
|
|
||||||
|
// Watch for changes to characterLimitInput and update characterLimit
|
||||||
|
watch(characterLimitInput, (newLimit) => {
|
||||||
|
characterLimit.value = newLimit;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Computed property to get the current length of the prompt
|
||||||
|
const promptLength = computed(() => prompt.value.length);
|
||||||
|
|
||||||
|
// Function to split the prompt into parts considering START and END
|
||||||
|
function splitPrompt(): void {
|
||||||
|
const sentences = prompt.value.split(/(?<=[.!?])\s+|\n/); // Split sentences using punctuation or new lines
|
||||||
|
const splitPromptsArray: string[] = [];
|
||||||
|
let currentSplit = '';
|
||||||
|
let currentLength = 0;
|
||||||
|
const numberOfParts = Math.ceil(prompt.value.length / characterLimit.value); // Calculate the total number of parts
|
||||||
|
|
||||||
|
sentences.forEach((sentence: string) => {
|
||||||
|
const sentenceLength = sentence.length;
|
||||||
|
|
||||||
|
// Calculate the exact lengths for [START PART X/Y] and [END PART X/Y]
|
||||||
|
const partIndex = splitPromptsArray.length + 1;
|
||||||
|
const startTagLength = `[START PART ${partIndex}/${numberOfParts}]`.length;
|
||||||
|
const endTagLength = `[END PART ${partIndex}/${numberOfParts}]`.length;
|
||||||
|
const totalTagLength = startTagLength + endTagLength + 2; // +2 for newlines between start/end
|
||||||
|
|
||||||
|
// Check if adding this sentence would exceed the character limit
|
||||||
|
if (currentLength + sentenceLength + totalTagLength > characterLimit.value) {
|
||||||
|
// Push the current split, trim spaces, and reset the current split and length
|
||||||
|
splitPromptsArray.push(currentSplit.trim());
|
||||||
|
currentSplit = '';
|
||||||
|
currentLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSplit += `${sentence} `; // Add the sentence to the current split
|
||||||
|
currentLength += sentenceLength + 1; // Account for sentence length and a space
|
||||||
|
});
|
||||||
|
|
||||||
|
// Push any remaining text in the last split
|
||||||
|
if (currentSplit.length > 0) {
|
||||||
|
splitPromptsArray.push(currentSplit.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the split prompts array
|
||||||
|
splitPrompts.value = splitPromptsArray;
|
||||||
|
clickedParts.value = Array(splitPrompts.value.length).fill(false); // Reset clicked parts on split
|
||||||
|
formattedPrompt.value = generateFormattedPrompt(); // Update the formatted prompt for display
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to generate the complete formatted prompt with [START] and [END]
|
||||||
|
function generateFormattedPrompt(): string {
|
||||||
|
const numberOfParts = splitPrompts.value.length;
|
||||||
|
let formatted = `The content I need to share is too large to send in a single message.
|
||||||
|
|
||||||
|
To make it manageable, I will break it into parts:
|
||||||
|
|
||||||
|
[START PART 1/${numberOfParts}]
|
||||||
|
this is the content of the part 1 out of ${numberOfParts} in total
|
||||||
|
[END PART 1/${numberOfParts}]
|
||||||
|
|
||||||
|
Please reply with: "Received part 1/${numberOfParts}"
|
||||||
|
|
||||||
|
Once I say 'ALL PARTS SENT,' you can start processing my requests.\n\n`;
|
||||||
|
|
||||||
|
// Append all split parts with [START] and [END] sections
|
||||||
|
splitPrompts.value.forEach((split, index) => {
|
||||||
|
const partIndex = index + 1;
|
||||||
|
formatted += `[START PART ${partIndex}/${numberOfParts}]
|
||||||
|
${split}
|
||||||
|
[END PART ${partIndex}/${numberOfParts}]\n\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to copy a specific prompt part to the clipboard
|
||||||
|
function copyToClipboard(part: string, index: number) {
|
||||||
|
const numberOfParts = splitPrompts.value.length;
|
||||||
|
const start = `[START PART ${index + 1}/${numberOfParts}]`;
|
||||||
|
const end = `[END PART ${index + 1}/${numberOfParts}]`;
|
||||||
|
const fullText = `${start}\n${part}\n${end}`;
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(fullText).then(() => {
|
||||||
|
clickedParts.value[index] = true; // Mark the button as clicked
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('Failed to copy: ', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to copy the initial instructions to the clipboard
|
||||||
|
function copyInstructions() {
|
||||||
|
const numberOfParts = splitPrompts.value.length;
|
||||||
|
const initialInstructions = `The content I need to share is too large to send in a single message.
|
||||||
|
|
||||||
|
To make it manageable, I will break it into parts:
|
||||||
|
|
||||||
|
[START PART 1/${numberOfParts}]
|
||||||
|
this is the content of the part 1 out of ${numberOfParts} in total
|
||||||
|
[END PART 1/${numberOfParts}]
|
||||||
|
|
||||||
|
Please reply with: "Received part 1/${numberOfParts}"
|
||||||
|
|
||||||
|
Once I say 'ALL PARTS SENT,' you can start processing my requests.`;
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(initialInstructions).then(() => {
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('Failed to copy instructions: ', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to copy the full prompt to the clipboard
|
||||||
|
function copyFullPrompt() {
|
||||||
|
const initialPrompt = formattedPrompt.value;
|
||||||
|
navigator.clipboard.writeText(initialPrompt).then(() => {
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('Failed to copy initial prompt: ', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to copy the final command to the clipboard
|
||||||
|
function copyFinalCommand() {
|
||||||
|
const finalCommand = 'ALL PARTS SENT';
|
||||||
|
navigator.clipboard.writeText(finalCommand).then(() => {
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('Failed to copy final command: ', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computed property for potential number of splits based on the current prompt
|
||||||
|
const numberOfSplits = computed(() => {
|
||||||
|
const sentences = prompt.value.split(/(?<=[.!?])\s+|\n/);
|
||||||
|
let currentLength = 0;
|
||||||
|
let splits = 0;
|
||||||
|
const numberOfParts = Math.ceil(prompt.value.length / characterLimit.value);
|
||||||
|
|
||||||
|
sentences.forEach((sentence: string) => {
|
||||||
|
const sentenceLength = sentence.length;
|
||||||
|
const partIndex = splits + 1;
|
||||||
|
const startTagLength = `[START PART ${partIndex}/${numberOfParts}]`.length;
|
||||||
|
const endTagLength = `[END PART ${partIndex}/${numberOfParts}]`.length;
|
||||||
|
const totalTagLength = startTagLength + endTagLength + 2; // +2 for newlines
|
||||||
|
|
||||||
|
if (currentLength + sentenceLength + totalTagLength > characterLimit.value) {
|
||||||
|
splits++; // Increment split count if exceeding limit
|
||||||
|
currentLength = 0; // Reset length for new split
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLength += sentenceLength + 1; // Update length
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentLength > 0) {
|
||||||
|
splits++; // Add an additional split for remaining text
|
||||||
|
}
|
||||||
|
|
||||||
|
return splits;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Computed property for formatted prompt display inside c-input-text
|
||||||
|
const formattedPromptDisplay = computed(() => {
|
||||||
|
if (splitPrompts.value.length > 0) {
|
||||||
|
return formattedPrompt.value.replace(/\n/g, '\n'); // Display formatted with line breaks
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear prompt function
|
||||||
|
function clearPrompt(): void {
|
||||||
|
prompt.value = '';
|
||||||
|
splitPrompts.value = []; // Clear the split prompts as well
|
||||||
|
formattedPrompt.value = ''; // Clear the formatted prompt
|
||||||
|
clickedParts.value = []; // Reset clicked parts
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to restrict character limit input to numbers only
|
||||||
|
function handleCharacterLimitInput(event: KeyboardEvent) {
|
||||||
|
const allowedKeys = ['Backspace', 'ArrowLeft', 'ArrowRight', 'Tab', 'Control', 'Alt',
|
||||||
|
'Meta', 'Shift', 'Enter', 'Escape', 'Delete', 'Home', 'End', 'PageUp', 'PageDown'];
|
||||||
|
if (!allowedKeys.includes(event.key) && (event.key < '0' || event.key > '9')) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<c-card>
|
||||||
|
<!-- Character Limit Input -->
|
||||||
|
<n-form-item label="Character Limit">
|
||||||
|
<n-input-number
|
||||||
|
v-model:value="characterLimitInput"
|
||||||
|
:min="1"
|
||||||
|
:input-default="characterLimit"
|
||||||
|
test-id="characterLimitInput"
|
||||||
|
placeholder="Set character limit..."
|
||||||
|
@keydown="handleCharacterLimitInput"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
|
||||||
|
<!-- Prompt Input with Character Length -->
|
||||||
|
<c-input-text
|
||||||
|
v-model:value="prompt"
|
||||||
|
:label="`Prompt Content (Length: ${promptLength}):`"
|
||||||
|
placeholder="Enter your prompt here..."
|
||||||
|
rows="15"
|
||||||
|
multiline
|
||||||
|
test-id="promptInput"
|
||||||
|
raw-text
|
||||||
|
mb-3
|
||||||
|
/>
|
||||||
|
<!-- Clear and Split Buttons under Full Prompt Label -->
|
||||||
|
<div style="display: flex; justify-content: space-between; margin-top: 5px;">
|
||||||
|
<c-button @click="splitPrompt">
|
||||||
|
Split ({{ numberOfSplits }})
|
||||||
|
</c-button>
|
||||||
|
<c-button @click="clearPrompt">
|
||||||
|
Clear
|
||||||
|
</c-button>
|
||||||
|
</div>
|
||||||
|
</c-card>
|
||||||
|
|
||||||
|
<!-- Split Prompts and Initial Prompt inside c-input-text -->
|
||||||
|
<c-card :label="`Split Prompts (${numberOfSplits})`">
|
||||||
|
<c-input-text
|
||||||
|
:label="`Split Prompts (${numberOfSplits})`"
|
||||||
|
:value="formattedPromptDisplay"
|
||||||
|
placeholder="Your formatted prompt will display here..."
|
||||||
|
rows="18"
|
||||||
|
multiline
|
||||||
|
test-id="splitPromptDisplay"
|
||||||
|
raw-text
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Copy Instructions, Full Prompt, and Final Command buttons -->
|
||||||
|
<div class="copy-commands-buttons">
|
||||||
|
<c-button @click="copyInstructions">
|
||||||
|
Copy Instructions
|
||||||
|
</c-button>
|
||||||
|
<c-button float-right @click="copyFullPrompt">
|
||||||
|
Copy Full Prompt
|
||||||
|
</c-button>
|
||||||
|
<c-button @click="copyFinalCommand">
|
||||||
|
Copy Final Command
|
||||||
|
</c-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Buttons for copying split prompts in a grid layout -->
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 10px; margin-top: 10px;">
|
||||||
|
<div v-for="(split, index) in splitPrompts" :key="index">
|
||||||
|
<c-button
|
||||||
|
style="width: 120px; height: 50px; text-align: center;"
|
||||||
|
:class="{ 'button-clicked': clickedParts[index] }"
|
||||||
|
@click="copyToClipboard(split, index)"
|
||||||
|
>
|
||||||
|
Copy Part {{ index + 1 }}
|
||||||
|
</c-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</c-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.button-clicked {
|
||||||
|
background-color: #229C60;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-commands-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
12
src/tools/ai-prompt-splitter/index.ts
Normal file
12
src/tools/ai-prompt-splitter/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { IconMessages } from '@tabler/icons-vue';
|
||||||
|
import { defineTool } from '../tool';
|
||||||
|
|
||||||
|
export const tool = defineTool({
|
||||||
|
name: 'Ai prompt splitter',
|
||||||
|
path: '/ai-prompt-splitter',
|
||||||
|
description: '',
|
||||||
|
keywords: ['ai', 'prompt', 'splitter'],
|
||||||
|
component: () => import('./ai-prompt-splitter.vue'),
|
||||||
|
icon: IconMessages,
|
||||||
|
createdAt: new Date('2024-10-18'),
|
||||||
|
});
|
|
@ -1,6 +1,7 @@
|
||||||
import { tool as base64FileConverter } from './base64-file-converter';
|
import { tool as base64FileConverter } from './base64-file-converter';
|
||||||
import { tool as base64StringConverter } from './base64-string-converter';
|
import { tool as base64StringConverter } from './base64-string-converter';
|
||||||
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
||||||
|
import { tool as aiPromptSplitter } from './ai-prompt-splitter';
|
||||||
import { tool as emailNormalizer } from './email-normalizer';
|
import { tool as emailNormalizer } from './email-normalizer';
|
||||||
|
|
||||||
import { tool as asciiTextDrawer } from './ascii-text-drawer';
|
import { tool as asciiTextDrawer } from './ascii-text-drawer';
|
||||||
|
@ -184,6 +185,7 @@ export const toolsByCategory: ToolCategory[] = [
|
||||||
textDiff,
|
textDiff,
|
||||||
numeronymGenerator,
|
numeronymGenerator,
|
||||||
asciiTextDrawer,
|
asciiTextDrawer,
|
||||||
|
aiPromptSplitter,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue