Skip to content

Commit

Permalink
Added the ability to save images in Capacitor and made general improv…
Browse files Browse the repository at this point in the history
…ements
  • Loading branch information
timamus committed Jul 26, 2024
1 parent 887a32d commit 3bce4ed
Show file tree
Hide file tree
Showing 10 changed files with 8,103 additions and 863 deletions.
7,337 changes: 7,337 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "qchatgpt",
"version": "0.3.1",
"version": "0.3.2",
"description": "ChatGPT cross-platform client made with Quasar Framework",
"productName": "QChatGPT",
"author": "timamus <[email protected]>",
Expand All @@ -13,6 +13,7 @@
"build": "quasar build"
},
"dependencies": {
"@capacitor/filesystem": "^6.0.0",
"@highlightjs/vue-plugin": "^2.1.0",
"@quasar/extras": "^1.16.4",
"dexie": "^4.0.7",
Expand Down
2 changes: 1 addition & 1 deletion src-capacitor/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "qchatgpt",
"version": "0.3.1",
"version": "0.3.2",
"description": "ChatGPT cross-platform client made with Quasar Framework",
"author": "timamus <[email protected]>",
"private": true,
Expand Down
16 changes: 10 additions & 6 deletions src/components/ChatComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import {
import {
sendMessage as sendOpenAIMessage,
isLoading,
isImageGeneration
} from "../services/openAIServices.js";
const scrollAreaRef = ref(null);
Expand Down Expand Up @@ -87,7 +88,8 @@ const TipButtons = [
];
const handleTipBtnAction = async (label) => {
await sendOpenAIMessage(ref(label), ref([]), null, ref(false));
isImageGeneration.value = false;
await sendOpenAIMessage(ref(label), ref([]), null);
};
/**
Expand Down Expand Up @@ -176,11 +178,13 @@ watch(selectedChatId, async (newId, oldId) => {
// Watch for changes in the lastApiCallTime
watch(lastApiCallTime, (newTime, oldTime) => {
scrollAreaRef.value.setScrollPosition(
"vertical",
scrollAreaRef.value.getScroll().verticalSize,
300
);
setTimeout(() => {
scrollAreaRef.value.setScrollPosition(
"vertical",
scrollAreaRef.value.getScroll().verticalSize,
300
);
}, 100); // Setting a delay of 100 milliseconds
});
</script>

Expand Down
76 changes: 62 additions & 14 deletions src/components/ImageViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@

<script setup>
import { ref, onMounted, onBeforeUpdate } from "vue";
import { morph, exportFile } from "quasar";
import { useQuasar, morph, exportFile } from "quasar";
import { getImageById } from "../services/chatDBServices.js";
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
const props = defineProps({
imageId: {
Expand Down Expand Up @@ -151,23 +152,70 @@ const imgLoaded = {
reject: () => {},
};
function downloadImage() {
const base64Data = image.value.split(",")[1]; // Remove the data:image/png;base64, prefix
const byteCharacters = atob(base64Data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
// Use Quasar to handle notifications
const $q = useQuasar();
const downloadImage = async () => {
const base64Data = image.value.split(",")[1]; // Remove the prefix data:image/png;base64,
console.log(process.env.MODE);
if (process.env.MODE === 'capacitor') {
// Code for Capacitor
await downloadImageWithCapacitor(base64Data);
} else {
// Code for other methods (e.g., browser)
downloadImageWithBrowser(base64Data);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: "image/png" });
};
const randomName = `image_${Math.random().toString(36).substring(2, 15)}.png`;
const status = exportFile(randomName, blob, { mimeType: "image/png" });
const downloadImageWithCapacitor = async (base64Data) => {
try {
const randomName = `image_${Math.random().toString(36).substring(2, 15)}.png`;
// Save base64 data as a file on the device
const result = await Filesystem.writeFile({
path: randomName,
data: base64Data,
directory: Directory.Documents,
});
if (status !== true) {
console.error("Image download error: " + status);
if (result.uri) {
$q.notify({
color: "green",
position: "bottom",
message: "<span style='font-size: 1.1em;'>Image Saved</span>",
icon: "done",
timeout: 2000,
html: true, // Allows HTML content
});
} else {
console.error(`Image download error: ${result}`);
}
} catch (error) {
console.error("Image download error: ", error);
}
}
};
const downloadImageWithBrowser = (base64Data) => {
try {
const byteCharacters = atob(base64Data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: "image/png" });
const randomName = `image_${Math.random().toString(36).substring(2, 15)}.png`;
const status = exportFile(randomName, blob, { mimeType: "image/png" });
if (status !== true) {
console.error("Image download error: " + status);
}
} catch (error) {
console.error("Image download error: ", error);
}
};
</script>

<style lang="sass">
Expand Down
4 changes: 2 additions & 2 deletions src/components/MessagesComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@
: "ChatGPT"
}}</q-item-label>
<!-- Block for displaying user files -->
<FileViewer v-if="message.fileIds" :fileIds="message.fileIds" />
<FileViewer v-if="message.fileIds" :fileIds="message.fileIds" :key="message.fileIds" />
<!-- Display plain text for user messages -->
<template v-if="message.role === 0">
<pre class="pre-wrap">{{ message.text }}</pre>
</template>
<!-- Display processed text for ChatGPT or system messages -->
<template v-else>
<!-- image generation form Dall-E -->
<ImageViewer v-if="message.imageId" :imageId="message.imageId" />
<ImageViewer v-if="message.imageId" :imageId="message.imageId" :key="message.imageId" />
<template
v-for="(part, index) in splitMessage(message.text)"
:key="'template-' + index"
Expand Down
5 changes: 2 additions & 3 deletions src/components/SendComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ import {
sendMessage as sendOpenAIMessage,
abortStream,
isLoading,
isImageGeneration
} from "../services/openAIServices.js";
import { settings } from "src/settings";
const newMessage = ref("");
const isImageGeneration = ref(false);
// Variables for file upload
const uploader = ref(null); // File uploader
const hasFiles = ref(false); // Flag indicating if files are present
Expand All @@ -123,8 +123,7 @@ const sendMessage = async () => {
await sendOpenAIMessage(
newMessage,
uploadedFiles,
uploader,
isImageGeneration
uploader
);
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/MainLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ const openDonateLink = async () => {
// Close the left drawer menu if in overlay mode
function closeDrawerIfOverlay() {
// Close the left drawer menu if the screen is small
if ($q.screen.sm) {
if ($q.screen.sm || $q.screen.xs) {
leftDrawerOpen.value = false;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/services/openAIServices.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ function getOpenAI() {

export let stream = null;
export let isLoading = ref(false);
export let isImageGeneration = ref(false);
let isNoAbort = false;

// Send a new message to the OpenAI API and handle the response stream
export async function sendMessage(
newMessage,
uploadedFiles,
uploader,
isImageGeneration
uploader
) {
// Ensure input is not empty and response is not in progress
if (!newMessage.value || isLoading.value) {
Expand Down
Loading

0 comments on commit 3bce4ed

Please sign in to comment.